Files
ROMFASTSQL/proxmox/lxc103-dokploy/README.md
Claude Agent ffe3806d43 docs(lxc103): document overlay network race condition + heal script fix
Bug confirmat Dokploy #2033 + docker/compose #12862: Docker Compose containers
cu external overlay network pică la restart Docker (exit 128, network not found).
Documentat cauza, fix-ul generic dokploy-compose-heal.service și fix-ul DNS daemon.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 11:54:53 +00:00

319 lines
9.7 KiB
Markdown

# LXC 103 - Dokploy + Traefik (Control Plane Public)
## Informații Generale
- **CTID:** 103
- **IP:** 10.0.20.167
- **Rol:** Dokploy Control Plane + Traefik routing public
- **Host Proxmox:** pvemini (10.0.20.201)
- **Status:** Running (onboot: enabled)
---
## Arhitectura
LXC 103 este nodul central pentru deployment-ul aplicațiilor publice ROMFAST.
```
Internet → 188.26.14.103 → VM 201 IIS (SSL termination)
*.roa.romfast.ro, roa-qr, dokploy
LXC 103 :443 (Traefik)
├── dokploy.romfast.ro → Dokploy UI :3000
├── roa-qr.romfast.ro → pdf-qr-app container
├── space.roa.romfast.ro → space-booking container
├── app1.roa.romfast.ro → app1 container
└── app2.roa.romfast.ro → app2 container
```
### De ce app-urile publice stau pe LXC 103
Dokploy instalează Traefik SEPARAT pe fiecare server. Traefik LXC 103 și
Traefik LXC 100 nu comunică între ele. Wildcadul `*.roa.romfast.ro` poate fi
conectat doar la UN singur Traefik — LXC 103.
**LXC 100** se folosește pentru:
- Backend-uri consumate intern (fără DNS public)
- Job-uri cron, workers, servicii administrative
---
## Servicii Instalate
| Serviciu | Port | Descriere |
|----------|------|-----------|
| Dokploy UI | 3000 | Management deployment, CI/CD |
| Traefik | 443 | Routing HTTPS pentru toate app-urile publice |
| Traefik Dashboard | 8080 | Monitoring routelor (intern) |
---
## Domenii Gestionate
| Domeniu | Destinație |
|---------|-----------|
| `dokploy.romfast.ro` | Dokploy UI (port 3000) |
| `roa-qr.romfast.ro` | pdf-qr-app container |
| `space.roa.romfast.ro` | space-booking container |
| `*.roa.romfast.ro` | Orice app deployată prin Dokploy |
---
## Setup Inițial
### Pasul 1 — Oprire nginx existent pe LXC 100
> **Executat pe LXC 100** (10.0.20.170) via Portainer terminal sau Proxmox console:
```bash
docker stop docker-nginx-1
docker rm docker-nginx-1
# Verifică porturile sunt libere
ss -tlnp | grep -E ':80|:443'
```
### Pasul 2 — Generare SSH Key în Dokploy + Adăugare LXC 100
**În Dokploy UI** (https://dokploy.romfast.ro):
1. Settings → SSH Keys → **Create SSH Key**
2. Copiază public key-ul generat
**Pe LXC 100** (10.0.20.170):
```bash
echo "ssh-ed25519 AAAA...[cheia copiată din Dokploy]" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
```
**În Dokploy UI:**
1. Servers → **Add Server**
2. IP: `10.0.20.170`, User: `root`
3. **Test Connection****Setup Server**
4. Verificare: pe LXC 100, `docker ps` → trebuie container Traefik
### Pasul 3 — Deploy pdf-qr-app pe LXC 103
**În Dokploy UI:**
1. Services → **Create Service** → Docker Compose
2. **Server:** LXC 103 (local)
3. Docker Compose: conținutul app-ului pdf-qr (vezi `docs/pdf-qr-app.md`)
4. Domain: `roa-qr.romfast.ro`
5. **Deploy**
---
## Workflow: Adăugare App Nouă
```
1. Dokploy UI → New Service → docker-compose → domain: numeapp.roa.romfast.ro
2. DNS: adaugă numeapp.roa.romfast.ro A 188.26.14.103
(dacă e sub *.roa.romfast.ro, wildcard DNS acoperă automat)
3. SSL: Win-ACME pe VM 201 generează cert dacă nu există wildcard
4. Gata — Traefik pe LXC 103 routează automat
```
---
## Verificare
```bash
# Traefik funcționează (din LAN)
curl -I https://10.0.20.167/
# Dokploy UI accesibil
curl -I https://dokploy.romfast.ro/
# pdf-qr-app accesibil
curl -I https://roa-qr.romfast.ro/
# Test wildcard (după deploy app cu hostname)
curl -I https://app1.roa.romfast.ro/
# LXC 100 Portainer funcționează în continuare
curl -sk https://10.0.20.170:9443/api/status
```
---
## Configurare Traefik (gestionat automat de Dokploy)
Traefik pe LXC 103 este configurat și actualizat automat de Dokploy la fiecare
deploy. Nu modificați manual configurația Traefik fără să înțelegeți impactul.
```bash
# Status containere pe LXC 103
docker ps
# Logs Traefik
docker logs traefik -f
# Verificare routere Traefik
curl http://localhost:8080/api/http/routers | jq .
```
---
## Fix: Docker Swarm VIP DNS (Bug Permanent Rezolvat)
### Problema
Docker Swarm folosește implicit **VIP (Virtual IP)** pentru load balancing intern.
IPVS (mecanismul kernel care implementează VIP) **nu funcționează în LXC containers**.
Când Traefik încearcă să rezolve `http://qr-qrgenerator-vqkwsu:80`, primea VIP-ul
serviciului (ex: 10.0.1.8) în loc de IP-ul real al task-ului (ex: 10.0.1.12) →
**502 Bad Gateway**.
### Soluția
Schimbăm endpoint mode-ul serviciilor Swarm din `vip``dnsrr` (DNS Round Robin).
Cu `dnsrr`, DNS-ul rezolvă direct la IP-ul real al containerului, bypassing IPVS.
### Implementare: `dokploy-dnsrr-fix` (systemd service)
Un listener permanent care prinde orice serviciu Swarm nou creat/actualizat de
Dokploy și îl setează automat la `dnsrr`:
```bash
# Script: /usr/local/bin/dokploy-dnsrr-fix.sh
# Service: /etc/systemd/system/dokploy-dnsrr-fix.service
```
```bash
# Verificare status
systemctl status dokploy-dnsrr-fix
# Logs (confirmare că fixul s-a aplicat la ultimul deploy)
journalctl -u dokploy-dnsrr-fix -n 20
```
Output normal după un deploy:
```
Setting dnsrr for qr-qrgenerator-vqkwsu (was: vip)
OK - qr-qrgenerator-vqkwsu now uses dnsrr
```
### Timeline la fiecare redeploy
```
0s Dokploy creează/actualizează serviciul Swarm (vip mode)
→ Traefik: 502 Bad Gateway
1s Listener prinde evenimentul update
3s docker service update --endpoint-mode dnsrr
13s Service converge, DNS rezolvă corect
→ Traefik: 200 OK
```
**Downtime la redeploy: ~13 secunde** (inevitabil cu Swarm în LXC).
### Dacă fix-ul nu funcționează (manual recovery)
```bash
# 1. Identifică serviciul cu problemă
docker service ls --format '{{.Name}} {{.Mode}}'
# 2. Setează manual dnsrr
docker service update --endpoint-mode dnsrr <service-name>
# 3. Repornește listener dacă e oprit
systemctl restart dokploy-dnsrr-fix
```
---
## Fix: Docker Compose — Overlay Network Race Condition la Restart (Bug Permanent Rezolvat)
### Problema
Proiectele **Docker Compose** din Dokploy (ex: space-booking) care folosesc
`dokploy-network` ca rețea externă (`external: true`) **pică la restart Docker**.
Eroarea din `docker inspect <container>`:
```
failed to set up container networking: could not find a network matching
network mode dokploy-network: network dokploy-network not found
```
Container-ul iese cu **exit code 128** și nu mai pornește singur.
### Cauza
Race condition confirmat în:
- **Dokploy** issues #2033, #1802 — fără fix livrat
- **Docker Compose** upstream issue #12862 — nerezolvat (aprilie 2026)
La restart Docker daemon:
1. Docker Swarm inițializează rețelele overlay (`dokploy-network`) — durează câteva secunde
2. Docker Compose containers cu `restart: unless-stopped` pornesc **imediat**
3. `dokploy-network` nu e încă disponibilă → exit 128
Serviciile **Swarm** (Application type în Dokploy) nu au această problemă —
orchestratorul știe să aștepte rețelele înainte de a programa task-urile.
### De ce Docker Compose în loc de Application (Swarm)
Proiectele multi-serviciu (ex: frontend + backend) rămân Compose deoarece
serviciile comunică intern între ele (ex: nginx `proxy_pass http://backend:8000`
pe o rețea bridge shared). Conversia la Swarm Application ar necesita modificări
în codul aplicației.
### Soluția
Script generic care rulează la boot, așteaptă `dokploy-network` și repornește
**orice** container Compose eșuat din cauza lipsei rețelei:
```bash
# Script: /usr/local/bin/dokploy-compose-heal.sh
# Service: /etc/systemd/system/dokploy-compose-heal.service (enabled)
```
Logica scriptului:
- Polling `dokploy-network` disponibil (max 90s, interval 3s)
- Caută containere cu `exit code 128` + eroare "network" + label `com.docker.compose.project`
- Le repornește — **nu atinge** containerele oprite intenționat (exit 0)
```bash
# Verificare status
systemctl status dokploy-compose-heal
# Logs la ultimul boot
journalctl -u dokploy-compose-heal -n 30
# Rulare manuală (după un docker restart neașteptat)
systemctl start dokploy-compose-heal
```
### Fix aditional: DNS Docker daemon
Docker containers foloseau implicit DNS-ul Tailscale (`100.100.100.100`)
care **nu e accesibil** din interiorul containerelor — Dokploy primea
`ESERVFAIL` la validarea domeniilor.
Fix aplicat în `/etc/docker/daemon.json`:
```json
{"dns": ["8.8.4.4", "8.8.8.8"]}
```
### Pentru orice proiect Compose nou în Dokploy
1. Adaugă domeniul în **Dokploy UI → Domains → Add Domain** (nu doar în labels din docker-compose.yml)
— Dokploy generează `.yml` static în Traefik dynamic config, persistent la restart
2. Dacă containerul pică după restart Docker → `systemctl start dokploy-compose-heal` rezolvă automat
---
## Documentație Asociată
- **Arhitectură completă IIS:** `../vm201-windows/docs/vm201-dokploy-infrastructure.md`
- **Setup IIS VM 201:** `../vm201-windows/scripts/setup-new-iis-sites.ps1`
- **Web.config IIS proxy:** `../vm201-windows/iis-configs/`
- **LXC 100 (Remote Node):** `../lxc100-portainer/README.md` *(de creat)*
- **space-booking deploy guide:** `docs/space-booking-app.md`
- **pdf-qr-app deploy guide:** `docs/pdf-qr-app.md`
---
**Ultima actualizare:** 2026-04-28
**Autor:** Marius Mutu
**Proiect:** ROMFASTSQL - LXC 103 Dokploy