Featured image of post Introdução com Jenkins e Docker

Introdução com Jenkins e Docker

Primeiros passos para executar localmente

Introdução

Projeto

O Jenkins é uma das ferramentas de automação mais populares e poderosas, amplamente utilizada para integrar e implantar projetos de software de forma contínua. Embora seja possível iniciar rapidamente o Jenkins usando um simples comando Docker, essa configuração básica é semelhante a um “Hello World” em programação: útil para começar, mas insuficiente para ambientes de produção.

Neste post, vamos além do básico. O objetivo aqui é guiá-lo com algumas técnicas para uma arquitetura robusta e escalável para o Jenkins, utilizando Docker e Docker Compose. Vamos explorar conceitos fundamentais, como o isolamento de máquinas, e configurar um ambiente mais próximo da realidade. Ao final, você terá uma estrutura sólida, pronta para ser adaptada e expandida conforme suas próprias necessidades.

Se você está pronto para sair do básico e construir um ambiente de CI/CD profissional, siga adiante!

“Ambiente mais próximo da realidade” não significa ambiente de produção. Utilize este post como um guia de referência, e não como uma solução pronta.

Pré-Requisitos

Antes de mergulharmos nos detalhes técnicos, é essencial garantir que alguns pré-requisitos estejam atendidos:

  • Linux (No meu caso, uso Windows como SO principal, entáo utilizo um virtualizador como WSL, Virtualbox, VmWare, etc…)
  • Docker

Conceitos principais

Com os pré-requisitos definidos, podemos agora explorar os conceitos principais que orientarão a configuração do Jenkins.

Isolamento

A primeira boa prática de utilização do Jenkins é não realizar tudo em uma mesma máquina. Tanto por uma questão de segurança quanto de escalabilidade, é recomendado o uso de, no mínimo, duas máquinas.

A primeira máquina é a que chamamos normalmente de controller. Ela é a máquina principal, sendo responsável principalmente pela interface, configurações e controle de execução de jobs.

As demais máquinas são chamadas de nodes. Estas máquinas são responsáveis pela execução de todos os jobs.

Configuração das máquinas

Tanto o controller quanto os nodes precisam ser configurados dentro de um sistema operacional.

Instalar os pré-requisitos, criar pastas, e definir permissões de usuário são alguns exemplos. Além disso, vamos aproveitar para realizar, neste momento, algumas configurações do Jenkins que só surtirão efeito na inicialização ou reinicialização.

Usaremos as imagens do Jenkins e o Docker Compose para esta fase.

Configurações do Jenkins

Com o Jenkins em execução, precisamos realizar as configurações para o ambiente funcionar:

  • Credenciais de acesso
  • Configuração da comunicação entre o controller e o node

Estas configurações não serão feitas manualmente. Utilizaremos o plugin Configuration as Code para automatizar este processo.

Estrutura básica

Entendidos os conceitos que serão aplicados, é hora de começar a definir a estrutura básica do projeto.

Esta é a estrutura básica. Este modelo será utilizado em posts futuros.

.
└── demo/
    ├── casc/                             #Configuração do Jenkins as code
    │   └── *.yaml
    ├── plugins/
    │   └── plugins.txt                   #Lista de plugins
    ├── scripts/
    │   └── *.groovy                      #Scripts de inicialização do jenkins
    ├── ssh                               #Chave pública e privada
    ├── docker-compose.yaml
    ├── Dockerfile
    ├── start.sh
    └── stop.sh

Dockerfile

Dentro dessa estrutura, o Dockerfile desempenha um papel central. Vamos examiná-lo em detalhes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
FROM jenkins/jenkins:lts

RUN mkdir -p $JENKINS_HOME/.ssh
RUN chown -R jenkins:jenkins $JENKINS_HOME/.ssh
RUN touch $JENKINS_HOME/.ssh/known_hosts
RUN chmod 600 $JENKINS_HOME/.ssh/known_hosts
COPY --chown=jenkins:jenkins ./ssh/jenkins_key  $JENKINS_HOME/.ssh/jenkins_key
RUN chmod 600 $JENKINS_HOME/.ssh/jenkins_key

COPY --chown=jenkins:jenkins ./scripts/ /usr/share/jenkins/ref/init.groovy.d/

COPY --chown=jenkins:jenkins ./plugins/plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt

RUN mkdir -p $JENKINS_HOME/casc_configs
RUN chown -R jenkins:jenkins $JENKINS_HOME/casc_configs
COPY --chown=jenkins:jenkins ./casc/* $JENKINS_HOME/casc_configs/

ENV CASC_JENKINS_CONFIG="$JENKINS_HOME/casc_configs/"
ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"

Um resumo do conteúdo do Dockerfile:

  • linhas 3 a 8: Criação do diretório .ssh e a cópia da chave privada armazenada na pasta ssh do projeto.
  • linha 10: Cópia dos scripts Groovy armazenada na pasta scripts do projeto.
  • linhas 12 e 13: Cópia da lista de plugins e a instalação dos plugins na imagem.
  • linhas 15 a 17: Criação da pasta do plugin JCasC e cópia dos arquivos de configuração da pasta casc (Configuration as Code) do projeto.
  • linha 20: Define a variável de ambiente CASC_JENKINS_CONFIG para configurar a pasta padrão onde o Jenkins irá recuperar estes arquivos.
  • linha 21: Configuração da variável de ambiente JAVA_OPTS informando que não é necessária a execução do Wizard de instalação.

docker-compose.yaml

Depois de configurar o Dockerfile, precisamos orquestrar os serviços com o docker-compose.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
version: '3.7'
volumes:
  jenkins_data:
    name: jenkins_data
services:
  jenkins:
    container_name: jenkins_master
    build: .
    ports:
      - 8080:8080
      - 50000:50000
    volumes:
      - jenkins_data:/var/jenkins_home/
      - /var/run/docker.sock:/var/run/docker.sock
  ssh-agent:
    container_name: jenkins_agent
    image: jenkins/ssh-agent
    environment:
      - JENKINS_AGENT_SSH_PUBKEY=${JENKINS_AGENT_SSH_PUB}
    depends_on:
      - jenkins

Não há muito segredo no docker-compose. O serviço jenkins executa a imagem oficial do Jenkins, com as principais portas mapeadas, e o serviço ssh-agent que será o node de execução dos pipelines.

Na linha 19 configuramos a variável de ambiente JENKINS_AGENT_SSH_PUBKEY para informar a chave pública gerada.

plugins.txt

Agora que os serviços estão configurados, vamos definir os plugins essenciais no arquivo plugins.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
cloudbees-folder
build-timeout
credentials
timestamper
ws-cleanup
pipeline-milestone-step
pipeline-input-step
pipeline-stage-step
pipeline-graph-analysis
pipeline-rest-api
pipeline-stage-view
pipeline-build-step
pipeline-model-api
pipeline-model-extensions
pipeline-stage-tags-metadata
pipeline-model-definition
ssh-credentials
ssh-slaves
config-file-provider
workflow-aggregator
configuration-as-code

executors.groovy

Seguindo as recomendações no início do artigo, é necessário ajustar os executores do Jenkins, o que faremos no script executors.groovy:

1
2
import jenkins.model.*
Jenkins.instance.setNumExecutors(0) // Recommended to not run builds on the built-in node

ssh-agent.yaml

Por fim, precisamos configurar a comunicação segura entre o controller e os nodes, usando o ssh-agent.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
credentials:
  system:
    domainCredentials:
      - credentials:
          - basicSSHUserPrivateKey:
              scope: SYSTEM
              id: ssh_agent_key
              username:
              description: "SSH passphrase with private key file. Private key provided"
              privateKeySource:
                directEntry:
                  privateKey: "${readFile:${JENKINS_HOME}/.ssh/jenkins_key}"
jenkins:
  nodes:
  - permanent:
      launcher:
        ssh:
          credentialsId: "ssh_agent_key"
          host: "jenkins_agent"
          port: 22
          sshHostKeyVerificationStrategy: "nonVerifyingKeyVerificationStrategy"
      name: "jenkins_agent"
      numExecutors: 2
      remoteFS: "/tmp"
      retentionStrategy: "always"

Aqui, configuramos o jenkins pelo plugin configuration as code:

  • Linhas 1 a 12: Criação de uma credencial do jenkins do tipo SSH Username with private key com a chave privada.
  • Linhas 13 a 25: Criamos a configuração do node

Start up

Com todas as configurações em ordem, é hora de colocar tudo em prática e iniciar o ambiente. Vamos utilizar o script start.sh para automatizar a execução do docker-compose (especialmente útil para execuções repetidas):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh
work_path=$(pwd)
echo $work_path
sh $work_path/stop.sh

ssh-keygen -t rsa -f $work_path/ssh/jenkins_key -N "" -C "jenkins"

export JENKINS_AGENT_SSH_PUB=$(cat $work_path/ssh/jenkins_key.pub)
echo $JENKINS_AGENT_SSH_PUB
docker-compose up --build -d

Em resumo, o script start.sh:

  • Executa o script stop.sh para garantir a limpeza de arquivos criados em execuções anteriores.
  • Gera as chaves públicas e privadas
  • Cria a variável de ambiente JENKINS_AGENT_SSH_PUB utilizada no docker-compose.yaml
  • Executa o docker-compose

E o script stop.sh:

  • Deleta as chaves da pasta ssh
  • Para o serviço
  • Remove os containers
  • Remove os volumes criados

Execução

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[+] Building 51.1s (18/18) FINISHED                                                                              docker:default
 => [jenkins internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 855B                                                                                       0.0s
 => [jenkins internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                            0.0s
 => [jenkins internal] load metadata for docker.io/jenkins/jenkins:lts                                                     0.0s
 => [jenkins internal] load build context                                                                                  0.0s
 => => transferring context: 4.15kB                                                                                        0.0s
 => [jenkins  1/13] FROM docker.io/jenkins/jenkins:lts                                                                     0.0s
 => CACHED [jenkins  2/13] RUN mkdir -p /var/jenkins_home/.ssh                                                             0.0s
 => CACHED [jenkins  3/13] RUN chown -R jenkins:jenkins /var/jenkins_home/.ssh                                             0.0s
 => CACHED [jenkins  4/13] RUN touch /var/jenkins_home/.ssh/known_hosts                                                    0.0s
 => CACHED [jenkins  5/13] RUN chmod 600 /var/jenkins_home/.ssh/known_hosts                                                0.0s
 => [jenkins  6/13] COPY --chown=jenkins:jenkins ./ssh/jenkins_key  /var/jenkins_home/.ssh/jenkins_key                     0.1s
 => [jenkins  7/13] RUN chmod 600 /var/jenkins_home/.ssh/jenkins_key                                                       1.8s
 => [jenkins  8/13] COPY --chown=jenkins:jenkins ./scripts/ /usr/share/jenkins/ref/init.groovy.d/                          0.2s
 => [jenkins  9/13] COPY --chown=jenkins:jenkins ./plugins/plugins.txt /usr/share/jenkins/ref/plugins.txt                  0.1s
 => [jenkins 10/13] RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt                                          40.7s
 => [jenkins 11/13] RUN mkdir -p /var/jenkins_home/casc_configs                                                            1.9s
 => [jenkins 12/13] RUN chown -R jenkins:jenkins /var/jenkins_home/casc_configs                                            2.7s
 => [jenkins 13/13] COPY --chown=jenkins:jenkins ./casc/* /var/jenkins_home/casc_configs/                                  0.5s
 => [jenkins] exporting to image                                                                                           2.8s
 => => exporting layers                                                                                                    2.8s
 => => writing image sha256:0bde8aac40a647cb9663b5b21d72d25a8eb607719a22bbe0ad9ea67e8e97890d                               0.0s
 => => naming to docker.io/library/jenkins-jenkins                                                                         0.0s
[+] Running 3/3
 ✔ Volume "jenkins_data"     Created                                                                                       0.0s 
 ✔ Container jenkins_master  Started                                                                                       0.4s 
 ✔ Container jenkins_agent   Started                                                                                       0.3s 

Jenkins

Concluões

O exemplo deste artigo está disponível aqui. Está muito longe de um ambiente produtivo de execução, mas para serve como uma base para meus testes quando preciso de um ambiente local de execução.

Até a próxima!

Referências

https://github.com/jenkinsci/docker/blob/master/README.md#install-plugins https://github.com/jenkinsci/docker-ssh-agent?tab=readme-ov-file#running-with-the-ssh-build-agents-plugin https://www.jenkins.io/doc/book/security/controller-isolation/ https://plugins.jenkins.io/configuration-as-code/

Foto de Chilli Charlie na Unsplash

comments powered by Disqus
Criado com Hugo
Tema Stack desenvolvido por Jimmy