Docker é uma plataforma que permite que aplicativos sejam executados de maneira consistente em diferentes ambientes, independentemente do sistema operacional em que estão sendo executados.
Ele usa a tecnologia de conteiner para empacotar a aplicação e suas dependências em um pacote portátil chamado container, podendo ser implantado em qualquer máquina que possua o Docker instalado. Isso torna o processo de implantação de aplicativos mais fácil, eficiente e escalável.
O Docker também oferece recursos como gerenciamento de rede, gerenciamento de volumes e orquestração de container, tornando-o uma solução popular para a implantação de aplicativos em nuvem e em ambientes de produção.
A seguir existem uma série de notas sobre meu aprendizado nos ultimos dias com docker e suas ferramentas.
Esse texto não é um artigo e sim um resumo para aprendizado próprio que estou disponibilizando por aqui.
Comandos
docker ps
lista containers ativos
docker ps -a
lista containers que já foram executados
docker ps -a -q
lista todos os ids de containers
docker run
inicia um container da imagem referenciada na versão latest
docker start
inicia um container que estava desligado
docker stop
finaliza um container que estava ligado
docker rm
remove um container
docker rm $(docker ps -a -q) -f
remove todos os containers
docker exec
executa uma comando dentro de um container
A ordem dos argumentos importa
-i
habilita o modo interativo com o container no terminal
-t
é para tornar possível digitar dentro de um container
-d
permite rodar o container sem prender a aba do terminal
-p
permite expor uma rota de quem utiliza o docker a ser redirecionada para uma porta dentro do container
Binding mounts
Sabendo da natureza efêmera de um container, binding mounts é como lidamos com mudanças que queremos persistir dentro do nosso container, onde para para docker run
eu tenho um -v
com o path
do meu arquivo do filesystem para o path
do arquivo que quero sempre sobrescrever dentro do meu container.
docker run -d --name foo -p 8080:80 -v ~/file.txt:~/app/file_in_container.txt command
Fazendo isso, caso eu derrube esse meu container foo
e o reinicie com esse comando ainda assim teria o conteúdo do meu filesystem.
Também é possível fazer o -v
com o comando --mount
docker run -d --name foo -p 8080:80 --mount type=bind,source="~/file.txt:",target="~/app/file_in_container.txt" command
Trabalhando com volumes
Ainda sobre persistência de arquivos e diferente dos caminhos exatos e associação direta que vimos em binding mounts, volumes é uma abstração bem mais coesa sobre isso, para cada diretório que eu quiser persistir seus arquivos em um determinado container eu posso criar um volume e esses volumes criam uma referência para fora do container.
docker volume create project
para cada volume você vai entrar um arquivo de configuração do seguinte tipo
[
{
"CreatedAt": "2023-03-10T03:06:59Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/project/_data",
"Name": "project",
"Options": {},
"Scope": "local"
}
]
Onde o Mountpoint
, por exemplo, substitui a sua declaração de -v
ao usar esse volume.
docker run -d --name foo -p 8080:80 -v project:~/app
Dockerfile
keywords
FROM
seguido da imagem de referência
WORKDIR
define o path do projeto dentro do container
RUN
define os comando que serão executados no bash, prende o terminal e o build. COPY
copia arquivos do file system para o container
CMD
define um comando padrão e que pode ser substituido por parâmetro, não prende o terminal.
ENTRYPOINT
define um comando imutável para executar
LABEL
define informações sobre uma imagem
ENV
define variáveis de ambientes
EXPOSE
define uma porta a ser exposta pela imagem, não significa um bind externo, porém que a porta está exposta.
Todas as vezes que houve uma alteração no dockerfile, é preciso invocar um novo
build
Network
Tipos de rede
bridge
network default, útil para comunicação entre containers.
host
mescla a network do host que está rodando o docker com a network do próprio container.
overlay
cria uma camada de network para comunicar vários dockers em diferentes hosts.
none
container sem network, de forma isolada.
Para criar um rede bridge
docker network create --driver bridge my-net
Para iniciar um container com uma network
docker run -dit --name ubuntu1 --network my-net bash
Para verificar a configuração de uma network
docker network inspect my-net
E você poderá ver no retorno as configurações de network para uma determinada rede.
{
"Name": "my-net",
"Id": "e014ca6c22cb673585c018450f018b1f5d9dc4d2eb7fbaceffeff",
"Created": "2023-03-11T05:34:29.729893126Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.10.0.0/11",
"Gateway": "172.12.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {
"com.docker.compose.network": "my-net",
"com.docker.compose.project": "mongoose",
"com.docker.compose.version": "2.15.1"
}
}
Otimização
Quando lidamos com docker, algo realtivamente fácil é construirmos imagens que demoram consideravelmente para iniciar e perdem muito em tempo em cada comando, para contornar isso uma possível saída é a concatenação de comandos(ou "layers"), onde para cada comando RUN
que eu determinaria um comando específico eu posso ter um conjunto de comandos unificados em uma mesma layer, por exemplo
# dockerfile
-- Lento
RUN command_x
RUN command_y
RUN command_z
-- Rápido
RUN command_x; \\
command_y; \\
command_z;
Docker Compose
Uma maneira efetiva de orquestrar alguns containers onde cada serviço possui seu próprio namespace, imagine um cenário onde você tem vários projetos, em que cada um deles dependem de uma versão específica de um banco de dados, a melhor solução possível é você orquestrar um container dessa versão do banco de dados com o container da sua aplicação.
Os comandos do docker compose se assemelham muito com os do dockerfile com a diferença que o docker compose utiliza yaml
veja um exemplo de aplicação onde existe um banco de dados, uma aplicação com um dockerfile e um proxy reverso via servidor nginx
version: '3'
services:
database:
platform: linux/x86_64
image: mysql:5.7
command: --innodb-use-native-aio=0
container_name: database
restart: always
tty: true
volumes:
- "./bkp/mysql:/var/lib/mysql"
ports:
- "3306"
env_file:
- .env
networks:
- proxy
nginx:
image: nginx:1.22-alpine
container_name: proxy
restart: always
ports:
- "80:80"
- "443:443"
networks:
- proxy
app:
build:
context: .
dockerfile: dockerfile
image: danielsuhett/app
container_name: app
volumes:
- .:/home/node/app
- ./node_modules:/home/node/app/node_modules
ports:
- "3000"
networks:
- proxy
env_file:
- .env
depends_on:
- nginx
networks:
proxy:
driver: bridge
Nesse exemplo, eu não exponho minha aplicação nem meu banco de dados ao host
e sim meu proxy
que é o nginx.
Kubernetes
Kubernetes é um conjunto de tecnologias de infraestrutura elástica, configurando VPC
, que são redes privadas isoladas para cada grupo de containers, lida com balanceadores, também gerencia pods
que são mini máquinas em tempo real adicionando ou removendo recurso computacional aos projetos.
Diferente do docker-compose, kubernetes tem a proposta de orquestrar muitos containers em larga escala, onde um trabalho poderia se tornar difícil com um simples arquivo yaml o kubernetes vem e lida com isso de uma maneira mais robusta.
Terraform
Define o conceito de IaaS
, infraestrutura como serviço, onde você utiliza da orquestração robusta dos containers com kubernetes e também configura todas as suas instâncias de aplicação e pipelines de deploy com código!
Terraform permite que as alterações na infraestrutura sejam tratadas como código, facilitando a colaboração em equipe, versionamento e a realização de alterações de forma segura e controlada.