Deployment de aplicações Python com Fabric

Uma coisa é fato: subir uma nova versão de uma aplicação é um procedimento que, se feito manualmente, costuma demandar tempo precioso, independente do tamanho da aplicação e dos frameworks utilizados no projeto. Não há o que discutir.

Na verdade, ninguém parecia se importar em automatizar tarefas “mecânicas” como o deployment, coisa que mudou de figura ao passo que metodologias ágeis — juntamente com técnicas como Integração Contínua — passaram a se tornar populares. Desde então, algum esforço vem sendo investido para criar ferramentas para automatização de muitas dessas tarefas.

Fabric: ferramenta Python para deployment

Apesar de já ter utilizado algumas dessas ferramentas no desenvolvimento de projetos em Java, plataforma na qual eu possuo uma experiência maior, eu ainda não conhecia nenhuma ferramenta em Python que me permitisse automatizar o deployment de uma aplicação.

Como eu estava começando a ficar de saco cheio de ter que atualizar os arquivos de uma aplicação Django via SCP, eu resolvi que já era hora de ir atrás de alguma ferramenta que me ajudasse a automatizar esse processo. Finalmente, depois de muita procura, eu encontrei Fabric, uma ferramenta mais ou menos parecida com um Capistrano “capado”.

A instalação do Fabric é bem simples para quem costuma usar o easy_install:

daniel ~ $ sudo easy_install Fabric

Basta executar esse comando para que os fontes da última versão do Fabric (0.0.9 no momento da publicação deste texto) sejam baixados e instalados.

Atenção: Quem utiliza máquinas Windows, provavelmente precisará do Cygwin, já que o Fabric assume o uso de ambientes Unix.

Fabric a 3.000 pés

Neste momento você já deve conseguir executar o comando fab no terminal. Se tudo correu bem até aqui, estamos prontos continuar.

Vamos começar criando o arquivo fabfile.py no diretório-raíz da aplicação. Coloque o seguinte conteúdo neste arquivo:

set(
    project = 'project_name',
 
    package     = '$(project).zip',
 
    # Remote servers
    fab_user    = 'usuario_ssh',
    fab_hosts   = ['servidor1.com', 'servidor2.com'],
)
 
 
def deploy():
    "Deploy the application to the production servers."
    build()
 
def build():
    "Build the application."
    prepare()
 
def prepare():
    "Prepare the build directory."
    clean()
 
def clean():
    "Remove the build directory."
    pass

Cada método definido no arquivo é equivalente a uma task do Capistrano. No início temos o método set que nos permite configurar as variáveis necessárias para o script. Veja que, neste método set, indicamos na variável fab_hosts os servidores nos quais os comandos remotos devem ser executados.

Ok, tente executar o comando fab no terminal, no mesmo diretório onde se encontra o arquivo fabfile.py. Se tudo correr bem, você receberá a seguinte saída:

daniel ~  $  fab
   Fabric v. 0.0.9, Copyright (C) 2008 Christian Vest Hansen.
   Fabric comes with ABSOLUTELY NO WARRANTY; for details type `fab warranty'.
   This is free software, and you are welcome to redistribute it
   under certain conditions; type `fab license' for details.

No commands given.
Available commands are:
   build    : Build the application.
   clean    : Remove the build directory.
   deploy   : Deploy the application to the production servers.
   help     : Display Fabric usage help, or help for a given command.
   license  : Display the Fabric distribution license text.
   list     : Display a list of commands with descriptions.
   prepare  : Prepare the build directory.
   set      : Set a Fabric variable.
   shell    : Start an interactive shell connection to the specified hosts.
   warranty : Display warranty information for the Fabric software.
Done.

Veja que as tasks que criamos estão disponíveis para serem executadas via linha de comando. Para executar as tasks clean e deploy, por exemplo, utilizaríamos o seguinte comando:

daniel ~  $  fab clean deploy

Simples, não é? Antes de vermos um exemplo de script completo, vamos conhecer os três principais métodos utilizados em scripts Fabric: local, run e put:

  • local: Serve para executar comandos na máquina local. Ex: local('mkdir build');
  • run: Igual ao local, mas executa os comandos nas máquinas remotas (indicadas na variável fab_hosts);
  • put: Envia um arquivo para as máquinas remotas. Ex: put('arquivo.zip', '/home/user/app.zip').

Antes de prosseguirmos, vamos voltar a olhar a chamada ao método set, no início do código. Nele, eu usei um recurso do Fabric conhecido como lazy evaluation. No caso, temos a variável package que recebe o valor '$(project).zip'. Esse $(project) indica que queremos que o valor da variável project substitua o placeholder. Isso permite que variáveis sejam interpoladas umas dentro das outras.

Por exemplo, se dentro de uma task executarmos o comando local('rm $(package)'), isso significa que o Fabric irá parsear a string, transformando-a em 'rm project_name' para só então executar tal comando na máquina local.

Como veremos a seguir, eu utilizei este recurso exaustivamente no meu script. Em caso de dúvidas, consulte a documentação do Fabric.

Finalmente, o script completo!

Antes de mostrar o script que eu venho utilizando para fazer o deployment de uma aplicação Django, veja como seus arquivos e diretórios estão organizados: (isso deve ajudar a entender melhor o script)

Estrutura do projeto

Estrutura do projeto

E, finalmente, o script:

set(
    project = 'destaqueweb',
 
    # Build output directory
    build_path  = 'build',
    package     = '$(project).zip',
 
    # Media
    media_src   = 'media',
    media_root  = 'public_html',
    media_path  = '$(media_root)/$(project)/$(media_src)',
    media_build = '$(build_path)/$(media_path)',
 
    # Application code
    app_root  = 'wsgi_apps',
    app_path  = '$(app_root)/$(project)',
    app_build = '$(build_path)/$(app_path)',
 
    # Remote servers
    fab_user    = 'destaquenet',
    fab_hosts   = ['destaquenet.com'],
 
    destination = '/home/destaquenet',
    touch       = '$(destination)/$(media_root)/$(project)/index.wsgi',
)
 
def deploy():
    "Deploy the application to the production server."
    build()
 
    # Upload the zip file
    put('$(build_path)/$(package)', '$(destination)/$(package)')
 
    # Extract the zip file
    run('cd $(destination); unzip -uo $(package); rm $(package)')
 
    # Reload the application
    run('touch $(touch)')
 
def build():
    "Build the application."
    prepare()
 
    # Copy application files to the build directory
    local('cp -R $(project)/* $(build_path)/$(app_path)')
    local('cp -R $(media_src)/* $(build_path)/$(media_path)')
 
    # Remove unecessary files
    local('find $(build_path) -name *.pyc -delete')
    local('find $(build_path) -path *upload* -delete')
    local('find $(build_path) -path *fixtures* -delete')
 
    # Create the zip file
    local('cd $(build_path);zip -r $(package) $(media_root) $(app_root)')
 
def prepare():
    "Prepare the build directory."
    clean()
 
    local('mkdir -p $(media_build)')
    local('mkdir -p $(app_build)')
 
def clean():
    "Remove the build directory."
    local('rm -fR $(build_path)')

Este código deve ser facilmente entendido por quem tem trabalha com uma certa facilidade no terminal.

Basicamente o que o código faz é organizar os arquivos da aplicação para que estes fiquem dispostos da mesma forma utilizada pelo servidor de produção, enviar os arquivos dentro de um pacote ZIP para os servidores e descompactá-los no local correto, além de executar um touch no script WSGI para que o Apache recarregue a aplicação.

Tudo pronto. Agora basta executar fab deploy no terminal para subir a aplicação para o(s) servidor(es) desejado(s).

Fabric e ambiente de homologação

Uma coisa que vem se tornando comum é o uso de diferentes ambientes onde uma aplicação costuma ser implantada (ex: ambiente de homologação). Se sua empresa costuma trabalhar dessa forma, o Fabric ainda pode te ajudar.

Por exemplo, vamos supor que sua empresa possua um servidor de produção e um servidor de homologação. Podemos, então, adequar o script para que ele possa trabalhar com ambos os ambientes:

set(
    # ...
    fab_user = 'production',
    fab_hosts = ['app.com']
)
 
def staging():
    # deploy to staging servers
    set(fab_user = 'staging', fab_hosts = ['staging.app.com'])
 
# other tasks...

Fazendo as mudanças mostradas acima, para executar o deployment no ambiente de homologação basta rodar o comando fab staging deploy; o deployment em ambiente de produção continua disponível através do comando fab deploy.

Isso é possível pois podemos substituir o valor de uma variável (ou configurar novas variáveis) a qualquer momento, através do método set. Então, quando executamos o comando fab staging deploy, o Fabric executa a task staging, substituindo o valor das variáveis fab_user e fab_hosts, fazendo com que os comandos contidos na task deploy sejam executados nos servidores de homologação em vez de nos servidores de produção.

Veja também:

Tags: , , , , , , ,

3 comentários para “Deployment de aplicações Python com Fabric”

  1. Lucas Fais disse:

    Muito bom!

    Pelo visto, dá pra fazer deploy de qualquer aplicação, mesmo que não seja Python, não é?

  2. Na verdade dá para automatizar muitas das tarefas que possam ser feitas através da execução comandos de terminal (locais e remotos).

  3. [...] começo deste mês eu dei dicas sobre como usar o Fabric para automatizar o build e o deployment de aplicações [...]

Deixe um comentário