diff --git a/runbooks/swarm-stack-template.yml b/runbooks/swarm-stack-template.yml new file mode 100644 index 0000000..9492e07 --- /dev/null +++ b/runbooks/swarm-stack-template.yml @@ -0,0 +1,120 @@ +# Template Canônico — Stack Docker Swarm (Octal) + +## Uso +```bash +# 1. Copiar template +cp runbooks/swarm-stack-template.yml runbooks/.yml + +# 2. Editar: services, imagens, réplicas, labels Caddy +vim runbooks/.yml + +# 3. Deploy +docker stack deploy -c runbooks/.yml + +# 4. Aplicar labels Caddy via Docker CLI (obrigatório — Swarm não herda do compose) +docker service update \ + --label-add 'caddy=' \ + --label-add 'caddy.reverse_proxy={{upstreams }}' \ + _ +``` + +--- + +## Template Completo (v3.9) + +```yaml +version: '3.9' + +networks: + public: + external: true # rede overlay swarm existente — NÃO criá-la aqui + +services: + + : + image: : + networks: + - public + deploy: + replicas: 1 + endpoint_mode: dnsrr # DNS round-robin — necessário para reverse proxy + update_config: # zero-downtime deploy + parallelism: 1 + delay: 10s + rollback_config: # rollback automático se falhar + parallelism: 1 + delay: 10s + restart_policy: + condition: on-failure + max_attempts: 3 +``` + +--- + +## Regras Obrigatórias + +### 1. Rede `public` +- É a **única rede overlay attachable** do cluster +- Criada uma única vez por cluster: `docker network create --driver overlay --attachable public` +- Nunca declarar `external: false` em services — quebra Swarm +- Nunca criar nova rede por stack — causa fragmentação + +### 2. Nomes de serviço +- **SEM prefixo** no compose: Swarm injeta `_` automaticamente +- Ex: `games-demo` no compose → `project_games-demo` no Swarm ✓ +- Ex: `project_games-demo` no compose → `project_project_games-demo` ✗ (duplicado) + +### 3. Labels Caddy +- **NÃO funcionam pelo compose** — `deploy.labels` não vira `Config.Labels` +- Aplicar **obrigatoriamente** via CLI após deploy: + `docker service update --label-add 'caddy=' _` +- Formato: `caddy=` + `caddy.reverse_proxy={{upstreams }}` + +### 4. Labels Traefik (fallback opcional) +- Funcionam pelo compose se colocadas em `deploy.labels`: + ```yaml + deploy: + labels: + - traefik.enable=true + - traefik.http.routers..rule=Host(``) + - traefik.http.services..loadbalancer.server.port= + ``` + +### 5. Registro no Portainer +```bash +# Login admin → JWT +JWT=$(curl -sk -X POST https://dock.octal.tec.br/api/auth \ + -H 'Content-Type: application/json' \ + --data-raw '{"Username":"admin","Password":"***"}' | python3 -c "import json,sys;print(json.loads(sys.stdin.read())['jwt'])") + +# Criar stack (type=2 = SwarmStack) +curl -sk -X POST 'https://dock.octal.tec.br/api/stacks?method=string&type=2&endpointId=1' \ + -H "Authorization: Bearer $JWT" \ + -H 'Content-Type: application/json' \ + -d '{"Name":"","SwarmID":"plz2xbh64yzhgy88jb9stm0pc","StackFileContent":"'"$(cat .yml)"'"}' | python3 -m json.tool +``` + +--- + +## Stacks Existentes (referência) + +| Stack | ID | Serviços | Domínio | +|-------|-----|----------|---------| +| `git` | 9 | gitea | `git.octal.tec.br` | +| `bot` | 2 | beebot, redis | `ai.octal.tec.br` | +| `pro` | 7 | leantime, db | `manager.octal.tec.br` | +| `design` | 6 | penpot (7 svc) | — | +| `proxy` | — | caddy, test-octal | `test.octal.tec.br` | +| `project` | 12 | games-demo, projects-landing | `games.octal.tec.br` | + +--- + +## Troubleshooting + +| Erro | Causa | Fix | +|------|-------|-----| +| `network public not manually attachable` | Rede `public` não existe ou `Attachable: False` | Criar com `docker network create --driver overlay --attachable public` | +| Serviços não aparecem no Portainer | Criado por `docker stack deploy` (CLI), não pela API | Registrar via API ou aceitar que está compatível Swarm nativo | +| Labels Caddy não aplicam | `deploy.labels` não vira `Config.Labels` no Swarm | Usar `docker service update --label-add` | +| Duplicação de nome (`project_project_X`) | Nomes com prefixo no compose | Remover prefixo, deixar Swarm injetar | +| 401 na API Portainer | Token `ptr_` não é JWT | Login `/api/auth` admin → JWT |