O objetivo deste tutorial é explicar os passos necessários para configuração de um pipeline de deploy contínuo de um bot Rasa, utilizando o GitLabCI.

Os exemplos e estratégias utilizados neste tutorial são baseados no pipeline utilizado na TAIS. Para uma referência completa basta analisar o arquivo de configuração do pipeline da TAIS no GitLab.

A configuração de pipelines utilizando o GitLabCI se dá a partir da utilização de um arquivo de configuração chamado gitlab-ci.yml. Neste tutorial aprenderemos configurar um arquivo de utilização do CI.

Cada um dos jobs criados no CI são executados dentro de containers na infraestrutura do GitLab.

O primeiro passo para configuração é definir uma imagem base a ser utilizada nos jobs do pipeline. Pode-se definir uma imagem padrão que será utilizado em todos os jobs ou definir imagens diferentes para cada um dos jobs existentes.

Para definir uma imagem global é necessário utilizar a configuração abaixo:

image: python:3.6-slim

test style:
  stage: test style
  script:
    - pip -V
    - python -V
    - pip install -r dev.requirements.txt
    - flake8 --exclude venv

run dataset validator:
  stage: validate format
  image: lappis/coach:latest
  script:
    - cd coach/
    - make run-validator

No exemplo acima, foi definida uma imagem base chamada python:3.6-slim. Em seguida foram definidos dois jobs de teste, o primeiro deles utilizará a imagem padrão do python que foi definida na primeira linha, já que este não possui nenhuma tag de definição de imagem. O segundo job utilizará a imagem lappis/coach:latest, já que possui uma tag de definição de imagem que sobreescreve a imagem base.

Definição dos stages

Os jobs serão criados a partir da organização em stages, sendo que estes serão executados de acordo com a ordem de prioridade definida. Essa característica define a dependência dos jobs, uma vez que caso o job de um estágio anterior falhe todos os jobs subsequentes de todos os próximos jobs serão cancelados e não serão executados.

Caso mais de um job seja definido com o mesmo estágio, a execução destes jobs será paralelizada pelo próprio GitLab e eles serão executados simultaneamente.

Uma estratégia que pode ser utilizada é separar os jobs do pipeline em três fases principais: test, build e deploy.

stages:
  - test
  - build
  - deploy

No job test style exemplificado acima, o stage é definido como test. Então ele será um dos jobs rodados no começo da execução do pipeline.

Estratégia de Build

Como este tutorial é baseado na utilização de serviços docker, a estratégia de build é focada na construção e publicação da imagem utilizada pelos serviços.

Está exemplificado abaixo um job de build para imagens docker no CI do GitLab.

build bot:
  stage: build
  image: docker
  tags:
    - docker
  services:
    - docker:dind
  script:
    - docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASSWORD
    - docker build -f docker/bot/bot.Dockerfile -t lappis/bot:latest .
    - docker push lappis/bot:latest
  only:
    - master
  environment: homolog

A imagem utilizada deve ser a imagem docker e deve ser adicionada uma label que defina a utilização do serviço docker:dind, um acrônimo para “Docker in Docker”. O que indica ao CI que serão utilizados comandos Docker dentro do container onde o job está sendo executado.

A label script define quais comandos serão executados durante esse job. Neste caso, são 3 comandos/etapas para a criação e publicação da imagem.

1 - Primeiro é feito o login no Dockerhub utilizando os dados de acesso configurados em variáveis secretas no próprio repositório. Para entender como utilizar estas variáveis no GitLab basta seguir a documentação oficial;

2 - Logo após, a imagem é construída a partir do Dockerfile contido no próprio repositório do projeto, com o nome definido;

3 - Por último, a imagem é publicada e enviada para o registry do Dockerhub, e estará pronta para ser utilizada no estágio de deploy;

Estratégias de Deploy

Serão ensinadas duas estratégias principais, estas estratégias são baseadas no uso de docker e arquiteturas de microserviços. Existem diversas estratégias que podem ser adotadas para fazer o deploy de um serviço docker, aqui serão ensinadas duas delas: A primeira utilizando o protocolo ssh e a segunda utilizando uma aplicação chamada Watchtower.

Deploy via ssh

Para esta estratégia é utilizado um job que utiliza o protocolo ssh para criar uma sessão dentro da máquina onde será feito o deploy do serviço e atualizar o serviço docker.

O job definido a seguir executa um script shell chamado deploy_bot que faz autenticação na máquina através da senha do usuário root e o IP da máquina, estas informações estão configuradas utilizando as variáveis secretas do GitLabCI.

deploy bot to homolog:
  stage: deploy
  <<: *set_ssh_config
  environment: homolog
  script:
    - ./scripts/deploy_bot.sh $TAIS_SERVER_PASSWORD $TAIS_SERVER_IP
  only:
    - master

A linha <<: *set_ssh_config é uma referência à um conjunto de comandos que está definido no mesmo arquivo de configuração, sendo ele:

.set_ssh_config: &set_ssh_config
  before_script:
    - apt-get update -y
    - apt-get install sshpass -y

O que essa linha faz é executar os comandos acima no começo do job, instalando a dependência de sshpass utilizada no script deploy_bot. Como mostrado abaixo, esse script recria o serviço de bot, baixando a nova imagem e recriando o container para este serviço.

#!/bin/bash

sshpass  -p $1 ssh -o StrictHostKeyChecking=no root@$2 <<-'ENDSSH'
    cd rouana/
    docker-compose stop bot
		docker-compose rm -f bot
		docker-compose pull bot
    docker-compose up -d bot
ENDSSH

Watchtower

O Watchtower é um serviço que monitora os containers criados dentro do mesmo contexto, e sempre que a imagem sendo utilizada pelo container é atualizada este serviço faz uma atualização no serviço, baixando a nova imagem e recriando o container do serviço com as mesma configurações, porém com a imagem nova.

Para utilizar esta estratégia basta adicionar um serviço utilizando a imagem do watchtower ao mesmo arquivo de configuração dos serviços, ou garantir manualmente que ele esteja na mesma rede dos serviços que se quer monitorar. Além disso, é preciso adicionar uma label com.centurylinklabs.watchtower.enable indicando quais serviços devem ser ou não monitorados e atualizados de acordo com o valor que pode ser false ou true.

version: '2'

services:

  kibana:
    image: docker.elastic.co/kibana/kibana:6.4.2
    restart: unless-stopped
    ports:
      - 5601:5601
    environment:
      - SERVER_PORT=5601
      - ELASTICSEARCH_URL=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    labels:
      - "com.centurylinklabs.watchtower.enable=false"

  watchtower:
     image: containrrr/watchtower
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock
     command: --interval 30
     labels:
       - "com.centurylinklabs.watchtower.enable=false"

O serviço Watchtower fica consultando o repositório da imagem a ser monitorado a cada X segundos, para saber ser houve alguma atulização ou não. O período de tempo utilizado nesta estratégi pode ser definido com o parâmetro --interval, como exemplificado acima onde é definido com o valor de 30 segundos.

Esta estratégia possui a vantagem de que a estratégia de deploy está totalmente contida dentro da própria infraestrutura onde estão rodando os serviços, desta forma não há dependência de um outro serviço e não é necessários ter credenciais de acesso configuradas em outros ambientes como na estratégia anterior. Além disso, caso o objetivo seja fazer somente deploy dos serviços e não haja um pipeline mais elaborado, utilizar esta abordagem traz uma solução simples para o problema. Porém, a utilização desta estratégia é menos flexível em relação à generização, uma vez que funciona apenas para estratégias de deploy baseadas em docker.

Testando Jobs

Configurar corretamente um pipeline muitas vezes pode ser um processo um tanto quanto demorado e custoso, uma vez que o teste das configurações deve ser realizado diretamente no CI executando builds reais.

Para testar localmente alguns jobs e facilitar o processo de debug e configuração do pipeline é possível utilizar uma instância local do GitLab Runner.

Utilizando como exemplo o job test style:

test style:
  stage: test style
  script:
    - pip -V
    - python -V
    - pip install -r dev.requirements.txt
    - flake8 --exclude venv

Para executar este job localmente bastaria instalar o runner do GitLab, e em seguida executar o seguinte comando:

gitlab-runner exec docker "test style"