Implementar Jenkins como código

Nota perev.: esta es una traducción de un artículo del blog de ingeniería de Preply sobre cómo puede usar la configuración como código para una herramienta CI / CD tan popular como Jenkins.

En nuestra empresa, intentamos seguir la práctica de "Todo es como el código", esto se aplica no solo a los recursos de infraestructura, sino también al monitoreo, el trabajo de Jenkins, etc. En este artículo, hablaré sobre cómo usamos esta práctica para implementar y apoyar a Jenkins. Y esto se aplica no solo a la infraestructura del servidor y los agentes, sino también a los complementos, accesos, trabajo y muchas otras cosas.

Además, en este artículo intentaremos encontrar respuestas a preguntas como:

  • ¿Nuestro Jenkins se ha vuelto más estable?
  • ¿Podemos hacer cambios frecuentes en la configuración del trabajo y el servidor?
  • ¿La actualización de Jenkins sigue siendo un dolor para nosotros?
  • ¿Podemos controlar todos nuestros cambios?
  • ¿Podemos restaurar Jenkins rápidamente en caso de un fakap?

imagen


Introducción


Por lo general, lo primero que viene a la mente al mencionar la frase "herramientas DevOps" es el sistema CI / CD. Por ejemplo, usamos Jenkins porque ejecutamos cientos de tareas todos los días, y eso es decenas de miles de compilaciones. Algunas funciones que utilizamos en Jenkins no están disponibles en otros sistemas CI / CD o tienen una funcionalidad limitada.


Nos gustaría controlar a Jenkins completamente desde el código, incluida la infraestructura, las configuraciones, el trabajo y los complementos. Intentamos ejecutar Jenkins en Kubernetes, pero no se ajustaba a nuestras necesidades, además no era fácil de escalar debido a su arquitectura .

imagen
Esto será discutido

Infraestructura para Jenkins


imagenUsamos AWS y configuramos toda la infraestructura usando Terraform y otras herramientas hash como Packer y Vault .

Como se mencionó anteriormente, intentamos usar Jenkins en Kubernetes y enfrentamos algunos problemas con el escalado de PVC , recursos y una arquitectura no tan bien diseñada.

Aquí, usamos los recursos habituales de AWS: instancias EC2, certificados SSL, balanceadores, Cloudfront, etc. La imagen del sistema operativo ( AMI ) se configura con Packer, que se integra perfectamente con Terraform y Vault.

{
    "variables": {
        "aws_access_key": "{{vault `packer/aws_access_key_id` `key`}}",
        "aws_secret_key": "{{vault `packer/aws_secret_access_key` `key`}}",
        "aws_region": "{{vault `packer/aws_region` `key`}}",
        "vault_token": "{{env `VAULT_TOKEN`}}"
    },
    "builders": [{
        "access_key": "{{ user `aws_access_key` }}",
        "secret_key": "{{ user `aws_secret_key` }}",
        "region": "{{ user `aws_region` }}",
        "type": "amazon-ebs",
        "communicator": "ssh",
        "ssh_username": "ubuntu",
        "instance_type": "c5.xlarge",
        "security_group_id": "sg-12345",
        "iam_instance_profile": "packer-role-profile",
        "ami_name": "packer-jenkins-master-{{timestamp}}",
        "ami_description": "Jenkins master image",
        "launch_block_device_mappings": [{
            "device_name": "/dev/sda1",
            "volume_size": 50,
            "volume_type": "gp2",
            "delete_on_termination": true
        }],
        "source_ami_filter": {
            "filters": {
                "virtualization-type": "hvm",
                "name": "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*",
                "root-device-type": "ebs"
            },
            "owners": ["099720109477"],
            "most_recent": true
        }
    }],
    "provisioners": [{
        "type": "shell",
        "environment_vars": ["VAULT_TOKEN={{ user `vault_token` }}"],
        "scripts": ["packer_bootstrap.sh"]
    }]
}
Un ejemplo de cómo se ve la configuración de una imagen del sistema operativo en Packer.

A su vez, el archivo packer_bootstrap.shcontiene un conjunto de comandos con los que se instala el software dentro de la imagen. Por ejemplo, podemos instalar Docker, docker-compose y vaultenv o Datadog-agent para el monitoreo. En cuanto a la infraestructura para esta imagen, podemos usar Terraform, Cloudformation, Pulumi o incluso Ansible.

Aquí hay un ejemplo de una posible infraestructura de AWS para Jenkins: los

usuarios inician sesión en Jenkins a través del equilibrador interno y los ganchos de Github acceden al servidor a través del externo. Utilizamos la integración de Jenkins con GitHub, por lo que algunos enlaces de servidores deben ser accesibles desde Internet. Aquí hay muchas soluciones diferentes (por ejemplo, una lista blanca para direcciones IP , URL o tokens, etc.), en nuestro caso utilizamos una combinación de URL permitidas y validación de token.
Entonces, después de las manipulaciones que hemos hecho, ya tenemos una infraestructura lista para usar con la imagen del sistema operativo ensamblada, la capacidad de monitorear y acceder a la tienda secreta corporativa.

Usamos Docker para instalar Jenkins y sus complementos


imagenLo siguiente que haremos es instalar Jenkins y sus complementos. Constantemente tuvimos problemas para actualizar los complementos, por lo que el objetivo principal era tener un reparto claro de los complementos instalados y sus versiones en el código.
Y aquí Docker nos ayudará, porque podemos tomar una imagen de Docker preinstalada y usarla como base para nuestra configuración.

FROM jenkins/jenkins:2.215

ENV CASC_JENKINS_CONFIG /jenkins_configs
USER root

#   
RUN apt update && \
    apt install -y python3 python3-pip && \
    pip3 install awscli jenkins-job-builder jjb-reactive-choice-param --no-cache-dir

USER jenkins
VOLUME /jenkins_configs
VOLUME /var/jenkins_home

#  
COPY plugins.txt /usr/share/jenkins/ref/
RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt
Dockerfile

Dentro de la imagen de Docker, algunos paquetes se instalan como Job Builder, del cual hablaré más adelante, los repositorios también se registran y se instalan los complementos especificados en el archivo plugins.txt.

Jenkins.instance.pluginManager.plugins.each{
  plugin ->
    println ("${plugin.getShortName()}:${plugin.getVersion()}")
}
Puede obtener una lista de complementos instalados en Jenkins haciendo clic en el enlace https://our-jenkins-url/scripty guardando la salida en un archivoplugins.txt

. Finalmente, la configuración de docker-compose, que ejecutará Jenkins en Docker.

version: "3"
services:
  jenkins:
    build: .
    container_name: jenkins
    restart: always
    ports:
      - "50000:50000"
      - "8080:8080"
    volumes:
      - ./configs/:/jenkins_configs/:ro
      - ./jenkins_home/:/var/jenkins_home/:rw
    environment:
      - VAULT_TOKEN
      - GITHUB_TOKEN
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
      - JAVA_OPTS=-Xms4G -Xmx8G -Xloggc:/var/jenkins_home/gc-%t.log -XX:NumberOfGCLogFiles=5 -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCCause -XX:+PrintTenuringDistribution -XX:+PrintReferenceGC -XX:+PrintAdaptiveSizePolicy -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:+UnlockDiagnosticVMOptions -XX:G1SummarizeRSetStatsPeriod=1
volumes:
  configs:
    driver: local
  jenkins_home:
    driver: local
También usamos vaultenv para forjar secretos de Vault.

Tenga en cuenta algunas opciones de Java que nos ayudaron con la recolección de basura y los límites de recursos. Este artículo es muy bueno acerca de cómo ajustar Jenkins.

Y, por supuesto, ahora podemos implementar localmente una copia de Jenkins y experimentar con nuevas versiones del servidor y los complementos. Es muy cómodo.
Ahora tenemos una instalación limpia de Jenkins y complementos, que se pueden iniciar fácilmente en el producto. Agreguemos más configuración para ella.

Configuración de Jenkins como un complemento de Código (JCaSC) para la configuración del servidor


imagenEn general, hay un complemento llamado Jenkins Configuration as Code ( JCasC ), que le permite almacenar la configuración del servidor en formato de texto legible para humanos.

Con este complemento, puede describir configuraciones de seguridad, accesos, configuraciones para complementos, agentes, pestañas y mucho más.

La configuración se presenta en formato YAML y se divide en 5 bloques:

  • credenciales (descripción de los secretos del sistema)
  • jenkins (configuración de autorización y nube, configuración global, descripciones de agentes, algunas configuraciones de seguridad y pestañas)
  • seguridad (configuración de seguridad global, como scripts permitidos)
  • herramienta (configuración para herramientas externas como git, allure, etc.)
  • sin clasificar (otras configuraciones, como la integración con Slack)

imagenimagenEl complemento admite la importación de configuraciones desde una instalación de Jenkins existente.

Además, el complemento proporciona soporte para varios proveedores secretos , sin embargo, en este ejemplo solo usaremos variables de entorno.

credentials:
  system:
    domainCredentials:
    - credentials:
      - usernamePassword:
          description: "AWS credentials"
          id: "aws-creds"
          password: ${AWS_SECRET_ACCESS_KEY}
          scope: GLOBAL
          username: ${AWS_ACCESS_KEY_ID}
      - string:
          description: "Vault token"
          id: "vault-token"
          scope: GLOBAL
          secret: ${VAULT_TOKEN}
      ...
Así es como se pueden describir los secretos.

También utilizamos el complemento Amazon EC2 para generar agentes en AWS, y su configuración también se puede describir utilizando este complemento. La autorización de matriz nos permite configurar el acceso de los usuarios mediante código.

jenkins:
  authorizationStrategy:
    projectMatrix:
      permissions:
      - "Overall/Administer:ivan.a@example.org"
      - "Credentials/View:petr.d@example.org"
      ...
  clouds:
  - amazonEC2:
      cloudName: "AWS"
      privateKey: ${EC2_PRIVATE_KEY}
      region: "${AWS_REGION}"
      templates:
      - ami: "ami-12345678"
        amiType:
          unixData:
            sshPort: "22"
        connectionStrategy: PRIVATE_IP
        deleteRootOnTermination: true
        description: "jenkins_agent"
        idleTerminationMinutes: "20"
        instanceCapStr: "100"
        minimumNumberOfInstances: 0
        mode: EXCLUSIVE
        numExecutors: 1
        remoteAdmin: "jenkins"
        remoteFS: "/home/jenkins"
        securityGroups: "sg-12345678"
        subnetId: "subnet-12345678"
        type: C52xlarge
        ...
Descripción de agentes y accesos

El complemento admite algunas otras cosas que utilizamos. Con un proceso de prueba local de Jenkins debidamente organizado, puede encontrar y corregir errores de manera efectiva antes de que puedan entrar en las ventas de Jenkins.
Ahora tenemos una configuración reproducible para el servidor, sigue siendo el caso para los pequeños, es decir, el trabajo.

Utilizamos Job Builder para proyectos de estilo libre.


imagenHay varias formas de crear trabajos de estilo libre en Jenkins:

  • usando la interfaz web (la forma más fácil, subí y seguí)
  • directamente usando la API REST
  • utilizando complementos como Job DSL o JJB wrapper

Jenkins Job Builder (JJB) le permite configurar trabajos utilizando YAML o JSON. Y es bastante conveniente, porque puede configurar todos los trabajos y almacenar su estado en git condicional. Es decir, de hecho, podemos construir un proceso de CI / CD para nuestra herramienta CI / CD usando JJB.

.
├── config.ini
├── jobs
│   ├── Job1.yaml
│   | ...
│   └── Job2.yaml
└── scripts
    ├── job1.sh
    | ...
    └── job2.sh
Así es como (simplificado) se ve la estructura de la configuración del trabajo en el FS

- job:
    name: Job1
    project-type: freestyle
    auth-token: mytoken
    disabled: false
    concurrent: false
    node: jenkins_agent
    triggers:
      - timed: '0 3 * * *'
    builders:
      - shell:
          !include-raw: ../scripts/job1.sh
Y así es como se ve el trabajo en el archivo Job1.yaml, pasos en el script. job1.sh

El archivo de configuración JJB también parece simple.

$ cat config.ini
[job_builder]
ignore_cache=True
exclude=jobs/Job2

[jenkins]
url=https://jenkins.example.org
user=some_user
password=some_password

$ jenkins-jobs --conf config.ini test -r jobs/
$ jenkins-jobs --conf config.ini update -r jobs/
La aplicación de nuevos cambios se puede iniciar fácilmente con el comando Porjenkins-jobs update

sí mismo, el usuario para el que se creó el token debe tener los privilegios adecuados para crear y configurar el trabajo. Solo necesitamos aplicar un trabajo de inicialización (trabajo inicial), que aplicará los cambios usando JJB en Jenkins.

Vale la pena mencionar que JJB no es una "bala de plata", ya que algunos complementos no muy populares no son compatibles. Sin embargo, es una herramienta muy flexible para almacenar trabajos en código, incluido el soporte de macros .

Resumen


Ahora que hemos llegado al final de este artículo, me gustaría volver al principio y responder las preguntas formuladas al principio. Podemos responder "sí" a cada una de las preguntas planteadas.

En este artículo, no profundizamos en las sutilezas de configurar ciertas tecnologías o cómo configurar Jenkins correctamente , solo compartimos nuestra experiencia, que también puede ser útil para usted.

PD: En el artículo a menudo uso la palabra "trabajo" (del inglés "Trabajo", "tarea"), para mí suena más familiar que la "tarea" en el contexto de CI / CD en general o Jenkins.

All Articles