Se você está dando seus primeiros passos com Docker ou quer começar, esse é um post de “boas vindas”.
Eu conheci docker em 2015, comecei a estudar de fato em dezembro desse mesmo ano. Logo em janeiro de 2016 comecei uma série de conteúdos sobre Docker. Principalmente destinado à comunidade .NET. Docker aqui era um tema muito distante nessa época. Eu resolvi abraçar a causa e daí surgiu a série Docker de A a Z.
De 2016 até hoje eu ajudo na comunidade, ensinando docker, seja como parte de treinamento ou gratuitamente em grupos de discussão ou em lives e vídeos para o youtube ou aqui no gaGO.io.
Uma coisa eu posso dizer: São sempre os mesmos erros.
A taxa de novidade ou novos erros é muito baixa.
Então eu resolvi empacotar o que eu considero 80/20 das perguntas da comunidade.
Objetivo
Nos primeiros dias com docker acontecem inúmeros erros. Se o novato não for aquele tipo de profissional cuidadoso com seu próprio aprendizado, levará consigo problemas e mal entendimento por anos. Como se não bastasse seu próprio problema, em geral dissemina erros por onde passa.
O objetivo desse texto é de fato ser indigesto o suficiente para causar reflexão. Se por algum motivo alguém entrar nesse texto e se motivar a perguntar em algum grupo ou pesquisar a respeito de qualquer tema que expus aqui, para complementar o entendimento, pra mim já valeu a pena.
O ponto é que eu acredito que esse FAQ resolva diversas questões que tomam tempo da comunidade.
A ideia é mostrar um conjunto de regras e fundamentos que ajude a dar clareza a respeito do uso de Docker e Containers.
Exceções
Eu estou tentando trazer pra cá aquilo que atende à maioria absoluta. Então inevitavelmente se você já tem experiência com Docker, provavelmente saberá discernir e ter bom senso.
Eu não espero discernimento e bom senso de quem está começando, é a falta desses 2 elementos que faz do novato, um novato.
Para quem está começando, dando os primeiros passos, esse conjunto de regras vai evitar stress e sofrimento.
Qual é o princípio por trás dessa cagação de regra toda?
Docker parece simples e familiar demais. Basta dois tutoriais na internet e você conclui: Já sei!
O ponto é que tutorial não tem o papel de ensinar, tem o papel de mostrar um caminho. Ensinar é algo beeeem mais profundo. Ensinar é mostrar para quê, porquê, onde e quando. Um tutorial tem a missão de detalhar uma jornada bem específica, sem ensinar o que está ao redor.
1 | Cada aplicação, uma imagem, um ou mais containers
Cada serviço é um container diferente.
Ou seja. Se você tem uma SPA, uma API, um Redis, um MongoDB e um MySQL.
Então devemos ter os seguintes containers:
- SPA
- API
- Redis
- MongoDB
- MySQL
E possivelmente, terá um proxy reverso ou api gateway nessa arquitetura. Obviamente: Outros containers.
Motivo:
Uma imagem é um conjunto de libraries. Não é um sistema operacional. Um container nada mais é do que uma única aplicação, inteira (inteira desde o runtime até a última dependência antes do kernel).
Cada imagem deve conter uma aplicação.
Esse desenho é natural, comum, corriqueiro.
Aqui está o exemplo do Harbor com 9 containers, 9 imagens.
Nosso stack de log com 7 containers, 7 imagens
2 | Expose no DockerFile
O Expose do Dockerfile tem a função de explicar para novos utilizadores quais portas a aplicação que está na imagem utiliza.
O Docker não usa esse dado para fazer nenhuma liberação nem restrição, nem automação.
Exceção: Só há uma exceção, que só é usada em ambiente de desenvolvimento e que trabalha alocando portas dinamicamente para as postas listadas no expose, trata-se do parâmetro -P (maiúsculo) do docker run.
Isso quer dizer que se você disser se seu Dockerfile tem EXPOSE 80 mas a aplicação conteinerizada está escutando a porta 12999, para o docker: Foda-se. Ele ignora completamente, não restringe, não limita, não atrapalha, não faz nada com essa informação.
No entanto essa abordagem (ignorar o expose) dificulte consumidores da imagem e portanto seja uma prática ruim, o docker não impede, não limita, sequer considera sua escolha para tomar alguma decisão.
Motivador
Muita gente perde tempo no troubleshooting colocando a porta no expose achando que isso vai resolver algum problema.
3 | Para um container conversar com o outro, preciso expor a porta no host?
Não. O único motivo para expor a porta de um container no host é o interesse de que alguém que esteja fora do ambiente dockernizado acesse o container.
Isso inclui você e seu browser, mesmo que esteja no mesmo host.
Para o docker, faz parte do ambiente dele:
- outros containers que estão no mesmo host
- containers que estejam rodando nos nós participantes do cluster (swarm ou kubernetes).
Seu browser em geral não é dockernizado. Caso fosse seria fácil acessar esse container internamente, sem precisar expor portas. Bastava estar na mesma rede bridge (docker) ou overlay (swarm).
Motivo
Os primeiros passos com docker podem ser traumáticos. Muitos tutoriais mal feitos te empurram na direção de expor uma porta. No final das contas o que temos é gente perdida rodando containers localmente de forma diferente como rodaria em produção e obviamente produzindo assim aberrações que não sanam o “na minha máquina funciona”.
4 | Tenho uma API Web que precisa falar com o banco, ambos estão em containers, como faço?
Você cria uma rede docker e coloca todos os containers que precisam falar com esse banco na mesma rede docker do banco.
Um container pode estar em diversas redes.
Se 2 containers estão na mesma rede docker, e essa rede foi criada por você (ou seja, não é a rede default que já vem criada), então o nome do container/service é resolvido pelo DNS, então o nome do container é resolvido no IP do atual daquele container de destino.
Se você tem um container com sua API chamado api1 e container, um MYSQL com o nome de db1. Na connectionstring da API você usaria db1:3306.
A rede docker vai resolver o IP do container dinamicamente.
PS:
Se o container de banco foi criado com o comando docker run … -p 3388:3306 … mysql, ou algo parecido, lembre-se que a porta do container continua a mesma 3306, ou seja o mysql não está na porta 3388, continua na porta 3306, no entanto um port forward foi criado para expor essa porta no host, e a porta do host usada foi a 3388, mas isso é outro assunto que não diz respeito à rede entre containers.
5 | Como descubro o IP do container?
Pela perspectiva do Docker, você nunca deveria precisar saber o IP de um container.
As redes não padrão implementam resolução de nomes (DNS) baseado no nome dos containers/serviços participantes da rede. Esse tipo de estratégia faz com que você não precise lidar com IP’s dos containers.
Entre restarts não há garantia de que o container pegue o mesmo IP, e se você forçar, imediatamente perderá a capacidade de escalar esse container até no mesmo host.
Por isso, a resolução de nomes garante que você não precise dessa informação.
Leitura Complementar:
Don’t Do That – Forçar IP’s nos Containers Docker
6 | Em produção quero mapear os fontes do seu disco, usando volumes ou binds para o disco, isso está ok?
Uma imagem contém uma versão da aplicação e todas as suas dependências.
A imagem foi feita para ser o pacote de publicação.
A substituição de uma versão de uma aplicação foi planejada para ser realizada com:
- Criação da nova versão da imagem
- Publish da nova imagem para um registry
- Rollout
- drop do container com a versão antiga da imagem
- criação do container com a versão nova da imagem
Um container é imutável e efêmero, ou seja, ele não é reaproveitável.
Os volumes foram pensados para conteúdo não efêmero, como arquivos de upload, ou qualquer tipo de informação que precisa resistir às substituições de imagem (causando drop e criação de containers).
Nos bancos de dados são os arquivos do banco de dados em si, como exemplo.
Fonte, binários, não foram pensados para estar mapeado fora da imagem, isso fere a imutabilidade. Visto que você precisa controlar a versão dos fontes que estão mapeados à aplicação. Com o mapeamento dos fontes no disco, a troca de uma imagem passa a não representar uma troca de runtime + aplicação, que deveria estar junto, mas por sua decisão de colocar em um volume, estão separados.
Isso quer dizer que você consegue controlar a versão do runtime mas não mais a versão da aplicação. E esse é o problema dessa decisão.
Essa forma de trabalho até pode ser executada para desenvolver, dependendo das restrições e limitações, mas nem de longe é uma ideia aceitável em produção.
7 | Em desenvolvimento quero mapear os fontes do seu disco, usando volumes, binds para o disco, você vai querer fazer a mesma coisa em produção.
Lembre-se de evitar sempre o na minha máquina funciona.
E você só evita esse problema, usando a comunicação container x container da forma certa, empacotando a aplicação como uma imagem, rodando com um simples docker run, docker compose sem muitas dependências externas.
Se você desenvolve de uma forma diferente daquela que você roda a aplicação em produção, você tende a querer rodar em produção da mesma forma como desenvolve.
Aí mora o problema.
Fontes no file system do host, em um ambiente dockenizado é motivo de chacota.
8 | Não tenho uma imagem da minha aplicação. Posso criar um container da minha aplicação, usando um docker-compose.yaml sem usar um Dockerfile?
Não.
O docker compose é uma cli que orquestra a subida de containers de um stack.
Ele possui inclusive um espaço dedicado para o build de novas aplicações, mas para isso ele vai exigir um dockerfile.
9 | Preciso de um Dockerfile quando?
Sempre que você precisa produzir uma imagem.
10 | Alterei somente uma linha de código na minha aplicação, preciso produzir uma imagem nova? Como eu coloco meu código-fonte no container para subir logo minha alteração sem criar uma nova imagem?
Você altera sua aplicação, faz um commit e seu pipeline de CI/CD produz a nova imagem, disponibilizando em um registry.
Você também pode produzir localmente para validar.
Uma vez com a imagem produzida e publicada, é hora de realizar o rollout.
Esse é o fluxo para qualquer cenário, desde 1 linha de alteração até 30 mil linhas.
Se você acha isso too much, provavelmente você acha que um container é uma máquina virtual. Não é.
11| Meu dockerfile tem um git clone para baixar todo o projeto, isso está certo?
Não.
O dockerfile deveria ter sido versionado junto com o projeto e o git clone deveria trazer o projeto junto com seu dockerfile.
Seu dockerfile não deveria depender de um repositório git.
Leitura Complementar:
Docker development best practices
12| Como posso alterar o ip da rede do docker?
Se não for um caso de conflito.
Volte para o Item 5.
13| Quero subir meu projeto para produção, quero executar uma sequencia de docker runs para construir meu ambiente, isso está certo?
Isso não está errado, mas é uma má prática.
Os comandos docker run, docker *** create são comandos imperativos em que você precisa lidar com a descoberta do estado atual, para saber quais comandos podem ser executados. Desfazer exige uma sequencia lógica também.
Versionar esses comandos é desafiador, visto que depende de um processo manual muito mais ineficiente do que se você tivesse um arquivo declarativo que expressasse esse stack.
Esse arquivo declarativo existe, é o docker-compose.yaml executado pela cli do docker-compose, binário que hoje é embarcado com o docker no ambiente de desenvolvimento e instalado facilmente em servidores.
14| Tenho dois docker-composes ou stacks swarm, um dedicado a resources compartilhados, outra para uma aplicação. Mas minha aplicação não conecta no recurso compartilhado.
A lógica de network do docker não muda em função do contexto.
Não importa sua necessidade, ela sempre é o caminho para estabelecer conexões entre 2 ou mais containers.
Nesse caso, temos 2 soluções. Criar uma network previamente e ambos os stacks dependerão dessa mesma network.
Ou o primeiro stack define a network enquanto os demais dependem dela.
Todos os 2 cenários são ok.
15| Tenho uma imagem baseada no Windows Server Core ou Nano Server e quero executar no Ubuntu ou Mac, como fazer?
Imagens possuem 2 informações relevantes para portabilidade:
OSType: Windows, Linux …
Architecture: x86_64, ARM 64 …
Cada imagem possui essas 2 propriedades, produzindo:
- Linux/x86_64
- Linux/armv7l
- Windows/x86_64
A portabilidade é entre mesmo OSType e mesma Architecture.
Imagens Linux/x86_64 só serão executadas em Linux/x86_64 (qualquer um).
Isso faz com que
- imagens baseadas em Windows só rodem no windows
- imagens linux, só rodem no linux.
- imagens arm só rodem em arm.
Leitura Complementar:
Docker no Windows vs Docker no Linux
16| Mas eu já vi imagens que não possuem essa distinção e rodam em qualquer lugar…
Na verdade não são imagens, são manifestos que apontam para mais de uma imagem. É como se a imagem fosse uma pseudo-imagem, com um ponteiro para cada versão de acordo com OSType e Architecture.
Isso foi lançado com o título “Docker Official Images are now Multi-platform” em 2017. Mas na prática você escreve um arquivo que dá para o docker-hub, e não sei se funciona em outro registry, as informações para a descoberta da imagem certa para o OSType/Architecture certo. Como um dicionário que precisa dar match com as características do servidor que estamos rodando a aplicação.
Exemplo do manifesto:
Leitura Complementar:
Docker Official Images are now Multi-platform
17| Kubernetes não suporta Docker… por isso…
Por isso a docker separou o containerd anos antes.
Para não precisar lidar com isso e deixar o caminho livre para que não precise dar manutenção ao suporte a Kubernetes.
A mídia diz: “Kubernetes não suporta mais docker” enquanto na verdade, Docker não tem o interesse de acompanhar as evoluções do Kubernetes. Por isso isolou o containerd (esse sim tem de acompanhar) e o cedeu à comunidade e se eximiu dessa responsabilidade.
Nada mudou, você continua usando docker diariamente e implantando no kubernetes, por mais que “docker” não seja suportado no Kubernetes.
A diferença prática para os usuários de um cluster é não conseguir enxergar os containers com a CLI do Docker.
Já para quem administra o cluster, é preciso reconfigurar o Container Runtime para ContainerD, ou Cri-O.
Não há perda de retrocompatibilidade, o ecossistema que está amadurecendo, a CNCF também ajudou nas especificações, e todos continuam felizes.
Essa era uma notícia de rodapé de site, que tomou proporções descabidas demonstrando a ignorância da comunidade como um todo a respeito do assunto.
Leitura Complementar:
Kubernetes sem Docker! É o fim do docker?
18| Tenho um NGINX na porta 80, um SPA na porta 81 e uma API na porta 82, proxy_pass http:/localhost:81/ e proxy_pass http:/localhost:82/ não funciona…
Você tem 3 aplicações, deveriam ser 3 containers:
- Proxy Reverso (NGINX)
- App Angular/React (NGINX)
- API (Node, Java, Python, PHP, C#, etc)
A primeira coisa é que para o NGINX que tem o papel de proxy reverso conseguir achar sua aplicação usando um NOME e não um IP, é necessário que ambos estejam na mesma rede docker. Lembrando que usar o IP do container é um erro, como dito no item #5 desse post.
Assim como no item #4, se você tem um container em uma rede, e esse container possui um nome dado por você, ele ganhará o nome como alias na rede.
Se você estiver usando docker-compose, o nome do service será usado com alias.
Assim vamos supor os containers com nome proxy, spa e api.
Nome | Porta no Container | Porta mapeada no host | Imagem base | Role |
proxy | 80 | 80 | nginx:latest | Proxy Reverso |
spa | 80 | 81 | nginx:latest | SPA (Angular/React etc) |
api | 80 | 82 | {tech}:latest | API (Node, Java, Python, PHP, C#, etc) |
Para o container de nome proxy ser um proxy para ambos, é necessário usar:
proxy_pass http:/spa:80/
proxy_pass http:/api:80/
As portas 81 e 82 expostas no host podem ser importantes para você como administrador acessar as aplicações sem um proxy reverso. No entanto para o proxy reverso essa exposição é IRRELEVANTE.
Cada container possui um stack próprio de rede, onde localhost para cada um container é o próprio container e não o host. Cada container consegue escutar a porta 80 dele mesmo. Mas isso não te permite aloca a mesma porta 80 do host para diversos containers, porque o consumo de uma porta é uma atividade exclusiva. O container consegue fazer isso porque possui um IP só dele. Quando você faz um bind da porta 80 do container para a porta 81 do host, você está criando um elemento adicional para realizar esse mapeamento. Ou seja, um novo processo no seu host é criado para fazer esse forward.
Containers não se comunicam usando portas expostas. Um container acessa diretamente a porta do outro container. Só que usamos a rede docker para conseguir usar nomes em vez de frágeis menções a IP’s que são voláteis por definição. Tentar burlar isso fixando IP’s é um erro em quase todos os casos.
Esse post te ajudou?
Deixe nos comentários!
0 comentários