Implantar Jenkins como código

Nota perev.: esta é uma tradução de um artigo do blog de engenharia da Preply sobre como você pode usar a configuração como código para uma ferramenta de CI / CD tão popular como a Jenkins.

Em nossa empresa, tentamos seguir a prática de "Tudo é como código", isso se aplica não apenas aos recursos de infraestrutura, mas também ao monitoramento, ao trabalho de Jenkins, etc. Neste artigo, falarei sobre como usamos essa prática para implantar e dar suporte ao Jenkins. E isso se aplica não apenas à infraestrutura do servidor e agentes, mas também a plug-ins, acessos, trabalho e muitas outras coisas.

Além disso, neste artigo, tentaremos encontrar respostas para perguntas como:

  • Nosso Jenkins se tornou mais estável?
  • Podemos fazer alterações freqüentes no servidor e na configuração do trabalho?
  • A atualização do Jenkins ainda é uma dor para nós?
  • Podemos controlar todas as nossas mudanças?
  • Podemos restaurar rapidamente o Jenkins em caso de fakap?

imagem


Introdução


Geralmente, a primeira coisa que vem à mente ao mencionar a frase "Ferramentas de DevOps" é o sistema de CI / CD. Por exemplo, usamos o Jenkins porque executamos centenas de tarefas todos os dias, e são dezenas de milhares de compilações. Alguns recursos que usamos no Jenkins não estão disponíveis em outros sistemas de CI / CD ou têm funcionalidade limitada.


Gostaríamos de controlar o Jenkins completamente a partir do código, incluindo infraestrutura, configurações, tarefas e plug-ins. Tentamos executar o Jenkins no Kubernetes, mas ele não atendia às nossas necessidades, além de não ser fácil de dimensionar devido à sua arquitetura .

imagem
Isso será discutido

Infraestrutura para Jenkins


imagemUsamos a AWS e configuramos toda a infraestrutura usando o Terraform e outras ferramentas de hash, como o Packer e o Vault .

Como mencionado anteriormente, tentamos usar o Jenkins no Kubernetes e enfrentamos alguns problemas com o dimensionamento de PVC , recursos e uma arquitetura não tão bem projetada.

Aqui, usamos os recursos habituais da AWS: instâncias EC2, certificados SSL, balanceadores, Cloudfront etc. A imagem do SO ( AMI ) é configurada usando o Packer, que se integra perfeitamente ao Terraform e ao 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"]
    }]
}
Um exemplo de como é a configuração de uma imagem do SO no Packer.Por

sua vez, o arquivo packer_bootstrap.shcontém um conjunto de comandos com os quais o software é instalado dentro da imagem. Por exemplo, podemos instalar o Docker, docker-compose e vaultenv ou Datadog-agent para monitoramento. Em relação à infraestrutura para esta imagem, podemos usar Terraform, Cloudformation, Pulumi ou até Ansible.

Aqui está um exemplo de uma possível infraestrutura da AWS para o Jenkins: os

usuários efetuam login no Jenkins através de um balanceador interno, e os ganchos do Github acessam o servidor através de um externo. Usamos a integração do Jenkins com o GitHub, portanto, alguns links do servidor devem estar acessíveis na Internet. Existem muitas soluções diferentes aqui (por exemplo, uma lista branca de endereços IP , URLs ou tokens etc.). No nosso caso, usamos uma combinação de URLs permitidos e validação de token.
Assim, após as manipulações que fizemos, já temos uma infraestrutura pronta com a imagem do SO montada, a capacidade de monitorar e acessar o armazenamento secreto corporativo.

Usamos o Docker para instalar o Jenkins e seus plugins


imagemA próxima coisa que faremos é instalar o Jenkins e seus plugins. Como constantemente tínhamos problemas para atualizar os plug-ins, o objetivo principal era ter uma base clara dos plug-ins instalados e de suas versões no código.
E aqui o Docker nos ajudará, porque podemos pegar uma imagem pré - instalada do Docker e usá-la como base para nossa configuração.

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 da imagem do Docker, alguns pacotes são instalados como o Job Builder, que discutirei mais adiante, os repositórios também são registrados e os plug-ins especificados no arquivo plugins.txt.

Jenkins.instance.pluginManager.plugins.each{
  plugin ->
    println ("${plugin.getShortName()}:${plugin.getVersion()}")
}
Você pode obter uma lista dos plug-ins instalados no Jenkins clicando no link https://our-jenkins-url/scripte salvando a saída em um arquivo.Finalmenteplugins.txt

, a configuração para docker-compose, que executará o Jenkins no 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
Também usamos o vaultenv para forjar segredos do Vault.

Observe algumas opções de Java que nos ajudaram na coleta de lixo e nos limites de recursos. Este artigo é muito interessante sobre o ajuste do Jenkins.

E, é claro, agora podemos implantar localmente uma cópia do Jenkins e experimentar novas versões do servidor e plugins. É muito confortável.
Agora temos uma instalação limpa de Jenkins e plugins, que podem ser facilmente lançados no produto. Vamos adicionar mais configurações para ela.

Configurando o plugin Jenkins como um código (JCaSC) para configuração do servidor


imagemEm geral, existe um plug-in chamado Jenkins Configuration as Code ( JCasC ), que permite armazenar a configuração do servidor em formato de texto legível por humanos.

Usando este plug-in, você pode descrever configurações de segurança, acessos, configurações de plug-ins, agentes, guias e muito mais.

A configuração é apresentada no formato YAML e é dividida em 5 blocos:

  • credenciais (descrição dos segredos do sistema)
  • jenkins (configurações de autorização e nuvem, configurações globais, descrições de agentes, algumas configurações e guias de segurança)
  • segurança (configurações globais de segurança, como scripts permitidos)
  • ferramenta (configuração para ferramentas externas como git, allure etc.)
  • não classificado (outras configurações, como integração com o Slack)

imagemimagemO plug-in suporta a importação de configurações de uma instalação existente do Jenkins

, além de fornecer suporte a vários provedores secretos , no entanto, neste exemplo, usaremos apenas variáveis ​​de ambiente.

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}
      ...
É assim que os segredos podem ser descritos

: também usamos o plug - in Amazon EC2 para aumentar agentes na AWS, e sua configuração também pode ser descrita usando esse plug-in. A autorização da matriz nos permite configurar o acesso do usuário usando o 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
        ...
Descrição de agentes e acessos

O plug-in suporta outras coisas que usamos. Com um processo de teste local organizado da Jenkins, você pode encontrar e corrigir bugs efetivamente antes que eles possam entrar nas vendas da Jenkins.
Agora, temos uma configuração reproduzível para o servidor, e continua sendo o caso de pequenos, a saber - o trabalho.

Usamos o Job Builder para projetos de estilo livre


imagemExistem várias maneiras de criar trabalhos de estilo livre no Jenkins:

  • usando a interface da web (a maneira mais fácil, pulou e continuou)
  • diretamente usando a API REST
  • usando Plug-ins como Job DSL ou Wrapper JJB

O Jenkins Job Builder (JJB) permite configurar tarefas usando YAML ou JSON. E é bastante conveniente, porque você pode configurar todos os trabalhos e armazenar seu estado no git condicional. Na verdade, podemos construir um processo de CI / CD para nossa ferramenta de CI / CD usando JJB.

.
├── config.ini
├── jobs
│   ├── Job1.yaml
│   | ...
│   └── Job2.yaml
└── scripts
    ├── job1.sh
    | ...
    └── job2.sh
É assim que (simplificada) a estrutura da configuração do trabalho no FS parece

- 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
E é assim que o trabalho no arquivo se parece Job1.yaml, as etapas dojob1.sh script.O

arquivo de configuração JJB também parece simples.

$ 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/
O aplicativo de novas alterações pode ser facilmente iniciado usando o comando Porjenkins-jobs update

si só, o usuário para quem o token foi criado deve ter os privilégios adequados para criar e configurar o trabalho. Só precisamos aplicar um trabalho de inicialização (trabalho inicial), que aplicará as alterações usando JJB no Jenkins.

Vale ressaltar que o JJB não é uma “bala de prata”, pois alguns plugins não muito populares não são suportados. No entanto, é uma ferramenta muito flexível para armazenar trabalhos em código, incluindo suporte a macro .

Sumário


Agora que chegamos ao final deste artigo, gostaria de voltar ao início e responder às perguntas feitas no início. Podemos responder "sim" a cada uma das perguntas colocadas.

Neste artigo, não nos aprofundamos nas sutilezas de configurar determinadas tecnologias ou em como configurar o Jenkins corretamente , apenas compartilhamos nossa experiência, que também pode ser útil para você.

PS No artigo, costumo usar a palavra "trabalho" (do inglês. "Trabalho", "tarefa"), para mim parece mais familiar do que a "tarefa" no contexto de CI / CD em geral ou Jenkins.

All Articles