Alisson Machado
10 June 2020

Jenkins: Pipeline as Code com Vagrant e Docker

Hoje eu terminei de escrever o capitulo 10 do meu livro e o tema foi Pipelines, no livro tem um pouco mais de teoria mas o objetivo aqui é a prática, então vamos lá. Nesse repositório: https://github.com/AlissonMMenezes/Chapter10 Tem todos os arquivos que eu vou usar nesse post. Resumindo o que será feito, eu vou criar uma máquina virtual utilizando Vagrant, que vai ser usada como servidor de Docker e em cima do Docker eu vou rodar um container com Jenkins que vai executar todos os passos da pipeline. Caso você não saiba nada de Vagrant, eu tenho esse post aqui: http://alissonmachado.com.br/vagrant-ambiente-de-desenvolvimento-agil/ Caso você não saiba nada de Docker, eu também tenho esse post: http://alissonmachado.com.br/docker-basico/ Jenkins vamos aprender agora. Então vamos lá, primeira coisa que vou fazer é clonar o repositório:
PS C:\Users\1511 MXTI> git clone https://github.com/AlissonMMenezes/Chapter10
Cloning into 'Chapter10'...
remote: Enumerating objects: 72, done.
remote: Counting objects: 100% (72/72), done.
remote: Compressing objects: 100% (68/68), done.
Receiving objects:  79% (57/72)used 0 (delta 0), pack-reused 0 eceiving objects:  77% (56/72)
Receiving objects: 100% (72/72), 17.01 KiB | 791.00 KiB/s, done.
Resolving deltas: 100% (37/37), done.
e provisionar a minha VM.
PS C:\Users\1511 MXTI\Chapter10> vagrant up --provision
Bringing machine 'docker' up with 'virtualbox' provider...
==> docker: Importing base box 'ubuntu/bionic64'...
==> docker: Matching MAC address for NAT networking...
==> docker: Setting the name of the VM: Chapter10_docker_1591817408579_33553
==> docker: Clearing any previously set network interfaces...
==> docker: Preparing network interfaces based on configuration...
    docker: Adapter 1: nat
    docker: Adapter 2: hostonly
==> docker: Forwarding ports...
    docker: 22 (guest) => 2222 (host) (adapter 1)
==> docker: Running 'pre-boot' VM customizations...
==> docker: Booting VM...
Assim que a sua máquina terminar de subir, logue nela via ssh:
PS C:\Users\1511 MXTI\Chapter10> vagrant ssh
vagrant@docker:~$ sudo su -
root@docker:~#
E ai a gente pode executar o comando do Docker para subir um servidor Jenkins.
root@docker:~# docker run -tdi --restart=always --name jenkins -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts
Nesse caso estou mapeando as portas 8080 para acessar o Jenkins e 50000 para o Jenkins Slave que a gente vai subir mais adiante. Assim que o container do Jenkins terminar de subir, você pode acessar exatamente por esse endereço: http://192.168.33.11:8080/ , no Vagrant, foi configurado para que a VM suba com esse IP. Ao acessar o Jenkins pela primeira, ele vai te pedir para informar uma senha inicial, para que você possa prosseguir com a instalação: Você pode pegar a senha utlizando o comando abaixo:
root@docker:~# docker exec -ti jenkins cat /var/jenkins_home/secrets/initialAdminPassword
70fd86c5bd2a4d9ea1dd8df3c5c1de1e
E ai é só prosseguir com o padrão Next, Next Finish. No final eu defini usuário e senha como admin / admin. Agora a gente já pode importar a nossa pipeline do Git, então vamos criar uma New Job, do tipo Multibranch Pipeline chamada API Pipeline, na configuração em Repository HTTPS URL eu coloquei o mesmo repositório do inicio do post. https://github.com/AlissonMMenezes/Chapter10.git Após salvar a essa New Job, o Jenkins já vai escanear o repositório em busca de um arquivo chamado Jenkinsfile, ler esse arquivo e exibir todos os estágios da Pipeline como na imagem abaixo, onde temos. Preparing the Environment - Code Quality - Tests - Build - Deploy O conteúdo do Jenkinsfile é esse abaixo:
pipeline {
    agent  any;
    stages {
        stage('Preparing the environment') {
            steps {
                sh 'python -m pip install -r requirements.txt'
            }
        }
        stage('Code Quality') {
            steps {
                sh 'python -m pylint app.py'
            }
        }
        stage('Tests') {
            steps {
                sh 'python -m pytest'
            }
        }
   
    stage('Build') {
          agent { 
            node{
              label "DockerServer"; 
              }
          }
          steps {
              sh 'docker build https://github.com/AlissonMMenezes/Chapter10.git -t chapter10:latest'
          }
      }        
      stage('Deploy') {
          agent { 
            node{
              label "DockerServer"; 
              }
          }
          steps {
              sh 'docker run -tdi -p 5000:5000 chapter10:latest'
          }
      }
    }

}
Em cada estapa eu estou utilizando um módulo do python diferente, para preparar o ambiente, temos o pip, que vai instalar todos as dependências de aplicação. O pylint que vai fazer a verificação da qualidade de código e o pytest que vai executar os testes funcionais da API. Todo o código utilizado eu copiei desse post aqui: http://alissonmachado.com.br/python-flask-decorators-e-pytest/ Então vamos lá, a única coisa que preciso fazer dentro do container, é instalar o python e o pip, então na minha VM eu vou rodar os seguintes comandos:
root@docker:~# docker exec -ti -u 0 jenkins apt clean
root@docker:~# docker exec -ti -u 0 jenkins apt update
root@docker:~# docker exec -ti -u 0 jenkins apt install python python-pip -y
E também configurar a própria VM como node do Jenkins, pois no final da Pipeline precisamos de um servidor com Docker instalado para gerar a imagem e fazer o deploy. Então em Manage Jenkins > Manage Nodes and Clouds > New Node, vou criar um node chamado DockerServer. Na configuração de Remote Root Directory eu defini /tmp, e ai ao clicar no Node já cadastrado você vai ver uma imagem com as linhas de comando que você tem que executar para subi-lo. Na VM com Docker, vou executar os seguintes comandos:
root@docker:~# apt install openjdk-8-jre-headless -y
root@docker:~# wget http://192.168.33.11:8080/jnlpJars/agent.jar
root@docker:~# java -jar agent.jar -jnlpUrl http://192.168.33.11:8080/computer/DockerServer/slave-agent.jnlp -secret 70e5458b9cffa23da790ba2682f9a4b8dc1d79356a36c26d34296a57b5bc9e44 -workDir "/tmp"
Feito isso o node já deve estar rodando perfeitamente, então a gente pode executar a pipeline manualmente e ver se ela vai rodar. E ai está a pipeline rodando com sucesso =)