() translation by (you can also view the original English article)
Containers Docker estão crescendo como uma melhor prática para deploy e administração de sistemas distribuídos nativos cloud. Containers são instâncias de imagens Docker. Acontece que existe muito a se saber e entender sobre imagens.
Nesse tutorial de duas partes, cobriremos imagens do Docker a fundo. Hoje, discutiremos os princípios básicos, considerações de projeto e inspecionaremos as "entranhas" de uma imagem. Cobriremos como criar nossas próprias imagens, resolução de problemas e trabalhar com repositórios de imagens.
Quando terminarmos, teremos um sólido entendimento do que são imagens Docker e como utilizá-las efetivamente em nossas aplicações e sistemas.
Criando Imagens
Há duas formas de criar imagens. Podemos modificar um container existente e então enviá-lo como uma nova imagem ou podemos criar um arquivo Dockerfile e criar uma imagem dele. Veremos ambos e explicaremos os prós e contras.
Criação Manual
Com criação manual, tratamos o container como um computador normal. Instalamos pacotes, criamos arquivos e quando tudo está feito, salvamos e acabamos com uma nova imagem para usarmos como modelo para criação de muitos outros containers ou mesmo como base de outras imagens.
Comecemos com a imagem alpine, que é bem pequena e simples, baseada no Linux Alpine. Podemos executá-la em modo interativo para acessar o terminal. Nosso objetivo é adicionar um arquivo chamado "yeah" com o texto "it works" no diretório raiz e então criar uma nova imagem chamada "yeah-alpine".
Aqui vamos nós. Certo, já estamos no diretório raiz. Vejamos o que temos lá.
1 |
> docker run -it alpine /bin/sh |
2 |
/ # ls
|
3 |
bin dev etc home lib linuxrc media mnt proc root run sbin srv sys tmp usr var |
Que editor está disponível? Nada de vim ou nano?
1 |
/ # vim
|
2 |
/bin/sh: vim: not found |
3 |
/ # nano
|
4 |
/bin/sh: nano: not found |
Bem... Apenas precisamos criar um arquivo:
1 |
/ # echo "it works!" > yeah
|
2 |
/ # cat yeah
|
3 |
it works! |
4 |
Saímos do terminal e podemos ver o container chamado "vibrant_spenc" com docker ps --all
. O semáforo --all
é importante porque o container não está mais em execução.
1 |
> docker ps --all |
2 |
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES |
3 |
c8faeb05de5f alpine "/bin/sh" 6 minutes ago Exited vibrant_spence
|
Aqui, criamos uma nova imagem a partir do container "vibrate_spence". Adicionamos uma mensagem de registro "mine, mine, mine" por boas práticas.
1 |
> docker commit -m "mine, mine, mine" vibrant_spence yeah-alpine |
2 |
sha256:e3c98cd21f4d85a1428...e220da99995fd8bf6b49aa |
Verifiquemos. Sim, há uma nova imagem e no histórico podemos ver a nova camada com o comentário "mine, mine, mine".
1 |
> docker images
|
2 |
REPOSITORY TAG IMAGE ID SIZE |
3 |
yeah-alpine latest e3c98cd21f4d 4.8 MB |
4 |
python latest 775dae9b960e 687 MB |
5 |
d4w/nsenter latest 9e4f13a0901e 83.8 kB |
6 |
ubuntu-with-ssh latest 87391dca396d 221 MB |
7 |
ubuntu latest bd3d4369aebc 127 MB |
8 |
hello-world latest c54a2cc56cbb 1.85 kB |
9 |
alpine latest 4e38e38c8ce0 4.8 MB |
10 |
nsqio/nsq latest 2a82c70fe5e3 70.7 MB |
11 |
|
12 |
> docker history yeah-alpine |
13 |
IMAGE CREATED SIZE COMMENT |
14 |
e3c98cd21f4d 40 seconds ago 66 B mine, mine, mine |
15 |
4e38e38c8ce0 7 months ago 4.8 MB |
Agora, o teste de verdade. Apaguemos o container e criemos um novo a partir da imagem. O resultado esperado é que o arquivo "yeah" esteja presente no novo container.
1 |
> docker rm vibrant_spence |
2 |
vibrant_spence |
3 |
|
4 |
> docker run -it yeah-alpine /bin/sh |
5 |
/ # cat yeah
|
6 |
it works! |
7 |
/ #
|
O que podemos dizer? Funciona!!
Usando um Dockerfile
Criar imagens a partir de containers modificados é legal, mas não é sustentável. É difícil registrar as mudanças e saber quais foram as modificações. A forma disciplinada de criar imagens é a partir do Dockerfile.
O Dockerfile é um arquio de texto similar a um script de terminal, mas suporta vários comandos. Cada comando que modifica o sistema de arquivo cria uma nova camada. Na parte um discutimos a importância de dividir a nossa imagem em camadas apropriadas. O Dockerfile é um gráde tópico por si só.
Aqui, apenas demonstraremos alguns comandos pra criar outra imagem, "oh-yeah-alpine", baseada no Dockerfile. Além de criar o infame arquivo "yeah", também instalaremos vim. A distribuição Linux Alpine usa um sistema administrador de pacotes chamado "apk". Eis o Dockerfile:
1 |
FROM alpine |
2 |
|
3 |
# Copy the "yeah" file from the host
|
4 |
COPY yeah /yeah |
5 |
|
6 |
# Update and install vim using apk
|
7 |
RUN apk update && apk add vim
|
8 |
|
9 |
CMD cat /yeah
|
A imagem base é alpine. Ele copia o arquivo "yeah" do diretório hospedeiro onde o Dockerfile está (o caminho do contexto de criação). Então executa apk update
e instala vim. Por fim, configura o comando executado quando o container é executado. Nesse caso, imprimirá na tela o conteúdo do arquivo "yeah".
OK. Agora que sabemos onde nos metemos, construamos essa coisa. A opção "-t" configura o repositório. Não especificamos uma tag, então será o padrão "latest".
1 |
> docker build -t oh-yeah-alpine . |
2 |
Sending build context to Docker daemon 3.072 kB |
3 |
Step 1/4 : FROM alpine |
4 |
---> 4e38e38c8ce0 |
5 |
Step 2/4 : COPY yeah /yeah |
6 |
---> 1b2a228cc2a5 |
7 |
Removing intermediate container a6221f725845 |
8 |
Step 3/4 : RUN apk update && apk add vim
|
9 |
---> Running in e2c0524bd792 |
10 |
fetch https://dl-cdn.alpinelinux.org/.../APKINDEX.tar.gz |
11 |
fetch http://dl-cdn.alpinelinux.org.../x86_64/APKINDEX.tar.gz |
12 |
v3.4.6-60-gc61f5bf [http://dl-cdn.alpinelinux.org/alpine/v3.4/main]
|
13 |
v3.4.6-33-g38ef2d2 [http://dl-cdn.alpinelinux.org/.../v3.4/community]
|
14 |
OK: 5977 distinct packages available |
15 |
(1/5) Installing lua5.2-libs (5.2.4-r2) |
16 |
(2/5) Installing ncurses-terminfo-base (6.0-r7) |
17 |
(3/5) Installing ncurses-terminfo (6.0-r7) |
18 |
(4/5) Installing ncurses-libs (6.0-r7) |
19 |
(5/5) Installing vim (7.4.1831-r2) |
20 |
Executing busybox-1.24.2-r9.trigger |
21 |
OK: 37 MiB in 16 packages
|
22 |
---> 7fa4cba6d14f |
23 |
Removing intermediate container e2c0524bd792 |
24 |
Step 4/4 : CMD cat /yeah
|
25 |
---> Running in 351b4f1c1eb1 |
26 |
---> e124405f28f4 |
27 |
Removing intermediate container 351b4f1c1eb1 |
28 |
Successfully built e124405f28f4 |
Parece legal. Verifiquemos se a imagem foi criada:
1 |
> docker images | grep oh-yeah |
2 |
|
3 |
oh-yeah-alpine latest e124405f28f4 About a minute ago 30.5 MB |
Notemos como instalar vim e suas dependências aumentaram o tamanho do container de 4.8MB da imagem alpine base para massivos 30.5MB!
É tudo muito bom. Mas funciona?
1 |
> docker run oh-yeah-alpine
|
2 |
it works! |
Ah, sim, funciona!
Caso ainda duvidemos, acessemos o container, examinemos o arquivo "yeah" com nosso vim recém instalado.
1 |
> docker run -it oh-yeah-alpine /bin/sh |
2 |
/ # vim yeah
|
3 |
|
4 |
it works! |
5 |
~ |
6 |
~ |
7 |
.
|
8 |
.
|
9 |
.
|
10 |
~ |
11 |
"yeah" 1L, 10C
|
O Contexto de Criação e o Arquivo .dockerignore
Não dissemos ante, mas ao tentar criar a imagem oh-yeah-alpine, ela simplesmente ficou suspensa por vários minutos. O problema é que colocamos o Dockerfile no diretório padrão. Quando o Docker cria uma imagem, ele primeiro empacota todo o diretório onde o Dockerfile está (incluindo sub-diretório) e os disponibiliza para comandos COPY no Dockerfile.
Docker não está tentando ser esperto e analisando nossos comandos COPY. Ele apenas empacota tudo. Notemos que o conteúdo da criação não aparece na imagem, mas atrasa o comando build se o contexto de criação for grande demais.
Nesse caso, apenas copiamos o Dockerfile e o arquivo "yea" para um subdiretório e executamos o comando build no sub-diretório. Mas, alguma vezes, temos uma árvore de diretórios complicada da qual queremos copia sub-pastas e arquivos específicos e ignorar outros. O .dockerignore está aí para isso.
Ele nos permite controlar exatamente o que vai no contexto de criação. Nosso truque preferido é excluir tudo e começar a adicionar cada parte necessária. Por exemplo, nesse caso, poderíamos criar o .dockerignore abaixo e manter o Dockerfile o arquivo "yeah" no diretório raiz:
1 |
# Exclude EVERYTHING first
|
2 |
*
|
3 |
|
4 |
# Now selectively include stuff
|
5 |
!yeah
|
Não é preciso incluir o Dockerfile em si ou o .dockerignore no contexto de criação.
Copiar vs. Montar
Copiar arquivos para a imagem é algo que precisamos às vezes, mas, em outros casos, queremos que os containers sejam mais dinâmicos e trabalhem com arquivos no hospedeiro. É aí que volumes e montagens entram em ação.
Montar diretórios do hospedeiro é complicado. Os dados pertencem ao hospedeiro e não ao container, podem ser modificados quando ele está parado. E o container pode ser iniciado com diferentes diretórios hospedeiros montados.
Rotulando Imagens
Rotular imagens é importante se desenvolvemos sistemas baseados em micro-serviços e geramos várias imagens que precisam ser associadas umas com as outras. Podemos adicionar quantos rótulos quisermos a uma imagem.
Já vimos o rótulo padrão "latest". Alguma vez, faz mais sentido adicionar outros, como "tested", "release-1.4" ou hash do estado no Git que corresponde à imagem.
Podemos rotulá-la durante criação ou depois. Eis como adicionar rótulo a uma imagem existente. Notemos que, embora chamemos de rótulo, podemos atribuir um novo repositório também.
1 |
> docker tag oh-yeah-alpine oh-yeah-alpine:cool-tag
|
2 |
> docker tag oh-yeah-alpine oh-yeah-alpine-2
|
3 |
|
4 |
> docker images | grep oh-yeah |
5 |
oh-yeah-alpine-2 latest e124405f28f4 30.5 MB |
6 |
oh-yeah-alpine cool-tag e124405f28f4 30.5 MB |
7 |
oh-yeah-alpine latest e124405f28f4 30.5 MB |
Também podemos remover rótulos de imagens usando o nome do rótulo. Isso é um pouco assustador por que se removermos o último rótulo sem querer, perdemos a imagem. Mas se criamo-na a partir de um Dockerfile, basta reconstruí-la.
1 |
> docker rmi oh-yeah-alpine-2
|
2 |
Untagged: oh-yeah-alpine-2:latest |
3 |
|
4 |
> docker rmi oh-yeah-alpine:cool-tag
|
5 |
Untagged: oh-yeah-alpine:cool-tag |
Se tentarmos remover remover a última imagem rotulada, obtemos um erro porque ela é usada por um container.
1 |
> docker rmi oh-yeah-alpine
|
2 |
|
3 |
Error response from daemon: conflict: unable to remove repository |
4 |
reference "oh-yeah-alpine" (must force) - |
5 |
container a1443a7ca9d2 is using its referenced image e124405f28f4 |
Mas se removermos o container...
1 |
> docker rmi oh-yeah-alpine
|
2 |
Untagged: oh-yeah-alpine:latest |
3 |
Deleted: sha256:e124405f28f48e...441d774d9413139e22386c4820df |
4 |
Deleted: sha256:7fa4cba6d14fdf...d8940e6c50d30a157483de06fc59 |
5 |
Deleted: sha256:283d461dadfa6c...dbff864c6557af23bc5aff9d66de |
6 |
Deleted: sha256:1b2a228cc2a5b4...23c80a41a41da4ff92fcac95101e |
7 |
Deleted: sha256:fe5fe2290c63a0...8af394bb4bf15841661f71c71e9a |
8 |
|
9 |
> docker images | grep oh-yeah |
Sim, sumiu. Mas não nos preocupemos. Podemos reconstruí-la:
1 |
> docker build -t oh-yeah-alpine . |
2 |
|
3 |
> docker images | grep oh-yeah |
4 |
oh-yeah-alpine latest 1e831ce8afe1 1 minutes ago 30.5 MB |
Yeah, está de volta! Viva o Dockerfile!
Trabalhando com Registros de Imagens
Imagens são similares a repositórios git. Também são criadas a partir de conjuntos ordenados de ordens. Podemos imaginar duas imagens usando a mesma imagem base como ramificação, mesmo não existindo mesclagem ou rebase no Docker). Um registro de imagem é o equivalente a uma hospedagem git centralizada, como o GitHub. Adivinhemos o nome do registro de imagens oficial do Docker: Sim, Docker Hub.
Baixando Imagens
Ao executar uma imagem, se não existir, Docker tentará baixá-la de um dos registros de imagens configurados. Por padrão, vai ao Docker Hub, mas podemos controlar pelo "~/.docker/config.json". Ao usar um registro diferente, seguimos suas instruções, o que costuma envolver autenticação com as credenciais deles.
Removamos a imagem "hello-world" e baixemo-na novamente usando o comando docker pull
.
1 |
> dockere images | grep hello-world |
2 |
hello-world latest c54a2cc56cbb 7 months ago 1.85 kB |
3 |
|
4 |
> docker rmi hello-world
|
5 |
hello-world |
Se foi. Baixemos novamente.
1 |
> docker pull hello-world
|
2 |
Using default tag: latest |
3 |
latest: Pulling from library/hello-world |
4 |
78445dd45222: Pull complete
|
5 |
Digest: sha256:c5515758d4c5e1e...07e6f927b07d05f6d12a1ac8d7 |
6 |
Status: Downloaded newer image for hello-world:latest
|
7 |
|
8 |
> dockere images | grep hello-world |
9 |
hello-world latest 48b5124b2768 2 weeks ago 1.84 kB |
A hello-world antiga foi substituída por uma versão mais nova.
Enviando Imagens
Enviar imagens é um pouco mais complicado. Primeiro precisamos criar uma conta no Docker Hub (ou outro registro). Depois, autenticar. Então precisamos rotular a imagem a ser enviada, de acordo com o nome da conta ("g1g1", nesse caso).
1 |
> docker login -u g1g1 -p <password> |
2 |
Login Succeeded |
3 |
|
4 |
> docker tag hello-world g1g1/hello-world |
5 |
|
6 |
> docker images | grep hello |
7 |
|
8 |
g1g1/hello-world latest 48b5124b2768 2 weeks ago 1.84 kB |
9 |
hello-world latest 48b5124b2768 2 weeks ago 1.84 kB |
Agora, podemos enviar a imagem rotulada g1g1/hello-word.
1 |
> docker push g1g1/hello-world
|
2 |
The push refers to a repository [docker.io/g1g1/hello-world]
|
3 |
98c944e98de8: Mounted from library/hello-world |
4 |
latest: digest: sha256:c5515758d4c5e...f6d12a1ac8d7 size: 524 |
Conclusão
Imagens Docker são modelos para containers, projetadas para serem eficientes e oferecer o máximo de reuso através de um driver de armazenamento em camadas de sistema de arquivos.
Docker provê muitas ferramentas para listar, inspecionar, construir e rotular imagens. Podemos baixar ou enviar imagens para registros, como o Docker Hub, para administrar ou compartilhar imagens facilmente.