Do Desktop à Nuvem

Este tutorial foi escrito pensando naqueles indivíduos que já possuem alguma experiência com o combo Python + Django, entretanto sempre têm problemas ao transferir os seus sistemas da máquina de desenvolvimento para um servidor de verdade.

Introdução


Muitas coisas que estão aqui possivelmente são óbvias para muitas pessoas e, talvez por isso, este texto tenha acabado ficando um pouco maior do que eu gostaria, entretanto acredito que um texto longo seja melhor do que um texto incompleto.

Além disto estou assumindo que você esteja utilizando uma distribuição como o Linux Mint em sua máquina local e, portanto, tem acesso de maneira fácil ao ssh, caso este não seja o seu caso você deverá recorrer à alguma ferramente que lhe permita isso, como por exemplo o Putty, se você estiver no Windows.

A distribuição que escolhi para o servidor é o Ubuntu 14.04 LTS, que no momento - dezembro de 2015 - é o release com suporte a longo prazo mais atual disponível.

Para que você possa completar esse tutorial é necessário que você tenha um domínio próprio. Se você não possui um eu acredito que seja possível utilizar um dot.tk mas eu sinceramente nunca testei.

O objetivo deste tutorial é mostrar como se realiza o deploy de uma aplicação, criada usando o django, em um servidor. Mais epecificamente iniciaremos realizando algumas configurações básicas, - relativas a segurança do servidor - em seguida vamos instalar o combo Python + Virtualenvwrapper + Django que nos permitirá executar nossa aplicação em um ambiente isolado, - isso trará a possibilidade de termos várias aplicações em um mesmo servidor - além disto iremos instalar e configurar o gunicorn, que irá servir de proxy para nosso servidor hhtp, o nginx, que será instalado a seguir. Por fim iremos instalar o cliente do Let’s Encrypt e iremos utilizá-lo para gerar um certificado SSL para nosso domínio. O tempo, aproximado, necessário para completar este tutorial é de 2 horas.

Configurando o Ubuntu

Logando no servidor pela primeira vez

Caso você esteja utilizando um seviço de hosting como Digital Ocean ou Linode ao fazer o deploy do servidor você receberá um email com as informações necessárias para fazer a conexão com seu servidor. Nele você encontrará o IP do servidor, juntamente com os dados para logar com a conta de root do sistema.

A primeira vez que você logar no novo servidor você receberá uma mensagem sobre a autenticidade do servidor, responda que você confia no host remoto e continue.

user@**local-dev** ~/ ssh root@10.1.1.1
The authenticity of host '([10.1.1.1]:22)' can 't be established.
ECDSA key fingerprint is 3a:47:4d:72:f3:d2:33:68:6a:69:3b:ba:16:94:95:1f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[10.1.1.1]:22' (ECDSA) to the list of known hosts.
root@ubuntu:~#

Locales problemático

A instalação padrão do Ubuntu fornecida pelo host que eu utilizei não veio com locales corretamente configurado, para resolver isto eu precisei efetuar a configuração manual. Este passo pode não ser necessário em seu caso, mas para motivos de referência futura eu estou disponibilizando a solução para este contratempo.

root@ubuntu:~# locale-gen pt_BR.UTF-8
root@ubuntu:~# update-locale LC_ALL=en_US.UTF-8 LANGUAGE=en_US.UTF-8

Atualizando o sistema

Agora que estamos com o locales configurado de maneira correta, vamos nos certificar de que o nosso sistema está atualizado, para isto basta executar um simples update/upgrade em nosso sistema.

root@ubuntu:~# apt-get update
root@ubuntu:~# apt-get upgrade -y

Este processo de atualização pode demorar vários minutos então aproveite para ir fazer um café.

Hostname

Para realizar a alteração do hostname do servidor basta alterar um arquivo de texto. Podemos realizar essa alteração com apenas um comando ou, se você preferir pode editar o arquivo com seu editor de texto preferido.

O hostname que eu irei utilizar será secure e isto deve ser a única coisa neste arquivo.

root@ubuntu:~# echo 'secure' > /etc/hostname

Para configurar o FQDN devemos alterar o arquivo /etc/hosts e adicionar lá as informações sobre nosso domnínio.

127.0.0.1       localhost
127.0.1.1       secure.nicolas.eti.br secure
10.1.1.1        secure.nicolas.eti.br secure

Neste caso 10.1.1.1 é o IP do servidor, o mesmo que você utilizou para se conectar, e deve ser alterado de acordo.

root@ubuntu:~# service hostname restart
root@ubuntu:~# hostname
secure
root@ubuntu:~# hostname --fqdn
secure.nicolas.eti.br

Dica: se você está com problemas para encontrar um bom nome para seu servidor, dê uma olhada neste site, lá você irá encontrar muitas sugestões algumas boas, outras nem tanto.

Não se equeça de apontar o seu subdomíno para o IP de seu servidor. Este serviço geralmente é oferecido pela empresa que realiza a venda do domínio, como por exemplo o registro.br para domínios .br.

Timezone

Pessoalmente eu prefiro que meus servidores estejam com o horário de Brasília porque isso facilita a minha vida. Para configurar o relógio basta executar este comando e selecionar America/Sao_Paulo, ou qualquer outro fuso que você considere mais conveniente.

root@ubuntu:~# dpkg-reconfigure tzdata
Current default time zone: 'America/Sao_Paulo'
Local time is now:      Sun Dec 13 20:29:49 BRST 2015.
Universal Time is now:  Sun Dec 13 22:29:49 UTC 2015.

SSHD

Algumas configurações padrões nem sempre são desejadas em ambientes de produção porque podem representar grandes perigos, um exemplo disto é a opção que permite o login com a conta de root via SSH. Sem isto não teríamos como nos conectar ao servidor mas esta não uma opção que quermos em nosso servidor de produção. Lembre-se de criar um backup das configurações originais antes de realizar qualquer alteração.

root@ubuntu:~# cp /etc/ssh/sshd_config /etc/ssh/ssh_config.default

Depois de realizado o backup, utilizando seu editor de texto preferido procure pela opção PermitRootLogin e mude para no. Além disto aproveite para alterar a porta do servidor para alguma coisa diferente do padrão, isso ajuda a prevenir contra ataques automatizados, eu irei utilizar a porta 1221.

Port 1221
PermitRootLogin no

Firewall

Com a ajuda do ufw fica extremamente fácil configurar as regras do firewall. Por padrão iremos bloquear tudo que tentar entrar e liberar tudo que precisar sair. Depois disto vamos liberar todo o tráfego nas portas 1221 (ssh), 80 (hhtp) e 443 (https).

root@ubuntu:~# ufw default deny incoming
root@ubuntu:~# ufw default allow outgoing
root@ubuntu:~# ufw allow 1221
root@ubuntu:~# ufw allow 80
root@ubuntu:~# ufw allow 443

Criando um novo usuário

Agora que nosso servidor está devidamente atualizado e configurado vamos criar um novo usuário para que não fiquemos utilizando a conta de root. O nome da conta de usuário que vou criar será hidden mas não há nenhum motivo especial para a escolha deste nome.

root@ubuntu:~# adduser hidden

Nosso usuário foi criado com sucesso, entretanto ele ainda não possui permissão para realizar tarefas administrativas, para isto basta adiciná-lo ao grupo sudo.

root@ubuntu:~# gpasswd -a hidden sudo

Finalizando

Agora basta iniciar o firewall e reiniciar o servidor ssh para que todas as configurações que fizemos entrem em vigor.

root@ubuntu:~# service sshd restart
root@ubuntu:~# ufw enable

Depois de reiniciar os serviços não feche a sua conexão, abra um novo terminal e tente conectar utilizando a conta que você acabou de criar se, por algum motivo, você não conseguir fazer o login ainda terá uma conexão estabelecida com o servidor para resolver o problema.

#Configurando o webserver

Até agora tudo que fizemos foram algumas configurações básicas relacionadas a segurança geral do servidor mas para termos um site rodando em nosso servidor precisaremos de um pouco mais esforço do que isto.

Instalando o Git

O gerenciador de versões que iremos utilizar é o git, e por padrão ele não vem instalado em nosso sistema. Sua instalação, como a maior parte dos programas no linux, é relativamente fácil. O único detalhe que devemos prestar atenção é que uma etapa de configuração é necessária antes de poder utilizá-lo. A configuração é simples também, basta informar ao git o seu nome e seu email.

hidden@secure:~$ sudo apt-get install git
hidden@secure:~$ git config --global user.name "Nicolas Zachow"
hidden@secure:~$ git config --global user.email nicolas@nicolas.eti.br

O git também será necessário para a instalação do cliente do Lte’s Encrypt, portanto mesmo que você utilize outro gerenciador para seeu projeto a instalação dele é necessária.

PIP

PIP é o gerenciador de pacotes do Python que facilita a nossa vida quando precisamos instalar pacotes no Python, como por exemplo o virtualenv ou o django. Ele pode ser facilmente instalado através do apt.

hidden@secure:~$ sudo apt-get install python-pip

Virtualenvwrapper

Tendo isntalado o PIP podemos passar à instalação do virtualenvwrapper que é uma extensão do virtualenv, uma de suas principais vantagens é a facilidade de gerenciar vários ambientes virtuais.

hidden@secure:~$ sudo pip install virtualenvwrapper

Agora é necessário alterar o arquivo ~/.bashrc e adicionar o seguinte ao final do arquivo

export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/dev
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python
source /usr/local/bin/virtualenvwrapper.sh

Depois de tudo devidamente configurado o resta é criar virtualenv em que estaremos trabalhando, estaremos utilizando o nome deploydjango para nosso ambiente virtual mas você pode escolher algo mais apropriado para seu projeto. A criação do ambiente é feita da seguinte forma

hidden@secure:~$ source .bashrc
hidden@secure:~$ mkvirtualenv --python=/usr/bin/python deploydjango
(deploydjango)hidden@secure:~$

Para sair do ambiente virtual utilize o comando deactivate e para retornar utilize workon deploydjango.

Preparando as dependências

Em sua máquina de desenvolvimento crie um arquivo que contenha todas as dependências de seu projeto utilizando o PIP e logo em seguida coloque este arquivo no repositório de seu projeto.

user@**local-dev**:~/dev/deploy-django-example$ pip freeze > req.txt
user@**local-dev**:~/dev/deploy-django-example$ git add req.txt
user@**local-dev**:~/dev/deploy-django-example$ git commit
user@**local-dev**:~/dev/deploy-django-example$ git push

Clone project

Finalmente podemos começar a trabalhar em nosso projeto. O primeiro é clonar o projeto utilizando o git.

(deploydjango)hidden@secure:~/dev$ git clone https://github.com/nikozc/deploy-django-example.git
(deploydjango)hidden@secure:~/dev$ cd deploy-django-example/

Instalando dependências

Tendo ciado um arquivo com todas as dependencias de nosso projeto podemos instalar todas elas de uma só vez utilizando o PIP e passando este arquivo para ele.

(deploydjango)hidden@secure:~$ pip install -r req.txt

Colocando configurações no path

Para evitar que informações sensíveis fiquem vulneráveis em seu projeto, é considerado uma boa prática colocar dados como informações sobre a conexão com o banco de dados e API keys em variáveis do ambiente ao invés de deixá-las expostas em um arquivo de configuração.

Utilizando o virtualenvwrapper isso pode ser feito adicionando os valores desejados no arquivo postactivate que fica na pasta ~/.virtualenvs/deploydjango/bin e não se esqueça de remover estes valores ao destivar o ambiente virtual, utilizando o arquivo postdeactivate que está na mesma pasta.

Para referência vou colocar aqui um arquivo postactivate fictício.

#!/bin/bash
# This hook is sourced after this virtualenv is activated.

export TWITTER_API='k2j0(a@55aty='
export DB_USER=username
export DB_PASS=senhasegura

E este, é um postdeactivate:

#!/bin/bash
# This hook is sourced after this virtualenv is deactivated.

unset TWITTER_API
unset DB_USER
unset DB_PASS

Por fim, para acessar estes valores no settings.py deve ser feito o seguinte:

from os import environ

TWITTER_API = environ['TWITTER_API']

Gunicorn

O gunicorn pode ser facilmente instalado pelo PIP:

(deploydjango)hidden@secure:~$ pip install gunicorn

Configurando o Gunicorn

Neste momento estamos com quase todas as peças necessárias em seus lugares mas uma parte vital ainda está faltando, a configuração para que o guincorn possa ser executado de maneira atônoma como um serviço. Pare isso precisamos criar o arquivo /etc/init/gunicorn.conf que irá conter todas as configurações necessárias para o gunicorn ser executado da maneira correta.

description "Gunicorn server para o projeto DEPLOY"

start on runlevel [2345]
stop on runlevel [!2345]

respawn
setuid hidden
setgid www-data
chdir /home/hidden/dev/deploy-django-example/deploy

exec /home/hidden/.virtualenvs/deploydjango/bin/gunicorn --workers 3 --bind unix:/home/hidden/dev/deploy-django-example/deploy.sock deploy.wsgi

Na primeira linha temos uma breve descrição do serviço, depois disto definimos em quais runlevel queremos que o serviço esteja ativo. Em seguida definimos o usuário, o grupo e o local onde o serviço deve rodar. Por último está o comando para executar o gunicorn.

Para comparação, a minha pasta /home/hidden/.virtualenvs/deploydjango/bin contém os seguintes arquivos:

hidden@secure:~/.virtualenvs/deploydjango/bin$ ls -l
total 3352
-rw-rw-r-- 1 hidden hidden    2249 Dec 14 02:08 activate
-rw-rw-r-- 1 hidden hidden    1275 Dec 14 02:08 activate.csh
-rw-rw-r-- 1 hidden hidden    2488 Dec 14 02:08 activate.fish
-rw-rw-r-- 1 hidden hidden    1137 Dec 14 02:08 activate_this.py
-rwxrwxr-x 1 hidden hidden     299 Dec 20 00:51 django-admin
-rwxrwxr-x 1 hidden hidden     158 Dec 20 00:51 django-admin.py
-rw-rw-r-- 1 hidden hidden     302 Dec 20 00:51 django-admin.pyc
-rwxrwxr-x 1 hidden hidden     266 Dec 14 02:08 easy_install
-rwxrwxr-x 1 hidden hidden     266 Dec 14 02:08 easy_install-2.7
-rwxr-xr-x 1 hidden hidden     150 Dec 14 02:08 get_env_details
-rwxrwxr-x 1 hidden hidden     253 Dec 20 01:00 gunicorn
-rwxrwxr-x 1 hidden hidden     255 Dec 20 01:00 gunicorn_django
-rwxrwxr-x 1 hidden hidden     255 Dec 20 01:00 gunicorn_paster
-rwxrwxr-x 1 hidden hidden     238 Dec 14 02:08 pip
-rwxrwxr-x 1 hidden hidden     238 Dec 14 02:08 pip2
-rwxrwxr-x 1 hidden hidden     238 Dec 14 02:08 pip2.7
-rw-r--r-- 1 hidden hidden      72 Dec 14 02:08 postactivate
-rw-r--r-- 1 hidden hidden      74 Dec 14 02:08 postdeactivate
-rwxr-xr-x 1 hidden hidden      69 Dec 14 02:08 preactivate
-rw-r--r-- 1 hidden hidden      75 Dec 14 02:08 predeactivate
-rwxrwxr-x 1 hidden hidden 3345416 Dec 14 02:08 python
lrwxrwxrwx 1 hidden hidden       6 Dec 14 02:08 python2 -> python
lrwxrwxrwx 1 hidden hidden       6 Dec 14 02:08 python2.7 -> python
-rwxrwxr-x 1 hidden hidden     245 Dec 14 02:08 wheel

E a pasta /home/hidden/dev/deploy-django-example/deploy:

hidden@secure:~/dev/deploy-django-example/deploy$ ls -l
total 16
-rw-r--r-- 1 hidden hidden 3072 Dec 20 00:52 db.sqlite3
drwxrwxr-x 2 hidden hidden 4096 Dec 20 00:52 deploy
-rwxr-xr-x 1 hidden hidden  249 Dec 20 00:50 manage.py

Agora que o gunicorn está devidamente configurado basta que iniciemos o serviço que acabamos de criar.

hidden@secure:~$ sudo service gunicorn start
gunicorn start/running, process 17959
hidden@secure:~$

Instalando o NGINX

O nginx pode ser instalado diretamente através do apt

hidden@secure:~$ sudo apt-get install nginx

Para configurar o servidor é necessário criar um arquivo com as configurações em /etc/nginx/sites-available, o nome do arquivo pode ser o nome de seu projeto, neste caso deploy. Já aproveitaremos para colocar as configurações referentes ao certificado SSL que adicionaremos depois, assim não precisaremos voltar para editar este arquivo outra vez.

server {
    listen 80;
    server_name secure.nicolas.eti.br;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/hidden/dev/deploy-django-example/deploy;
    }
  location '/.well-known/acme-challenge' {
  default_type "text/plain";
    root        /home/hidden/le/letsencrypt/letsencrypt-auto;
  }
    location / {
        include proxy_params;
        proxy_pass http://unix:/home/hidden/dev/deploy-django-example/deploy.sock;

    }
}
server {

        listen  443;
        server_name  secure.nicolas.eti.br;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/secure.nicolas.eti.br/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/secure.nicolas.eti.br/privkey.pem;

        ssl_stapling on;
        ssl_stapling_verify on;
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";



        location ~* /\.\./ {
                deny all;
                return 404;
        }

        location / {
        proxy_pass http://unix:/home/hidden/dev/deploy-django-example/deploy.sock;
                proxy_set_header Host      $host;
                proxy_set_header X-Real-IP $remote_addr;
        }
}

Neste arquivo temos o a porta, o hostname e o caminho para o gunicorn, além disto já aproveitamos para colocar todas as configurações necessárias para que nosso certificado SSL seja utilizado de maneira correta. Não esqueça de criar um link para este arquivo na pasta sites-enabled. Feito isso, a única coisa que falta é reiniciarmos o nginx.

hidden@secure:~$ sudo service nginx restart

Configurando o LetsEncrypt

Até então não haviamos visto nada de novo mas agora veremos como configurar nosso nosso servidor para utilizar conexões encriptadas através do serviço fornecido pelo recém criado Let’s Encrypt.

Infelizmente o cliente do Let’s Encrypt ainda não está disponível no repositório do Ubuntu, e por isso, precisa ser instalado através do git. Para nossa felicidade eles disponibilizaram uma ferramenta que automagicamente instala tudo que é necessário.

hidden@secure:~$ git clone https://github.com/letsencrypt/letsencrypt
hidden@secure:~$ cd letsencrypt
hidden@secure:~/letsencrypt$ sudo service nginx stop
hidden@secure:~/letsencrypt$ sudo ./letsencrypt-auto certonly --standalone --agree-tos --redirect --duplicate --text --email nicolas@nicolas.eti.br -d secure.nicolas.eti.br

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/secure.nicolas.eti.br/fullchain.pem. Your
   cert will expire on 2016-03-19. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

hidden@secure:~/letsencrypt$ sudo service nginx start

Como já haviamos configurado o arquivo do nginx anteriormente agora já está tudo devidamente funcionando.

Renovando o certificado

A ideia dos criadores do Let’s Encrypt é que os certificados sejam criados de maneira automatizada e por isso os certificados que eles emitem possuem uma validade menor se comparada com os certificados tradicionais.

Para a automatização do processo podemos utilizar o cron, adicionando uma tarefa que será executada a cada 60 dias, nesse caso, mas se você achar conveniente pode alterar este prazo já que o certificado tem validade de 90 dias. Além disto é uma boa prática escolher uma data e hora aleatória para renovar o certificado para evitar um “DDOS” aos servidores do Let’s Encrypt.

hidden@secure:~$ sudo crontab -e
0 0 1 */2 * /home/hidden/lerenew.sh

No arquivo lerenew.sh vamos, basicamente, parar o nginx, renovar o certificado e então reiniciar o nginx.

#!/bin/sh
service nginx stop
/home/hidden/letsencrypt/letsencrypt-auto renew --standalone --agree-tos --redirect --force-renewal --text --email nicolas@nicolas.eti.br > /var/log/letsencrypt/renew.log 2>&1
LE_STATUS=$?
service nginx start
if [ "$LE_STATUS" != 0 ]; then
    echo Automated renewal failed:
    cat /var/log/letsencrypt/renew.log
    exit 1
fi

Se você gostou deste post, compartilhe com seus seguidores via twitter, e se você quiser ficar sabendo quando houverem novas publicações, me acompanhe no Twitter.