docs(.wiki): ingest iis-host-migration attempt 2 (success) + docker-host-loopback-detect
Phase 10 успех — recipe из post-mortem применён полностью: backend port :8089, bak-pre-attempt2 серия, IIS binding + Stop+Start, loop-detect через docker exec wget, 2 canary phone-tests от мобильного интернета, batch 9 cms; stayer routes user-ом подтверждены internal → .yml.disabled. Touched: - sources/iis-host-migration-2026-05-19.md — append Phase 10 - concepts/iis-migration-2026-05-19-postmortem.md — footer attempt-2-succeeded с маппингом recipe A-G - concepts/recovery-architecture-snapshot.md — major rewrite, chain через host-IIS:8089, VM = parallel fallback - entities/snolla-recovery-vm.md — status parallel-fallback, 24-48h soak - entities/windows-recovery-host.md — IIS sites active prod - NEW concepts/docker-host-loopback-detect.md — recipe loop-detect + WinHTTP-proxy gotcha - index.md, log.md
This commit is contained in:
128
docker-host-loopback-detect.md
Normal file
128
docker-host-loopback-detect.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: Docker host-loopback detection — как доказать что host.docker.internal:N не петля в traefik
|
||||
type: concept
|
||||
tags: [docker, traefik, debugging, recipe, gotcha, networking]
|
||||
sources: [../sources/iis-host-migration-2026-05-19.md]
|
||||
updated: 2026-05-19
|
||||
---
|
||||
|
||||
# Docker host-loopback detection
|
||||
|
||||
Конкретная техника **доказать что `host.docker.internal:<port>` внутри docker-контейнера действительно резолвится в host-уровневый процесс**, а не возвращается обратно в тот же docker-контейнер (Docker Desktop NAT loopback). Эта проверка обязательна **прежде traefik patch'a** на host-backend, иначе можно повторить incident attempt 1 из [[iis-migration-2026-05-19-postmortem]] (traefik backend `host.docker.internal:80` → Docker NAT loop в собственный HTTP entrypoint → 301 от http-catchall middleware → TOO_MANY_REDIRECTS).
|
||||
|
||||
## Когда применять
|
||||
|
||||
- Любой раз когда **traefik** (или другой reverse-proxy в docker container) должен идти **на host** (нативный IIS, nginx, Postgres, etc.).
|
||||
- Особенно когда backend port **совпадает** с одним из traefik publish-ports или потенциально может маршрутизироваться обратно в traefik через Docker Desktop port-mapping.
|
||||
|
||||
## Recipe
|
||||
|
||||
### Шаг 1 — выбрать backend port вне traefik publish-set
|
||||
|
||||
Запомни traefik publish-ports — это **запретный список** для backend. Например при наших traefik publish'ах `:8000, :4443, :8080` — backend `:80` тоже опасен (Docker Desktop NAT loopback creates implicit mapping в нижележащих случаях).
|
||||
|
||||
Безопасные porter — те что **никем другим в docker не публикуются** и **не совпадают** с traefik publish-set. Прежде выбора — `Test-NetConnection -Port N` на host, чтобы убедиться нет другого listener'а.
|
||||
|
||||
### Шаг 2 — IIS binding и Stop/Start
|
||||
|
||||
```powershell
|
||||
# 🖥️ ELEVATED PowerShell on Windows host
|
||||
Import-Module WebAdministration
|
||||
$site = 'snolla'; $port = 8089
|
||||
New-WebBinding -Name $site -Protocol http -Port $port -IPAddress '*'
|
||||
Stop-Website -Name $site
|
||||
Start-Website -Name $site # ← КРИТИЧНО, без restart binding не активируется
|
||||
Get-NetTCPConnection -LocalPort $port -State Listen # verify
|
||||
```
|
||||
|
||||
### Шаг 3 — probe изнутри traefik container (НЕ через локальный curl)
|
||||
|
||||
**КЛЮЧЕВОЕ:** probe должен идти **изнутри docker-контейнера**, чтобы воспроизвести точно тот же путь что traefik будет использовать.
|
||||
|
||||
```powershell
|
||||
# 💻 Windows host (non-elevated)
|
||||
docker exec traefik wget --spider -S --header="Host: <domain>" "http://host.docker.internal:8089/" 2>&1 | Select-Object -First 10
|
||||
```
|
||||
|
||||
`--spider` = HEAD-only, не follow redirects. `-S` = show response headers. Без этих флагов wget может **уйти follow public DNS → router → traefik → backend** → искусственная петля через интернет (не имеет отношения к Docker NAT).
|
||||
|
||||
### Шаг 4 — интерпретировать
|
||||
|
||||
**Хороший знак (traefik найдёт host IIS):**
|
||||
|
||||
```
|
||||
Connecting to host.docker.internal:8089 (192.168.65.254:8089)
|
||||
HTTP/1.1 200 OK
|
||||
Server: Microsoft-IIS/10.0 ← НАШ IIS отвечает
|
||||
X-Powered-By: ASP.NET
|
||||
Content-Length: 28413
|
||||
```
|
||||
|
||||
Признаки:
|
||||
- IP-резолв `host.docker.internal` = **192.168.65.254** (Docker Desktop host gateway, может быть другой адрес в зависимости от версии).
|
||||
- `Server: Microsoft-IIS/10.0` ⇒ это IIS, не traefik.
|
||||
- `X-Powered-By: ASP.NET` ⇒ ASP.NET runtime обработал.
|
||||
- Content-Length ≠ 17 (traefik «Moved Permanently» body длиной 17 = подозрительный знак, см. ниже).
|
||||
|
||||
**Плохой знак (Docker NAT loopback):**
|
||||
|
||||
```
|
||||
HTTP/1.1 301 Moved Permanently
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Length: 17 ← traefik signature (= "Moved Permanently\n")
|
||||
Location: https://<host>/ ← redirect-to-https middleware
|
||||
```
|
||||
|
||||
Признаки:
|
||||
- **Нет** `Server: Microsoft-IIS/10.0` (или `Server: traefik`).
|
||||
- `Content-Length: 17` — общая длина text/plain "Moved Permanently".
|
||||
- 301 на `https://<входной-host>/` — это traefik http-catchall middleware (см. `https.yml` `redirect-to-https`).
|
||||
|
||||
Если видишь второй паттерн — **STOP**. Backend port пересекается с traefik. Не делай traefik patch, выбери другой port.
|
||||
|
||||
### Шаг 5 — public-path verification
|
||||
|
||||
После step 4 — проверь по реальному пути client → traefik → backend:
|
||||
|
||||
```powershell
|
||||
# 💻 Windows host
|
||||
curl.exe -k -sS -I --max-redirs 0 -m 10 -H "Host: <domain>" "https://localhost:4443/"
|
||||
```
|
||||
|
||||
Должно быть first response = `Server: Microsoft-IIS/10.0` (либо CMS canonical 301 от IIS — main thing — Server header указывает на IIS, не на traefik).
|
||||
|
||||
## Pitfall — WinHTTP proxy на хосте strip's headers
|
||||
|
||||
Локальный `curl.exe http://localhost:<host-port>/` на Windows может ходить **через WinHTTP system proxy** который **strips `Server` / `X-Powered-By` headers** на response (плюс часто добавляет `Proxy-Connection: keep-alive`). Это даёт ложное впечатление «не IIS отвечает», хотя на самом деле IIS работает корректно.
|
||||
|
||||
**Признаки** что probe идёт через WinHTTP proxy:
|
||||
- Нет `Server:` в response, но контент корректный (например HTML страница сайта).
|
||||
- `Proxy-Connection: keep-alive` в response.
|
||||
- Headers выглядят «обрезанными».
|
||||
|
||||
**Решение:** для loop-detect / IIS-confirmation тестов используй **`docker exec traefik wget`** (изнутри docker, минует Windows-уровневые proxy) или **`Invoke-WebRequest` через PS** с `-Proxy ''`. НЕ используй `curl.exe` как единственный источник truth для headers.
|
||||
|
||||
## Подтверждённый рабочий пример (2026-05-19 attempt 2)
|
||||
|
||||
```
|
||||
docker exec traefik wget --spider -S --header="Host: emspb.ru" "http://host.docker.internal:8089/"
|
||||
→ Connecting to host.docker.internal:8089 (192.168.65.254:8089)
|
||||
→ HTTP/1.1 301 Moved Permanently
|
||||
Server: Microsoft-IIS/10.0
|
||||
X-Powered-By: ASP.NET
|
||||
Location: http://www.emspb.ru/
|
||||
|
||||
docker exec traefik wget --spider -S --header="Host: localhost:8089" "http://host.docker.internal:8089/"
|
||||
→ HTTP/1.1 200 OK
|
||||
Server: Microsoft-IIS/10.0
|
||||
X-Powered-By: ASP.NET
|
||||
Content-Length: 28413
|
||||
```
|
||||
|
||||
Заключение: `host.docker.internal:8089` → `192.168.65.254:8089` (Docker Desktop host gateway) → host IIS. **NO Docker NAT loop.** 301 — это CMS canonical (CMS-side www-redirect), не traefik http-catchall (тот бы дал `Content-Length: 17` text/plain без `Server: Microsoft-IIS/10.0`).
|
||||
|
||||
## Связано
|
||||
|
||||
- [[iis-migration-2026-05-19-postmortem]] — почему attempt 1 сломался (этот же loop, но с `:80` который пересекался с docker-NAT mapping).
|
||||
- [[traefik-on-windows-docker-desktop]] — общие traefik pitfalls на Docker Desktop.
|
||||
- [[iis-host-migration-2026-05-19]] Phase 10 — где этот recipe применён первый раз и подтверждён.
|
||||
Reference in New Issue
Block a user