Pipelines complexos no Jenkins com BuildFlow Plugin

Neste post, vou mostrar o plugin que uso para criar pipelines de build no Jenkins, o BuildFlow Plugin.

Um pouco sobre pipelines

No livro "DevOps na prática", o Danilo Sato descreve uma ótima definição sobre um pipeline de entrega:

"Um pipeline de entrega é uma manifestação automatizada do processo de entrega de software, desde uma mudança até sua implantação em produção. Ele modela as etapas automatizadas e manuais do processo de entrega e é uma extensão natural da prática de integração contínua."

A figura abaixo, retirada do excelente livro "Continuous Delivery", mostra um exemplo de pipeline. Na imagem, é possível ver que o pipeline possui etapas como:

  • Build e testes de unidade
  • Testes de aceitação automatizados
  • Testes de aceitação de usuário
  • Release

pipeline cd


Mas peraí... já não existe o Build Pipeline Plugin?

O Build Pipeline Plugin é o plugin mais utilizado para esse fim. Ele oferece uma visualização bastante razoável, como podemos ver abaixo:

buildpipePlugin

Imagem: Exemplos de pipelines com o Build Pipeline Plugin

Porém, existem vários fatores que me fizeram desistir de usá-lo:

1. Pipelines são criados através de dependências upstream/downstream entre jobs

Isso significa que, para o funcionamento do plugin, seus jobs devem ter dependências entre eles. Considerando o pipeline que vimos na seção anterior, essas dependências funcionariam mais ou menos assim:

  • O job "Build e testes de unidade" deverá ter um passo pós-build com trigger para o job "Testes de aceitação automatizados"

    OU

  • O job "Testes de aceitação automatizados" deverá ser executado somente quando o job "Build e testes de unidade" terminar.

Em um pipeline simples como o da seção anterior, criar essas dependências não seria tão "doloroso". Entretanto, quando trabalhamos com pipelines mais complexos (com etapas em paralelo, forks, joins, etc.), isso se torna um problema. Além disso, é comum termos que utilizar outros plugins para nos auxiliar, como o Join Plugin, o Parameterized Trigger Plugin, entre outros, o que torna a configuração dos nossos jobs extremamente poluída.

2. Incluir uma nova etapa no pipeline é frustrante

Imagine que eu queira incluir um novo job no pipeline, mas de forma que fique entre dois jobs já existentes. Vou precisar:

  • Configurar o novo job
  • Ajustar o job anterior ao novo
  • Ajustar o job posterior ao novo

Quanto mais complexo seu pipeline fica, mais difícil se torna a manutenção dos jobs. E mais: já parou para pensar que a lógica de criação do seu pipeline está espalhada em vários jobs?

3. Executar jobs de forma isolada se torna uma dor de cabeça

Quando estava começando a criar os pipelines que uso diariamente, muitas vezes precisei rodar jobs isoladamente, sem que isso desencadeasse na execução do pipeline inteiro. Como cada job possui um trigger para outro, eu tinha duas opções:

  • Remover o trigger temporariamente (e lembrar de colocar de volta!). Depois de algumas vezes, isso se torna muito maçante.
  • Criar um job duplicado, sem conter nada relacionado à lógica do pipeline.

4. Bugs

Caso seu pipeline tenha passos executados em paralelo, forks ou joins, a visualização quebra. Ou seja, o que você visualiza acaba não representando o seu pipeline.

Outro bug conhecido: jobs triggados por plugins como o Promoted Builds Plugin não aparecem na visualização.


OK, mas então como o BuildFlow Plugin funciona?

O BuildFlow oferece uma DSL (Domain Specific Language) para orquestrar seus pipelines, de forma clara e simples.

O plugin é bastante extensível, mas os recursos básicos já são suficientes para montar pipelines interessantes. A página do plugin explica com mais detalhes cada comando, além de mostrar extensões criadas e como criar a sua própria extensão. Também recomendo consultar a página do projeto no GitHub.

Os comandos básicos são:

1. Build

Para fazer o build de um job:

build("nome_do_job")  

Caso o seu job seja parametrizado, você também pode passar os parâmetros que desejar:

build("nome_do_job", parametro1:TRUE, parametro2:"param")  

2. Parallel

Para executar dois ou mais jobs em paralelo:

parallel (  
    { build("job1") },
    { build("job2") },
    { build("job3") }
)

3. Retry

Caso um job possa gerar um falso negativo, você pode usar o comando retry para que o plugin tente executá-lo novamente.

Para rodar um job novamente, caso ele falhe:

retry(2) {  
    build("job_que_pode_falhar")
}

4. Ignore

Para ignorar o resultado de um job:

ignore(FAILURE) {  
    build("nome_do_job")
}

OBS: Além de FAILURE, tambem é possível ignorar os estados UNSTABLE e ABORTED.

5. Guard/Rescue

Esse comando funciona como um bloco try/finally. Caso você precise rodar um job de cleanup depois que um ou mais jobs executem (mesmo que eles falhem):

guard {  
    build("job_que_pode_falhar")
} rescue {
    build("cleanup")
}

Instalação

Para instalar o plugin, vá até a opção "Gerenciar Jenkins" na página inicial e, em seguida, na opção "Gerenciar plugins". Clique na aba "Disponíveis" e busque por "build flow plugin", conforme a imagem abaixo:

plugin install

Também é necessário instalar o plugin "Build Graph View", que gera uma visualização do nosso pipeline. Com isso, busque também por "build graph" e instale o plugin, conforme a imagem abaixo:

build graph install


Criando e Visualizando um Pipeline

Para criar um novo pipeline, basta clicar em "Novo item" na página inicial, dar um nome e selecionar a opção "Build Flow":

new flow

Os comandos para orquestrar seu pipeline devem ser informados na seção Flow da página de configuração, no campo Define build flow using flow DSL:

flow instructions

Imagem: configuração simples de um pipeline com o Build Flow Plugin, informando dois comandos: build("job1") e build("job2")

Após a execução de um pipeline, você pode visualizá-lo selecionando a opção Build Graph dentro do build:

graph


Exemplos

1) Pipeline simples

Considere uma aplicação chamada App1, cujo pipeline consiste nas seguintes etapas:

  • Build e Testes de Unidade
  • Deploy para ambiente de QA
  • Testes de UI
  • Release

Como ficaria esse pipeline com o BuildFlow plugin?

build("Build e Testes de Unidade")  
build("Deploy para ambiente de QA")  
build("Testes de UI")  
build("Release")  

A visualização gerada seria (os nomes dos jobs estão em inglês):

exemplo1

2) Pipeline um pouco mais complexo

Considere uma aplicação chamada App2, cujo pipeline consiste nas seguintes etapas:

  • Build e Testes de Unidade
  • Testes de Integração
  • Deploy para ambiente de QA (1)
  • Testes de API (1)
  • Deploy para ambiente de Performance (2)
  • Testes de Performance (2)
  • Release

Para executarmos essas etapas sequencialmente, basta fazer o mesmo que o exemplo anterior. Repare que as etapas marcadas com (1) e (2) poderiam ser executadas em paralelo, certo?

Vamos utilizar o comando parallel para isso:

build("Build e Testes de Unidade")  
build("Testes de Integração")

parallel (  
  { 
    build("Deploy para ambiente de QA")
    build("Testes de API") 
  },
  { 
    build("Deploy para ambiente de Performance")
    build("Testes de Performance")
  }
)

build("Release")  

A visualização ficaria assim:

exemplo2

3) Pipeline de Pipelines (pipelineception)

Suponha que ambas as aplicações App1 e App2 utilizem o mesmo banco de dados (criado por um job chamado "Criar Banco de Dados") e que eu queira um megazord pipeline que rode o job de criação do banco E os pipelines das aplicações (em paralelo!).

Peraí, posso fazer pipeline de pipelines? SIM! :)

Nosso pipeline gigante seria:

build("Criar Banco de Dados")

parallel (  
    {
        build("App1 - Build Pipeline")
    },
    {
        build("App2 - Build Pipeline")
    }
)

A visualização ficaria assim:

exemplo3


O BuildFlow Plugin também suporta passos manuais?

Infelizmente, o BuildFlow não tem suporte a passos manuais. Entretanto, você pode combinar o BuildFlow Plugin com o BuildPipeline Plugin para isso.

Considere a nossa aplicação App1 e o pipeline que fizemos no exemplo 1. Suponha que, após a etapa de Release, eu deva ter uma etapa manual de Deploy para Produção (ou seja, um pipeline de Continuous Delivery). Como eu poderia fazer isso?

Caso não tenha o BuildPipeline Plugin instalado, instale-o da mesma forma que instalamos os outros plugins. Na seção "Ações pós-builds" do pipeline "App1", selecione a opção "Build other projects (manual step)" e informe o job que deseja rodar manualmente, no campo Downstream Project Names. No nosso caso, vamos chamar esse job de App1 - Deploy to Production:

manual step

Na página inicial, clique na aba "+" para adicionar uma nova view:

new vis

Selecione a opção "Build Pipeline view", informe um nome para sua view e clique em OK:

new pipe

No campo "Select Initial Job", selecione o flow que representa o pipeline inicial da App1 e clique em OK:

pipe conf

Você verá o pipeline composto pelo anterior (com BuildFlow) e o job manual:

flow plus pipe

Imagem: Pipeline simples com dois estágios: Pipeline do Build Flow e App 1 - Deploy to Production. No canto inferior direito do segundo estágio, temos o trigger manual.


Conclusão

Com o BuildFlow, não se torna mais necessário poluir a configuração dos jobs com a lógica de criação do nosso pipeline. O plugin oferece uma DSL extremamente simples e com inúmeras possibilidades.

Não deixe de conferir a seção de Referências. Qualquer dúvida ou sugestão, entre em contato ou comente no final do post.

Abraços! :)


Referências

https://wiki.jenkins-ci.org/display/JENKINS/Build+Flow+Plugin
https://github.com/jenkinsci/build-flow-plugin
https://wiki.jenkins-ci.org/display/JENKINS/Build+Graph+View+Plugin
http://www.infoq.com/articles/orch-pipelines-jenkins
http://www.amazon.com/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912
https://www.coveros.com/using-the-build-flow-plugin-in-jenkins/


Sobre o autor: Stefan Teixeira trabalha como QA Engineer e, desde o final de 2014, tem se aventurado no mundo DevOps. É Bacharel em Ciência da Computação pela UFRJ e MBA em Garantia de Qualidade de Software pela Escola Politécnica da UFRJ. Entusiasta de Testes Automatizados (e de tudo que possa ser automatizado!), Agile Testing e da cultura DevOps.

Contatos: stefanfk@gmail.com | Twitter | LinkedIn


comments powered by Disqus