Docker, do Básico a Orquestração e Clusterização - 3. Montando containers

Docker Logo Nessa série de artigos estamos abordando tópicos para uma boa utilização do Docker .

Dando continuidade ao artigo anterior vamos abordar a criação “on the fly” de containers para rodar sua aplicação, a criação utilizando receitas em arquivos Dockerfile e algumas dicas para montar um bom Dockerfile para sua aplicação.

Montando container “na unha”

Primeiramente vamos para a montagem de um container. O jeito mais simples onde você consegue ver de maneira direta o que está acontecendo é criando na hora, “on the fly”, passo a passo até o container estar pronto.

Seu serviço do docker tem que estar rodando Para iniciar no Linux:

$ sudo service docker start

Para iniciar no Mac e no Windows

$ boot2docker start

Antes que me perguntem sobre o docker machine, escreverei sobre ele em breve abordando as novidades do mundo docker.

Nesse exemplo vamos começar baixando um container do Debian 7:

$ docker pull debian
debian:latest: The image you are pulling has been verified
511136ea3c5a: Already exists
30d39e59ffe2: Already exists
c90d655b99b2: Already exists
Status: Image is up to date for debian:latest

Entramos no container para começar a “montagem”:

$ docker run -ti debian bash
root@ba11f3c8394c:/#

Podemos perceber que estamos agora no bash dentro do container do Debian:

root@ba11f3c8394c:/# cat /etc/issue
Debian GNU/Linux 7 \n \l

Vamos agora cria o diretório para os fontes, atualizar os fontes, instalar o wget, instalar os repositórios dotdeb, instalar nginx, php5.4, o fpm tudo com os comandos que estamos acostumados para subir um servidor:

root@ba11f3c8394c:/# mkdir /src && apt-get update && apt-get install -y wget
root@ba11f3c8394c:/# echo "deb http://packages.dotdeb.org wheezy all" > /etc/apt/sources.list.d/dotdeb.list && echo "deb-src http://packages.dotdeb.org wheezy all" >> /etc/apt/sources.list.d/dotdeb.list && wget -O - http://www.dotdeb.org/dotdeb.gpg |apt-key add -
root@ba11f3c8394c:/# apt-get install php5-cli php5-fpm php5-mysql php5-intl php5-xdebug php5-recode php5-snmp php5-mcrypt php5-memcache php5-memcached php5-imagick php5-curl php5-xsl php5-snmp php5-dev php5-tidy php5-xmlrpc php5-gd php5-pspell php-pear nginx
2 upgraded, 162 newly installed, 0 to remove and 6 not upgraded.
Need to get 103 MB of archives.
After this operation, 289 MB of additional disk space will be used.
Do you want to continue [Y/n]? Y

Depois de tudo instalado vamos configurar o php.ini o fpm e o nginx:

root@ba11f3c8394c:/# sed -i "s/;date.timezone =/date.timezone = America\/Sao_Paulo/" /etc/php5/cli/php.ini \
    && sed -i "s/;date.timezone =/date.timezone = America\/Sao_Paulo/" /etc/php5/fpm/php.ini \
    && sed -i "s/short_open_tag = On/short_open_tag = Off/" /etc/php5/cli/php.ini \
    && sed -i "s/short_open_tag = On/short_open_tag = Off/" /etc/php5/fpm/php.ini \
    && sed -i "s/error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT/error_reporting = E_ALL/" /etc/php5/cli/php.ini \
    && sed -i "s/error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT/error_reporting = E_ALL/" /etc/php5/fpm/php.ini \
    && sed -i "s/display_errors = Off/display_errors = On/" /etc/php5/cli/php.ini \
    && sed -i "s/display_errors = Off/display_errors = On/" /etc/php5/fpm/php.ini \
    && sed -i "s/display_startup_errors = Off/display_startup_errors = On/" /etc/php5/cli/php.ini \
    && sed -i "s/display_startup_errors = Off/display_startup_errors = On/" /etc/php5/fpm/php.ini

Se quiser pode instalar o vi ou nano para editar os arquivos de configurações que mencionamos.

Por fim criamos um virtual host teste.dev

root@ba11f3c8394c:/# cat << FIM >  /etc/nginx/sites-available/teste.dev.conf
server {

  server_name teste.dev;

  client_max_body_size  5m;
  client_header_timeout 1200;
  client_body_timeout   1200;
  send_timeout          1200;
  keepalive_timeout     1200;

  root        /src/;
  try_files   $uri $uri/ /index.php?$args;
  index       index.php;

  location ~ \.php$ {
    fastcgi_pass    unix:/var/run/php5-fpm.sock;
    fastcgi_index   index.php;
    include         fastcgi_params;

    fastcgi_connect_timeout 1200;
    fastcgi_send_timeout    7200;
    fastcgi_read_timeout    7200;
    fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}
FIM

E ativamos ele nos sites enable:

ln -sf /etc/nginx/sites-available/teste.dev.conf /etc/nginx/sites-enabled/teste.dev.conf

Daemon vs no Daemon

Agora um passo que apanhei bastante e consegui resolver com a ajuda do Mário Rezende. Quando iniciamos um container ele executa o comando que informamos e ao encerrar o comando ele finaliza esse container.

Você pode confirmar fazendo o seguinte teste. Abra 2 terminais, e um execute:

$ docker run -it debian sleep 40 && echo fim

Isso vai subir o container debian, vai aguardar 40 segundos vai imprimir fim e vai encerrar o container

Antes de acabar os 40 segundos em outro terminal execute várias vezes o comando docker ps

$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
a233a963c968        debian:latest        "sleep 40"          2 hours ago         Up 34 seconds

Primeiro aparece o container em execução depois ele some:

$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES

Podemos listar o último container executado com o parâmetro -l:

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
a233a963c968        debian:latest       "sleep 40"          2 hours ago         Exited (0) 9 seconds ago                       nostalgic_wozniak

Com isso na cabeça podemos entender porque quando subimos um container logo após o Nginx iniciar com sucesso ele finaliza (o container todo finaliza).

Sabendo disso vamos rodar o Nginx como um executável normal, não como daemon e para isso no nosso container, que ainda está rodando porque no comando inicial mandamos rodar o bash (e o bash ainda está rodando), vamos mudar uma configuração no nginx.conf:

root@ba11f3c8394c:/# sed -i "s/www-data;/www-data;\\ndaemon off;/g" /etc/nginx/nginx.conf

Também vamos criar um script básico, maroto para rodar nosso container:

root@ba11f3c8394c:/# cat << FIM >  /run.sh
#!/bin/bash
/etc/init.d/php5-fpm restart && nginx
FIM
root@ba11f3c8394c:/# chmod a+x /run.sh # tornando executável

Container pronto vamos pegar o container id, gerar uma nova imagem e enviar para o docker hub:

root@ba11f3c8394c:/# exit
$ docker ps -l
CONTAINER ID        IMAGE                         COMMAND             CREATED             STATUS                       PORTS               NAMES
ba11f3c8394c        wfsilva/nginx-phpfpm:latest   "bash"              2 minutes ago
$ docker commit ba11f3c8394c wfsilva/nginx-phpfpm
aca05520d01b7057baacd4f7f9e9342c1c888ed063c4a1fd557a6d206999c65c
$ docker push wfsilva/nginx-phpfpm

Pronto sempre que quiser essa imagem é só rodar:

docker run -p 80:80 -v /caminho/para/meu/projeto/php:/src wfsilva/nginx-phpfpm /run.sh

Mesmo que o container não esteja na minha máquina ele será baixado do meu repositório no docker hub.

O caminho para os fontes do projeto (opção -v) vai ser montada em /src que também foi configurado como path para nosso virtualhost. A porta 80 foi mapeada para que possamos acessar e o comando que executamos foi o script run.sh que inicia o php-fpm e o Nginx.

Podemos adicionar o nosso virtualhost em nosso arquivo de hosts Se for mac sugiro usar o boot2docker ip ou o docker-machine ip:

echo "`boot2docker ip` teste.dev" | sudo tee -a  /etc/hosts
echo "`docker-machine ip` teste.dev" | sudo tee -a  /etc/hosts

Se for linux:

echo "127.0.0.1 teste.dev" | sudo tee -a  /etc/hosts

Se for Windows, bom você deve saber onde fica seu arquivo de hosts. Se não souber pode perguntar.

Acessando http://testes.dev batemos em nosso container que responde acessando os arquivos mapeados em nossa maquina “host”.

Montando container com Dockerfile

Cool meme

Para mim esse é o jeito mais legal porque com um simples arquivo e com instruções simples montamos um container. Todos os comandos possíveis, e não são muitos, num Dockerfile estão em https://docs.docker.com/reference/builder/ .

Também acho legal porque posso montar um repositório no Github ou no Bitbucket com meu Dockerfile e demais arquivos que ele precisa para “buildar” (eu e esses neologismos gringos, aff) e criar um repositório no Dockerhub apontando para esse repositório Git. O Dockerhub vai procurar esses arquivos e tentar buildar um container, e a cada push feito para seu Github ou Bitbucket criado o Dockerhub vai tentar buildar novamente.

Como exemplo segue esse Dockerfile disponível em https://github.com/wsilva/nginx-php-fpm-docker

FROM    debian
MAINTAINER Wellington Silva <****@***.br>

# Keep upstart from complaining
RUN dpkg-divert --local --rename --add /sbin/initctl && ln -sf /bin/true /sbin/initctl

# Let the conatiner know that there is no tty
ENV DEBIAN_FRONTEND noninteractive

# installing wget and creating /src path
RUN apt-get update && apt-get install -y wget && mkdir /src

# add dotdeb source list
RUN echo "deb http://packages.dotdeb.org wheezy all" > /etc/apt/sources.list.d/dotdeb.list \
    && echo "deb-src http://packages.dotdeb.org wheezy all" >> /etc/apt/sources.list.d/dotdeb.list \
    && wget -O - http://www.dotdeb.org/dotdeb.gpg |apt-key add -

# installing php stuff
RUN apt-get update && apt-get install -y php5-cli php5-fpm php5-mysql php5-intl php5-xdebug php5-recode \
    php5-snmp php5-mcrypt php5-memcache php5-memcached php5-imagick php5-curl php5-xsl php5-snmp \
    php5-dev php5-tidy php5-xmlrpc php5-gd php5-pspell php-pear php-apc nginx

# setting up php.ini, fpm pool conf and nginx.conf
RUN sed -i "s/;date.timezone =/date.timezone = America\/Sao_Paulo/" /etc/php5/cli/php.ini \
    && sed -i "s/;date.timezone =/date.timezone = America\/Sao_Paulo/" /etc/php5/fpm/php.ini \
    && sed -i "s/short_open_tag = On/short_open_tag = Off/" /etc/php5/cli/php.ini \
    && sed -i "s/short_open_tag = On/short_open_tag = Off/" /etc/php5/fpm/php.ini \
    && sed -i "s/error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT/error_reporting = E_ALL/" /etc/php5/cli/php.ini \
    && sed -i "s/error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT/error_reporting = E_ALL/" /etc/php5/fpm/php.ini \
    && sed -i "s/display_errors = Off/display_errors = On/" /etc/php5/cli/php.ini \
    && sed -i "s/display_errors = Off/display_errors = On/" /etc/php5/fpm/php.ini \
    && sed -i "s/display_startup_errors = Off/display_startup_errors = On/" /etc/php5/cli/php.ini \
    && sed -i "s/display_startup_errors = Off/display_startup_errors = On/" /etc/php5/fpm/php.ini \
    && sed -i "s/www-data;/www-data;\\ndaemon off;/g" /etc/nginx/nginx.conf

COPY run.sh /run.sh
RUN chmod a+x /run.sh

# virtualhosts configuration
COPY teste.dev.conf /etc/nginx/sites-available/teste.dev.conf

RUN ln -sf /etc/nginx/sites-available/teste.dev.conf /etc/nginx/sites-enabled/teste.dev.conf

# removing deb files
RUN apt-get clean

EXPOSE 80:80

ENTRYPOINT ["/run.sh"]

Esse é o run.sh que é copiado para dentro do container durante o build, também disponível no Github:

#!/bin/bash
/etc/init.d/php5-fpm restart && nginx

E esse o teste.dev.conf, também copiado para dentro:

server {

  server_name teste.dev;

  client_max_body_size  5m;
  client_header_timeout 1200;
  client_body_timeout   1200;
  send_timeout          1200;
  keepalive_timeout     1200;

  root        /src/;
  try_files   $uri $uri/ /index.php?$args;
  index       index.php;

  location ~ \.php$ {
    fastcgi_pass    unix:/var/run/php5-fpm.sock;
    fastcgi_index   index.php;
    include         fastcgi_params;

    fastcgi_connect_timeout 1200;
    fastcgi_send_timeout    7200;
    fastcgi_read_timeout    7200;
    fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}

O repositório no Dockerhub que monitora esse github é o https://registry.hub.docker.com/u/wfsilva/nginx-php-fpm-docker/.

Se você não quiser utilizar o Dockerhub basta ir até a pasta onde gravou o seu Dockerfile e rodar o comando:

$ docker build -t wfsilva/nginx-php-fpm-home-build ./

A opção -t é para criar a tag, e como pode imaginar a parte wfsilva/nginx-php-fpm-home-build foi por minha conta. E para a minha conta (no dockerhub).

Dicas

  • Sempre use tags para construir seus container, senão você ficará louco com os hashes:
$ docker build -t yabadaba/dooo ./path/to/wherever/dockerfile/is
  • Sempre use o cache de imagens - cada instrução do Dockerfile é reutilizada no build, portanto o build vai ser praticamente instantâneo até o ponto onde foi feita a mudança no seu dockerfile.

  • EXPOSE-ing ports - tentar não mapear portas públicas para evitar colisão com portas de serviços que estejam rodando no host. Ao invés de -p 80:80 ou EXPOSE 80:80 utilizemos -p 80 ou EXPOSE 80

  • CMD e ENTRYPOINT - sempre usar a sintaxe de array: CMD [“/bin/echo”] porque quando colocamos CMD /bin/echo automaticamente no container vai ser colocado um /bin/sh -c antes do comando.

  • Tentar usar CMD e ENTRYPOINT juntos - o entry point é tipo um binário, se nenhum parâmetro for passado o cmd seguinte será chamado. ex:

ENTRYPOINT ["/usr/bin/script"]
CMD ["--help"]

Ao rodar o container só chamando o script /usr/bin/script ele será executado com a opção –help. Se executarmos com alguma outra opção qualquer, –verbose por exemplo, o –help será ignorado.

  • Se você se cansar de testar e quiser fazer uma limpa geral docker rm $(docker ps -a -q) para remover todos containers parados e docker rmi $(docker images -q) para remover todas imagens.

  • Para mostrar histórico das imagens: docker images –tree.

  • docker run - inicia / executa o container.

  • docker exec - executa um outro comando ou entra em um container que já esteja rodando.

  • docker attach - te anexa no shell do container que esteja rodando

  • Reforçando Container vs Imagem - Container é a “imagem que está rodando”, não vai esquecer mais.

To Be Continued

Ainda abordaremos como utilizar o docker em ambiente Windows e Mac (boot2docker e docker-machine), orquestração de containers usando fig (agora docker-composer), e as novidades (já em beta e liberadas para teste) anunciadas na Dockercon em dezembro de 2014 para a próxima versão do docker: (docker-machine, docker-composer, docker-swarm).

Té +