Criando um container Docker para um projeto Django Existente

Neste post, vou mostrar como criar um container Docker para um projeto Django já existente. Como exemplo, resolvi buscar por uma issue aberta no github que estivesse pedindo para ser dockerizada. Criei um PR para a Issue e usei como exemplo aqui.

Por quê você vai querer dockerizar uma aplicação web django que já existe? Bom, existem muitas razões, se você acha que não tem uma, faça pela diversão!

Eu decidi usar o docker em uma das minhas aplicações porque ela estava ficando muito difícil de instalar. Muitos requisitos do sistema, vários bancos de dados, celery, rabbitmq e por aí vai. Sem dockerizar cada vez que uma nova pessoa entra no time é um inferno pra setar tudo porque levava tempo demais.

O mundo ideal é que o programador tenha em seu ambiente de desenvolvimento o mais próximo que puder do ambiente de produção. Se você usa SQLite na sua máquina mas Postgres no servidor pode ser que tenha problemas de dados que são simplesmente truncados localmente mas que vão levantar erros na base de produção. Só para ter ideia de um exemplo.

Se você não sabe o que é o docker, imagine que é um imenso virtualenv que no lugar de ter apenas pacotes python tem o sistema operacional “inteiro”. Isso consegue isolar seu app de tudo que está no seu SO, bancos de dados, workers, etc.

Mão na massa

Ok, falar é fácil, vamos codar um pouco.

Primeiro de tudo, instale o Docker. Fiz isso no Ubuntu e no Mac sem nenhum problema. Já no Windows Home não consegui fazer funcionar.

Para dizer ao docker que sua aplicação é um container você precisa criar um arquivo Dockerfile:



FROM python:3.6
ENV PYTHONUNBUFFERED 1

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

# Django service
EXPOSE 8000

Vamos lá, linha a linha:

Docker Images

FROM python:3.6

Aqui estamos usando uma imagem do docker hub. Isto, é um container pré-formatado do docker que permite que você monte sua máquina a partir daquela configuração inicial. Nesse caso, Python 3.6 é um container de um Ubuntu que tem o Python 3.6 instalado nele. Você pode procurar por containers no docker hub.

Environment Variables (Variáveis de ambiente)

Você pode criar todas as variáveis de ambiente que quiser com o ENV.

ENV PYTHONUNBUFFERED 1  # Here we can create all Environment variables for our container

Por exemplo, se você usa variáveis de ambiente para guardar sua secret key do Django é só fazer assim:

ENV DJANGO_SECRET_KEY abcde0s&&$uyc)[email protected]!a95nasd22e-dxt^9k^7!f+$jxkk+$k-

E usar no seu settings, desse jeito:

import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']

Run Commands

Docker Run Commands tem um nome meio óbvio. Você pode rodar um comando “dentro” do seu container. Estou colocando dentro entre aspas porque o docker na verdade cria algo como sub containers para que não precise rodar os mesmos comandos novamente no caso de precisar dar um rebuild do container.

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

Aqui estamos criando o diretório que guardará os arquivos do projeto: webapps/.

Workdir é uma instrução para mostrar ao docker em qual diretório ele vai rodar os comandos a partir dali.

Depois disso estou instalando as dependências do sistema operacional. Quando estamos usando requirements.txt no projeto, estamos colocando apenas os requisitos do python e não os do SO. Mais um motivo para querer usar o docker para desenvolver. Quanto maior seu projeto, mais requisitos de sistema operacional vão aparecer.

COPY e ADD

Copy e ADD são basicamente a mesma cosia. Ambos copiam um arquivo do seu computador (o Host) dentro do container (o Guest). No meu exemplo, estou apenas copiando o requirements para dentro do docker, para que eu possa dar pip install nos pacotes.

EXPOSE

Expose é para mapear uma porta do Guest (o Container) para o Host (seu computador)

# Django service
EXPOSE 8000

Ok, e agora? Como podemos adicionar mais containers para rodá-los juntos? E se precisarmos colocar um postgresql para rodar em um container também? Não se preocupe, vamos usar o docker-compose.

Docker-Compose

O compose é uma ferramenta para rodar múltiplos containers do docker. Só precisa criar um arquivo yml na pasta do seu projeto com o nome docker-compose.yml:

version: '3.3'

services:
  # Postgres
  db:
    image: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres

  web:
    build: .
    command: ["./run_web.sh"]
    volumes:
      - .:/webapps
    ports:
      - "8000:8000"
    links:
      - db
    depends_on:
      - db

Aqui estou usando uma imagem do Postgres que também peguei no Docker Hub.

Agora vamos mudar o settings.py para poder usar o Postgres do container como banco de dados.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': '5432',
    }
}

Quase lá, deixa eu falar um pouco sobre o arquivo docker-compose.yml,

VOLUMES

Lembra do vagrant?

Era uma vez o Vagrant. Ele era uma forma de rodar um projeto dentro de uma Máquina Virtual que permitia configurar e mapear portas fácilmente, provisionar requisitos do sistema e compartilhar volumes. Seu computador (o Host) podia compartilhar um volume com a máquina virtual (o Guest, ou convidado). No docker, o volume é exatamente a mesma coisa. Quando você escreve um arquivo em um volume que está compartilhado o arquivo também está sendo escrito dentro do container.

volumes:
  - .:/webapps

Nesse o caso, o diretório em que nos encontramos (.) é o que está sendo compartilhado com o container.

LINKS

links:
  - db

Você pode se referir a outro container que pertence ao seu arquivo docker-compose utilizando o nome dele. Como criamos um container com o nome db para o Postgres nós podemos criar um link para ele no nosso container chamado web. Pode ver que no settings.py nós colocamos ‘db‘ como host.

DEPENDS_ON

Para rodar sua aplicação, seu banco de dados precisa estar pronto antes do container web, senão vai dar algum pau!

depends_on:
  - db

Command

Command é o comando padrão que o docker vai rodar logo depois que você subir, ou seja, colocar os containeres para funcionar.

No nosso exemplo, eu criei um run_web.sh que vai rodar as migrações, coletar o static files e iniciar o servidor de desenvolvimento.

#!/usr/bin/env bash

cd django-boards/
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py runserver 0.0.0.0:8000

Alguém pode argumentar que migrar assim automaticamente toda vez que subir o container pode não ser uma boa prática. Eu concordo. Você pode (e deve) rodar o migrate direto na máquina web. Você pode acessar seu container para rodar comandos assim (como no bom e velho vagrant ssh):

docker-compose exec web bash

Se você quiser , você pode rodar o comando sem acessar o container mesmo, apenas mudando o último argumento do comando acima:

docker-compose exec web python manage.py migrate

O mesmo para outros comandos:

docker-compose exec web python manage.py test
docker-compose exec web python manage.py shell

Rodando o Docker

Com nosso Dockerfile, docker-compose.yml e o run_web.sh no lugar, vamos rodar tudo junto:

docker-compose up

Você pode ver esse projeto aqui no meu GitHub.

Escrevi esse texto originalmente em inglês.

**EDITADO**

Antes eu estava usando run no lugar de exec. Mas o Bruno FS me mostrou que o exec é melhor pois está acessando exatamente o container que já está rodando e que o run na verdade está criando um novo container.

References:

Dockerizing Django for Development

In this post, I’ll show how to containerize an existing project using Docker. I’ve picked a random project from GitHub that had an open issue saying Dockerize to contribute and use as an example here.

Why in the world do you want to Dockerize an existing Django web application? There are plenty of reasons, but if you don’t have one just do it for fun!

I decided to use docker because one of my applications was getting hard to install. Lots of system requirements, multiple databases, celery, and rabbitmq. So every time a new developer joined the team or had to work from a new computer, the system installation took a long time.

Difficult installations lead to time losses and time losses lead to laziness and laziness leads to bad habits and it goes on and on… For instance, one can decide to use SQLite instead of Postgres and not see truncate problems on a table until it hits the Test server.

If you don’t know what docker is, just picture it as a huge virtualenv that instead of containing just some python packages have Containers for isolating everything from the OS to your app, databases, workers, etc.

Getting Things Done

Ok, talk is cheap. Show me some code, dude.

First of all, install Docker. I did it using Ubuntu and Mac OS without any problem, but on Windows Home, I couldn’t have it working.

To tell Docker how to run your application as a container you’ll have to create a Dockerfile



FROM python:3.6
ENV PYTHONUNBUFFERED 1

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

# Django service
EXPOSE 8000

So, let’s go line by line:

Docker Images

FROM python:3.6

Here we’re using an Image from docker hub. e.q. One pre-formated container that helps build on top of it. In this case, Python 3.6 is an Ubuntu container that already has Python3.6 installed on it.

Environment Variables

You can create all sort of Environment Variables using Env.

ENV PYTHONUNBUFFERED 1  # Here we can create all Environment variables for our container

For instance, if you use them for storing your Django’s Secret Key, you could put it here:

ENV DJANGO_SECRET_KEY abcde0s&&$uyc)[email protected]!a95nasd22e-dxt^9k^7!f+$jxkk+$k-

And in your code use it like this:

import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']

Run Commands

Docker Run Commands are kinda obvious. You’re running a command “inside” your container. I’m quoting inside because docker creates something as sub-containers, so it doesn’t have to run the same command again in case of rebuilding a container.

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

In this case, We are creating the directory that will hold our files webapps/.

Workdir is also kind of self-evident. It just telling docker to run the commands in the indicated directory.

After that, I am including one OS dependency. When we’re just using requirements.txt  we are not including any OS requirement for the project and believe me, for large projects you’ll have lots and lots of OS requirements.

COPY and ADD

Copy and ADD are similar. Both copy a file from your computer (the Host) into the container (The Guest OS). In my example, I’m just coping python requirements to pip install them.

EXPOSE

Expose Instruction is for forwarding a port from Guest to the Host.

# Django service
EXPOSE 8000

Ok, so now what? How can we add more containers and make them work together? What if I need a Postgresql inside a container too? Don’t worry, here we go.

Docker-Compose

Compose is a tool for running multiple Docker containers. It’s a yml file, you just need to create a docker-compose.yml on your project folder.

version: '3.3'

services:
  # Postgres
  db:
    image: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres

  web:
    build: .
    command: ["./run_web.sh"]
    volumes:
      - .:/webapps
    ports:
      - "8000:8000"
    links:
      - db
    depends_on:
      - db

In this case, I’m using an Image of Postgres from Docker Hub.

Now, let’s change the settings.py to use Postgres as Database.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': '5432',
    }
}

We’re almost done. Let me talk a little about the docker-compose file.

VOLUMES

Remember vagrant?

Once upon a time, there was Vagrant and it was a form to run a project inside a Virtual Machine but easily configuring it, forwarding ports, provisioning requirements and, sharing volumes. Your machine (Host) could share a volume with your Virtual Machine (Guest). In docker, it’s exactly the same. When you’re writing a file on a shared volume this file is being written on your container as well.

volumes:
  - .:/webapps

In this case, the current directory (.) is being shared as webapps on the container.

LINKS

links:
  - db

You can refer to another container that belongs to your compose using its name. Since we created a db container for our Postgres we can link it to our web container. You can see in our settings.py file that I’ve used ‘db‘ as host.

DEPENDS_ON

In order for your application to work, your database has to be ready for use before web container, otherwise, it will raise an exception.

depends_on:
  - db

Command

Command is the default command that your container will run right after it is up.

For our example, I’ve created a run_web.sh, that will run the migrations, collect the static files and start the development server.

#!/usr/bin/env bash

cd django-boards/
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py runserver 0.0.0.0:8000

One can argue that run the migrate at this point, automatically, every time the container is up is not a good practice. I agree. You can run it directly on the web machine. You can access your container (just like the good’ol vagrant ssh) :

docker-compose exec web bash

If you’d like you can run it without accessing the container itself, just change the last argument from the previous command.

docker-compose exec web python manage.py migrate

The same for other commands

docker-compose exec web python manage.py test
docker-compose exec web python manage.py shell

Running Docker

With our Dockerfile, docker-compose.yml and run_web.sh set in place, just run it all together:

docker-compose up

You can see this project here on my GitHub.

**EDIT**

At first, I was using run instead of exec. But Bruno FS convinced me that exec is better because you’re executing a command inside the container you’re already running, instead of creating a new one.

References:

4 Melhores Podcasts sobre Tecnologia e Startups

Ouvir Podcasts é uma forma muito eficiente para se manter por dentro do que acontece na sua área.  Ouço vários Podcasts sobre tecnologia e startups, além de outros temas e tenho certeza que eles são importantes para que eu continue atualizado no meu ramo. Estar informado pode ser a diferença entre ser bom e ser ótimo.

Para quem não conhece, podcast é como um programa de rádio com a diferença de que você pode ouvir quando quiser, escolher os episódios e sempre ficará sabendo quando publicarem um programa novo. Podcast não é novidade mas não chegou a virar mainstream no Brasil ainda. Vai crescer muito este ano.

Meu aplicativo para assinar (baixar e ouvir) podcasts é o PocketCasts ele é pago, paga uma vez só e usa todos os dias, mas existem várias outras opções gratuitas aí para quem não quiser desembolsar os R$ 12 reais. Bora pra lista:

Hipsters.tech

logo do podcast hipsters.tech

Apesar do nome péssimo, clichê e modinha é na minha opinião o melhor podcast brasileiro. Tem um episódio por semana e é um programa muito bem formatado. Normalmente os episódios tem menos de uma hora e variam sobre desenvolvimento, design, empreendedorismo, etc.

Esse é um dos episódios que gostei bastante sobre Squads. Você pode dar uma olhada em outros episódios no site deles https://hipsters.tech

Software Engineering Daily

software engineering daily logo

Esse é um podcast gringo (em inglês). Eles soltam um episódio por dia útil, ou seja, é daily mesmo. Mesmo que não dê para ouvir todos os dias é bom ficar por dentro e olhar o que eles já fizeram. Tem entrevistas com gente da área de tecnologia e de startups, normalmente engenheiros e empreendedores de empresas renomadas e criadores das tecnologias que estão sendo discutidas. Você pode ver os episódios aqui

Talk Python to Me

talk python to me podcast logo

Esse podcast é mais específico para pythonistas, mas quem não gosta de python? Tem participações de caras muito fodas da comunidade como Kenneth Reitz, David Beasley e até do Guido Van Rossum.

O site é esse aqui: https://talkpython.fm/ . Meu único problema com esse podcast é que ele tinha a melhor música de abertura de qualquer podcast até o meio do ano passado, depois entrou uma música chata =P

Like a Boss

like a boss podcast logo

Esse é um podcast dos mesmos criadores do Hipsters. O objetivo é “trazer entrevistas com líderes, fundadores de startups e empresas inovadoras” nas próprias palavras deles. Está bem no comecinho, tem apenas 7 episódios.

Gostei bastante dessa entrevista com David Vélez, o fundador do Nubank.

Outros podcasts:

Eu gostava muito do ZOFE (Zone of Front-Enders) , mas infelizmente ele não tem novas publicações já faz tempo. O site está mais desatualizado que o podcast mas ainda assim dá para ouvir conteúdo do passado. Era realizado pelo Daniel Filho, um cara diferenciado que eu sempre via no melhor meetup de front-ends de São Paulo, o FEMUG-SP.

Falando em podcasts antigos, outro que curti bastante mas já não solta coisa nova é o Grok Podcast. Isso que é bom dos podcasts, eles podem ter acabado mas os episódios estão aí para sempre.

Um podcast que eu descobri recentemente foi o Castálio Podcast. Também fala bastante de Python. Por enquanto, só ouvi um episódio e foi sobre serverless. Aliás, dá pra ver o podcast sendo gravado ao vivo no youtube e mandar perguntas para eles.

Mais um podcast gringo que estou esperando ver como vai desenrolar é o Modern CTO. Me parece uma estratégia audaciosa para criar conteúdo para CTOs uma coisa que não se vê todo dia. Estou começando a acompanhar.

Bom, esses foram os meus podcasts favoritos sobre esses temas. Quem tiver outros podcasts que quiser indicar deixe nos comentários.

 

Deploy for kids – Guide for deploying Django Python 3

crianças fazendo deploy

There is a lot of tutorials out there, especially in English. Here it goes another one. I wrote it originally in Portuguese.

The reason many people has problems deploying is that they don’t pay enough attention to details. Deploying is easy when you are familiarized with all parts involved. You must know how to authenticate through ssh, be used to command line and Linux, understand how to configure and set up your project, have an idea of what serving static files is, what is Gunicorn… Ok, it’s not that simple. That’s why there is a lot of deploy tools, kits, and tutorials. Currently, with Ansible, Docker and whatever kids are using these days it’s easier to deploy, but what happens under the hood gets more abstract.

Maybe in a couple of years, this post is going to be obsolete if it’s not already with serverless and everything else. Anyway, just a few people want to learn how to deploy Django as I’ll show here, but if it helps at least one person, I’ll be satisfied.

Enjoy this Old-Style guide!

The Server

I presume you don’t have a server or AWS account, DigitalOcean, Linode… Nothing! You have to create an account in one of them and launch a server with the distro you want. If it’s your first time, don’t go with AWS because it’s way more complicated than the others.

In this tutorial, I’m using an Ubuntu 16.04, the most common distro you’ll see around. You can also pick a Debian if you like.

Initial Set Up

Configure server timezone

sudo locale-gen --no-purge --lang pt_BR  # I'm using pt_BR, because HUE HUE BR BR
sudo dpkg-reconfigure tzdata

Update and upgrade OS Packages:

sudo apt-get update 
sudo apt-get -y upgrade

Installing Python 3.6 over Python 3.5

Replace Python 3.5 which is default on our distro with Python 3.6.

sudo apt-get update
sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get install python3.6
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 2

You can choose which Python version the OS will call when you type python3.

sudo update-alternatives --config python3

Having trouble, take a look here:

How to Install Python 3.6.1 in Ubuntu 16.04 LTS

Install OS requirements

sudo apt-get install python3-pip nginx supervisor git git-core libpq-dev python-dev 
python-virtualenv

If your project has more OS requirements, install them as well.

VirtualEnvWrapper for Python3

I’m a fan of VirtualEnvWrapper. It’s super easy and creates all my virtual environments in the same place. That’s a personal choice, if you don’t like it, use what you know how to use.

First, you install virtualenvwrapper, and then define where to put your virtualenvs. (WORKON_HOME).

If you need to use it with multiple Python versions, you must define VIRTUALENVWRAPPER_PYTHON. Here I’m using always with python3. It’s not a problem since you can create a virtualenv pointing which Python that env will use.

sudo pip3 install virtualenvwrapper
echo 'export WORKON_HOME=~/Envs' >> ~/.bashrc
echo ‘export VIRTUALENVWRAPPER_PYTHON=`which python3`’ >> ~/.bashrc
echo 'source /usr/local/bin/virtualenvwrapper.sh' >> ~/.bashrc
source ~/.bashrc

Now, create your virtualenv and define what Python is going to use.

mkvirtualenv name_venv --python=python3

VirtualEnvWrapper is really easy to use. If you want to activate a virtual env, you can use workon.

workon name_venv

To deactivate this virtualenv:

deactivate

To remove a virtualenv:

rmvirtualenv name_venv

Generate SSH for GitHub Authentication

You don’t want (neither should) write your password to git pull your project on the server.

Generating SSH Keys:

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

See and copy the content of your public key (id_rsa.pub)

cat ~/.ssh/id_rsa.pub

Then sign in your GitHub account and go to Settings > SSH and GPG Keys. Click on New SSH Key, give it a name, like (“test server keys”) and in Key paste the content of your  id_rsa.pub

Clone your Django Project

Copy the SSH link from GitHub to clone your project. In this case, I’m using a project that I just have found as an example.

git clone [email protected]:kirpit/django-sample-app.git

In the project folder, install the project requirements.

Remember that you have to be in your virtual environment

cd django-sample-app/
pip install -r requirements.txt

Now, make the necessary alterations for your deploy, such as create a settings_local.py file, change database settings or anything specific to your project.

After you’re done, run your migrations and collect your static files (if you’re using it).

python manage.py migrate
python manage.py collectstatic

Configuring NGINX

Nginx, like Apache, is an entirely separate world. Right now, you just need the basics.

/etc/nginx/sites-available/ is a directory where you have to put the config files of available sites. There is another directory, /etc/nginx/sites-enabled/ that shows which sites are enabled. They are the same thing, but what is put on enabled will be served by Nginx.

It’s usual to create your config file on sites-available and create just a symlink to sites-enabled.

First of all, I’ll remove the default site from Nginx.

sudo rm /etc/nginx/sites-enabled/default

Now, create the config file for your site. (If you don’t know how to use VIM, use nano instead of vi)

sudo vi /etc/nginx/sites-available/mysite

Past this on your file, changing the necessary paths:

server {
 listen 80;
 access_log /home/username/logs/access.log;
 error_log /home/username/logs/error.log;

 server_name nome-site.com.br;

 location / {
 proxy_pass http://127.0.0.1:8000; 

 proxy_pass_header Server;
 proxy_set_header X-Forwarded-Host $server_name;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;


 }

 location /static {

   alias /home/username/project_path/static/;

 }

And create a symlink to sites-enabled:

sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite

Restart Nginx:

sudo service nginx restart

Ok, if you made it till here, if you access your website you will see a 502 Bad Gateway from Nginx. That’s because it’s nothing here: http://127.0.0.1:8000

Now, configure the website to run on 8000 port.

Configuring Gunicorn

Are you guys alive? Don’t give up, we’re almost there.

In your virtualenv (remember workon name_env?) install Gunicorn

pip install gunicorn

In your project’s directory, make a gunicorn_conf file:

bind = "127.0.0.1:8000"
logfile = "/home/username/logs/gunicorn.log"
workers = 3

Now, if you run Gunicorn you will see your website working!

/home/username/Envs/name_venv/bin/gunicorn project.wsgi:application -c gunicorn_conf

But what are you going to do? Run this command inside a screen and walk away? Of course not! You’ll use Supervisord to control Gunicorn.

Configuring Supervisor

Now create a gunicorn.conf:

sudo vi /etc/supervisor/conf.d/gunicorn.conf

That’s the content:

[program:gunicorn]
command=/home/username/Envs/name_venv/bin/gunicorn project.wsgi:application -c /home/username/project/project_django/gunicorn_conf
directory=/home/username/project/project-django
user=username
autostart=true
autorestart=true
redirect_stderr=true

And now, you just tell Supervisor that there is a new process in town and Supervisord will take care of it:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

And voilá! A new running you will have.

Conclusion

There is a lot of things involved in a deploy process. You have to configure a firewall, probably you’ll have to serve more than one static folder, etc, etc… But you have to start somewhere.

I can’t believe I wrote a whole post without using any GIF. So, just to finish, pay attention to all paths I’ve used here.

Oops..

Using celery with multiple queues, retries and scheduled tasks

On this post, I’ll show how to work with multiple queues, scheduled tasks, and retry when something goes wrong.

If you don’t know how to use celery, read this post first: https://fernandofreitasalves.com/executing-time-consuming-tasks-asynchronously-with-django-and-celery/

Retrying a task

Let’s say your task depends on an external API or connects to another web service and for any reason, it’s raising a ConnectionError, for instance. It’s plausible to think that after a few seconds the API, web service, or anything you are using may be back on track and working again. In this cases, you may want to catch an exception and retry your task.


from celery import shared_task
 
@shared_task(bind=True, max_retries=3)  # you can determine the max_retries here
def access_awful_system(self, my_obj_id):
    from core.models import Object
    from requests import ConnectionError
 
    o = Object.objects.get(pk=my_obj_id)
 
    # If ConnectionError try again in 180 seconds
    try:
 
        o.access_awful_system()
  
    except ConnectionError as exc:
        self.retry(exc=exc, countdown=180)  # the task goes back to the queue

The self.retry inside a function is what’s interesting here. That’s possible thanks to bind=True on the shared_task decorator. It turns our function access_awful_system into a method of Task class. And it forced us to use self as the first argument of the function too.

Another nice way to retry a function is using exponential backoff:

self.retry(exc=exc, countdown=2 ** self.request.retries)

ETA – Scheduling a task for later

Now, imagine that your application has to call an asynchronous task, but need to wait one hour until running it.

In this case, we just need to call the task using the ETA(estimated time of arrival)  property and it means your task will be executed any time after ETA. To be precise not exactly in ETA time because it will depend if there are workers available at that time. If you want to schedule tasks exactly as you do in crontab, you may want to take a look at CeleryBeat).


from django.utils import timezone
from datetime import timedelta

now = timezone.now() 
 
# later is one hour from now
later = now + timedelta(hours=1)

access_awful_system.apply_async((object_id), eta=later) 

Using more queues

When you execute celery, it creates a queue on your broker (in the last blog post it was RabbitMQ). If you have a few asynchronous tasks and you use just the celery default queue, all tasks will be going to the same queue.

Suppose that we have another task called too_long_task and one more called quick_task and imagine that we have one single queue and four workers.

In that scenario, imagine if the producer sends ten messages to the queue to be executed by too_long_task and right after that, it produces ten more messages to quick_task. What is going to happen? All your workers may be occupied executing too_long_task that went first on the queue and you don’t have workers on quick_task.

The solution for this is routing each task using named queues.


# CELERY ROUTES
CELERY_ROUTES = {
    'core.tasks.too_long_task': {'queue': 'too_long_queue'},
    'core.tasks.quick_task': {'queue': 'quick_queue'},
}

Now we can split the workers, determining which queue they will be consuming.


# For too long queue
celery --app=proj_name worker -Q too_long_queue -c 2

# For quick queue
celery --app=proj_name worker -Q quick_queue -c 2

I’m using 2 workers for each queue, but it depends on your system.

As, in the last post, you may want to run it on Supervisord

There is a lot of interesting things to do with your workers here.

Calling Sequential Tasks

Another common issue is having to call two asynchronous tasks one after the other. It can happen in a lot of scenarios, e.g. if the second tasks use the first task as a parameter.

You can use chain to do that


from celery import chain
from tasks import first_task, second_task
 
chain(first_task.s(meu_objeto_id) | second_task.s())

The chain is a task too, so you can use parameters on apply_async, for instance, using an ETA:


chain(salvar_dados.s(meu_objeto_id) | trabalhar_dados.s()).apply_async(eta=depois)

Ignoring the results from ResultBackend

If you just use tasks to execute something that doesn’t need the return from the task you can ignore the results and improve your performance.

If you’re just saving something on your models, you’d like to use this in your settings.py:


CELERY_IGNORE_RESULT = True

 

Sources:

http://docs.celeryproject.org/en/latest/userguide/tasks.html

http://docs.celeryproject.org/en/latest/userguide/optimizing.html#guide-optimizing

https://denibertovic.com/posts/celery-best-practices/

https://news.ycombinator.com/item?id=7909201

http://docs.celeryproject.org/en/latest/userguide/workers.html

http://docs.celeryproject.org/en/latest/userguide/canvas.html

 

Super Bônus

Celery Messaging at Scale at Instagram – Pycon 2013

Creating and populating a non-nullable field in Django

Hey, what’s up guys, that’s another quick post! I’ll show you how to create a new non-nullable field in Django and how to populate it using Django migrations.

SAY WUUUUT?????

george costanza taking his glasses off

Here’s the thing, do you know when you have your website in production and everything set in order and then some guy (there’s always some guy) appears with a new must-have mandatory field that nobody, neither the client nor the PO, no one, thought about it? That’s the situation.

But it happens that you use Django Migrations and you want to add those baby fields and run your migrations back and forth, right?

For this example, I decided to use a random project from the web. I chose this Django Polls on Django 1.10.

So, as usual, clone and create your virtual environment.

git clone [email protected]:garmoncheg/django-polls_1.10.git
cd django-polls_1.10/
mkvirtualenv --python=/usr/bin/python3 django-polls 
pip install django==1.10  # Nesse projeto o autor não criou um requirements.txt
python manage.py migrate  # rodando as migrações existentes
python manage.py createsuperuser 
python manage.py runserver

Note: This project has one missing migration, so if you’re following this step-by-step run python manage.py makemigrations to create the migration 0002 (that’s just a minor change on a verbose_name)

Now, access the admin website and add a poll

django polls admin

Alright, you can go to the app and see your poll there, answer it and whatever. Until now we did nothing.

The idea is to create more questions with different pub_dates to get the party started.

After you use your Polls app a little you’ll notice that any poll stay forever on your website, i.e., you never close it.

So, our update on this project will be that: From now on, all polls will have an expiration date. When the user creates a poll, he/she must enter the expiration date. That’s a non-nullable, mandatory field. For the polls that already exists in our database, we will arbitrarily decide they will have a single month to expire from the publication date.

Before migrations exist, it was done through SQL, you had to add a DateField that allowed NULL, then you’d create a query to populate this field and finally another ALTER TABLE to turn that column into a mandatory field. With the migrations, it works in the same way.

So, let’s add the expires_date field to models.py

expires_date = models.DateTimeField('expires at', null=True)

The whole models:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at', null=True)

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'


It’s time to make migrations:

python manage.py makemigrations

This is going to generate the 0003_question_expires_date migration, like this:

class Migration(migrations.Migration):

    dependencies = [
        ('polls', '0002_auto_20170429_2220'),
    ]

    operations = [
        migrations.AddField(
            model_name='question',
            name='expires_date',
            field=models.DateTimeField(null=True, verbose_name='expires at'),
        ),
    ]

Let’s alter this migration’s code, NO PANIC!

Populating the new field

First of all, create a function to populate the database with the expires dates:

def populate_expires_date(apps, schema_editor):
    """
    Populates expire_date fields for polls already on the database.
    """
    from datetime import timedelta

    db_alias = schema_editor.connection.alias
    Question = apps.get_model('polls', 'Question')

    for row in Question.objects.using(db_alias).filter(expires_date__isnull=True):
        row.expires_date = row.pub_date + timedelta(days=30)
        row.save()

Originally, I’ve used this code in a project with multiple databases, so I needed to use db_alias and I think it’s interesting to show it here.

Inside a migration, you’ll find a operations list. On that list, we’ll add the commands to run our populate_expires_date function and after that, we’ll alter this field to make it non-nullable.

operations = [
    migrations.AddField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(null=True, verbose_name='expires at'),
    ),
    migrations.RunPython(populate_expires_date, reverse_code=migrations.RunPython.noop),
    migrations.AlterField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(verbose_name='expires at'),
    )
]

You can see that we used migrations.RunPython to run our function during the migration. The reverse_code is for cases of unapplying a migration. In this case, the field didn’t exist before, so we’ll do nothing.

Right after, we’ll add the migration to alter the field and turn null=True. We also could have done that just removing that from the model and running makemigrations again. ( Now, we have to remove it from the model, anyway).

models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

And we’re ready to run the migrations

python mange.py migrate

Done! To see this working I’ll add this field to admin.py:

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date', 'expires_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]
    list_display = ('question_text', 'pub_date', 'expires_date', 'was_published_recently')
    list_filter = ['pub_date']
    search_fields = ['question_text']

And voilá, all Questions you had on polls now have an expires_date, mandatory and with 30 days by default for the old ones.

That’s it, the field we wanted! The modified project is here on my GitHub: https://github.com/ffreitasalves/django-polls_1.10

If you like it, share it and leave a comment, if you didn’t, just leave the comment.

XD

 

References:

http://stackoverflow.com/questions/28012340/django-1-7-makemigration-non-nullable-field

http://stackoverflow.com/questions/29217706/django-view-sql-query-without-publishinhg-migrations

https://realpython.com/blog/python/data-migrations/

https://docs.djangoproject.com/en/1.10/howto/writing-migrations/#non-atomic-migrations

Pip Installing a Package From a Private Repository

That’s a python quick tip. It’s very basic, but still very helpful. When your company uses GitHub for private repositories you often want to put them on the requirements.

First of all, remember to add your public key on your GitHub settings.

 

You just have to use like this:

pip install git+ssh://[email protected]/<<your organization>>/<<the project>>[email protected]<< the tag>>

You can even use on your requirements, without the pip install. E.g. if your organization is called Django and your project is called… let’s say… Django and you’d like to add Django 1.11.4 in your requirements you can use like this:

git+ssh://[email protected]/django/[email protected]

Probably you already have a deploy key configured to your server or a machine user, it will work for your private Repos on your server, if you don’t, take a look at this

 

SSH Keys

If you don’t know how to generate your ssh-key, It’s easy:

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

Now, copy the content of your public key (id_rsa.pub).

cat ~/.ssh/id_rsa.pub

In your GitHub account go to  Settings > SSH and GPG Keys and add it.

Guia para Deploy Django Python 3

crianças fazendo deploy

Tutorial de Deploy por aí é o que não falta, a maioria em inglês. Esse que estou criando é pra engrossar o caldo de deploys em português. Esse é um Guia Definitivo Rápido, ou não tão rápido, para fazer Deploy Django com Python 3. É um deploy para Kids.

A dificuldade de fazer um deploy reside nos detalhes. No fundo é fácil se você está familiarizado com as partes envolvidas. Você precisa saber fazer uma autenticação ssh, estar acostumado com a linha de comando, conhecer linux, saber configurar o projeto, entender o que é servir arquivos estáticos, gunicorn…. tá, tá… nunca é fácil e muito menos rápido, justamente por isso criaram um monte de ferramentas pra deploy. E hoje com Ansible, Docker e whatever kids are using these days fica fácil fazer o deploy mas muito abstrato entender o funcionamento.

Em alguns anos esse post será obsoleto pra sempre, com serverless e tudo mais acho que pouca gente vai querer saber como fazer um deploy django dessa forma. Mas, mesmo assim, se ajudar uma pessoa já está bom. Será um tutorial Old-Style.

O Servidor

Vou imaginar que você não tem um servidor, não tem uma conta na AWS, nem DigitalOcean, nem Linode, nada… Você pode criar uma conta em um deles, lançar uma máquina com as configurações que quiser (Na AWS é tudo mais complicado pra quem não está acostumado, se for sua primeira vez, prefira outro).

Pra esse tutorial estou falando de Ubuntu 16.04, que é o servidor que você mais vai ver por aí nesse momento nesses serviços. Você pode escolher um Debian qualquer também.

Configuração inicial

Configure o timezone do servidor

sudo locale-gen --no-purge --lang pt_BR
sudo dpkg-reconfigure tzdata

Atualize os pacotes:

sudo apt-get update 
sudo apt-get -y upgrade

Instalando Python 3.6 no lugar do Python 3.5

Agora substitua o Python 3.5 instalado, pelo Python3.6 (O Ubuntu que indiquei, ele vem com Python 3.5.1)

sudo apt-get update
sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get install python3.6
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 2

Você pode escolher qual versão do Python o SO vai usar ao chamar python3 com:

sudo update-alternatives --config python3

Se você se enrolar, dê uma olhada aqui.

Instale os requisistos do Sistema Operacional

Aqui tem alguns pacotes que eu sempre uso em um deploy.

sudo apt-get install python3-pip nginx supervisor git git-core libpq-dev python-dev 
python-virtualenv

Seu projeto pode ter outros requirements do SO pra instalar.

VirtualEnvWrapper para o Python3

Eu gosto muito do VirtualEnvWrapper, acho simples de começar um virtualenv, e deixa todos os meus virtualenvs no mesmo lugar, mas isso é escolha pessoal, se você não gosta, faça como está acostumado

Você instala o virtualenvwrapper, depois define a pasta dos seus virtualenvs (WORKON_HOME).

Para usar com múltiplos Pythons você vai precisar definir VIRTUALENVWRAPPER_PYTHON. No caso estou usando sempre o padrão que defini para o comando python3. Isso não é um problema porque você pode criar um virtualenv depois apontando qual python aquele virtualenv vai usar.

sudo pip3 install virtualenvwrapper
echo 'export WORKON_HOME=~/Envs' >> ~/.bashrc
echo ‘export VIRTUALENVWRAPPER_PYTHON=`which python3`’ >> ~/.bashrc
echo 'source /usr/local/bin/virtualenvwrapper.sh' >> ~/.bashrc
source ~/.bashrc

Crie seu VirtualEnv apontando qual python aquele virtualenv irá usar

mkvirtualenv nome_venv --python=python3

O VirtualEnvWrapper é muito fácil, para entrar em um Virtualenv que você criou, você pode usar:

workon nome_venv

Para sair do virtualenv:

deactivate

Para excluir um virtualenv:

rmvirtualenv nome_venv

Gere as Chaves SSH para autenticar no GitHub

Você não quer (e nem deveria) escrever a senha pra fazer o git pull do seu projeto no servidor.

Gere as chaves SSH

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

Veja e copie o conteúdo da sua chave pública (id_rsa.pub)

cat ~/.ssh/id_rsa.pub

Depois entre no seu github, em Settings > SSH and GPG Keys. Clique em New SSH Key, dê um nome pra essa chave, como (“chaves do servidor teste”) e em Key cole o conteúdo da chave pública id_rsa.pub

Faça o clone do seu projeto Django

Copie o link SSH do Github para fazer o clone, no caso estou usando um projeto que encontrei agora pra exemplo

git clone [email protected]:kirpit/django-sample-app.git

Entre na pasta do projeto e instale os requirements do projeto.

Lembre-se de estar no virtual env correto.

cd django-sample-app/
pip install -r requirements.txt

Agora faça as alterações que forem necessárias para o deploy django, como criar um arquivo de settings_local, banco de dados, ou qualquer outra coisa específica do seu projeto.

Depois de tudo certo, você ainda precisa rodar as migrações e gerar os arquivos estáticos (se estiver usando)

python manage.py migrate
python manage.py collectstatic

Configurando o NGINX

Nginx, assim como o Apache, tem um mundo inteiro só deles, nesse momento você precisa conhecer o básico.

Existe um diretório /etc/nginx/sites-available/ onde ficão os arquivos de configuração de sites disponíveis para o nginx servir e existe o diretório /etc/nginx/sites-enabled/ que é a mesmíssima coisa, mas o que estiver aqui o Nginx estará servindo mesmo.

Normalmente você cria o arquivo de configuração no sites-available/ e cria um link simbólico para o sites-enabled/

Nós vamos fazer isso. Primeiramente, vou excluir o site default do nginx

sudo rm /etc/nginx/sites-enabled/default

Agora crie o arquivo de configuração para o seu site. (Se você não está acostumado com o VIM, use troque vi por nano)

sudo vi /etc/nginx/sites-available/meusite

No conteúdo do arquivo, coloque isto, mudando os caminhos necessários:

server {
 listen 80;
 access_log /home/usuario/logs/access.log;
 error_log /home/usuario/logs/error.log;

 server_name nome-site.com.br;

 location / {
 proxy_pass http://127.0.0.1:8000; 

 #As proximas linhas passam o IP real para o gunicorn nao achar que sao acessos locais
 proxy_pass_header Server;
 proxy_set_header X-Forwarded-Host $server_name;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;


 }

 location /static {

   alias /home/usuario/caminho_projeto/static/;

 }

}

Agora crie o link simbólico para o sites-enabled:

sudo ln -s /etc/nginx/sites-available/meusite /etc/nginx/sites-enabled/meusite

Reinicie o Nginx:

sudo service nginx restart


(Se você configurou tudo direitinho até aqui, ao acessar o site você verá uma página com um erro 502 Bad Gateway do próprio nginx)

Isso acontece porque ainda não tem nada aqui http://127.0.0.1:8000

Vamos colocar o site pra rodar nessa porta pelo gunicorn

Configurando o Gunicorn

Alguém vivo até essa parte? Cansa não, falta pouco.

No seu virtualenv (lembra workon nome_env?) instale o gunicorn

pip install gunicorn

Na pasta do seu projeto crie um arquivo chamado gunicorn_conf com:

bind = "127.0.0.1:8000"
logfile = "/home/usuario/logs/gunicorn.log"
workers = 3

Agora se você rodar o gunicorn, você vai ver seu site rodando:

/home/usuario/Envs/nome_venv/bin/gunicorn projeto.wsgi:application -c gunicorn_conf

Mas o que você pretende fazer? Rodar esse comando dentro de um screen e ir embora? Não dá né! Então, você vai usar o Supervisor pra controlar o funcionamento do gunicorn.

Configurando o Supervisor

Crie o seguinte arquivo de configuração

sudo vi /etc/supervisor/conf.d/gunicorn.conf

Com o seguinte conteúdo:

[program:gunicorn]
command=/home/usuario/Envs/nome_venv/bin/gunicorn projeto.wsgi:application -c /home/usuario/projeto/projeto_django/gunicorn_conf
directory=/home/usuario/projeto/projeto-django
user=usuario
autostart=true
autorestart=true
redirect_stderr=true

Depois é só avisar o supervisor que existe um novo processo que ele precisa controlar da seguinte forma:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

E voilá! Um site rodando você terá!

Conclusão

Existem muito mais coisas envolvidas no processo de um deploy. Você precisa configurar um firewall, provavelmente precisará servir mais pastas estáticas, etc, etc, etc… Mas precisa começar por algum lugar.

Não acredito que fiz um post inteiro sem colocar nenhum gif no meio, então só pra terminar, PRESTE ATENÇÃO em TODOS os caminhos que eu coloquei acima, você vai ter que usar os seus próprios paths corretamente

Oops..

Executing time-consuming tasks asynchronously with Django and Celery

This post is based on a lightning talk I gave on 2015, at GruPy-SP (July/15) in Sao Paulo.

What’s the matter of having time-consuming tasks on the server side?

Every time the client makes a request, the server has to read the request, parse the received data, retrieve or create something into the database, process what the user will receive, renders a template and send a response to the client. That’s usually what happens in a Django app.

Depending on what you are executing on the server, the response can take too long and it leads to problems such as poor user experience or even a time-out error. It’s a big issue. Loading time is a major contributing factor to page abandonment. So, slow pages lose money.

There’s a lot of functions that can take a long time to run, for instance, a large data report requested by a web client, emailing a long list or even editing a video after it’s uploaded on your website.

Real Case:

That’s a problem that I’ve face once when I was creating a report. The report took around 20 minutes to be sent and the client got a time-out error and obviously, nobody wants to wait 20 minutes for getting something. So, to handle this I’d have to let the task run in the background. (On Linux, you can do this putting a & at the end of a command and the OS will execute the command in the background)

django-view-os-system

It looks like the worst code ever.

Me arrependo imediatamente dessa decisão

Celery – the solution for those problems!

Celery is a distributed system to process lots of messages. You can use it to run a task queue (through messages). You can schedule tasks on your own project, without using crontab and it has an easy integration with the major Python frameworks.

How does celery work?

Celery Architecture Overview. (from this SlideShare)

estrutura-celery

  • The User (or Client or Producer) is your Django Application.
  • The AMPQ Broker is a Message Broker. A program responsible for the message queue, it receives messages from the Client and delivers it to the workers when requested. For Celery the AMPQ Broker is generally RabbitMQ or Redis
  • The workers (or consumers) that will run your tasks asynchronously.
  • The Result Store, a persistent layer where workers store the result of tasks.

The client produces messages, deliver them to the Message Broker and the workers read this messages from the broker, execute them and can store the results on a Memcached, RDBMS, MongoDB, whatever the client can access later to read the result.

Installing and configuring RabbitMQ

There is a lot of examples on How to Use Celery with Redis. I’m doing this with RabbitMQ.

  1. Install RabbitMQ
    sudo apt-get install rabbitmq-server
  2. Create a User, a virtual host and grant permissions for this user on the virtual host:
    
    sudo rabbitmqctl add_user myuser mypassword
    sudo rabbitmqctl add_vhost myvhost
    sudo rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"
    

Installing and configuring Celery

pip install celery

In your settings.py:

#Celery Config
BROKER_URL = 'amqp://guest:[email protected]:5672//'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

In your project’s directory (the same folder of settings.py), creates a celery.py file as following.


from __future__ import absolute_import

import os
from celery import Celery
from django.conf import settings

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nome_do_proj.settings')

app = Celery('nome_do_proj')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

This autodiscover_tasks allows your project to find the asynchronous task of each Django App. In the same directory, you have to modify your  __init__.py:


from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

Creating tasks for your app

In your app’s directory, put a tasks.py:


from __future__ import absolute_import
from celery import shared_task
from reports import generate_report_excel

@shared_task  # Use this decorator to make this a asyncronous function
def generate_report(data_inicial, data_final, email):
    generate_report_excel(
        ini_date = ini_date,
        final_date = final_date,
        email = email
    )

Now you just have to import this function anywhere you want and call the delay method, that was added by the shared_task decorator.

from tasks import generate_report

@login_required()
def my_view(request):
    ...
    generate_report.delay(ini_date, final_date, email)
    return "You will receive an email when the report is done"

 

Running celery workers

Now you have to run the celery workers so they can execute the tasks getting the messages from the RabbitMQ Broker. By default, celery starts one worker per available CPU. But you can change it using the concurrency parameter (-c)

celery --app=nome_projeto worker --loglevel=INFO

And your screen will look like this:

celery-rodando

In another terminal you can open the shell and call your task to see it working:

In [1]: from app.tasks import generate_report

 In [2]: generate_report("2012-01-01", "2015-03-14", "[email protected]")

 

And you will see something like this on Celery:

aparece no celery

Deploying Celery

To use celery in production you’ll need a process control system like Supervisor

To install supervisor:

sudo apt-get install supervisor

Now you have to create a configuration file for celery in /etc/supervisor/conf.d/

[program:celery]
command=/home/deploy/.virtualenvs/my_env/bin/celery --app=proj_name worker --loglevel=INFO
directory=/home/deploy/webapps/django_project
user=user_name
autostart=true
autorestart=true
redirect_stderr=true

Now inform Supervisor that there is a new process:

sudo supervisorctl reread
sudo supervisorctl update

And now starts celery on supervisor:

sudo supervisorctl start celery

My presentation in Brazilian Portuguese:

Originally posted in Portuguese

Sources:

http://www.celeryproject.org/

https://www.rabbitmq.com/

http://stackoverflow.com/questions/9140716/whats-the-advantage-of-using-celery-with-rabbitmq-over-redis-mongodb-or-django/9176046#9176046

http://www.slideshare.net/fireantology/europython-2011-playing-tasks-with-django-celery?qid=f37c05cf-f153-4ee3-bbbb-3e6e48a10e4d&v=default&b=&from_search=3

https://speakerdeck.com/allisson/tarefas-assincronas-com-django-e-celery?#stargazers

http://www.slideshare.net/alexef/django-celery-18659986?qid=f37c05cf-f153-4ee3-bbbb-3e6e48a10e4d&v=default&b=&from_search=6

http://www.slideshare.net/idangazit/an-introduction-to-celery

http://michal.karzynski.pl/blog/2014/05/18/setting-up-an-asynchronous-task-queue-for-django-using-celery-redis/

http://supervisord.org/

http://docs.celeryproject.org/en/latest/django/first-steps-with-django.html#using-celery-with-django

http://www.onurguzel.com/supervisord-restarting-and-reloading/

Bonus:

How Instagram Feed Work – Celery and RabbitMQ

Distributing Python Apps for Windows Desktops

I’ve started working on a blog post about how to create a Python app auto-update and it turned into three. After these 3 articles, you will be able to create a Python app that fully works on windows and you can distribute it within an installer.

This text was originally written in Portuguese.

  1. How to create a Python .exe with MSI Installer and Cx_freeze
  2. How to create an application with auto-update using Python and Esky
  3. How to create an MSI installer using Inno Setup

It has just 4 Steps:

  • Create a simple project called boneca
  • Build an MSI installer using Cx_freeze
  • Add an Auto-update feature to the project, using Esky
  • Show how to use Inno Setup to build a more powerful and custom installer

In the end will be able to pack and distribute Python apps for windows desktop in an easy way.

Some people still think Python is just a script language or it works only for web development through frameworks, but it’s not. It can be compiled and it can be shipped without source code, turned into a commercial application.

The great example of all time is Dropbox. Dropbox client was written in Python to be portable for Windows, Mac, and Linux. The only difference is the interface. For Windows and Linux, Dropbox uses wxPython and for Mac it uses Python-ObjC. I like this words from Guido Van Rossum about Dropbox:

 

“Python plays an important role in Dropbox’s success: the Dropbox client, which runs on Windows, Mac and Linux (!), is written in Python. This is key to the portability: everything except the UI is cross-platform. (The UI uses a Python-ObjC bridge on Mac, and wxPython on the other platforms.) Performance has never been a problem — understanding that a small number of critical pieces were written in C, including a custom memory allocator used for a certain type of objects whose pattern of allocation involves allocating 100,000s of them and then releasing all but a few. Before you jump in to open up the Dropbox distro and learn all about how it works, beware that the source code is not included and the bytecode is obfuscated. Drew’s no fool. And he laughs at the poor competitors who are using Java.”

From depth and breadth of python