Dicas e Truques no uso do docker-compose

As dicas que serão encontradas nesse artigo também poderiam ser válidas para o Docker puro mas como a linha de comando acaba ficando muito grande é raro que utilizem diretamente por ela. São dicas simples e conceitos interessantes utilizados mais no docker-compose do que na commandline.

Comandos de healthcheck

Esse é um recurso fundamental que define condições de existência do contêiner. Uma ferramenta deve validar se o contêiner está ok e o Docker fará uso dessa ferramenta (ou linha de comando). Além disso há parâmetros como o intervalo entre as verificações, retentativas em caso de falha, a partir de quando começa e qual é o timeout. Veja o docker-compose.yml com o healthcheck.

services:
  web:
    build: app/aspnetapp
    ports:
      - 80:80
  db:
    environment:
      ACCEPT_EULA: "Y"
      SA_PASSWORD: example_123
 
    image: mcr.microsoft.com/azure-sql-edge:1.0.4

    restart: always
    healthcheck:
        test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P example_123 -Q 'SELECT 1' || exit 1"]
        interval: 10s
        retries: 10
        start_period: 10s
        timeout: 3s     

Service_healthy

É possível indicar que o contêiner tem como dependência a sua saúde. Esse é um item muito relevante que se conecta diretamente com a ideia do healthcheck. Utiliza-se assim:

# Desse modo o backend depende do serviço db estar saudável, caso contrário ele também estará falho
services:
  backend:
    depends_on:
      db:
        condition: service_healthy

Context e target

Em muitos casos o docker-compose carrega informações sobre uma imagem que se deseja dar build e não apenas de uma imagem pronta. Nesse caso basta adicionar uma entrada para build: imagem. Entretanto é possível ser mais específico, como no exemplo a seguir.

services:
  web:
    build:
      context: angular
      target: builder
    ports:
      - 4200:4200
    volumes:
      - ./angular:/project
      - /project/node_modules

Context -> é a pasta onde está o dockerfile em questão
Target -> qual será o estágio a ser gerado no multistage building

Veja o dockerfile do cenário

# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM node:17.0.1-bullseye-slim as builder
RUN mkdir /project
WORKDIR /project
RUN npm install -g @angular/cli@13
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["ng", "serve", "--host", "0.0.0.0"]

FROM builder as dev-envs
RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends git
EOF

RUN <<EOF
useradd -s /bin/bash -m vscode
groupadd docker
usermod -aG docker vscode
EOF

COPY --from=gloursdocker/docker / /
CMD ["ng", "serve", "--host", "0.0.0.0"]

Restart

É possível definir que o contêiner será reiniciado em algumas condições específicas. Esse recurso é importante para ambientes que não possuem um orquestrador (Docker Swarm ou Kubernets). Para mais informações consulte https://docs.docker.com/config/containers/start-containers-automatically/. Basicamente há quarto possíveis configurações para essa condição:

no -> Opção padrão. Nada acontece.

on-failure:n -> Após ocorrerem n falhas

always -> Sempre que ocorrer uma falha.

unless-stopped -> Igual ao always porém não reiniciando quando houver um stop manual no Docker.

Diferença entre Ports e Expose

A exposição de portas para consumo externo é algo natural e desejado quando se publica containers docker. Entretanto há uma questão que, por vezes, gera alguma confusão nas pessoas: qual é a diferença entre PORTS e EXPOSE.

PORTS é a exposição de uma porta do container para uma porta da maquina host. Ele tem um propósito de acesso externo.

EXPOSE é a exposição de uma porta para as maquinas linkadas ao conteiner em questão. Vale notar que esse parâmetro é irrelevante tendo carater apenas ilustrativo nas versões mais recentes do docker.

Uso do Dockerize

Não é incomum que na dependência entre contêineres seja necessário ter certeza se um deles efetivamente subiu ou não. O depends on não serve exatamente para isso: ele serve apenas para indicar a ordem de start dos containeres. A ferramenta dokerize segura o processo e não dá o ok para o Docker subir o próximo se os critérios configurados não estiverem validos.

Para usar o dockerize (https://github.com/jwilder/dockerize) é necessário criar um dockerfile que carregue essa dependência. O uso prático pode ser feito pelo dockerfile mas é mais fácil pelo docker-compose ao chamar essa imagem. Veja o exemplo a seguir:

# Dockerfile que baixa o dokerize
RUN apt-get update && apt-get install -y wget

ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
# Esse é o entrypoint dentro do docker-compose.yml
# substitua o 'docker-entrypoint.sh' pelo entrypoint que quiser. 
#   Nesse caso uma maquina espera que o db esteja ativo na porta 3306, esperando por até 60s
entrypoint: dokerize -wait tcp://db:3306 -timeout 60s docker-entrypoint.sh

Limites de recursos

Uma questão importante é a definição dos limites de consumo dos conteineres. Quando não há configuração que limite ele pode consumir 100% dos recursos do computador host. Para fazer a configuração é importante atentar-se às possibilidades: limits é o valor máximo, reservation é o valor mínimo caso seja necessário (se a maquina estiver consumindo menos do que isso, o recurso não fica preso); além disso é possível definir quantidade de CPU, RAM, Swap, memória para o Kernel, e outras particularidades. Para mais detalhes consulte https://docs.docker.com/config/containers/resource_constraints/. Veja a seguir um docker-compose.yml de exemplo.

 services:

  meuservico:
    image: nginx
    deploy:
        resources:
            limits:
              cpus: 0.50
              memory: 512M
            reservations:
              cpus: 0.25
              memory: 128M

Para ver em tempo real como está a utilização dos recursos do docker utilize o comando:

 docker stats 

Utilizando Secrets

Há uma estrutura específica no Docker para lidar com tokens, senhas e outras informações sigilosas: essas são as secrets. São repositórios específicos que organizam e encriptam esses dados. Veja abaixo um exemplo do uso de secrets

services:
  backend:
    build:
      context: backend
      target: builder
    secrets:
      - db-password
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mariadb:10-focal
    command: '--default-authentication-plugin=mysql_native_password'
    restart: always
    healthcheck:
      test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 --password="$$(cat /run/secrets/db-password)" --silent']
      interval: 3s
      retries: 5
      start_period: 30s
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=example
      - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 3306

  proxy:
    image: nginx
    volumes:
      - type: bind
        source: ./proxy/nginx.conf
        target: /etc/nginx/conf.d/default.conf
        read_only: true
    ports:
      - 80:80
    depends_on: 
      - backend

volumes:
  db-data:

secrets:
  db-password:
    file: db/password.txt

Esse é o arquivo ./db/password.txt

db-q5n2g

Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário