Como construir um pipeline de implantação inicial eficaz
Publicados: 2022-03-11Adoro construir coisas — que desenvolvedor não gosta? Adoro pensar em soluções para problemas interessantes, escrever implementações e criar belos códigos. No entanto, o que eu não gosto é de operações . Operações são tudo o que não está envolvido na construção de um ótimo software, desde a configuração de servidores até o envio do código para a produção.
Isso é interessante porque, como desenvolvedor freelancer de Ruby on Rails, frequentemente tenho que criar novos aplicativos da Web e repetir o processo de descobrir o lado DevOps das coisas. Felizmente, depois de criar dezenas de aplicativos, finalmente defini um pipeline de implantação inicial perfeito. Infelizmente, nem todos entenderam como eu — eventualmente, esse conhecimento me levou a mergulhar e documentar meu processo.
Neste artigo, mostrarei meu pipeline perfeito para usar no início do seu projeto. Com meu pipeline, cada push é testado, o branch master é implantado para preparação com um novo dump de banco de dados da produção e as tags versionadas são implantadas na produção com backups e migrações acontecendo automaticamente.
Observe que, como é meu pipeline, também é opinativo e adequado às minhas necessidades; no entanto, você pode se sentir à vontade para trocar qualquer coisa que não goste e substituí-la por qualquer coisa que lhe agrade. Para meu pipeline, usaremos:
- GitLab para hospedar o código.
- Por quê: Meus clientes preferem que seu código permaneça em segredo, e o nível gratuito do GitLab é maravilhoso. Além disso, o CI gratuito integrado é incrível. Obrigado GitLab!
- Alternativas: GitHub, BitBucket, AWS CodeCommit e muito mais.
- GitLab CI para construir, testar e implantar nosso código.
- Por quê: Integra-se ao GitLab e é gratuito!
- Alternativas: TravisCI, Codeship, CircleCI, DIY com Fabric8 e muito mais.
- Heroku para hospedar nosso aplicativo.
- Por quê: Funciona fora da caixa e é a plataforma perfeita para começar. Você pode alterar isso no futuro, mas nem todos os novos aplicativos precisam ser executados em um cluster Kubernetes criado especificamente. Até a Coinbase começou no Heroku.
- Alternativas: AWS, DigitalOcean, Vultr, DIY com Kubernetes e muito mais.
Old-school: crie um aplicativo básico e implante-o no Heroku
Primeiro, vamos recriar um aplicativo típico para alguém que não está usando pipelines sofisticados de CI/CD e deseja apenas implantar seu aplicativo.
Não importa o tipo de aplicativo que você está criando, mas você precisará do Yarn ou do npm. Para o meu exemplo, estou criando uma aplicação Ruby on Rails porque ela vem com migrações e uma CLI, e já tenho a configuração escrita para ela. Você pode usar qualquer estrutura ou linguagem de sua preferência, mas precisará do Yarn para fazer o controle de versão que faço mais tarde. Estou criando um aplicativo CRUD simples usando apenas alguns comandos e sem autenticação.
E vamos testar se nosso aplicativo está rodando conforme o esperado. Eu fui em frente e criei alguns posts, só para ter certeza.
E vamos implantá-lo no Heroku enviando nosso código e executando migrações
$ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...
Finalmente vamos testá-lo em produção
E é isso! Normalmente, é aqui que a maioria dos desenvolvedores deixa suas operações. No futuro, se você fizer alterações, precisará repetir as etapas de implantação e migração acima. Você pode até fazer testes se não estiver atrasado para o jantar. Isso é ótimo como ponto de partida, mas vamos pensar um pouco mais sobre esse método.
Prós
- Rápido para configurar.
- As implantações são fáceis.
Contras
- Not DRY: Requer repetir os mesmos passos em cada mudança.
- Sem versão: “Estou revertendo a implantação de ontem para a da semana passada” não é muito específico daqui a três semanas.
- Não é à prova de código ruim: você sabe que deve executar testes, mas ninguém está olhando, então você pode forçar apesar do teste ocasional quebrado.
- Não é à prova de mau ator: e se um desenvolvedor insatisfeito decidir quebrar seu aplicativo enviando código com uma mensagem sobre como você não pediu pizzas suficientes para sua equipe?
- Não é dimensionado: permitir que todos os desenvolvedores possam implantar daria a eles acesso no nível de produção ao aplicativo, violando o Princípio do Mínimo Privilégio.
- Sem ambiente de teste: os erros específicos do ambiente de produção não serão exibidos até a produção.
O pipeline de implantação inicial perfeito
Vou tentar algo diferente hoje: vamos ter uma conversa hipotética. Vou dar voz a “você” e falaremos sobre como podemos melhorar esse fluxo de corrente. Vá em frente, diga alguma coisa.
Diga o quê? Espere, posso falar?
Sim, foi isso que eu quis dizer sobre dar-lhe uma voz. Como você está?
Estou bem. Isso parece estranho
Eu entendo, mas apenas role com ele. Agora, vamos falar sobre o nosso pipeline. Qual é a parte mais irritante sobre a execução de implantações?
Ah, isso é fácil. A quantidade de tempo que eu perco. Você já tentou empurrar para Heroku?
Sim, assistir o download de suas dependências e o aplicativo sendo construído como parte do git push
é horrível!
Eu sei direito? É insano. Eu gostaria de não ter que fazer isso. Há também o fato de que eu tenho que executar migrações *após* a implantação, então eu tenho que assistir ao show e verificar se minha implantação é executada
Ok, você poderia realmente resolver esse último problema encadeando os dois comandos com &&
, como git push heroku master && heroku run rails db:migrate
, ou apenas criando um script bash e colocando-o em seu código, mas ainda assim, ótima resposta, o tempo e repetição é uma verdadeira dor.
Sim, é realmente chato
E se eu lhe disser que você pode consertar esse bit imediatamente com um pipeline de CI/CD?
E agora? O que é aquilo?
CI/CD significa integração contínua (CI) e entrega/implantação contínua (CD). Foi bastante difícil para mim entender exatamente o que era quando eu estava começando, porque todo mundo usava termos vagos como “amalgamação de desenvolvimento e operações”, mas simplificando:
- Integração Contínua: Garantir que todo o seu código seja mesclado em um só lugar. Faça com que sua equipe use o Git e você usará o CI.
- Entrega Contínua: Garantir que seu código esteja continuamente pronto para ser enviado. Ou seja, produzir rapidamente uma versão de leitura para distribuição do seu produto.
- Implantação Contínua: Retirar o produto da entrega contínua e simplesmente implantá-lo em seus servidores.
Ah, entendi agora. Trata-se de fazer meu aplicativo magicamente ser implantado no mundo!
Meu artigo favorito explicando CI/CD é da Atlassian aqui. Isso deve esclarecer todas as suas dúvidas. De qualquer forma, voltando ao problema.
Sim, de volta a isso. Como evito implantações manuais?
Configurando um pipeline de CI/CD para implantar em Push to master
E se eu lhe disser que você pode consertar essa parte imediatamente com um CI/CD? Você pode enviar para o seu controle remoto do GitLab ( origin
) e um computador será gerado para simplesmente enviar esse seu código para o Heroku.
De jeito nenhum!
Sim caminho! Vamos voltar ao código novamente.
Crie um .gitlab-ci.yml
com o seguinte conteúdo, trocando toptal-pipeline
pelo nome do seu aplicativo Heroku:
image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master
Empurre isso para cima e observe-o falhar na página Pipelines do seu projeto. Isso porque está faltando as chaves de autenticação da sua conta Heroku. Corrigir isso é bastante simples, no entanto. Primeiro, você precisará da sua chave de API Heroku. Obtenha-o na página Gerenciar conta e adicione as seguintes variáveis secretas nas configurações de CI/CD do repositório GitLab:
-
HEROKU_EMAIL
: o endereço de e-mail que você usa para fazer login no Heroku -
HEROKU_AUTH_KEY
: A chave que você recebeu do Heroku
Isso deve resultar em um GitLab funcional para a implantação do Heroku em cada push. Quanto ao que está acontecendo:
- Ao empurrar para dominar
- A CLI do Heroku é instalada e autenticada em um contêiner.
- Seu código é enviado para o Heroku.
- Um backup do seu banco de dados é capturado no Heroku.
- As migrações são executadas.
Você já pode ver que não apenas está economizando tempo automatizando tudo para um git push
, como também está criando um backup do seu banco de dados em cada implantação! Se alguma coisa der errado, você terá uma cópia do seu banco de dados para voltar.
Criando um ambiente de teste
Mas espere, pergunta rápida, o que acontece com seus problemas específicos de produção? E se você encontrar um bug estranho porque seu ambiente de desenvolvimento é muito diferente da produção? Certa vez, encontrei alguns problemas estranhos do SQLite 3 e do PostgreSQL quando executei uma migração. As especificidades me escapam, mas é bem possível.

Eu uso estritamente o PostgreSQL no desenvolvimento, nunca misturo mecanismos de banco de dados como esse e monitoro diligentemente minha pilha quanto a possíveis incompatibilidades.
Bem, isso é um trabalho tedioso e eu aplaudo sua disciplina. Pessoalmente, sou muito preguiçoso para fazer isso. No entanto, você pode garantir esse nível de diligência para todos os futuros desenvolvedores, colaboradores ou contribuidores em potencial?
Errrr— Sim, não. Você me pegou lá. Outras pessoas vão estragar tudo. Qual é o seu ponto, no entanto?
Meu ponto é, você precisa de um ambiente de encenação. É como a produção, mas não é. Um ambiente de teste é onde você ensaia a implantação na produção e detecta todos os erros antecipadamente. Meus ambientes de teste geralmente espelham a produção, e eu despejo uma cópia do banco de dados de produção na implementação de teste para garantir que nenhum caso de canto chato atrapalhe minhas migrações. Com um ambiente de teste, você pode parar de tratar seus usuários como cobaias.
Isso faz sentido! Então, como eu faço isso?
Aqui é onde fica interessante. Eu gosto de implantar o master
diretamente no staging.
Espere, não é onde estamos implantando a produção agora?
Sim, é, mas agora estaremos implantando no staging.
Mas se master
for implantado no staging, como implantaremos na produção?
Usando algo que você deveria estar fazendo anos atrás: versionar nosso código e enviar tags Git.
Git tags? Quem usa tags Git?! Isso está começando a parecer muito trabalho.
Com certeza foi, mas felizmente, eu já fiz todo esse trabalho e você pode simplesmente despejar meu código e funcionará.
Primeiro, adicione um bloco sobre o staging deploy ao seu arquivo .gitlab-ci.yml
, eu criei um novo aplicativo Heroku chamado toptal-pipeline-staging
:
… variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...
Em seguida, altere a última linha do seu bloco de produção para executar em tags Git com versão semântica em vez do branch master:
deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619
Executar isso agora falhará porque o GitLab é inteligente o suficiente para permitir apenas o acesso de branches “protegidos” às nossas variáveis secretas. Para adicionar tags de versão, acesse a página de configurações do repositório do seu projeto GitLab e adicione v*
às tags protegidas.
Vamos recapitular o que está acontecendo agora:
- Ao enviar para master, ou enviar um commit marcado
- A CLI do Heroku é instalada e autenticada em um contêiner.
- Seu código é enviado para o Heroku.
- Um backup de sua produção de banco de dados é capturado no Heroku.
- O backup é despejado em seu ambiente de teste.
- As migrações são executadas no banco de dados de preparo.
- Ao enviar um commit com tag semanticamente de versão
- A CLI do Heroku é instalada e autenticada em um contêiner.
- Seu código é enviado para o Heroku.
- Um backup de sua produção de banco de dados é capturado no Heroku.
- As migrações são executadas no banco de dados de produção.
Você se sente poderoso agora? Eu me sinto poderoso. Lembro-me que, na primeira vez que cheguei até aqui, liguei para minha esposa e expliquei todo esse pipeline em detalhes excruciantes. E ela nem é técnica. Fiquei super impressionada comigo mesma, e você também deveria estar! Ótimo trabalho chegando até aqui!
Testando cada empurrão
Mas há mais, já que um computador está fazendo coisas para você de qualquer maneira, ele também pode executar todas as coisas que você tem preguiça de fazer: testes, erros de linting, praticamente qualquer coisa que você queira fazer, e se algum deles falhar, eles ganharam não passar para a implantação.
Eu adoro ter isso no meu pipeline, isso torna minhas revisões de código divertidas. Se uma solicitação de mesclagem passar por todas as minhas verificações de código, ela merece ser revisada.
Adicione um bloco de test
:
test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop
Vamos recapitular o que está acontecendo agora:
- A cada push ou solicitação de mesclagem
- Ruby e Node são configurados em um contêiner.
- As dependências estão instaladas.
- O aplicativo é testado.
- Ao enviar para master, ou enviar um commit marcado, e somente se todos os testes passarem
- A CLI do Heroku é instalada e autenticada em um contêiner.
- Seu código é enviado para o Heroku.
- Um backup de sua produção de banco de dados é capturado no Heroku.
- O backup é despejado em seu ambiente de teste.
- As migrações são executadas no banco de dados de preparo.
- Ao enviar um commit com tag semanticamente de versão, e somente se todos os testes passarem
- A CLI do Heroku é instalada e autenticada em um contêiner.
- Seu código é enviado para o Heroku.
- Um backup de sua produção de banco de dados é capturado no Heroku.
- As migrações são executadas no banco de dados de produção.
Dê um passo para trás e maravilhe-se com o nível de automação que você alcançou. A partir de agora, tudo o que você precisa fazer é escrever código e enviar. Teste seu aplicativo manualmente na preparação, se quiser, e quando se sentir confiante o suficiente para empurrá-lo para o mundo, marque-o com versão semântica!
Versionamento Semântico Automático
Sim, é perfeito, mas falta alguma coisa. Não gosto de procurar a última versão do aplicativo e marcá-lo explicitamente. Isso exige vários comandos e me distrai por alguns segundos.
Ok, cara, pare! É o bastante. Você está apenas superestimando isso agora. Funciona, é brilhante, não estrague uma coisa boa passando por cima.
Ok, eu tenho uma boa razão para fazer o que estou prestes a fazer.
Ore, ilumine-me.
Eu costumava ser como você. Fiquei feliz com essa configuração, mas depois errei. git tag
lista as tags em ordem alfabética, v0.0.11
está acima v0.0.2
. Certa vez, acidentalmente marquei um lançamento e continuei fazendo isso por cerca de meia dúzia de lançamentos até ver meu erro. Foi quando decidi automatizar isso também.
Aqui vamos nós novamente
Ok, então, felizmente, temos o poder do npm à nossa disposição, então encontrei um pacote adequado: Execute yarn add --dev standard-version
e adicione o seguinte ao seu arquivo package.json
:
"scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },
Agora você precisa fazer uma última coisa, configurar o Git para enviar tags por padrão. No momento, você precisa executar git push --tags
para enviar uma tag, mas fazer isso automaticamente no git push
normal é tão simples quanto executar git config --global push.followTags true
.
Para usar seu novo pipeline, sempre que quiser criar uma execução de lançamento:
-
yarn patch
para lançamentos de remendo -
yarn minor
para lançamentos menores -
yarn major
para grandes lançamentos
Se você não tiver certeza sobre o significado das palavras “maior”, “menor” e “patch”, leia mais sobre isso no site de versão semântica.
Agora que você finalmente concluiu seu pipeline, vamos recapitular como usá-lo!
- Escrever código.
- Confirme e envie-o por push para testá-lo e implantá-lo no teste.
- Use o
yarn patch
para marcar uma versão de patch. -
git push
para empurrá-lo para produção.
Resumo e etapas adicionais
Acabei de arranhar a superfície do que é possível com pipelines de CI/CD. Este é um exemplo bastante simplista. Você pode fazer muito mais trocando o Heroku pelo Kubernetes. Se você decidir usar o GitLab CI, leia os documentos do yaml porque há muito mais que você pode fazer armazenando arquivos em cache entre implantações ou salvando artefatos!
Outra grande mudança que você pode fazer nesse pipeline é introduzir gatilhos externos para executar o versionamento e o lançamento semânticos. Atualmente, o ChatOps faz parte do plano pago e espero que eles o liberem para planos gratuitos. Mas imagine poder acionar a próxima imagem por meio de um único comando do Slack!
Eventualmente, à medida que seu aplicativo começa a se tornar complexo e requer dependências no nível do sistema, pode ser necessário usar um contêiner. Quando isso acontecer, confira nosso guia: Getting Started with Docker: Simplifying Devops .
Este aplicativo de exemplo realmente está ativo e você pode encontrar o código-fonte dele aqui.