Integración de Ansible con Jenkins: Automatizando su Pipeline de CI/CD

Integra Ansible con Jenkins para ejecutar playbooks desde pipelines CI/CD, gestionar credenciales SSH y realizar despliegues consistentes.

Integración de Ansible con Jenkins: Automatizando tu Pipeline CI/CD

Integrar Ansible con Jenkins permite que tu pipeline CI/CD construya un artefacto, ejecute pruebas y lo despliegue usando los mismos playbooks que utilizas fuera de Jenkins. El principal desafío es conectar credenciales, inventario y ejecución de playbooks sin convertir el pipeline en un montón de comandos shell.

Esta guía muestra una configuración práctica: Jenkins orquesta el pipeline, mientras que Ansible maneja el despliegue y la configuración en los hosts de destino. Los ejemplos asumen despliegues SSH en Linux, pero el mismo patrón funciona con inventarios y roles específicos para cada entorno.

Cómo encajan Jenkins y Ansible

Antes de escribir el pipeline, separa las responsabilidades:

  • Jenkins: Revisa el código, construye artefactos, ejecuta pruebas, recopila logs y decide cuándo se ejecutan las etapas de despliegue.
  • Ansible: Se conecta a los hosts de destino, copia artefactos, escribe configuraciones, gestiona servicios y mantiene los pasos de despliegue idempotentes.

¿Por qué integrar Jenkins con Ansible?

Esta división le da a tu equipo un límite claro:

  1. Jenkins almacena el flujo de lanzamiento en un Jenkinsfile.
  2. Ansible almacena las acciones de infraestructura en playbooks y roles.
  3. Los archivos de inventario deciden qué hosts pertenecen a staging, producción u otros entornos.
  4. Las credenciales de Jenkins protegen claves SSH, contraseñas de Vault y tokens.

Prerrequisitos para la Integración

Antes de comenzar, asegúrate de tener:

  • Un controlador Jenkins en funcionamiento y al menos un agente que pueda ejecutar trabajos de despliegue.
  • Ansible instalado en el agente Jenkins que ejecutará ansible-playbook.
  • Máquinas de destino accesibles desde ese agente a través de SSH.
  • Un par de claves SSH dedicado para la automatización.
  • Código de la aplicación, inventario, playbooks y roles en control de versiones.

Configurando Jenkins para Ansible

Jenkins necesita dos cosas antes de poder ejecutar Ansible de forma segura: el runtime de Ansible en un agente y las credenciales para los hosts de destino.

1. Instalar el Plugin de Ansible

Puedes llamar a ansible-playbook directamente desde un paso shell, pero el plugin de Ansible para Jenkins puede facilitar la configuración y la gestión de la salida.

  1. Navega a Manage Jenkins > Manage Plugins.
  2. Ve a la pestaña Available y busca "Ansible".
  3. Selecciona el plugin "Ansible" y haz clic en Install without restart o Download now and install after restart.

2. Configurar Credenciales SSH

Ansible usará SSH para conectarse a los servidores de destino. Almacena la clave privada en las credenciales de Jenkins, no en el repositorio.

  1. Ve a Manage Jenkins > Manage Credentials.
  2. Selecciona el almacén de credenciales utilizado por tus trabajos.
  3. Haz clic en Add Credentials.
  4. Elige SSH Username with private key.
  5. Establece un ID estable, por ejemplo ansible-ssh-key.
  6. Ingresa el nombre de usuario SSH utilizado en las máquinas de destino.
  7. Agrega la clave privada y guarda la credencial.

Consejo: Mejores Prácticas para la Gestión de Claves

  • Nunca codifiques claves privadas SSH en un Jenkinsfile, inventario o playbook.
  • Usa claves de automatización dedicadas, no claves personales.
  • Rota las claves según un cronograma que coincida con la política de seguridad de tu equipo.

Diseñando tu Pipeline de Jenkins con Ansible

Usa un Pipeline Declarativo en un Jenkinsfile para que la lógica de despliegue sea revisada junto con el código de la aplicación.

Estructura Básica del Pipeline

Aquí tienes un ejemplo compacto para una construcción, despliegue en staging y despliegue en producción con compuerta:

// Jenkinsfile
pipeline {
    agent any

    environment {
        // Define variables de entorno si es necesario
        ANSIBLE_HOST_KEY_CHECKING = 'False' // Ten cuidado en producción, prefiere known_hosts
    }

    stages {
        stage('Checkout Source') {
            steps {
                git 'https://your-scm-url/your-repo.git'
            }
        }

        stage('Build Application') {
            // Esta etapa podría construir un JAR, WAR, imagen Docker, etc.
            // Ejemplo: construir un JAR de Spring Boot
            steps {
                sh 'mvn clean package'
            }
        }

        stage('Run Unit Tests') {
            steps {
                sh 'mvn test'
            }
        }

        stage('Deploy to Staging') {
            steps {
                script {
                    // Usa el agente SSH para que la clave privada esté disponible para Ansible
                    sshagent(credentials: ['ansible-ssh-key']) {
                        // Ejecuta el playbook de Ansible
                        sh 'ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }

        stage('Deploy to Production') {
            // Esta etapa podría requerir aprobación manual
            input {
                message "¿Proceder con el despliegue a Producción?"
                ok "Desplegar a Producción"
            }
            steps {
                script {
                    sshagent(credentials: ['ansible-ssh-key']) {
                        sh 'ansible-playbook -i inventory/production.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }
    }

    post {
        always {
            echo 'Pipeline finalizado.'
        }
        success {
            echo '¡Pipeline exitoso!'
            // slackSend channel: '#deployments', message: "Despliegue exitoso: ${env.BUILD_URL}"
        }
        failure {
            echo '¡Pipeline fallido!'
            // slackSend channel: '#deployments', message: "Despliegue fallido: ${env.BUILD_URL}"
        }
    }
}

Detalles Clave del Pipeline

  • agent any funciona para una demo. En producción, usa una etiqueta de agente que garantice que Ansible esté instalado.
  • sshagent(credentials: ['ansible-ssh-key']) expone la clave privada solo dentro de ese bloque.
  • ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml mantiene explícito el entorno de destino.
  • El bloque input de producción agrega una compuerta manual antes de un despliegue sensible.
  • Evita deshabilitar la verificación de clave de host en producción. Gestiona known_hosts en el agente Jenkins en su lugar.

Ejemplo de Playbook de Ansible: Desplegando una Aplicación Web

Aquí tienes un inventory/staging.ini y playbooks/deploy_app.yml simplificados para una aplicación web Java. El nombre de tu artefacto, el gestor de servicios y las rutas pueden diferir.

inventory/staging.ini

[web_servers]
web1.example.com
web2.example.com

[database_servers]
db1.example.com

[all:vars]
ansible_user=ubuntu

playbooks/deploy_app.yml

---
- name: Desplegar Aplicación Web
  hosts: web_servers
  become: yes
  vars:
    app_name: my-webapp
    app_path: /opt/{{ app_name }}
    app_port: 8080
    app_version: "{{ app_version | default('1.0.0') }}"

  tasks:
    - name: Asegurar que el directorio de la aplicación existe
      ansible.builtin.file:
        path: "{{ app_path }}"
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0755'

    - name: Copiar el JAR de la aplicación al destino
      ansible.builtin.copy:
        src: "target/{{ app_name }}-{{ app_version }}.jar"
        dest: "{{ app_path }}/{{ app_name }}.jar"
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0644'
      notify: reiniciar servicio de la aplicación

    - name: Asegurar que el archivo de servicio systemd existe
      ansible.builtin.template:
        src: templates/my-webapp.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        owner: root
        group: root
        mode: '0644'
      notify: reiniciar servicio de la aplicación

    - name: Asegurar que el servicio de la aplicación está iniciado y habilitado
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: yes

  handlers:
    - name: reiniciar servicio de la aplicación
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

templates/my-webapp.service.j2 (Plantilla de servicio systemd)

[Unit]
Description={{ app_name }} Application
After=network.target

[Service]
User={{ ansible_user }}
ExecStart=/usr/bin/java -jar {{ app_path }}/{{ app_name }}.jar --server.port={{ app_port }}
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

Este playbook crea el directorio de la aplicación, copia el JAR construido, escribe una unidad systemd e inicia el servicio. El handler reinicia el servicio solo cuando el artefacto o el archivo de unidad cambian.

Mejores Prácticas y Consejos

  • Mantén los playbooks idempotentes para que reejecutar un pipeline fallido no cause daños adicionales.
  • Almacena las contraseñas de Vault en las credenciales de Jenkins y pásalas a través de un archivo de contraseña temporal o un enlace de credenciales aprobado.
  • Organiza la lógica de despliegue en roles, como webserver, database y app_deploy.
  • Usa inventarios separados o group_vars para staging y producción.
  • Ejecuta Ansible en agentes Jenkins dedicados, no en el controlador.
  • Usa -v o -vv solo cuando necesites más detalle; evita filtrar secretos en los logs.
  • Prueba los playbooks antes de que Jenkins los ejecute, especialmente para roles de producción.

Cuándo Consultar a un Profesional

Busca la ayuda de un administrador de Jenkins o plataforma cuando el pipeline despliegue en producción, gestione credenciales compartidas, escriba en muchos hosts o necesite controles de auditoría. El manejo de credenciales, la verificación de clave de host y el comportamiento de reversión merecen una segunda revisión antes de la primera ejecución en producción.

Conclusión

Usa Jenkins para la orquestación y Ansible para el estado del despliegue. Mantén las credenciales en Jenkins, los playbooks en control de versiones, el inventario explícito y prueba los mismos comandos de Ansible fuera del pipeline antes de automatizar el despliegue en producción.