Aprendendo Docker

Photo by Ian Taylor on Unsplash

Aprendendo Docker

Reaprendendo comandos e conceitos

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.