Rails em produção com Docker e Fig

Já 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

Comentários