Docker

Jobs em contentores agendados, sem ter de adotar Kubernetes para isso.

Se a equipa usa Docker mas não Kubernetes, um job agendado em contentor não tem onde ficar. O Dagu é um único binário que comanda o daemon Docker existente e acrescenta aos contentores dependências, retentativas, logs e interface web.

Sem Kubernetes e sem plataforma de contentores
Os passos partilham um contentor, ou cada um traz a sua imagem
Exec em contentores que a stack compose já executa
O Podman funciona pela mesma API compatível com Docker
01

O vazio entre docker run e o CronJob do Kubernetes

Agendar um contentor é o ponto em que as boas opções se esgotam. O CronJob do Kubernetes é a resposta madura, mas só com um cluster já existente. Abaixo disso, a resposta habitual é o cron a chamar docker run, que agenda e nada mais.

  • cron mais docker run não dá retentativas, dependências entre jobs, histórico de execuções nem forma de ver porque falhou esta noite
  • Adotar Kubernetes para agendar um relatório noturno é muita plataforma para pouco job
  • Um orquestrador genérico que trata contentores como um tipo de passo, e não como destino de implantação, encaixa exatamente neste intervalo
02

Um contentor para todo o workflow

Declarar um contentor ao nível do workflow faz todos os passos correrem dentro do mesmo contentor de vida longa. Partilham o sistema de ficheiros, por isso o que um passo instala continua lá para o seguinte.

  • Instalar dependências uma vez e reutilizá-las entre passos, sem reconstruir imagens nem reinstalar por tarefa
  • Montar caminhos do host com volumes e definir variáveis de ambiente uma só vez para todo o workflow
  • Retentativas, dependências e tratamento de falhas continuam campos normais do workflow, independentemente de correrem num contentor

Neste modo os passos correm através de docker exec, por isso o ENTRYPOINT e o CMD da imagem não são invocados para os comandos do passo. Coloque o comando no passo.

Um job noturno inteiramente numa imagem
# nightly-report.yaml
schedule: "0 3 * * *"
max_active_runs: 1

container:
  image: python:3.12
  volumes:
    - ./data:/data
  env:
    - TZ=Asia/Tokyo

steps:
  - id: install
    run: pip install -r /data/requirements.txt

  - id: build_report
    run: python /data/build_report.py
    depends: install
    retry_policy:
      limit: 2
      interval_sec: 120

  - id: publish
    run: python /data/publish.py
    depends: build_report

mail_on:
  failure: true
03

Manutenção dentro dos contentores que já estão a correr

O modo exec aponta um workflow para um contentor já em execução, por exemplo iniciado pelo Docker Compose. O trabalho agendado acontece dentro do contentor real da aplicação e não numa cópia nova.

  • Migrações de base de dados, limpeza de cache e manutenção de filas correm contra o serviço realmente em execução
  • Nomear o contentor como uma string é toda a configuração
  • O workflow mantém dependências e notificação de falha, que um ficheiro compose não consegue exprimir
Manutenção agendada sobre um serviço compose em execução
# app-maintenance.yaml
schedule: "0 4 * * *"
max_active_runs: 1

# docker compose で起動済みのコンテナに exec する
container: myapp-web

steps:
  - id: migrate
    run: php artisan migrate --force

  - id: prune_sessions
    run: php artisan session:prune
    depends: migrate

  - id: clear_cache
    run: php artisan cache:clear
    depends: prune_sessions

mail_on:
  failure: true
04

Uma imagem diferente por passo

Quando um pipeline atravessa ferramentas, cada passo pode trazer a sua imagem. O workflow continua a ser um ficheiro e um agendamento enquanto os passos permanecem independentes.

  • Extrair com uma imagem de cliente de base de dados, transformar com uma imagem de linguagem e carregar com outro cliente, sem uma imagem que contenha as três
  • Um contentor ao nível do passo sobrepõe-se ao do workflow, por isso um workflow quase uniforme ainda admite exceções
  • A política de pull da imagem é configurável por passo, para imagens fixadas e para as reconstruídas com frequência
Um workflow, três imagens
# etl-pipeline.yaml
schedule: "0 2 * * *"
max_active_runs: 1

steps:
  - id: extract
    container:
      image: mysql:8
      volumes:
        - ./work:/work
    run: mysqldump --host db.internal -u svc app > /work/dump.sql

  - id: transform
    container:
      image: python:3.12
      volumes:
        - ./work:/work
    run: python /work/transform.py
    depends: extract

  - id: load
    container:
      image: postgres:16
      volumes:
        - ./work:/work
    run: psql -h dw.internal -f /work/out.sql
    depends: transform

handler_on:
  failure:
    run: ./scripts/notify-failure.sh

mail_on:
  failure: true
05

O que isto exige do host

Os passos em contentor falam com uma API compatível com Docker. É o único requisito e também a restrição que vale a pena conhecer antes de planear uma implantação.

  • Funciona tanto um socket Docker local como um daemon remoto através de DOCKER_HOST
  • O Podman é suportado pela sua API compatível com Docker definindo DAGU_CONTAINER_RUNTIME=podman
  • Como o Dagu é um único binário, o próprio agendador não precisa de cluster, base de dados de metadados nem broker

As instâncias geridas do Dagu Cloud correm com isolamento gVisor e não expõem um socket de daemon de contentores, pelo que aí não há passos em contentor. Use Dagu auto-hospedado ou encaminhe o workflow para um worker auto-hospedado.

06

Onde o Kubernetes continua a ser a resposta certa

Esta página defende uma ferramenta mais pequena numa situação específica, não evitar Kubernetes de forma geral.

  • Se já opera um cluster, o CronJob é um lugar razoável para contentores agendados e não exige nada de novo
  • Se os jobs precisam de escalonamento ao nível do pod, autoscaling ou bin-packing entre nós, isso é trabalho de um cluster e não de um orquestrador
  • Para workflows que submetem trabalho a um cluster mantendo a orquestração fora dele, o Dagu tem um passo de Kubernetes

FAQ

Practical questions before adopting

Preciso de Kubernetes para correr jobs em contentores agendados?

Não. O Dagu fala diretamente com um daemon compatível com Docker, por isso basta um host com Docker instalado. O Kubernetes compensa quando precisa de escalonamento e escalabilidade ao nível do cluster, não apenas para correr um contentor às 3 da manhã.

Em que difere do operador Docker do Airflow?

Sobretudo no custo de operação. O Airflow precisa de um agendador, uma base de dados de metadados e um framework de DAG em Python antes de correr um contentor. O Dagu é um binário com estado em ficheiros, e os contentores são um campo do workflow ou do passo em vez de um operador Python.

Os passos podem partilhar ficheiros entre si?

Podem. Com um contentor ao nível do workflow, os passos correm no mesmo contentor e partilham diretamente o seu sistema de ficheiros. Com imagens por passo, monte um caminho do host partilhado em cada passo.

O Podman funciona?

Sim, através da API compatível com Docker do Podman. Defina DAGU_CONTAINER_RUNTIME=podman numa instalação auto-hospedada e os passos em contentor comportam-se da mesma forma.

Posso correr um passo dentro de um contentor iniciado pelo Docker Compose?

Pode, é esse o modo exec. Indique o nome do contentor em execução e os passos do workflow executam dentro dele. Migrações agendadas e manutenção de cache contra uma stack compose viva são normalmente ligadas assim.

Next step

Start with one workflow.

Install Dagu, move one script that runs on cron today into YAML, and decide from a real run history.