Files
admin/.wiki/concepts/docker-host-loopback-detect.md

129 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 применён первый раз и подтверждён.