Os contêineres do Docker estão em ascensão como uma prática recomendada para implantar e gerenciar sistemas distribuídos nativos da nuvem. Os contêineres são instâncias de imagens do Docker. Acontece que há muito para saber e entender sobre imagens.
Neste tutorial de duas partes, estou abordando as imagens do Docker em profundidade. Na primeira parte, discuti os princípios básicos, considerações de design e inspeção de imagens internas. Nesta parte, abordo a criação de suas próprias imagens, a solução de problemas e o trabalho com repositórios de imagens.
Quando você sair do outro lado, terá uma compreensão sólida do que são exatamente as imagens do Docker e como utilizá-las efetivamente em seus próprios aplicativos e sistemas.
Imagens de construção
Existem duas maneiras de construir imagens. Você pode modificar um contêiner existente e, em seguida, confirmá-lo como uma nova imagem ou pode escrever um Dockerfile e construí-lo em uma imagem. Analisaremos ambos e explicaremos os prós e os contras.
Compilações manuais
Com as compilações manuais, você trata seu contêiner como um computador normal. Você instala pacotes, escreve arquivos e, quando tudo está dito e feito, você o confirma e acaba com uma nova imagem que usa como modelo para criar muitos outros contêineres idênticos ou até mesmo basear outras imagens.
Vamos começar com a imagem alpine, que é uma imagem muito pequena e espartana baseada no Alpine Linux. Podemos executá-lo no modo interativo para entrar em um shell. Nosso objetivo é adicionar um arquivo chamado “yeah” que contenha o texto “it works!” para o diretório raiz e, em seguida, crie uma nova imagem chamada “yeah-alpine”.
Aqui vamos nós. Legal, já estamos no diretório raiz. Vamos ver o que há.
> docker run -it alpine /bin/sh / # ls bin dev etc home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
Qual editor está disponível? Sem vim, sem nano?
/ # vim /bin/sh: vim: not found / # nano /bin/sh: nano: not found
Ah bem. Queremos apenas criar um arquivo:
/ # echo "it works!" > yeah / # cat yeah it works!
Saí do shell interativo e posso ver o contêiner chamado “vibrant_spenc” com docker ps --all
. o --all
sinalizador é importante porque o contêiner não está mais em execução.
> docker ps --all CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES c8faeb05de5f alpine "/bin/sh" 6 minutes ago Exited vibrant_spence
Aqui, crio uma nova imagem do contêiner “vibrate_spence”. Eu adicionei a mensagem de commit “meu, meu, meu” para garantir.
> docker commit -m "mine, mine, mine" vibrant_spence yeah-alpine sha256:e3c98cd21f4d85a1428...e220da99995fd8bf6b49aa
Vamos dar uma olhada. Sim, há uma nova imagem, e em seu histórico você pode ver uma nova camada com o comentário “meu, meu, meu”.
> docker images REPOSITORY TAG IMAGE ID SIZE yeah-alpine latest e3c98cd21f4d 4.8 MB python latest 775dae9b960e 687 MB d4w/nsenter latest 9e4f13a0901e 83.8 kB ubuntu-with-ssh latest 87391dca396d 221 MB ubuntu latest bd3d4369aebc 127 MB hello-world latest c54a2cc56cbb 1.85 kB alpine latest 4e38e38c8ce0 4.8 MB nsqio/nsq latest 2a82c70fe5e3 70.7 MB > docker history yeah-alpine IMAGE CREATED SIZE COMMENT e3c98cd21f4d 40 seconds ago 66 B mine, mine, mine 4e38e38c8ce0 7 months ago 4.8 MB
Agora, para o teste real. Vamos deletar o container e criar um novo container a partir da imagem. O resultado esperado é que o sim arquivo estará presente no novo contêiner.
> docker rm vibrant_spence vibrant_spence > docker run -it yeah-alpine /bin/sh / # cat yeah it works! / #
O que posso dizer? Sim, funciona!
Usando um Dockerfile
Criar imagens a partir de contêineres modificados é legal, mas não há responsabilidade. É difícil acompanhar as mudanças e saber quais foram as modificações específicas. A maneira disciplinada de criar imagens é construí-las usando um Dockerfile.
o Dockerfile é um arquivo de texto semelhante a um script de shell, mas suporta vários comandos. Cada comando que modifica o sistema de arquivos cria uma nova camada. Na primeira parte, discutimos a importância de dividir sua imagem em camadas adequadamente. o Dockerfile é um grande tópico por si só.
Aqui, vou apenas demonstrar alguns comandos para criar outra imagem, oh-sim-alpino, com base em um Dockerfile. Além de criar o famigerado sim arquivo, vamos também instalar o vim. A distribuição Alpine Linux usa um sistema de gerenciamento de pacotes chamado apk. Aqui está o Dockerfile:
FROM alpine # Copy the "yeah" file from the host COPY yeah /yeah # Update and install vim using apk RUN apk update && apk add vim CMD cat /yeah
A imagem base é alpina. Ele copia o sim arquivo do mesmo diretório do host onde o Dockerfile é (o caminho do contexto de construção). Então, ele corre apk update
e instala o vim. Por fim, define o comando que é executado quando o contêiner é executado. Neste caso imprimirá na tela o conteúdo do sim Arquivo.
OK. Agora que sabemos no que estamos entrando, vamos construir essa coisa. o -t
opção define o repositório. Eu não especifiquei uma tag, então será o padrão “mais recente”.
> docker build -t oh-yeah-alpine . Sending build context to Docker daemon 3.072 kB Step 1/4 : FROM alpine ---> 4e38e38c8ce0 Step 2/4 : COPY yeah /yeah ---> 1b2a228cc2a5 Removing intermediate container a6221f725845 Step 3/4 : RUN apk update && apk add vim ---> Running in e2c0524bd792 fetch https://dl-cdn.alpinelinux.org/.../APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org.../x86_64/APKINDEX.tar.gz v3.4.6-60-gc61f5bf [http://dl-cdn.alpinelinux.org/alpine/v3.4/main] v3.4.6-33-g38ef2d2 [http://dl-cdn.alpinelinux.org/.../v3.4/community] OK: 5977 distinct packages available (1/5) Installing lua5.2-libs (5.2.4-r2) (2/5) Installing ncurses-terminfo-base (6.0-r7) (3/5) Installing ncurses-terminfo (6.0-r7) (4/5) Installing ncurses-libs (6.0-r7) (5/5) Installing vim (7.4.1831-r2) Executing busybox-1.24.2-r9.trigger OK: 37 MiB in 16 packages ---> 7fa4cba6d14f Removing intermediate container e2c0524bd792 Step 4/4 : CMD cat /yeah ---> Running in 351b4f1c1eb1 ---> e124405f28f4 Removing intermediate container 351b4f1c1eb1 Successfully built e124405f28f4
Parece bom. Vamos verificar se a imagem foi criada:
> docker images | grep oh-yeah oh-yeah-alpine latest e124405f28f4 About a minute ago 30.5 MB
Observe como a instalação do vim e suas dependências aumentou o tamanho do contêiner de 4,8 MB da imagem alpina básica para 30,5 MB!
É tudo muito legal. Mas funciona?
> docker run oh-yeah-alpine it works!
Ah sim, funciona!
Caso você ainda esteja desconfiado, vamos entrar no contêiner e examinar o sim arquivo com nosso vim recém-instalado.
> docker run -it oh-yeah-alpine /bin/sh / # vim yeah it works! ~ ~ . . . ~ "yeah" 1L, 10C
O Contexto de Construção e o .dockerignore Arquivo
Eu não contei a você, mas originalmente, quando tentei criar a imagem oh-yeah-alpine, ela ficou suspensa por vários minutos. O problema é que acabei de colocar o Dockerfile no meu diretório pessoal. Quando o Docker cria uma imagem, ele primeiro empacota todo o diretório onde o Dockerfile é (incluindo subdiretórios) e o torna disponível para comandos COPY no Dockerfile.
O Docker não está tentando ser inteligente e analisar seus comandos COPY. Apenas embala a coisa toda. Observe que o conteúdo de compilação não terminará em sua imagem, mas diminuirá a velocidade do comando de compilação se o contexto de compilação for desnecessariamente grande.
Neste caso, eu simplesmente copiei Dockerfile e sim em um subdiretório e executei o comando docker build nesse subdiretório. Mas às vezes você tem uma árvore de diretório complicada da qual deseja copiar subdiretórios e arquivos específicos e ignorar outros. Introduzir o .dockerignore Arquivo.
Este arquivo permite controlar exatamente o que vai para o contexto de construção. Meu truque favorito é primeiro excluir tudo e depois começar a incluir os pedaços de que preciso. Por exemplo, neste caso eu poderia criar o seguinte .dockerignore arquivo e manter Dockerfile e sim no meu diretório pessoal:
# Exclude EVERYTHING first * # Now selectively include stuff !yeah
Não há necessidade de incluir o Dockerfile em si ou o .dockerignore arquivo no contexto de compilação.
Copiar x Montar
Copiar arquivos para a imagem às vezes é o que você precisa, mas em outros casos você pode querer que seus contêineres sejam mais dinâmicos e trabalhem com arquivos no host. É aqui que os volumes e montagens entram em jogo.
A montagem de diretórios de host é um jogo diferente. Os dados são de propriedade do host e não do contêiner. Os dados podem ser modificados quando o container é parado. O mesmo contêiner pode ser iniciado com diferentes diretórios de host montados.
Marcando Imagens
A marcação de imagens é muito importante se você desenvolver um sistema baseado em microsserviços e gerar muitas imagens que às vezes devem ser associadas umas às outras. Você pode adicionar quantas tags quiser a uma imagem.
Você já viu a tag padrão “mais recente”. Às vezes, faz sentido adicionar outras tags, como “tested”, “release-1.4” ou o git commit que corresponde à imagem.
Você pode marcar uma imagem durante uma construção ou posteriormente. Veja como adicionar uma tag a uma imagem existente. Observe que, embora seja chamado de tag, você também pode atribuir um novo repositório.
> docker tag oh-yeah-alpine oh-yeah-alpine:cool-tag > docker tag oh-yeah-alpine oh-yeah-alpine-2 > docker images | grep oh-yeah oh-yeah-alpine-2 latest e124405f28f4 30.5 MB oh-yeah-alpine cool-tag e124405f28f4 30.5 MB oh-yeah-alpine latest e124405f28f4 30.5 MB
Você também pode desmarcar removendo uma imagem pelo nome da marca. Isso é um pouco assustador, porque se você remover a última tag acidentalmente, perderá a imagem. Mas se você construir imagens de um Dockerfilevocê pode apenas reconstruir a imagem.
> docker rmi oh-yeah-alpine-2 Untagged: oh-yeah-alpine-2:latest > docker rmi oh-yeah-alpine:cool-tag Untagged: oh-yeah-alpine:cool-tag
Se eu tentar remover a última imagem marcada restante, recebo um erro porque ela é usada por um contêiner.
> docker rmi oh-yeah-alpine Error response from daemon: conflict: unable to remove repository reference "oh-yeah-alpine" (must force) - container a1443a7ca9d2 is using its referenced image e124405f28f4
Mas se eu remover o recipiente…
> docker rmi oh-yeah-alpine Untagged: oh-yeah-alpine:latest Deleted: sha256:e124405f28f48e...441d774d9413139e22386c4820df Deleted: sha256:7fa4cba6d14fdf...d8940e6c50d30a157483de06fc59 Deleted: sha256:283d461dadfa6c...dbff864c6557af23bc5aff9d66de Deleted: sha256:1b2a228cc2a5b4...23c80a41a41da4ff92fcac95101e Deleted: sha256:fe5fe2290c63a0...8af394bb4bf15841661f71c71e9a > docker images | grep oh-yeah
Sim. Foi-se. Mas não se preocupe. Podemos reconstruí-lo:
> docker build -t oh-yeah-alpine . > docker images | grep oh-yeah oh-yeah-alpine latest 1e831ce8afe1 1 minutes ago 30.5 MB
Sim, está de volta. Dockerfile pela vitória!
Trabalhando com registros de imagem
As imagens são muito semelhantes em alguns aspectos aos repositórios git. Eles também são construídos a partir de um conjunto ordenado de commits. Você pode pensar em duas imagens que usam as mesmas imagens base como ramificações (embora não haja mesclagem ou rebase no Docker). Um registro de imagem é o equivalente a um serviço central de hospedagem git como o GitHub. Adivinha qual é o nome do registro de imagem oficial do Docker? Isso mesmo, Docker Hub.
Puxando Imagens
Quando você executa uma imagem, se ela não existir, o Docker tentará extraí-la de um de seus registros de imagem configurados. Por padrão, ele vai para o Docker Hub, mas você pode controlá-lo em seu ~/.docker/config.json Arquivo. Se você usar um registro diferente, poderá seguir as instruções, que geralmente envolvem o login usando suas credenciais.
Vamos deletar a imagem “hello-world” e puxá-la novamente usando o comando docker pull
comando.
> dockere images | grep hello-world hello-world latest c54a2cc56cbb 7 months ago 1.85 kB > docker rmi hello-world hello-world
Foi-se. Vamos puxar agora.
> docker pull hello-world Using default tag: latest latest: Pulling from library/hello-world 78445dd45222: Pull complete Digest: sha256:c5515758d4c5e1e...07e6f927b07d05f6d12a1ac8d7 Status: Downloaded newer image for hello-world:latest > dockere images | grep hello-world hello-world latest 48b5124b2768 2 weeks ago 1.84 kB
O último hello-world foi substituído por uma versão mais recente.
Empurrando Imagens
Enviar imagens é um pouco mais complicado. Primeiro você precisa criar uma conta no Docker Hub (ou outro registro). Em seguida, você faz login. Em seguida, você precisa marcar a imagem que deseja enviar de acordo com o nome da sua conta (“g1g1” no meu caso).
> docker login -u g1g1 -pLogin Succeeded > docker tag hello-world g1g1/hello-world > docker images | grep hello g1g1/hello-world latest 48b5124b2768 2 weeks ago 1.84 kB hello-world latest 48b5124b2768 2 weeks ago 1.84 kB
Agora, posso enviar a imagem marcada g1g1/hello-world.
> docker push g1g1/hello-world The push refers to a repository [docker.io/g1g1/hello-world] 98c944e98de8: Mounted from library/hello-world latest: digest: sha256:c5515758d4c5e...f6d12a1ac8d7 size: 524
Conclusão
As imagens do Docker são os modelos para seus contêineres. Eles são projetados para serem eficientes e oferecer reutilização máxima usando um driver de armazenamento do sistema de arquivos em camadas.
O Docker fornece muitas ferramentas para listar, inspecionar, construir e marcar imagens. Você pode extrair e enviar imagens para registros de imagens como o Docker Hub para gerenciar e compartilhar facilmente suas imagens.