Como utilizar Docker em aplicações Ruby

Publicado por Hugo Luchessi , Jônatas Davi Paganini e Daniel de Oliveira Filho no dia dev

Docker logo

Há um tempo atrás mostramos como configurar o ambiente de desenvolvimento com vagrant para agilizar o setup da máquina. Hoje vamos mostrar como fazer isso com Docker e quais são os primeiros passos para “dockerizar” uma aplicação Ruby.

Quando um novo RDoer entra no time ou troca de máquina, temos sempre um ou 2 dias para configurar todo o ambiente com nossa infra de desenvolvimento. Usando docker baixamos esse tempo para 40 minutos :rocket:

O que é Docker

Docker é uma ferramenta que permite desenvolvedores especificar qual é a configuração do sistema operacional que irá rodar a aplicação. O docker fornece uma camada de abstração para configurações desse ambiente virtual denominado como container. O que faz o docker ser adotado em larga escala é o fato de permitir empacotar todas as dependências de uma apilcação em uma única unidade de container.

Aqui na Resultados Digitais temos sempre uma preocupação de espalhar a tecnologia das novas tecnologias que estamos aprendendo/interessados em adotar. Quando escolhemos alguma como o Docker, nos preparamos para disseminar o conhecimento. Por isso temos algumas mídias alternativas para mostrar o que é o docker. Abaixo segue um podcast que gravamos falando sobre o assunto.

Também fizemos um seminário interno sobre docker e promovemos um DOJO para os early adopters :wink:

Instalação do Docker

Veja os manuais oficiais de instalação conforme seu sistema operacional.

Mas basicamente você pode executar no linux:

1
sudo apt-get install docker

Ou no OSX:

1
brew install docker

Multiplos ambientes com Docker

Na vida real trabalhando com projetos, você precisa organizar imagens para trabalhar com vários ambientes. Por exemplo, em uma imagem de produção você não precisa ter o seu editor de textos configurado.

Em uma imagem que apenas roda sua suíte de testes você não precisa ter a mesma configuração de tunning do banco de dados.

Imagens

As imagens servem para você ter um bootstrap da sua aplicação. Então se você quiser subir uma imagem com Ubuntu você pode criar um arquivo Dockerfile com o seguinte conteúdo:

1
2
FROM ubuntu
CMD echo "Sou um container rodando Ubuntu"

Uau! Com apenas um FROM <imagem-sistema-operacional> já é possível iniciar uma máquina com o Ubuntu.

Para dockerizar uma app, ou seja, portar para o mundo dos containers, precisamos definir qual o sistema operacional e quais ferramentas vamos instalar. Existem algumas imagens oficiais que já garantem o set básico de ferramentas para cada stack. Então vale a pena estender estas imagens e adaptar para sua necessidade. Nesse exemplo abaixo, vamos usar a imagem oficial que contém o ruby.

1
2
FROM ruby
RUN ruby -e "p ENV"

As imagens também podem ser versionadas, e cada versão fica dividida com <imagem:versão>, então poderia ser FROM ruby:2.2.4 para baixar especificamente a versão 2.2.4 :wink:

No Dockerfile você especificou a imagem que quer usar e um comando quer executar dentro da imagem.

Dessa maneira, agora falta baixar a imagem e construir localmente com docker build.

docker build

Para construir a imagem acima, é necessário usar o docker build na pasta onde está o Dockerfile.

Ao rodar localmente, você terá um output como este:

1
2
3
4
5
6
7
8
9
10
docker build .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ruby
 ---> cc0ac307dc6d
Step 2 : RUN ruby -e "p ENV"
 ---> Running in e0dc868ac0b0
{"RUBY_MAJOR"=>"2.3", "BUNDLER_VERSION"=>"1.11.2", "HOSTNAME"=>"e5c68db50333", "RUBYGEMS_VERSION"=>"2.6.2", "HOME"=>"/root", "BUNDLE_APP_CONFIG"=>"/usr/local/bundle", "BUNDLE_BIN"=>"/usr/local/bundle/bin", "RUBY_VERSION"=>"2.3.0", "PATH"=>"/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "BUNDLE_PATH"=>"/usr/local/bundle", "GEM_HOME"=>"/usr/local/bundle", "RUBY_DOWNLOAD_SHA256"=>"ba5ba60e5f1aa21b4ef8e9bf35b9ddb57286cb546aac4b5a28c71f459467e507", "PWD"=>"/", "BUNDLE_SILENCE_ROOT_WARNING"=>"1"}
 ---> 2e5fdfd86039
Removing intermediate container e0dc868ac0b0
Successfully built 2e5fdfd86039

Uau! Rodou até o p env dentro do ruby mostrando o Hash com as variáveis de ambiente.

Agora se tentar rodar novamente, verá que a imagem no output já foi cacheado.

1
2
3
4
5
6
7
8
➜  ruby docker build .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ruby
 ---> 2e5fdfd86039
Step 2 : RUN ruby -e "p ENV"
 ---> Using cache
 ---> 493f215a2325
Successfully built 493f215a2325

Observe que ele não rodou o comando Ruby pois esta parte já está cacheada.

Mas o que aconteceu na prática? Vamos inspecionar as imagens.

docker images

Use o comando docker images para verificar quais imagens estão disponíveis no seu computador:

1
2
3
$ docker images
REPOSITORY   TAG     IMAGE ID       CREATED         SIZE
ruby         latest  493f215a2325   5 minutes ago   725.4 MB

725 MB? para fazer um simples hello world :cry:

A imagem oficial do ruby é baseada em UBUNTU e por isso é bem grande.

Existe uma versão baseada no Alpine Linux., que é uma versão mais compacta de linux.

Então vamos alterar o Dockerfile para usar o alpine.

1
2
FROM ruby:alpine
RUN ruby -e "p ENV"

E vamos atualizar a imagem novamente rodando um $ ruby docker build ..

E agora conferindo o tamanho da imagem:

1
2
3
$ ruby docker images
REPOSITORY    TAG      IMAGE ID       CREATED              SIZE
ruby          latest   a3553913b652   About a minute ago   125.3 MB

Bem melhor com 125.3 MB :wink:

Legal, agora já temos um espaço para trabalhar com ruby dentro de um container. Basta fazermos a sincronização do projeto pra dentro do container. E existem algumas formas de fazer isso:

Copiando recursos

Fazer o build copiando os arquivos da aplicação para a imagem é útil quando se quer fazer o deploy auto contido da aplicação. Alterações nesses arquivos vão precisar ser feitos internamento no container, o que deixa o desenvolvimento mais difícil, mas para deploy em ambientes de produção é a melhor maneira de se trabalhar.

Através de volumes

Mapear volumes para dentro de containers é útil quando se quer alterar os arquivos do container sem ter que buildar a imagem novamente. Ideal para ambientes de desenvolvimento, onde se altera um arquivo e se tem a necessidade de testar as alterações na hora.

Linkando recursos

Fazer link de recursos é útil para fazer containers mais compactos. Ideal para organizar e manter os containers simples porém compartilhando informações entre si.

docker run

O comando docker build gerou uma imagem de um container com o Ruby instalado.

Isso permite re-utilizar essa imagem. Estender a imagem adicionando mais funcionalidades ou configurar com as especificações do projeto.

Também é possível você rodar um comando diretamente de dentro do container.

1
2
3
4
docker run -it ruby:alpine bash -c "ruby -e '3.times {|i|puts i}'"
0
1
2

Legal! Estou rodando ruby sem precisar ter nada instalado localmente!

Opções de build

No processo de build é possível criar um repositório/tag na imagem com a opção -t <repo/tag>. O que pode ser útil para re-aproveitar as imagens no futuro. Exemplo:

1
docker build -t resultadosdigitais/sinatra .

Conferindo a imagem com docker images:

1
2
REPOSITORY                   TAG     IMAGE ID       CREATED              SIZE
resultadosdigitais/sinatra   latest  1f606fc59a77   About a minute ago   141.2 MB

A imagem agora aparece com REPOSITORY e vai facilitar para reutilizarmos em outros containers.

Para rodar o servidor com a imagem digite:

1
2
3
4
5
6
docker run -p 4567:4567 resultadosdigitais/sinatra ruby app.rb
[2016-06-07 15:13:12] INFO  WEBrick 1.3.1
[2016-06-07 15:13:12] INFO  ruby 2.3.1 (2016-04-26) [x86_64-linux]
== Sinatra (v1.4.7) has taken the stage on 4567 for development with backup
from WEBrick
[2016-06-07 15:13:12] INFO  WEBrick::HTTPServer#start: pid=1 port=4567

Onde -p serve para fazer a ponte entre <porta do container>:<porta local>.

Tentando acessar localmente via localhost:4567 não estará disponível pois o bind foi feito para 127.0.0.1 enquanto o docker espera o host 0.0.0.0.

Dessa forma teremos que alterar o servidor sinatra para dar o bind no host correto:

1
2
3
4
5
6
7
8
9
require "bundler/setup"
require "sinatra"

set :bind, '0.0.0.0'

$counter = 0
get "/counter" do
  puts "counter: #{$counter += 1}"
end

Registry

O docker registry é a plataforma oficial fornecida pelo próprio pessoal do docker para você manter as imagens docker e compartilhar imagens.

docker push

Com o comando docker push é possível compartilhar sua imagem atualizada em um repositório de imagens ou no Docker Hub. Posteriormente não é necessário rodar docker build recompilando tudo. Então é só fazer um docker pull para baixar a imagem pronta.

docker pull

Com o comando docker pull é utilizado para baixar as imagens já prontas do repositório.

Cada imagem é composta por várias camadas e é possível baixar e sincronizar apenas os fragmentos da imagem.

docker compose

Uau! :scream: agora você deve estar pensando em instalar toda sua parafernalha de ferramentas dentro de um container e não sujar sua máquina espalhando bibliotecas aleatórias por onde passa.

Mas devagar! Dê uma olhada no docker compose que ele serve exatamente pra isso! Ele te ajuda a compor unidades de containers e trabalhar com um subset de containers que conversam entre si.

Já no processo de usar em produção, também vale a pena ver:

Um pouco mais!

Ficou interessado? Não deixe de conferir a talk do Daniel Oliveira sobre nossa jornada com docker aqui na Resultados Digitais.

E você está usando docker no seu dia a dia? Não deixe de compartilhar sua experiência nos comentários!

Hugo Luchessi

Hugo Luchessi

Software Engineer

Jônatas Davi Paganini

Jônatas Davi Paganini

Full Stack Trainer

Daniel de Oliveira Filho

Daniel de Oliveira Filho

DevOps Engineer

Comentários