docs(template): swarm-stack-template.yml — Attachable=false explicado, CLI Swarm recomendado, project em modo CLI

This commit is contained in:
Pulse Agent
2026-05-20 19:05:42 -03:00
parent 3185d26e81
commit 1a13c4d2f2
+32 -43
View File
@@ -8,7 +8,7 @@ cp runbooks/swarm-stack-template.yml runbooks/<NOME_STACK>.yml
# 2. Editar: services, imagens, réplicas, labels Caddy # 2. Editar: services, imagens, réplicas, labels Caddy
vim runbooks/<NOME_STACK>.yml vim runbooks/<NOME_STACK>.yml
# 3. Deploy # 3. Deploy via Docker CLI (Swarm nativo — recomendado)
docker stack deploy -c runbooks/<NOME_STACK>.yml <NOME_STACK> docker stack deploy -c runbooks/<NOME_STACK>.yml <NOME_STACK>
# 4. Aplicar labels Caddy via Docker CLI (obrigatório — Swarm não herda do compose) # 4. Aplicar labels Caddy via Docker CLI (obrigatório — Swarm não herda do compose)
@@ -18,6 +18,21 @@ docker service update \
<NOME_STACK>_<SERVICE> <NOME_STACK>_<SERVICE>
``` ```
## ⚠️ Rede `public` — Attachable=false
| Método | Funciona? | Motivo |
|--------|-----------|--------|
| `docker stack deploy` (CLI) | ✅ SIM | Daemon Docker local ignora a restrição |
| Portainer API (`POST /api/stacks`) | ❌ NÃO | Docker Remote API respeita `Attachable=false` |
**Decisão**: Usar `docker stack deploy` CLI para todas as stacks.
Stacks criadas por CLI aparecem no `docker stack ls` e no Swarm, mas
NÃO aparecem como gerenciáveis no Portainer UI.
Para ter controle total pelo Portainer, a rede `public` precisaria ser recriada com
`--attachable`, o que causaria uma breve interrupção nos containers existentes.
Atualmente não vale a pena — 19 containers em produção usam `public`.
--- ---
## Template Completo (v3.9) ## Template Completo (v3.9)
@@ -27,10 +42,9 @@ version: '3.9'
networks: networks:
public: public:
external: true # rede overlay swarm existente — NÃO criá-la aqui external: true # overlay swarm — Attachable=false (modelo atual)
services: services:
<service-name>: <service-name>:
image: <IMAGEM>:<TAG> image: <IMAGEM>:<TAG>
networks: networks:
@@ -54,10 +68,10 @@ services:
## Regras Obrigatórias ## Regras Obrigatórias
### 1. Rede `public` ### 1. Rede `public`
- É a **única rede overlay attachable** do cluster - É a **única rede overlay** do cluster Octal
- Criada uma única vez por cluster: `docker network create --driver overlay --attachable public` - Criada uma única vez: `docker network create --driver overlay --attachable public`
- `Attachable: false` no modelo atual — não alterar sem planejamento
- Nunca declarar `external: false` em services — quebra Swarm - Nunca declarar `external: false` em services — quebra Swarm
- Nunca criar nova rede por stack — causa fragmentação
### 2. Nomes de serviço ### 2. Nomes de serviço
- **SEM prefixo** no compose: Swarm injeta `<stack>_` automaticamente - **SEM prefixo** no compose: Swarm injeta `<stack>_` automaticamente
@@ -68,53 +82,28 @@ services:
- **NÃO funcionam pelo compose** — `deploy.labels` não vira `Config.Labels` - **NÃO funcionam pelo compose** — `deploy.labels` não vira `Config.Labels`
- Aplicar **obrigatoriamente** via CLI após deploy: - Aplicar **obrigatoriamente** via CLI após deploy:
`docker service update --label-add 'caddy=<DOMINIO>' <STACK>_<SERVICE>` `docker service update --label-add 'caddy=<DOMINIO>' <STACK>_<SERVICE>`
- Formato: `caddy=<dominio>` + `caddy.reverse_proxy={{upstreams <PORTA>}}`
### 4. Labels Traefik (fallback opcional) ### 4. Labels Traefik (fallback opcional)
- Funcionam pelo compose se colocadas em `deploy.labels`: - Funcionam pelo compose via `deploy.labels`:
```yaml ```yaml
deploy: deploy:
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.<NOME>.rule=Host(`<DOMINIO>`) - traefik.http.routers.<NOME>.rule=Host(`<DOMINIO>`)
- traefik.http.services.<NOME>.loadbalancer.server.port=<PORTA>
``` ```
### 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":"<NOME>","SwarmID":"plz2xbh64yzhgy88jb9stm0pc","StackFileContent":"'"$(cat <ARQUIVO>.yml)"'"}' | python3 -m json.tool
```
--- ---
## Stacks Existentes (referência) ## Stacks Existentes (referência)
| Stack | ID | Serviços | Domínio | | Stack | Criada por | Controlável Portainer? |
|-------|-----|----------|---------| |-------|------------|----------------------|
| `git` | 9 | gitea | `git.octal.tec.br` | | `git` | Portainer API | ✅ SIM |
| `bot` | 2 | beebot, redis | `ai.octal.tec.br` | | `bot` | Portainer API | ✅ SIM |
| `pro` | 7 | leantime, db | `manager.octal.tec.br` | | `pro` | Portainer API | ✅ SIM |
| `design` | 6 | penpot (7 svc) | — | | `design` | Portainer API | ✅ SIM |
| `proxy` | — | caddy, test-octal | `test.octal.tec.br` | | `proxy` | Portainer API | ✅ SIM |
| `project` | 12 | games-demo, projects-landing | `games.octal.tec.br` | | `project` | `docker stack deploy` CLI | ⚠️ NÃO (Attachable=false) |
| `dock` | Portainer API | ✅ SIM |
--- | `code` | Portainer API | ✅ SIM |
| `database` | Portainer API | ✅ SIM |
## 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 |