Rails em produção com Docker e Fig
03/01/2015Já mostrei em outros posts as facilidades do Docker e como é rápido por uma app web com banco de dados em funcionamento. Hoje apresento o Fig que veio para simplificar todo o processo de criação de containers Docker, usaremos ambos para por uma app Rails em produção.
Introdução
As boas práticas de Dockerising dizem que você deve ter apenas 1 serviço em execução por container criado, no exemplo temos 3 serviços:
- PostgreSQL
- App Rails
- Nginx
No projeto Rails temos 2 novos arquivos o fig.yml e um Dockerfile,
não foi necessário controlar o Dockerfile de imagens do PostgreSQL ou do
Nginx, no próprio Fig é possível especificar o nome da imagem que será
usada na criação de cada container e ele entende que caso a imagem não
exista no host ele deve fazer um pull do Docker Hub.
O Dockerfile
O Dockerfile utilizado no projeto é super simples e contém apenas uma chamada para o nome de uma imagem personalizada:
$ cat Dockerfile
FROM infoslack/rails:onbuild
A imagem infoslack/rails:onbuild em sua receita faz uso de instruções
ONBUILD onde é possível adiar a execução de algumas tarefas:
FROM infoslack/docker-ruby
MAINTAINER Daniel Romero <infoslack@gmail.com>
RUN bundle config --global frozen 1
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ONBUILD COPY Gemfile /usr/src/app/
ONBUILD COPY Gemfile.lock /usr/src/app/
ONBUILD RUN bundle install
ONBUILD COPY . /usr/src/app
RUN apt-get update \
        && apt-get install -y nodejs --no-install-recommends \
        && rm -rf /var/lib/apt/lists/*
RUN apt-get update \
        && apt-get install -y \
        mysql-client \
        postgresql-client \
        sqlite3 \
        --no-install-recommends \
        && rm -rf /var/lib/apt/lists/*
EXPOSE 3000
CMD ["rails", "server"]
Basicamente a receita herda de outra imagem que já possui a instalação do Ruby e prepara o ambiente para executar a aplicação Rails, apenas quando a imagem referente ao projeto for criada as tasks onbuild serão disparadas.
O bom desta abordagem é que sempre que a imagem do projeto sofrer
alterações a tarefa do bundle install será verificada no cache do
Docker, ou seja se o seu Gemfile.lock não sofreu alterações a execução
do bundle é ignorada.
Fig
Para configurar o Fig no projeto só precisamos de um arquivo fig.yml,
nele teremos algo assim:
db:
  image: postgres:9.3
  volumes:
    - ~/.docker-volumes/blog/db/:/var/lib/postgresql/data/
  expose:
    - 5432
app:
  build: .
  command: bundle exec puma -p 9001 -e production
  environment:
    - RAILS_ENV=production
  volumes:
    - .:/usr/src/app
  expose:
    - 9001
  links:
    - db
web:
  image: infoslack/nginx-puma
  volumes_from:
    - app
  ports:
    - 80:80
  links:
    - app
No Fig separamos cada serviço que será executado e de quais imagens eles
devem ser criados, no exemplo o db está usando a imagem oficial do
Postgres.
Em app não declaramos a imagem pois queremos que ela seja criada partindo
do Dockerfile existente no projeto, em vez disso usamos a opção build,
em seguida temos o command onde disparamos o puma na porta 9001.
É possível manter diferentes environments no fig, no nosso exemplo estou
setando apenas o de production. Na última opção temos o links onde
informo o nome do serviço que o container de app terá relacionamento.
Este relacionamento permite utilizarmos o nome do serviço em vez do número
ip em algumas configurações, como por exemplo o database.yml:
production:
  adapter: postgresql
  enconding: unicode
  pool: 5
  username: postgres
  password:
  database: app_rails_demo
  host: db
O mesmo vale para o serviço web onde o container para o Nginx será
criado, nas configurações do nginx basta informar o nome do serviço em
vez do ip do container da aplicação rails:
...
upstream rails {
  server app:9001 fail_timeout=0;
}
...
Inicializando a aplicação
Para a primeira inicialização da app podemos carregar o serviço de db
sozinho em background e em seguida rodar o rake db:setup:
$ fig up -d db
Creating blog_db_1...
$ fig run --rm app rake db:setup
app_rails_prod already exists
-- enable_extension("plpgsql")
-> 0.0247s
-- create_table("comments", {:force=>:cascade})
-> 0.0423s
...
-- initialize_schema_migrations_table()
-> 0.0053s
Removing blog_app_run_1...
Na segunda tarefa utilizei a opção run do fig para criar um container
intermediário apenas para executar o rake db:setup e depois ele foi
destruído.
Agora posso inicializar a aplicação com o comando fig up:
$ fig up
Recreating blog_db_1...
Creating blog_app_1...
Creating blog_web_1...
Attaching to blog_db_1, blog_app_1, blog_web_1
app_1 | Puma starting in single mode...
app_1 | * Version 2.10.2 (ruby 2.2.0-p0), codename: Robots on Comets
app_1 | * Min threads: 0, max threads: 16
app_1 | * Environment: production
app_1 | * Listening on tcp://0.0.0.0:9001
app_1 | Use Ctrl-C to stop
Os containers serão criados e linkados e os serviços inicializados, mas
claro que em produção a execução do fig será em background: fig up -d,
utilizando a opção ps podemos ver os 3 containers criados:
$ fig ps
Name        Command                State    Ports
------------------------------------------------------
r_app_1     bundle exec puma ...   Up       3000/tcp
r_dba_1     docker-entrypoint...   Up       5432/tcp
r_web_1     nginx -g daemon  ...   Up       80/tcp
Algumas tarefas não foram automatizadas como por exemplo o
rake assets:precompile essa tarefa poderia ser executada por um
container intermediário:
$ fig run --rm app rake assets:precompile
Ou poderiamos criar uma regra de ONBUILD no Dockerfile. Sempre que um
deploy for feito com alterações no projeto, o fig pode executar a tarefa
build para aplicar as mudanças na imagem da aplicação:
$ fig build app
Podemos conferir a aplicação funcionando normalmente aqui: http://54.172.19.70
Finalizando
Abordarei sobre formas de deploy contínuo em outros posts, mas por enquanto você poderia usar a sua ferramenta de deploy favorita sem maiores problemas, ela apenas enviaria a aplicação para o host e a execução do fig poderia ser automatizada com monit por exemplo.
Não deixe de conferir os links com os códigos dos exemplos e se você ficou interessado na combinação Docker + Fig vai rolar um workshop de 05/01/15 a 09/01/15, mais detalhes em: http://infoslack.com/workshops/docker/
Happy Hacking ;)
Referências
- https://github.com/infoslack/rails_docker_demo
- http://www.fig.sh/yml.html
- https://github.com/infoslack/docker-nginx
- https://github.com/infoslack/docker-rails
- https://github.com/infoslack/docker-ruby
- https://docs.docker.com/userguide/dockerlinks/
- https://docs.docker.com/reference/builder/#onbuild
- https://hub.docker.com/u/infoslack/
- https://hub.docker.com/postgres/
- https://docs.docker.com/articles/dockerfile_best-practices/