Docker para desenvolvedores

Anderson Vinicius

O que é Docker?

Criado com a linguagem GO em 2010 pela empresa dotCloud hoje Docker, teve sua versão 0.9 OpenSource liberada em março de 2013.

 

O Docker é uma ferramenta para gerenciamento de containers de forma isolada do SO host. O seu back-end é baseado em LXC

(LinuX Containers).

 

O LXC isola processos do sistema operacional host. É um tipo de virtualização leve, não faz uso de emulação ou suporte a hardware, apenas proporciona a execução de vários sistemas Linux de forma isolada, dai vem a palavra container. Utiliza o mesmo Linux Kernel do host, o que o torna rápido.

Qual a diferença entre container e máquina virtual?

No Bare Metal, o software que proporciona a virtualização é instalada diretamente sobre o hardware, por exemplo, Xen, VMware e Hyper-V. Esse tipo proporciona um isolamento maior e, ao mesmo tempo, uma sobrecarga, pois cada máquina virtual que é criada executará seu próprio kernel e instância do sistema operacional.

 

Já o tipo Hosted, o software que proporciona a virtualização é executada sobre um sistema operacional, por exemplo, o VirtualBox.

 

A virtualização com containers, proposta pelo LXC, ocorre de forma menos isolada, pois compartilha algumas partes do kernel do host, assim tendo uma sobrecarga menor.

Máquina virtual vs Container

Resumindo, containers são mais leves pois não precisam de um ambiente virtual completo, pois o kernel do host faz o gerenciamento de memória, I/O, CPU e etc. Assim o processo de boot pode levar poucos sengundos.

Funcionamento

O Linux Kernel tem um recurso chamado Cgroups (control groups), que é utilizado para limitar o uso de CPU, memória, disco, rede e etc. Outro recurso é o de Namespaces, responsáveis por isolar grupos de processos, de modo que eles não vejam os processos de outros grupos, em outros containers ou no sistema host.

Quando um container é criado, automaticamente o namespace é criado para ele, o que cria uma camada de isolamento para grupos de processos, no caso do Docker são:

  • PID - isolamento de processos (PID).
  • NET - controle de interface de rede.
  • IPC - controle de recursos do IPC (InterProcess Communication).
  • MNT - gestão de pontos de montagem.
  • UTS - (Unix Timesharing System) isolar recursos do kernel.

Também utiliza o union file systems que são sistemas de arquivos que funcionam por meio da criação de camadas. O Docker utiliza camadas para a construção de imagens que serão usadas na criação de containers.

Instalação

Kernel >= 3.8

Selecione sua plataforma e siga as instruções do site oficial.

Adicione seu usuário no grupo Docker, se você não quiser usar o comando sudo.

$ sudo usermod -aG docker $USER
$ docker version
Client:
 Version:      1.12.2
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   bb80604
 Built:        Tue Oct 11 18:29:41 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.2
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   bb80604
 Built:        Tue Oct 11 18:29:41 2016
 OS/Arch:      linux/amd64

OU

$ wget -qO- https://get.docker.com | sh

Hello, Docker!

$ docker run ubuntu /bin/echo Hello,Docker!
Unable to find image 'ubuntu' locally
Pulling	repository ubuntu
c4ff7513909d: Download complete
511136ea3c5a: Download complete
1c9383292a8f: Download complete
9942dd43ff21: Download complete
d92c3c92fa73: Download complete
0ea0d582fd90: Download complete
cc58e55aa5a5: Download complete
Hello,Docker!
$

O Docker fez o download da imagem oficial do ubuntu na sua última versão estável.

Instanciou um novo container, configurou a interface de rede e deu um IP dinâmico para ele.

Selecionou um sistema de arquivos para esse novo container, e executou o comando /bin/echo graças ao run, que é uma das formas de enviar comandos ao container.

Por fim o Docker capturou a saída do comando que injetamos.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              3034b40630a2        4 weeks ago         389.8 MB
mysql               latest              eda6a4884645        5 weeks ago         383.4 MB

Aqui podemos listar as imagens baixadas:

Interatividade com Docker

Containers em execução:

$ docker ps 
CONTAINER ID   IMAGE   COMMAND    CREATED      STATUS      PORTS    NAMES
$

Criando um container e acessando seu shell:

$ docker run -i -t ubuntu bin/bash
root@8ce39fd86a74:/# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
root@8ce39fd86a74:/#

Utilizamos dois parâmetros o -i diz ao Docker que queremos ter interatividade com o container, e -t que nos redireciona ao terminal do container, em seguida o nome da imagem utilizada para montar o container e passamos o comando bin/bash como argumento.

Ao sair do container, ele será colocado em estado de pausa pelo Docker, abaixo listamos os containers passando o parâmetro -a que listar todos os containers.

$ docker ps -a
CONTAINER ID  IMAGE    COMMAND        CREATED             STATUS                        PORTS           NAMES
8ce39fd86a74  ubuntu   "bin/bash"     11 minutes ago      Exited (127) 11 seconds ago                     furious_ritchie

Temos também uma opção bem útil que retorna o ID do container:

$ docker ps -qa
13b9dfa2d701
$

O parâmetro pode ser usado para excluir containers em massa, como nesse exemplo:

$ docker rm $(docker ps -qa)
13b9dfa2d701
0eeb727a0e2b
$

O argumento rm é utilizado para excluir um container, nesse caso ele retornou os IDs dos containers excluidos.

A partir da versão 1.5, o Docker possui a feature stats, que retorna em tempo de execução, detalhes do nível de consumo de recursos dos containers.

$ docker stats 7c259f14e905
CONTAINER     CPU %   MEM USAGE/LIMIT     MEM %   NET I/O            BLOCK I/O   PIDS
0fb50d0cf6cf  0.00%   520 KiB/7.725 GiB   0.01%   7.809 kB / 648 B   0 B / 0 B   1

Manipulando containers

$ docker run -it --name nginx ubuntu
root@4444a3382c91:/# apt-get update
root@4444a3382c91:/# apt-get install -y	nginx
root@4444a3382c91:/# nginx -v
nginx version: nginx/1.10.0 (Ubuntu)
root@4444a3382c91:/# exit

Utilizando a opção --name, criará um apelido para o container, caso não seja informado um apelido aleatório será criado.

É importante saber que qualquer alteração feita no container é volátil, ou seja se iniciarmos novamente esse container com a imagem do Nginx, as alterações não permanecerão. Para tornar as alterações permanentes, temos que commitar o container.

O Docker possui um sistema de versionamento semelhante ao Git,  sempre que um commit for realizado o estado atual do container será salvo, temos também outros comandos como diff, pull, push e etc.

$ docker commit 4444a3382c91 avinicius/nginx
a3e066e9c70651ec7f2b2a805621eb1a98a31de6b46e5b494510bd80b2a88fec
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
avinicius/nginx     latest              a3e066e9c706        33 seconds ago      223.3 MB

Uma outra opção é o uso de containers autodestrutivos com o uso da opção --rm, isso significa que quando o container for fechado ele será automaticamente excluído. Observe o uso da opção -p, estamos falando ao Docker que a porta 8080 do host será aberta e mapeada para a porta 80 do container.

$ docker run -it --rm -p 8080:80 avinicius/nginx /bin/bash
root@e0c7516d346f:/# service nginx start
 * Starting nginx nginx                                                                                                                                     [ OK ] 
root@e0c7516d346f:/#

Testando no browser do host.

root@e0c7516d346f:/# exit
exit
$ docker ps -a
CONTAINER ID        IMAGE          COMMAND     CREATED     STATUS     PORTS     NAMES
$

O container só existiu em quanto estávamos acessando ele, podemos usar a opção -d para rodarmos em background.

$ docker run -d -p 8080:80 avinicius/nginx /usr/sbin/nginx -g "daemon off;"
b4a89ac6955bfc8da14e59843b0e319ca8899215f7ee3b9a1a41f708a72b7495
$ docker ps -q
b4a89ac6955b

Manipular o funcionamento do container utilizando a opção start, stop:

$ docker stop b4a89ac6955b
b4a89ac6955b
$ docker ps -q
$
$ docker start b4a89ac6955b
b4a89ac6955b
$ docker ps -q
$ b4a89ac6955b

Subindo um container sem -d e saindo dele sem derrubá-lo, utilizando CTRL + P  e CTRL + Q.

$ docker ps
CONTAINER ID  IMAGE  COMMAND   CREATED       STATUS              PORTS               NAMES
7d520bbb10cd  ubuntu           "/bin/bash"   About a minute ago   Up About a minute  serene_snyder                 
$ docker attach 7d520bbb10cd
root@7d520bbb10cd:/#
$ docker run -it ubuntu 
root@b4a89ac6955b:/#
$ docker rmi -f avinicius/nginx
Untagged: avinicius/nginx:latest
Deleted: sha256:a3e066e9c70651ec7f2b2a805621eb1a98a31de6b46e5b494510bd80b2a88fec
Deleted: sha256:669f2f97d4319e864bde59de70bc7e39505bfba2688052004851725935259ace
Deleted: sha256:f753707788c5c100f194ce0a73058faae1a457774efcda6c1469544a114f8644
Deleted: sha256:3b25e17d01de5200842cadbdc53f4afa1a3ed17a7121e4036e744a83a2732f76
Deleted: sha256:cebd67936ff89b117f72c410b11cc835c96cc8eabd161564ca38bfff99d781f5
Deleted: sha256:5d232b0ea43a530cd4db4be2c175b9dd46f534fa9cc8dcbf1d413c851f74e817
Deleted: sha256:dafccc932aece6c4914ca049df6539b75145ee2cf51f76e8d129aade450c87c3
Deleted: sha256:c854e44a1a5a22c9344c90f68ef6e07fd479e8ce1030fe141e3236887f1a4920
$

Excluindo uma imagem usando rmi e a opção -f pois existe um container que tem vinculo com a imagem, assim ele vai pausar o container antes de excluir a imagem.

Construindo imagens

Antes de começar a executar comandos, temos que conhecer o Dockerfile. Ele é um recurso criado para executar tarefas no Docker. Com esse recurso podemos descrever o que queremos na nossa  imagem como se fosse uma receita, quando executamos o build ele cria um snapshot com toda a instalação que elaboramos no Dockerfile.

O Dockerfile é um arquivo que aceita rotinas em shell script para serem executadas.

FROM ubuntu
MAINTAINER Anderson Vinicius <avinicius.adorno@gmail.com>
RUN apt-get update
RUN apt-get install -y	nginx
EXPOSE 80
  • FROM - imagem base a ser utilizada
  • MAINTAINER -  quem mantem a imagem
  • RUN - permite a execução e um comando no container
  • EXPOSE - expor a porta 80
$ docker build -t nginx .

O '.' indica o caminho aonde se encontra o Dockerfile, nesse caso esta na raiz de onde o shell foi executado, depois é só executar o comando docker images e veremos que a imagem nginx estará disponível.

Ao criar um container passando a instrução -P (maiúsculo), estamos permitindo que o Docker faça o mapeamento automático da porta da máquina host, buscando uma porta aleatória.

O -P funciona por que usamos o EXPOSE na criação da imagem.

$ docker run -d -P nginx /usr/sbin/nginx -g "daemon off;"
9d23bf0debb2d064cc526cef3f7da30b5ab54fd6dbd3795ec2188f3e60c7f70f
$ docker ps
CONTAINER ID  IMAGE  COMMAND               CREATED        STATUS        PORTS                  NAMES
9d23bf0debb2  nginx  "/usr/sbin/nginx -g"  2 seconds ago  Up 2 seconds  0.0.0.0:32769->80/tcp  silly_wright
$ docker port 9d23bf0debb2
80/tcp -> 0.0.0.0:32769

Utilizando o comando docker port passando o id do container como argumento, obteremos a porta que foi mapeada para o container.

Copiando arquivos

FROM ubuntu
MAINTAINER Anderson Vinicius <avinicius.adorno@gmail.com>
RUN apt-get update
RUN apt-get install -y	nginx
ADD file /etc/nginx/sites-enabled/default
EXPOSE 8080

Com a instrução ADD podemos copiar arquivos da máquina host para o container.

$ docker build -t nginx .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu
 ---> e4415b714b62
Step 2 : MAINTAINER Anderson Vinicius <avinicius.adorno@gmail.com>
 ---> Running in 155466ed03fd
 ---> 64f6b011ddcf
Removing intermediate container 155466ed03fd
Step 3 : RUN apt-get update
 ---> Running in 7b232e5e2526
...
Removing intermediate container c408a681d017
Step 4 : RUN apt-get install -y	nginx
 ---> Running in 567d4280b933
Removing intermediate container 567d4280b933
Step 5 : ADD vhost /etc/nginx/sites-enabled/default
 ---> 7b1af2dd05d4
Removing intermediate container 81693833dff0
Step 6 : EXPOSE 80
 ---> Running in 3a86ca93d4d9
 ---> 8041099f5031
Removing intermediate container 3a86ca93d4d9
Successfully built 8041099f5031

O arquivo adicionado ao container é para alterar a porta default do nginx para 8080.

server	{
    listen 8080	default_server;
    server_name	localhost;
    root /usr/share/nginx/html;
    index index.html index.htm;
}
$ docker run -d	nginx /usr/sbin/nginx -g "daemon off;"
$ curl -IL http://localhost:8080
curl: (7) Failed to connect to	localhost port 8080: Connection	refused

Vamos criar um novo container sem informar instruções de portas, pois colocamos como default 8080, e testar para verificar se está tudo funcionando:

Ocorreu o erro por que não mapeamos com -p ou -P, que mapeia uma porta do container para nosso host. Então teremos que utilizar o IP do container.

$ docker inspect e0e85212e448 | grep IPAddress
 "SecondaryIPAddresses": null,
 "IPAddress": "172.17.0.2",
 "IPAddress": "172.17.0.2",

Definindo Workdir

A área de trabalho default do Docker é o diretório raiz /. Pode-se mudar essa opção ao subir um container com a opção -w, ou definir isso como padrão na criação de uma imagem no Dockerfile com a diretiva WORKDIR.

Imagine que queremos usar um container para um projeto. O Dockerfile poderia ser adicionado juntamente com o projeto e ter as seguintes configurações.

FROM ubuntu
MAINTAINER Anderson Vinicius <avinicius.adorno@gmail.com>
RUN apt-get update
RUN apt-get install -y  nginx
RUN apt install -y php7.0 
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
ADD vhost /etc/nginx/sites-enabled/default
ADD ./ /home/site
WORKDIR /home/site
EXPOSE 8080
CMD service nginx start

O ADD copia os dados do contexto que foi executado o Dockerfile para a pasta /home/site no container.

Com a nova imagem gerada vamos criar um container.

$ docker run -d	-p 8080:8080 --name app nginx
2d0800281d9084273940247a36956b4510195ebe26b35c3acf0a6a9b98344d24

Para vermos o que de fato aconteceu nas instruções ADD e WORKDIR, que foram    adicionadas ao  Dockerfile, podemos  acessar o container  utilizando docker  exec :

$ docker exec -it app bash
root@2d0800281d90:/home/site# ls -al                                                                                                                              
total 36
drwxr-xr-x 2 root root 4096 Nov 22 01:14 .
drwxr-xr-x 3 root root 4096 Nov 22 01:14 ..
-rw-rw-r-- 1 root root  311 Nov 22 01:10 Dockerfile
-rw-rw-r-- 1 root root   17 Nov 22 01:09 index.html
-rw-rw-r-- 1 root root  116 Nov 22 01:09 vhost
root@2d0800281d90:/home/site# 

server	{
    listen 8080	default_server;
    server_name	localhost;
    root /home/site;
    index index.html index.htm;
}

Arquivo do vhost:

Agora o container possui todos os arquivos do nosso projeto, note que  fomos direcionados para a pasta que definimos no WORKDIR quando entramos no container.

Tratamento de Logs

O Docker possui um recurso para visualizar os logs de erro padrão (stdout stderr). Isso é interessante para visualizarmos o que está acontecendo no container sem acessá-lo diretamente.

FROM ubuntu
MAINTAINER Anderson Vinicius <avinicius.adorno@gmail.com>
RUN apt-get update
RUN apt-get install -y nginx
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE	80
CMD ["nginx", "-g", "daemon off;"]
$ docker build -t nginx .
$ docker run -d -p 8080:80 nginx
66244d15b3d6
$ docker logs 66244d15b3d6
172.17.0.1 - - [22/Nov/2016:22:56:25 +0000] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36"

Ciando uma imagem e  rodando um container para testar o comando docker logs.

Dessa forma, conseguimos capturar saídas do serviço Nginx de dentro do container sem checar os arquivos de log.

Exportação e importação de containers

Imagine que tenhamos que enviar um container do host local e para a produção, como fazer isso? O Docker tem um recurso para salvar os containers com o comando save e importar com o comando load.

$ docker ps -q
66244d15b3d6
$ docker commit 66244d15b3d6 new_image
7d05b2db8fd704f79ba2f19c29dec1b9e94b204dfff43336539c318abb641e9b
$ docker save new_image > /tmp/new_image.tar
$ ls /tmp
new_image.tar

Agora listando a pasta /tmp do nosso host veremos que o arquivo foi criado com sucesso. Podemos enviar para onde desejarmos e importar e executar o comando a baixo:

$ docker load < /tmp/new_image.tar

Trabalhando com volumes

Um volume pode ser um diretório localizado fora do sistema de arquivos do container. O volume sincroniza um diretório do host com um no container, permitindo que um diretório possa ser compartilhado em diversos containers.

Ex: um projeto na sua máquina pode ser um volume para rodar em um container.

Veremos um exemplo criando um volume para o Nginx. Foi mapeado o vhost do Nginx para uma pasta /tmp/nginx do nosso host, quado criado o arquivo no host ele é sincronizado com o container.

$ docker run -d -p 8080:8080 -v /tmp/nginx:/usr/share/nginx/html:ro nginx 
92755beb58e84c2510de4b2cc88e082f3a996ae87827d83a7a9bb350dd2a2b74
$ echo "It works" > /tmp/nginx/index.html

Configurações de Rede

O Docker é inicializado, e uma interface de rede virtual é criada na máquina host.

Então o Docker cria um endereço IP privado, uma máscara de sub-rede e configura na interface virtual docker0.

Essa interface virtual é a ponte que encaminha os pacotes de forma automática para quaisquer outras interfaces que estejam conectadas.

Para que haja comunicação entre os containers, dependemos de dois fatores:

  • A topologia de rede do host deve permitir a conexão das interfaces de rede dos containers, usando a ponte docker0.
  • O iptables deve permitir as ligações especias que serão feitas, são regras de refirecionamento de pacotes.

A configuração padrão o Docker utiliza a interface bridge para concentrar todas as comunicações. Além disso, ele providência as regras de firewall no iptables para prover as rotas de tráfego.

Testando a comunicação entre containers

$ docker run -d --name app nginx
$ docker run -d -e MYSQL_ROOT_PASSWORD=1234 --name db mysql
$ docker ps
CONTAINER ID  IMAGE   COMMAND                  CREATED           STATUS          PORTS             NAMES
37c92fb8eaec  mysql   "docker-entrypoint.sh"   4 seconds ago     Up 3 seconds    3306/tcp          db
1f3bf47acce7  nginx   "nginx -g 'daemon off"   38 seconds ago    Up 37 seconds   80/tcp, 443/tcp   app

Temos os dois containers executando, usando as imagens oficiais do nginx e msyql.

$ docker exec -it 1f3bf47acce7 bash
root@1f3bf47acce7:/# apt-get update -y
root@1f3bf47acce7:/# apt-get install mysql-client -y
root@1f3bf47acce7:/# mysql -h 172.17.0.3 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.16 MySQL Community Server (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

'Linkando' os containers

Uma outra forma de estabelecer conexão entre containers no mesmo host é utilizar a opção link. Essa opção providência o mapeamento de rede entre os containers que estão sendo linkados.

$ docker run -d -e MYSQL_ROOT_PASSWORD=1234 --name db mysql
$ docker run -d --name app --link mysql:db nginx

Note que na opção link, informamos o nome do container que queremos linkar e um apelido para ele, neste caso mysql é o nome do container, e db o seu apelido.

Como testar isso ?

$ docker exec -it app bash
root@c358d19468df:/# ping db
PING	db	(172.17.0.89)	56(84)	bytes	of	data.
64	bytes	from	mysql	(172.17.0.89):	icmp_seq=1	ttl=64	time=0.211ms
64	bytes	from	mysql	(172.17.0.89):	icmp_seq=2	ttl=64	time=0.138ms
64	bytes	from	mysql	(172.17.0.89):	icmp_seq=3	ttl=64	time=0.137ms
---	db	ping	statistics	---
3	packets	transmitted,	3	received,	0%	packet	loss,	time	2000ms
rtt	min/avg/max/mdev	=	0

Docker HUB

O docker hub (https://hub.docker.com) é um local de registro público, com milhares de imagens para serem baixadas e usadas de forma fácil. Possui autenticação e organização de grupos de trabalho, além de ter a funcionalidade de repositórios privados para imagens que não devem ser compartilhadas.

Buscando e baixando imagens no docker hub:

$ docker search rails
NAME                           DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
rails                          Rails is an open-source web application fr...   613       [OK]       
dkubb/alpine-rails-nginx       An Alpine Linux container for Rails applic...   3                    [OK]
$ docker pull rails

Enviando imagem para o repositório:

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: avinicius
Password: 
Login Succeeded

Agora que estamos autenticados no docker hub, podemos enviar nossas imagens de desenvolvimento e mandar para o repositório para fazer pull no servidor de produção.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
avinicius/ubuntu    1.0                 52ee0bd874c5        9 seconds ago       128.1 MB

Alterando a TAG da imagem caso necessário antes de enviar:

$ docker tag 52ee0bd874c5 avinicius/ubuntu:2.0
$ docker images
REPOSITORY             TAG    IMAGE ID            CREATED              SIZE
avinicius/ubuntu       2.0    52ee0bd874c5        About a minute ago   128.1 MB
$ docker push avinicius/ubuntu:2.0
The push refers to a repository [docker.io/avinicius/ubuntu]
c1bd37d01c89: Mounted from library/ubuntu 
943edb549a83: Mounted from library/ubuntu 
bf6751561805: Mounted from library/ubuntu 
f934e33a54a6: Mounted from library/ubuntu 
e7ebc6e16708: Mounted from library/ubuntu 
2.0: digest: sha256:b6d18e497aef22ab466c1b9a844d2170217c03933bd3dd1ace2cfe69b5ca6140 size: 1357

Enviando a imagem para o repositório:

Referências 

Até a próxima talk na qual falaremos sobre docker compose e o uso do Docker no ambiente de desenvolvimento. 

Made with Slides.com