Série API em Flask - Parte 15 - Automatizando o processo de deploy com Fabric
Eae beleza galera. O objetivo deste artigo é aprender a utilizar o Fabric para para automatizar essa tarefas repetitivas e não termos que ficar entrando no servidor e fazendo todos os passos manuais para deploy. Vem comigo.
Para entendimento, segue os capítulos que serão abordados.
Capítulo 1: Introdução, configuração e Hello World
Capítulo 2: Organizando as dependências e requerimentos
Capítulo 3: Configurando o pytest e nosso primeiro teste
Capítulo 4: Configurando o Makefile
Capítulo 5: Adicionando o MongoDB
Capítulo 6: Criando e testando o modelo de usuários
Capítulo 7: Criando usuários
Capítulo 8: Listando usuários
Capítulo 9: Buscando usuários
Capítulo 10: Editando um usuário
Capítulo 11: Deletando um usuário
Capítulo 12: Autênticação por JWT
Capítulo 13: Criando um container Docker
Capítulo 14: Deploy Flask na Digital Ocean
Capítulo 15: Automatizando o processo de deploy com Fabric Estamos aqui
Capítulo 16: CI e CD com Jenkins, Python, Flask e Fabric
Capítulo 17: Utilizando o RabbitMQ com Flask e Sendgrid para enviar e-mails de boas vindas e ativar a conta do usuário
O repositório com todo o código fonte esta aqui. Os capítulos estão em branches
.
O que vamos automatizar
Para o propósito deste artigo e da necessidade de não ter que fazer todo o processo manual, vou utilizar o Fabric
para cumprir os seguintes passos:
Conectar no(s) servidor(es) remoto(s)
Entrar no diretório do aplicativo e executar um
git pull && git checkout
Instalar as bibliotecas do aplicativo
pip install
Executar migrations. Se tiver é claro.
Reiniciar os serviços do
supervisor
enginx
Configurações no servidor
root@teste-devops:~# groupadd fabric
Adicionando o usuário ao grupo criado:
root@teste-devops:~# usermod -a -G fabric apiflask
Entendendo o sudoers
Antes de abrir o arquivo /etc/sudoers
é preciso dar permissão de escrita. Por padrão ele é setado somente com a permissão de leitura.
root@teste-devops:~# chmod 600 /etc/sudoers
Em seguida podemos abrir o arquivo, vim /etc/sudoers
e adicionar as seguintes configurações.
# ....
# User privilege specification
root ALL=(ALL:ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
%fabric ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl, /usr/sbin/service
A sintaxe para configurar os privlégios são:
user/group host=users:groups tags commands
user/group - usuário ou grupo a qual estamos setando a permissão de sudo.
host - lista de hosts que podem executar o sudo.
users:groups - lista de usuarios e grupos.
tags – lista de configurações como NOPASSWD.
commands – comandos que aquele usuário ou grupo podem executar.
No nosso caso temos:
%fabric ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl, /usr/sbin/service
O %
representa que estamos indicando um grupo seguido do nome do grupo fabric
criado anteriormente. O ALL=(ALL)
representa todos hosts, usuários e grupo de usuários. O NOPASSWD:
representa que não será necessário se autenticar através de senha, ja que, configuramos nosso servidor para se autenticar via SSH e nem criamos uma senha para ele. E por úlimo temos limitamos os comandos, /usr/bin/supervisorctl, /usr/sbin/service
que aquele grupo pode executar com o sudo
.
Depois disso basta logar em outro terminal com o usuário apiflask
e testar as configurações.
O Fabric
De acordo com a definição do http://www.fabfile.org/ o Fabric é uma biblioteca desenvolvida para executar comandos shell remotamente através do SSH.
Ele não chega a ser uma ferramenta de configuração completa, com controle de estados de pacotes como o Ansible
porém conseguimos criar tarefas que automatiza o processo de um servidor remoto. Basta usar de acordo com a necessidade.
fabric1 x fabric2 x fabric3
O Fabric 1.x
foi desenvolvido em cima da versão do Python 2.7 e após mais de 10 anos de uso e estabilidade do Python 3.x a biblioteca Fabric
foi reescrita com a versão 3.x do python e por ter tido grandes mudanças deu-se o nome do pacote de fabric2
.
Ja o Fabric3
é um fork do Fabric1x
a qual a equipe mantenedora do fabric original não faz parte. Logo as próximas versões estarão no pacote fabric2
.
Criando o fabfile
Portanto vamos instalar o fabric, pip install fabric==2.4.0
, com o virtualenv ativado é claro.
A partir de agora na raíz do projeto criamos nosso arquivo fabfile.py
e vamos criar nossa primeira task.
# -*- coding: utf-8 -*-
from fabric import Connection
from invoke import task
def whoami(c):
"""
Print the envirioment and user
"""
c.run('whoami')
@task
def uname(c):
""" Prints information about the host. """
c.run('uname -a')
whoami(c)
O parâmetro c
da função whoami
e da task uname
correspondem ao contexto
atual a qual o fabric se encontra.
Por exemplo ao executarmos o comando abaixo ele irá pegar os dados minha maquina, no caso o contexto atual.
$ fab uname
Linux notebook 4.15.0-39-generic #42-Ubuntu SMP Tue Oct 23 15:48:01 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
lucas
Porém ao executar o mesmo comando com a opção --hosts
, temos uma outra saída de resultados:
$ fab --hosts apiflask@165.227.124.70 uname
Linux teste-devops 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64 GNU/Linux
apiflask
Alguns comandos básicos são:
fab
: Para exibir um help do comandofab --hosts ou -H
: Lista de hosts separados por virgulafab --list ou -l
: Exibe a lista de tasks, possuem o decorator@task
dofabfile.py
Exemplo:
$ fab -l
Available tasks:
deploy Deploy the code to context
status Status supervisor and webserver
stop Stopping supervisor
uname Prints information about the host.
Sem mais delongas, vamos ao arquivo fabfile.py
completo. Observe bem os comentários em cada função ou task.
c.run
: Executa um comando shell. Utilizo ele para chamar ogit
, oupip
por exemplo.with c.cd
: Altero para o diretório especificado.c.sudo
: Executo um comando como sudo.
# -*- coding: utf-8 -*-
from fabric import Connection
from invoke import task
# Crio constantes referente as confgurações realizadas no servidor
SITE_DIR = '~/sites/flask-api-users'
VENV = '~/venvs/'
PYTHON_BIN = VENV + 'bin/python'
PIP_BIN = VENV + 'bin/pip'
def whoami(c):
"""
Print the envirioment and user
"""
# Executo o comando whoami
c.run('whoami')
def git(conn, cmd):
"""
Create a method to execute git on the server
"""
# Altero para o diretorio SITE_DIR e executo o comando git
with conn.cd('{}'.format(SITE_DIR)):
conn.run('git {}'.format(cmd))
def checkout(conn, branch='master', tag=None):
"""
Run git checkout to branch or tag
"""
if tag:
git(conn, 'checkout {}'.format(tag))
else:
git(conn, 'checkout {}'.format(branch))
def pull(conn):
"""
Run git pull command on repository
"""
git(conn, "pull")
def fetch(conn):
"""
Run git pull command on repository
"""
git(conn, "fetch")
def sudo(conn, cmd):
conn.sudo(cmd)
def install_requirements(conn, env='prod'):
"""
Install all requirements by the server
"""
# Crio um comando para executar o pip de acordo
cmd = "{} install -r requirements/{}.txt".format(PIP_BIN, env)
with conn.cd('{}'.format(SITE_DIR)):
conn.run(cmd)
@task
def uname(c):
""" Prints information about the host. """
c.run('uname -a')
whoami(c)
def restart(conn):
"""
Restart supervisor and webserver
"""
# restarto o supervisor e o nginx
conn.sudo("supervisorctl restart api_users:gunicorn_api_users")
conn.sudo("service nginx restart")
@task
def status(c):
"""
Status supervisor and webserver
"""
c.sudo("supervisorctl status api_users:gunicorn_api_users")
c.sudo("service nginx status")
@task
def stop(c):
"""
Stopping supervisor
"""
c.sudo("supervisorctl stop api_users:gunicorn_api_users")
@task
def deploy(c, tag=None):
"""
Deploy the code to context
"""
# 1 - Atualizo meu repo com um git pull
fetch(c)
# 2 - Altero para branch master ou para uma tag
if tag:
checkout(c, tag)
else:
checkout(c)
# 3 - Instalo os requirements, por padrão, de prod.txt
install_requirements(c)
# Restarto o serviço.
restart(c)
Resultado
Vamos ver nosso fab deploy em ação: