PHP

19 mai, 2020

Tarefas remotas: Laravel Envoy & GitlabCI

Publicidade

Olá pessoal hoje vamos ver uma ferramenta muito útil do Laravel chamada Envoy, que a grosso modo serve para executar tarefas remotas e é possível utilizá-lo para a tarefa de deploy. Nesse artigo, vamos ver como isso pode ser feito no gitlabCI, que é uma das ferramentas de CI mais utilizadas hoje em dia.

Se você não sabe o que é um pipeline de CI/CD leia essa introdução do gitlab assim estaremos falando a mesma língua durante o artigo (https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/).

Particularmente vejo a adoção de um pipeline de CI/CD eficaz como uma mudança de paradigma em relação ao fluxo clássico de deploy, onde trabalhamos durante semanas em um branch separado e no final fazemos um merge com dezenas ou até centenas de arquivos alterados e adicionados, com um pipeline eficaz podemos fazer deploys menores com uma frequência maior, assim a previsibilidade dos deploys aumenta e os métodos de validação, como análise estática e testes, tendem a capturar os erros antes de irem para produção aumentando a integridade do código sendo entregue.

Mas porque o Envoy?

Existem várias formas de estruturar o job de deploy, porém nas aplicações mais simples, como sites institucionais, blogs e até MVPs, o deploy consiste em um simples git pull seguido de alguns passos bem previsíveis, o que torna o Envoy a ferramenta perfeita para a missão, como veremos a seguir.

Importante ressaltar também que o envoy pode ser utilizado com aplicações não Laravel, tudo que você precisa é do Envoy instalado no computador em que os comandos serão executados, porém caso a aplicação seja Laravel podemos incluir o envoy como uma dependência de desenvolvimento, isso vai nos permitir utilizar o Envoy em todo computador que o nosso projeto estiver.

Pré requisitos

Para que isso funcione precisamos nos certificar de que o servidor da aplicação está corretamente configurado, o passo a passo abaixo descreve essa configuração:

  1. Acesso SSH no servidor em que a aplicação está, vamos precisar também das chaves SSH do usuário que o site foi configurado, caso você ainda não tenha gerado as chaves SSH pode seguir os passos descritos nessa documentação do github: (https://help.github.com/pt/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
  2. Agora vamos adicionar a chave pública recém gerada no nosso arquivo authorized_keys com o seguinte comando: cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys, isso vai permitir que o runner do gitlab acesse o nosso servidor.
  3. Seu repositório do projeto deve ser criado no gitlab e devemos adicionar a chave privada do servidor, que acabamos de gerar nos passos anteriores, como uma variável de CI/CD para que o nosso runner consiga acessar o servidor, podemos fazer isso acessando Settings > CI/CD > Variables na página do projeto no gitlab, como mostrado na imagem abaixo.

  1. por último vamos adicionar a chave pública(a mesma que adicionamos no authorized_keys) como uma deploy key no nosso projeto, acessando Settings > CI/CD > Deploy keys, o resultado deve parecer com o mostrado na seção enabled deploy keys da imagem seguinte.

Mão na massa

A grosso modo, o runner é um computador que vai executar o seu script do pipeline, dependendo do runner que você está usando na sua aplicação e dependendo se existem outras aplicações utilizando o mesmo runner, pode ser que o seu pipeline tenha que esperar em uma fila para ser executado, nesse caso pode ser interessante desenvolver o seu pipeline localmente e só subir ele quando o script do pipeline estiver pronto, como o Fábio Akita explica nesse artigo.

Você pode registrar seu próprio runner, isso vai te trazer muita liberdade, você pode até mesmo fazer um runner escalável isso vai te trazer agilidade no caso de haver muitos jobs rodando ao mesmo tempo, para saber como registrar seu próprio runner sugiro que você leia a documentação.

O pipeline no gitlab é configurado através do arquivo .gitlab-ci, que deve ficar na raiz do nosso projeto, esse arquivo é responsável por definir os estágios e os jobs presentes em cada estágio, como mostrado na figura abaixo.

O pipeline mostrado acima é bem simples e foi criado apenas para introduzir o Envoy neste artigo, mas uma aplicação real pode facilmente ter mais estágios, seeds e documentações automáticas são coisas comuns que costumam aparecer em um pipeline, você é livre para montar de acordo com a sua necessidade.

Esse é o .gitlab-ci completo que gerou o pipeline mostrado na imagem acima, vamos explicá-lo melhor adiante.

image: php:7.4

services:
  - mysql:5.7

variables:
  MYSQL_DATABASE: project
  MYSQL_ROOT_PASSWORD: secret
  DB_HOST: mysql

before_script:
  # Install PHP and composer dependencies
  - apt-get update
  - apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
  # Install needed extensions
  - docker-php-ext-install pdo_mysql
  # Install Composer
  - curl --silent --show-error https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

stages:
  - validation
  - deploy

phpunit:
  stage: validation
  script:
    - cp .env.gitlabci .env
    - composer install
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

deploy_production:
  stage: deploy
  script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

    - composer install
    - ./vendor/bin/envoy run deploy
  only:
    - master

A primeira linha do arquivo contém a declaração image: php:7.4, isso instrui o runner a rodar o nosso script dentro da imagem oficial do php 7.4, nos bastidores o runner do gitlab vai executar o nosso script dentro de um container Docker, você pode utilizar qualquer imagem presente no dockerhub ou em qualquer outro repositório de imagens, como o registry do gitlab, você pode até mesmo fazer suas próprias imagens para utilizar no seu pipeline.

As linhas seguintes contém as declarações de services:

services:
  - mysql:5.7

variables:
  MYSQL_DATABASE: project
  MYSQL_ROOT_PASSWORD: secret
  DB_HOST: mysql

Nessa seção você vai declarar cada serviço que sua aplicação usa, no nosso caso apenas o MySQL, geralmente podemos configurar os serviços através de variáveis de ambiente, como por exemplo as variáveis MYSQL_DATABASEMYSQL_ROOT_PASSWORD e DB_HOST que aparecem dentro da declaração variables:, essas linhas configuram o nome do banco de dados, o password do usuário root e o host que usaremos para conectar no MySQL, respectivamente.

A seção before_script, mostrada abaixo, define os passos que vão ser executados antes de cada job, geralmente procedimentos muito triviais e comuns aos jobs, no nosso caso estamos instalando todas as extensões do PHP que o Laravel utiliza e também o composer, interessante notar que o script leva mais de dois minutos para rodar cada job por isso pode ser uma boa ideia fazer uma imagem já contendo as extensões e o composer, assim conseguimos acelerar os jobs pois não precisaríamos da seção before_script.

before_script:
  # Install PHP and composer dependencies
  - apt-get update
  - apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
  # Install needed extensions
  - docker-php-ext-install pdo_mysql
  # Install Composer
  - curl --silent --show-error https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

A seção stages define os estágios do nosso pipeline, ela que é responsável por “desenhar” aquela imagem do pipeline que mostramos no início do projeto:

stages:
  - validation
  - deploy

O código abaixo define o nosso job do phpunit fazendo parte do estágio “validation”,

phpunit:
  stage: validation
  script:
    - cp .env.gitlabci .env
    - composer install
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

Por último temos o nosso job “deploy_production” que é responsável por entrar no servidor e executar a task de deploy, repare que ele tem a tag only com valor master isso garante que o deploy vai acontecer somente em eventos que acontecerem no branch master:

deploy_production:
  stage: deploy
  script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

    - composer install
    - ./vendor/bin/envoy run deploy
  only:
    - master

Nosso job de deploy executa a task “deploy” do Envoy porém os procedimentos da task de deploy estão definidos no nosso arquivo Envoy.blade.php:

@servers(['production' => 'cicd@l30.space'])

@task('deploy')
    cd ~/www
    git pull
    /usr/bin/php7.4 /usr/local/bin/composer install --ignore-platform-reqs
    /usr/bin/php7.4 artisan config:cache
    /usr/bin/php7.4 artisan migrate --force
@endtask

@task('phpmodules')
    /usr/bin/php7.4 -m
@endtask

A primeira declaração do arquivo é a diretiva @servers(['production' => 'cicd@l30.space']) essa diretiva define nosso server de produção, podemos declarar vários servidores e escolher o ambiente correto através de parâmetros ao executar o envoy, é possível fazer muita coisa com esse arquivo, para entender melhor todas as possibilidade sugiro que você leia a documentação do Envoy.

Reparem no usuário cicd na declaração do servidor de produção, é muito importante que as chaves inseridas no gitlab sejam referentes a esse usuário caso contrário o runner não conseguirá acessar o servidor.

A parte mais importante desse arquivo é o conteúdo da diretiva @task('deploy'), nela definimos os passos do deploy que no nosso caso são entrar no diretório wwwexecutar um git pullinstalar as dependências do projetocachear as configurações e executar as migrações respectivamente, um deploy com Laravel pode fugir ligeiramente desse template, como por exemplo reiniciar seu comando php artisan queue:work, mas esse é o grosso das tarefas.

Lembrando que o envoy pode ser executado do seu ambiente local também, para enfatizar isso fiz um comando phpmodules que lista os módulos do php, desde que sua chave pública local esteja inserida no arquivo authorized_keys referente ao usuário cicd você será capaz de executar os comandos remotamente.

Conclusão

Como já disse antes o Envoy pode ser uma ótima opção para projetos simples ou mesmo para um start rápido no seu projeto, isso vai te trazer agilidade para fazer o setup do pipeline permitindo que você foque no seu código.

Espero ter contribuído de alguma forma com esse artigo, se tiver alguma dúvida ou sugestão deixe nos comentários.