Anderson Vinicius
Redução de duplicação de código, alta expressividade e criação no início de abstrações simples. É isso que torna para min um código limpo. — Ron Jeffries
Anderson Vinicius
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.
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.
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.
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:
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.
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
$ 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:
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
$ 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.
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
$ 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.
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",
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.
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.
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
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
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 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.
$ 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>
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
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:
Até a próxima talk na qual falaremos sobre docker compose e o uso do Docker no ambiente de desenvolvimento.
By Anderson Vinicius
Docker para desenvolvedores
Redução de duplicação de código, alta expressividade e criação no início de abstrações simples. É isso que torna para min um código limpo. — Ron Jeffries