Un Jenkins que no se pudre: configuración como código, agentes efímeros y bibliotecas compartidas
Los tres cambios que convierten un Jenkins configurado a mano en uno que puedes reconstruir desde una máquina vacía en veinte minutos, y lo que cuesta adoptar cada uno.
Un controlador de Jenkins con cinco años de vida contiene una cantidad enorme de estado que no existe en ningún otro sitio: credenciales que alguien pegó en 2021, definiciones de jobs editadas en la interfaz, versiones de plugins que resulta que funcionan juntas. El plan de recuperación de esa máquina es "confiar".
Hay tres cambios que lo arreglan. Son independientes, así que puedes hacerlos en cualquier orden, pero este es el que usamos.
1. Configuración como código (JCasC)
El plugin Configuration as Code toma un fichero YAML y lo aplica al controlador en el arranque: dominio de seguridad, autorización, nubes, plantillas de agente, instalación de herramientas, ajustes globales. Lo que no esté en el YAML está por defecto o ha derivado.
jenkins:
systemMessage: "Gestionado con JCasC — no editar en la interfaz"
numExecutors: 0
authorizationStrategy:
roleBased:
roles:
global:
- name: admin
permissions: [Overall/Administer]
entries: [{ group: "equipo-plataforma" }]
clouds:
- kubernetes:
name: k8s
namespace: jenkins-agents
jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080"
templates:
- name: build
label: build
containers:
- name: jnlp
image: ghcr.io/acme/ci-agent:2026.09
resourceRequestCpu: "1"
resourceLimitMemory: "4Gi"
unclassified:
location:
url: https://ci.acme.example/
numExecutors: 0 en el controlador no es cosmético. Las compilaciones que corren en el controlador tienen acceso al directorio home de Jenkins, es decir, a todas las credenciales. No ejecutes builds ahí.
La adopción es incremental: exporta la configuración actual desde la página "ver configuración" del plugin, súbela al repositorio y ve borrando las partes que no entiendes hasta que el arranque siga funcionando. Cuenta una semana. La meta es poder hacer docker run de la imagen del controlador con el YAML montado y recuperar tu Jenkins.
Las credenciales se quedan fuera del YAML. Refiérelas desde el almacén de secretos que ya tienes con el plugin de Vault o los proveedores de credenciales nativos de cada nube; mejor aún, quita las credenciales estáticas del todo.
2. Agentes efímeros
Los agentes estáticos son una flota permanente de máquinas, cada una un copo de nieve que va acumulando herramientas de compilación, y cada una facturada 24 horas al día para hacer 90 minutos de trabajo.
El plugin de Kubernetes (configurado arriba) arranca un pod por build y lo borra al terminar. La economía cambia por completo: pagas segundos de compilación, y una máquina que nunca sobrevive a un build no puede derivar. En el Jenkinsfile:
pipeline {
agent {
kubernetes {
yaml '''
spec:
containers:
- name: build
image: ghcr.io/acme/ci-agent:2026.09
command: ["cat"]
tty: true
resources:
requests: { cpu: "2", memory: "4Gi" }
'''
}
}
stages {
stage('test') {
steps { container('build') { sh 'make test' } }
}
}
}
Dos notas prácticas. Primera: guarda la imagen de agente en tu registro y ponle versión; una imagen anclada a latest reintroduce la deriva que acabas de quitar. Segunda: los agentes efímeros hacen explícita la caché — nada sobrevive al pod, así que necesitas una caché remota o tus compilaciones irán más lentas antes de ir más rápidas. Planifícalo en el mismo cambio.
Si no estás en Kubernetes, las nubes de agentes de EC2 y de máquinas virtuales de Azure hacen lo mismo con un arranque más lento (de 60 a 120 segundos en vez de 5 a 15). Sigue mereciendo la pena.
3. Bibliotecas compartidas con una interfaz de verdad
La mayoría de los Jenkinsfile se copian y luego divergen. Una biblioteca compartida convierte la forma común en un artefacto con versión:
// vars/serviceBuild.groovy
def call(Map cfg) {
pipeline {
agent { kubernetes { yaml libraryResource('agents/build.yaml') } }
stages {
stage('test') { steps { container('build') { sh cfg.testCmd ?: 'make test' } } }
stage('image') { steps { container('build') { sh "make image TAG=${env.GIT_COMMIT}" } } }
stage('deploy') {
when { branch 'main' }
steps { deployTo(env: 'staging', service: cfg.name) }
}
}
}
}
y el Jenkinsfile de cada repositorio se queda en:
@Library('acme-ci@v4') _
serviceBuild(name: 'orders', testCmd: 'make test-integration')
Ancla la biblioteca a una etiqueta, no a main. Una biblioteca en main significa que todos los repositorios de la empresa reciben tu refactor en el mismo instante, que es como un cambio de una línea se convierte en una caída. Etiquétala, despliégala repositorio a repositorio y mantén viva la etiqueta anterior durante una release o dos.
La biblioteca es además donde vive la política — firma de imágenes, generación de SBOM, el paso de aprobación antes de producción — porque es el único sitio donde cambiar algo cambia todos los pipelines.
Qué ganas
Después de los tres: el controlador es desechable, los agentes son ganado y la lógica de pipeline está versionada y es revisable. Reconstruir desde cero es un helm install más un fichero YAML, y lleva unos veinte minutos.
Es también el punto en el que la pregunta de conservar o migrar se vuelve fácil de responder: un Jenkins con esta forma es barato de mantener y, si acabas migrando, los pipelines expresados como llamadas a biblioteca se portan mucho mejor que un Jenkinsfile de 600 líneas.