O Problema
O time está gastando muito tempo com implantações e atualizações de sistemas nos ambientes de homologação e produção, esse time faz essas operações de forma manual ou seja acessando o servidor e rodando todos os comandos necessários, vale lembrar que esse processo é repetido diversas vezes durante o mês.
Precisamos de alguma forma melhorar esse processo dando mais agilidade e deixando o time livre para trabalhar em outras tarefas.
Quais práticas existem para ajudar ?
Em uma situação como essa podemos tirar proveito dos conceitos de CI/CD. Antes de entrar no significado dessas siglas, vamos deixar claro que DevOps não é CI/CD, DevOps é sobre pessoas, um desenvolvedor não pode simplesmente avisar que terminou e esperar que o time de Operação faça a implantação, o time de Operação quando se depara com um erro após a implantação não pode simplesmente abrir um card de erro e encerrar sua parte. Em caso de falhas sistêmicas logo após a implantação quem é o profissional mais apto para resolver o problema ? um profissional de Operações ou o desenvolvedor que criou determinada feature ?
Vamos imaginar agora que o time de operação não faz mais implantações, quem vai fazer agora é o próprio time de desenvolvimento. Mas por que isso? “Quem pariu Mateus que o balance” ou “cachorro com muitos donos morre de fome”, essas frases falam de responsabilidade, quando atualizamos uma aplicação no servidor do cliente não estamos apenas trocando o .jar
ou exe
, estamos entregando uma solução para resolver um problema, o acompanhamento dessa solução desde a implantação até está em uso diário deve ser do time que planejou e desenvolveu essa solução. Estruturas como Servidores, Sistema operacional, versão de libs e etc depois de configurados é muito difícil dar problema, o que está mais propício a dar problema é código desenvolvido, é aplicação.
Ok, como podemos melhorar isso?
Vamos falar sobre como o Jenkins e Jenkinsfile conseguem ajudar na criação de um pipeline, a sintaxe que precisamos usar para conseguir escrever um pipeline é baseada em duas formas uma chamada de Declarativa e outra chamada de Script, nesse nosso papo vamos usar a forma Declarativa.
A Arquitetura ficará mais ou menos assim:
Iniciando com Jenkinsfile
Um jenkinsfile nada mais é do que um arquivo com código que será interpretado pelo Jenkins, esse código é baseado em Groovy uma linguagem de programação bastante usada no mercado e roda na JVM do Java. Um Jenkinsfile é como qualquer outro código do software,precisa ser versionado, pode evoluir e que provavelmente precisará de manutenções.
Vamos rapidamente voltar nos passos básicos para entregar um software em produção ou homologação.
- Devemos rodar testes de unidade
- Devemos rodar build
- Devemos armazenar artefato
- Devemos implantar artefato no servidor
pronto, agora que mapeamos os passos básicos que fazemos para entregar um software, podemos começar a pensar em como automatizar e deixar uma máquina fazendo esse trabalho.
Para iniciarmos vamos criar um arquivo na raiz do projeto chamado Jenkinsfile, não precisa de extensão, basta Jenkinsfile, vamos colocar o conteúdo abaixo:
pipeline {
agent any
stages {
stage('Test') {
steps {
echo 'Testando..'
}
}
stage('Build') {
steps {
echo 'Compilando..'
}
}
stage('Store') {
steps {
echo 'Armazenando....'
}
}
stage('DeploY') {
steps {
echo 'Implantando....'
}
}
}
}
Analisando o conteúdo que colamos, vamos observar algumas palavras reservadas como, pipeline, agent, stages, stage e steps, vamos conhecer um pouco mais sobre eles nos blocos abaixo.
Pipeline
Quando estamos montando um Pipeline de forma Declarativa todo o conteúdo fica dentro desse nó
principal que chamamos de pipeline {}
Agent
Define o Executor
que será responsável por executar os comando dos pepiline, no formato Declarativo essa definição é obrigatória pois sem não saberá como executar. Podemos definir um agent
de forma global como em nosso exemplo, no topo do arquivo, ou dentro de cada stage
dando mais possibilidades para nosso pipeline.
Temos alguns tipos de Agent, mas para não alongar muito vamos focar em none, any e Docker.
Tipo none
é bem simples, não será executado por nenhum Executor
mas obriga a definição nos stage
.
Tipo any
diz que será executado por qualquer Executor
disponível, em nosso exemplo até o momento estamos usando esse valor.
agent any
Tipo Docker
diz que o Executor
usará uma imagem docker especificada para que ele execute seu trabalho, essa forma é a que usaremos em nossos exemplos, usando esse tipo de agent conseguimos realizar várias operações bastando ter uma imagem docker que contenha o que precisamos e pronto, não precisaremos “encher” nosso Jenkins com Plugins.
Confira mais detalhes aqui.
Stages e Stage
Agrupa um ou vários stage
cada stage representa uma etapa do nosso pipeline, como test,build, deploy e etc.
...
stages {
stage('Test') {
steps {
echo 'Testando..'
}
}
stage('Build') {
steps {
echo 'Compilando..'
}
}
stage('Store') {
steps {
echo 'Armazenando....'
}
}
stage('DeploY') {
steps {
echo 'Implantando....'
}
}
}
...
Steps
Define como o stage será executado, em nosso caso estamos apenas executando o comando echo
mas podemos ter vários outros comando para executar o objetivo do stage
.
Evoluindo colunas
Agora vamos evoluir mais ainda nosso Jenkinsfile preenchendo algumas colunas para entender como o Jenkins vai executar os passos que definimos.
Evoluindo coluna de Teste
Vamos ver como podemos evoluir a stage
de teste, atualmente está super simples, apenas exibindo um texto no console, veja abaixo:
...
stage('Test') {
steps {
echo 'Testando..'
}
}
...
Nosso projeto foi escrito em java usando maven como gerenciador, para rodarmos os testes unitários executamos o comando:
$ mvn test
Mas a partir de agora quem vai executar esse comando será o Jenkins, nesse nosso papo usaremos um agent
do tipo docker
com uma imagem contendo o Java já configurado, primeiro vamos mudar o agent
global que está como agent any
para agent none
dessa forma vamos definir o agent
apenas dentro do stage
como no exemplo abaixo:
...
agent none
stages {
stage('Test') {
agent { docker 'adoptopenjdk/openjdk11:jdk-11.0.9.1_1' }
}
}
...
agora basta implementar o steps
passando o comando para rodar os testes, veja como fica:
...
agent none
stages {
stage('Test') {
agent { docker 'adoptopenjdk/openjdk11:jdk-11.0.9.1_1' }
sh './mvnw test'
}
}
}
...
Se observar usamos o comando sh
dentro da chave steps
, pronto com isso finalizamos nossa primeira coluna, nosso arquivo está fincando assim:
pipeline {
agent none
stages {
stage('Test') {
agent { docker 'adoptopenjdk/openjdk11:jdk-11.0.9.1_1' }
steps {
sh './mvnw test'
}
}
stage('Build') {
agent any
steps {
echo 'Compilando..'
}
}
stage('Store') {
agent any
steps {
echo 'Armazenando....'
}
}
stage('DeploY') {
agent any
steps {
echo 'Implantando....'
}
}
}
observe que nos demais stage
adicionei um agent
do tipo any
pois como comentei, se colocarmos no agent
global o tipo none
seremos obrigados a definir um agent
em cada stage
.
Evoluindo coluna de Build
Como na coluna de teste, vamos definir um agent
do tipo docker
, passar uma imagem com java e executar um outro comando para fazer o build da aplicação.
stage('Test') {
agent { docker 'adoptopenjdk/openjdk11:jdk-11.0.9.1_1'}
steps {
sh './mvnw package'
}
}
pronto agora o Jenkins saberá empacotar toda nossa aplicação em um arquivo jar
que ficará disponível para a próxima etapa que pode ser por exemplo criar uma imagem docker.
Conclusão
Vimos com é simples criar um arquivo Jenkinsfile e começamos a entender como o Jenkins vai usa-lo para automatizar os passos das nossas entregas de Software.
Curtiu ? Me segue nas redes 😉