15-hour cross-hypervisor recovery from WD40EFAX SMR RAID5 cascade failure. 5 entities + 7 concepts + 1 source documenting: - Root cause: WD40EFAX SMR cascade in 3-disk RAID 5 - Hyper Backup .hbk structure + SFTP-jail / ACL workarounds - OVA from Synology VMM (KVM) → VirtualBox: SCSI→SATA, Hyper-V driver disable, paravirt=kvm, GA install, NAT switch - MSSQL restore via named volume + chown, sqlcmd -x, mssql-conf - Web.config UTF-16 vs UTF-8 BOM IIS 500.19 trap - Traefik on Windows DD: configFile, named volume for acme.json, file-provider as docker.sock workaround - Snapshot of current recovery architecture + SPOF list - Placeholder for future resilient-architecture work Plus .tasks/nas-recovery.md and STATUS.md updates closing the task. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
186 lines
7.8 KiB
Markdown
186 lines
7.8 KiB
Markdown
---
|
||
title: Traefik на Windows Docker Desktop — нюансы
|
||
type: concept
|
||
tags: [traefik, docker, windows, letsencrypt, reverse-proxy]
|
||
sources: [../sources/nas-recovery-session-2026-05-18.md]
|
||
updated: 2026-05-19
|
||
---
|
||
|
||
# Traefik на Windows Docker Desktop
|
||
|
||
Запуск traefik 2.6.6 на Windows Docker Desktop (WSL2 backend) с импортированным production-конфигом и сертификатами — пара ловушек.
|
||
|
||
## Pitfall 1: traefik.yml не загружается автоматом
|
||
|
||
Symptom: traefik стартует, но routers из `data/custom/*.yml` ругаются на "non-existent resolver: letsEncrypt", сертификаты не отдаются.
|
||
|
||
Reason: traefik 2.x при отсутствии `--configFile=` ищет в дефолтных путях (`/etc/traefik/`, `./traefik.yml`), но Linux-контейнер с CWD=`/` и bind-mount `./data/traefik.yml:/traefik.yml` — почему-то не подхватывает.
|
||
|
||
**Fix:** явно указать в compose `command:`:
|
||
|
||
```yaml
|
||
command:
|
||
- "--configFile=/traefik.yml"
|
||
- "--log.level=DEBUG"
|
||
```
|
||
|
||
Подтверждение в логах: `Configuration loaded from file: /traefik.yml`.
|
||
|
||
## Pitfall 2: acme.json permissions через bind-mount
|
||
|
||
Symptom: traefik ругается `permissions 777 for /letsencrypt/acme.json are too open, please use 600` и **отключает** letsEncrypt resolver (даже если у него есть валидные certs).
|
||
|
||
Reason: bind-mount Windows-файла в Linux-контейнере **всегда** показывает permissions `0777`. Изменить через `chmod` нельзя — bind-mount не транслирует POSIX-perms на Windows-side.
|
||
|
||
**Fix:** named volume вместо bind для `/letsencrypt`:
|
||
|
||
```yaml
|
||
volumes:
|
||
- "traefik_letsencrypt:/letsencrypt" # вместо ./letsencrypt:/letsencrypt
|
||
- "./data/traefik.yml:/traefik.yml:ro"
|
||
- "./data/custom/:/custom/:ro"
|
||
|
||
# и снизу:
|
||
volumes:
|
||
traefik_letsencrypt:
|
||
```
|
||
|
||
Population volume единоразово:
|
||
|
||
```powershell
|
||
docker volume create traefik_traefik_letsencrypt
|
||
docker run --rm \
|
||
-v traefik_traefik_letsencrypt:/dest \
|
||
-v <local-path>/traefik/letsencrypt:/src:ro \
|
||
alpine sh -c "cp /src/acme.json /dest/acme.json && cp /src/acme.old.json /dest/acme.old.json && chmod 600 /dest/*"
|
||
```
|
||
|
||
## Pitfall 3: docker.sock provider не работает
|
||
|
||
Symptom:
|
||
```
|
||
Failed to retrieve information of the docker client and server host: Error response from daemon:
|
||
providerName=docker
|
||
```
|
||
|
||
С `-v /var/run/docker.sock:/var/run/docker.sock:ro` сокет монтируется (видно `srw-rw---- 1 root root` внутри контейнера), но соединение с daemon обрывается.
|
||
|
||
Reason: Docker Desktop на Windows транслирует docker.sock через WSL2 layer. Иногда permission-моэль клиента (libdocker) не принимает то, что предоставляет Desktop's proxy.
|
||
|
||
**Workaround:** **полностью отключить docker-provider, использовать только file-provider** для всех routes.
|
||
|
||
Маршруты, которые в производстве были как traefik labels на контейнерах (minio, elasticsearch, imgproxy с `traefik.http.routers...labels`), переписываются в `data/custom/<name>.yml` руками:
|
||
|
||
```yaml
|
||
# data/custom/minio.yml
|
||
http:
|
||
routers:
|
||
minio:
|
||
entryPoints: [https]
|
||
rule: Host(`minio.kzntsv.site`)
|
||
tls:
|
||
certResolver: letsEncrypt
|
||
service: minio
|
||
services:
|
||
minio:
|
||
loadBalancer:
|
||
servers:
|
||
- url: http://minio:9000 # docker DNS name (работает потому что все на одной network=proxy)
|
||
```
|
||
|
||
Подобно для elasticsearch (с basicAuth middleware), imgproxy (→ imgproxy-nginx:80), etc.
|
||
|
||
## Pitfall 4: file-provider не подхватывает изменения через bind-mount
|
||
|
||
`traefik.yml` имеет `providers.file.watch: true`, но на Windows bind-mount inotify не работает через WSL2-слой. Изменения в `data/custom/*.yml` не подхватываются автоматически.
|
||
|
||
**Workaround:** `docker compose restart traefik` после изменений в custom/. Несколько секунд downtime, ничего страшного.
|
||
|
||
## Production порты vs нашa конфигурация
|
||
|
||
Production traefik compose биндил на host:
|
||
|
||
```yaml
|
||
ports:
|
||
- "8000:80" # router forwards public 80 → host 8000 → container 80
|
||
- "4443:443"
|
||
- "8080:8080"
|
||
```
|
||
|
||
То есть **роутер делает port-translation** 80→8000, 443→4443. Не стандартные порты, но работает.
|
||
|
||
На Windows-хосте оставили те же порты (8000/4443) — не конфликтуют с локальным IIS на 80 (который не используется, но не выключен). И не требуют admin для bind.
|
||
|
||
## host.docker.internal
|
||
|
||
Из traefik-контейнера достучаться до VM (которая в VBox NAT, не в docker network) — через **`host.docker.internal`**:
|
||
|
||
```yaml
|
||
servers:
|
||
- url: http://host.docker.internal:18080/ # 18080 = VBox NAT-forwarded port → VM:80
|
||
```
|
||
|
||
Docker Desktop резолвит `host.docker.internal` в IP хоста (обычно 172.x.x.1 из docker bridge perspective). Дальше Windows-host обрабатывает 18080 → VBox NAT → VM:80 → IIS → CMS.
|
||
|
||
## Pitfall 5: X-Forwarded headers НЕ настроены
|
||
|
||
Traefik отдаёт backend (IIS) запрос с заголовком `Host: snolla.com`, но **не** добавляет `X-Forwarded-Proto: https` / `X-Forwarded-Host`. Backend (IIS) считает, что получил HTTP-запрос, делает 301 redirect на канонический URL `http://www.<domain>:4443/`.
|
||
|
||
Симптом для клиента: видит URL с портом 4443 в адресной строке после редиректа.
|
||
|
||
**Fix (TODO, не делали в emergency):**
|
||
|
||
```yaml
|
||
# data/custom/forwarded-headers-middleware.yml
|
||
http:
|
||
middlewares:
|
||
secure-headers:
|
||
headers:
|
||
customRequestHeaders:
|
||
X-Forwarded-Proto: https
|
||
customResponseHeaders:
|
||
X-Forwarded-Proto: ""
|
||
```
|
||
|
||
И применить middleware к каждому router (или к глобальному entrypoint-level через `--entrypoints.https.forwardedheaders.insecure=true` в command).
|
||
|
||
Plus на стороне IIS: установить ARR module + URL Rewrite, доверять `X-Forwarded-*` от 127.0.0.1 (host).
|
||
|
||
## DNS-01 challenge через REGRU
|
||
|
||
`traefik.yml` имеет HTTP-01 challenge:
|
||
|
||
```yaml
|
||
certificatesResolvers:
|
||
letsEncrypt:
|
||
acme:
|
||
email: vitya.kuznetsov@gmail.com
|
||
storage: /letsencrypt/acme.json
|
||
httpChallenge:
|
||
entryPoint: http
|
||
```
|
||
|
||
В compose env уже:
|
||
```
|
||
REGRU_USERNAME=OpeItcLoc03
|
||
REGRU_PASSWORD=ytyYqC%u%QAJ
|
||
```
|
||
|
||
Эти креды для **DNS-01** через REGRU API. Просто закомментировать `httpChallenge` и активировать `dnsChallenge` в traefik.yml:
|
||
|
||
```yaml
|
||
certificatesResolvers:
|
||
letsEncrypt:
|
||
acme:
|
||
email: ...
|
||
storage: /letsencrypt/acme.json
|
||
dnsChallenge:
|
||
provider: regru
|
||
```
|
||
|
||
DNS-01 более надёжный (не требует public:80 reachable для validation) и работает даже если HTTP-01 challenge не пройдёт (например, домен временно на другой хостинге).
|
||
|
||
**Текущие 40 LE-сертификатов из acme.json валидны ~3 месяца** (LE default). Когда подойдут к истечению — пора переключать на DNS-01.
|
||
|
||
Связано: [[snolla-recovery-vm]], [[windows-recovery-host]], [[recovery-architecture-snapshot]].
|