Files
admin/iis-host-migration-2026-05-19.md
vitya 5d8d017766 docs(.wiki): iis-migration rollback + post-mortem + xml-escape concept
Phase 9 — rollback миграции на host-IIS из-за Docker port-loop
(host.docker.internal:80 от traefik container резолвится обратно
в сам traefik через NAT, https.yml http-catchall middleware
отдавал 301 -> TOO_MANY_REDIRECTS). Prod снова через VM.

- iis-migration-2026-05-19-postmortem: 10 ошибок миграции +
  recipe для следующей попытки (backend port НЕ :80, smoke с
  MaximumRedirection 0, тест из НЕ-LAN, parallel VM x N часов,
  atomic revert plan)
- webconfig-password-xml-escape: новая gotcha — & в conn-string
  пароле требует & в Web.config (XML reserved char)
- iis-host-migration-2026-05-19: Phase 9 rollback chronology +
  что осталось на хосте inert
- snolla-recovery-vm: статус -> active prod (обратно)
- windows-recovery-host: host IIS sites -> inert artifacts
- recovery-architecture-snapshot: chain снова через VM,
  traefik backends восстановлены из .bak-phase3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:08:07 +03:00

179 lines
18 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: IIS Host Migration Session 2026-05-19
type: source
tags: [migration, iis, windows, recovery, traefik]
ingested: 2026-05-19
raw_path: ../../.tasks/iis-on-host-migration.md
updated: 2026-05-19
---
# IIS Host Migration Session 2026-05-19
Перенос основного CMS-сайта (`MoreThenCms.Web`, обслуживает 11 клиентских доменов через Host header) с IIS внутри [[snolla-recovery-vm]] на нативный IIS [[windows-recovery-host]]. Цель — убрать VBox как слой нестабильности.
## Хронология
**Старт сессии** (после [[nas-recovery-session-2026-05-18]]) — VM один раз отвалилась сетью, оживили через `VBoxManage guestcontrol` + `ipconfig /release /renew` (recipe в [[vbox-windows-stability-tuning]] подтверждён ещё раз).
**Phase 1 — discovery (read-only):**
- В VM через SSH (NAT-forward 127.0.0.1:8022, не bridged 192.168.1.15 — это устарело): `appcmd list site/apppool/app/vdir /xml`.
- На хосте: `Get-WindowsOptionalFeature -Online IIS-*` через elevated PS — все нужные фичи (`IIS-WebServer`, `IIS-ASPNET45`, `IIS-NetFxExtensibility45`, `IIS-ISAPIFilter`, `IIS-ManagementConsole`, `IIS-IIS6ManagementCompatibility`, `IIS-Metabase`) уже установлены. `WebAdministration` PS-module грузится из admin-PS.
- В источниках на хосте (`grep`): hardcoded `C:\inetpub\wwwroot\` в коде CMS нет — всё через `ConfigurationManager.AppSettings["sitePath"]`. **НО** в prod Web.config (из backup `vm-sites\`) ключ `sitePath` явно проставлен абсолютным путём — патч в Phase 2 необходим.
**Phase 1 находки (важные расхождения с wiki):**
- `Snolla.IdentityManager` в VM **на :8089, не :80** (вики говорила :80). Sub-apps stostayer.old `/calc`, `/price`, `/price/tireService` существуют с отдельными app pools.
- `C:\stayer\Snolla.IdentityManager\Web.config` указывает на **`SRV-1135520-1\SQLEXPRESS` + Integrated Security** — мёртвый внешний сервер. Сайт точно не работает, deploy-артефакт без живого консьюмера.
- `C:\stayer\MoreThenCms.Web\Web.config` (stostayer) использует **внешний production MSSQL `89.253.219.2,1433`** с user `stostayer` — это **другая инфраструктура**, не наш контейнер.
**Phase 2 — миграция (admin PS):**
- `Default Web Site` остановлен (`autoStart=false`).
- `robocopy` из `C:\nas-recovery\vm-sites\``C:\sites\MoreThenCms.Web` (8.7 GB / 44.7k файлов / 1m13s) и → `C:\stayer\` (2.2 GB / 13.9k файлов / 21s).
- Web.config patch только для `C:\sites\MoreThenCms.Web\Web.config`: `sitePath` `C:\inetpub\wwwroot\MoreThenCms.Web\``C:\sites\MoreThenCms.Web\` + `Data Source=10.0.2.2``Data Source=localhost`. UTF-8 with BOM через `[System.IO.File]::WriteAllText` (паттерн из [[cms-config-rewrite-pattern]]).
- 3 AppPool (`.NET v4.0` Integrated, `ApplicationPoolIdentity`): `MoreThenCms.Web`, `stostayer`, `stostayer.old`.
- 3 IIS-сайта: `MoreThenCms.Web *:80` (catch-all), `stostayer *:8090`, `stostayer.old *:8091`.
- ACL `icacls /grant 'IIS AppPool\<site>:(OI)(CI)M' /T` на каждый physical root.
- Skip: `Snolla.IdentityManager` (по решению — публично не нужен), 3 sub-apps stostayer.old (по решению).
- Smoke test через `http://localhost/` с Host header: все 11 хостов отвечают (большинство 302 redirect — CMS работает). `rimiz.ru` — 404 (CMS-side, не инфра). stostayer/oldstostayer на :8090/:8091 — timeout, **в текущей сессии не разбирались**.
**Phase 3 — traefik switch (file-provider auto-reload):**
- Backup `*.yml.bak-phase3-2026-05-19` для 11 файлов в `C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\`.
- Patch: `host.docker.internal:18080``host.docker.internal:80` (11 yml: snolla, rimiz, labtools, labtoolspro, pilorama98, tandemmebel, emspb, kupimknigi, maljarka, sestech, isc-artmaterials). UTF-8 без BOM.
- **Не трогали** `stostayer.yml` (`:18180`) и `oldstostayer.yml` (`:18181`) — stayer-сайты остаются на VM.
- Public smoke через `https://localhost:4443/` с Host header: **10/11 → HTTP 200**. Только `rimiz.ru` → 404 (как и в локальном тесте).
## Результат
Production-трафик 10 главных доменов теперь идёт **полностью без VM**: client → OpenWRT → traefik → host-IIS native → MSSQL container на хосте. VM продолжает обслуживать только stostayer/oldstostayer через старые NAT port forwards (`:18180/:18181`).
## Открытые вопросы / нюансы
- **`rimiz.ru` отдаёт 404** на host-IIS (и на VM до миграции тоже отдавал?). CMS-routing не знает этот host. Проверить mapping в БД / CMS-сайт-таблице.
- **stostayer + stostayer.old timeout** на host-IIS (`http://localhost:8090/`, `:8091`). Возможно cold-start ASP.NET, возможно реальный bug. Сейчас не разбирали — traefik для них всё ещё на VM.
- **Snolla.IdentityManager не мигрирован** — если что-то внутри CMS дёргает его через `localhost:8089`, может тихо ломаться (пока симптомов не видно).
- **VM можно глушить** только после: либо доделать stostayer на host, либо явное решение не мигрировать stayer на этот хост.
- **Phase 5 не сделан**: URL Rewrite + ARR (фикс `:4443` в редиректах CMS), Application Initialization (warm-start), log rotation `C:\inetpub\logs\`.
## Технические артефакты
- `.tasks/iis-on-host-migration.md` — детальный план фаз, обновлён со статусом Phase 1-3 = done.
- Backup конфигов traefik: `*.yml.bak-phase3-2026-05-19`.
- Скрипты Phase 2 и Phase 3 — в истории чата сессии (не сохранены отдельным файлом, идемпотентны на повторный запуск).
## Phase 4 — реорг `C:\sites\` + перенос stayer'ов (вечер 2026-05-19)
Пользователь возмутился разбросом (`C:\sites\` только для snolla, `C:\stayer\` для stayer). Реорганизация ради единой иерархии:
- `C:\sites\MoreThenCms.Web``C:\sites\snolla` (rename)
- `C:\stayer\MoreThenCms.Web``C:\sites\stostayer` (move)
- `C:\stayer\stostayer.old``C:\sites\stostayer.old` (move)
- `C:\stayer\Snolla.IdentityManager``C:\sites\snolla-identity-manager` (move + kebab-rename)
- Удалены: `C:\stayer\Mis.StoStayer.{Price,TireService,Calculator}.{Api,Web}` (sub-apps не нужны).
- `C:\stayer\` полностью удалён.
IIS sync:
- Site `MoreThenCms.Web` → переименован в `snolla` (`Set-ItemProperty IIS:\Sites\... -Name name`)
- AppPool `MoreThenCms.Web`**rename невозможен in-place**, пересоздан как `snolla` с тем же набором properties (.NET v4.0 Integrated, `ApplicationPoolIdentity`), site rebound, старый pool удалён.
- `physicalPath` всех 3 sites обновлён под `C:\sites\<name>`.
- Web.config sitePath patches:
- `C:\sites\snolla\Web.config`: `C:\sites\MoreThenCms.Web\``C:\sites\snolla\`
- `C:\sites\stostayer\Web.config`: `C:\stayer\MoreThenCms.Web\``C:\sites\stostayer\`
- ACL re-grant + cleanup stale ACE для `IIS AppPool\MoreThenCms.Web`.
## Phase 5 — stostayer.old DB-conn патч (запутанная история conn-string'ов в stayer\)
Локальный smoke `:8091` падал в timeout. Причина: `C:\sites\stostayer.old\web.config` имел `Data Source=10.0.2.2` (адрес VBox NAT gateway, на хосте не резолвится) с user=`snolla` — фактически идентичная нашему MSSQL контейнеру конфигурация, только адрес неправильный. Patch: `10.0.2.2``localhost`. После — `:8091` → 200.
Это **наша ошибка** Phase 2 — пропустили patch этого файла, так как фокус был только на `MoreThenCms.Web\Web.config`.
## Phase 6 — stostayer DB-conn миграция на новый сервер (`www.stostayer.ru`)
Локальный smoke `:8090` тоже timeout — но по другой причине. `C:\sites\stostayer\Web.config` указывал на `89.253.219.2,1433` (внешний production MSSQL), оказался **полностью недоступен**: TCP timeout, ping fail, traceroute затухает на 8-м hop у `139.45.230.171`. На VM (через тот же путь) тоже не работал — пользователь подтвердил.
Пользователь предоставил новые creds: `www.stostayer.ru,1433` / `stayer_site` / `^I9D)LB)DK)8J#xBDG$t}W&_ioaT!M!LF`. Тест из не-elevated PS: TCP reachable, SQL login OK (SQL Server 2022). Patched Web.config.
**Pitfall:** после patch IIS отдавал HTTP 500. Причина — символ `&` в пароле, который **в XML является зарезервированным**. ASP.NET Web.config XML парсер падал на парсинг conn-string. Fix: `&``&amp;` (XML entity escape). См. [[webconfig-password-xml-escape]].
## Phase 7 — отключение traefik routes для stayer'ов
Пользователь решил: stayer'ы наружу светить не нужно вообще (даже после миграции на хост). `stostayer.yml` и `oldstostayer.yml` переименованы в `*.yml.disabled` — traefik file-provider не подхватывает, route gone. Site'ы на хосте `:8090/:8091` остаются для возможного внутреннего использования.
Backup yml для этих изменений: `*.yml.bak-stayer-switch-2026-05-19` (содержит вариант с переключением на `:8090/:8091` — на случай если решим включить обратно).
## Phase 8 — VM savestate
После того как 100% prod-трафика идёт через host-IIS, VM `snolla-recovery` заморожена через `VBoxManage controlvm "snolla-recovery" savestate`. VMState = `saved`. Конфигурация и диски сохранены — resume за 5-10 сек если что-то понадобится. Не удалена.
## Финальное состояние (2026-05-19 конец сессии)
```
C:\sites\
├── snolla\ (was MoreThenCms.Web in C:\inetpub\wwwroot, was in vm-sites\wwwroot\MoreThenCms.Web)
├── stostayer\ (was C:\stayer\MoreThenCms.Web)
├── stostayer.old\ (was C:\stayer\stostayer.old)
└── snolla-identity-manager\ (was C:\stayer\Snolla.IdentityManager — deploy artifact, без живого consumer'а)
IIS sites/pools (имя=имя=пуло):
snolla *:80 → C:\sites\snolla (pool snolla, .NET v4.0 Integrated, AppPoolIdentity)
stostayer *:8090 → C:\sites\stostayer (pool stostayer)
stostayer.old *:8091 → C:\sites\stostayer.old (pool stostayer.old)
Connection strings:
snolla: Data Source=localhost → MSSQL container на хосте (DB MoreThenCms)
stostayer: Data Source=www.stostayer.ru,1433 (user stayer_site) → внешний SQL Server 2022
stostayer.old: Data Source=localhost → MSSQL container на хосте (DB stostayer, user snolla)
Traefik active routes: 10 главных доменов (snolla.com и др.) + rimiz.ru → host:80.
Stayer routes (stostayer.snolla.com, old.stostayer.ru) DISABLED — приватны.
ртвые: disk.yml, dsm.yml (192.168.1.10).
VM snolla-recovery: VMState=saved (frozen, ~92 GB на диске + ~3 GB savestate RAM dump).
```
## Открытые вопросы (что унесли в следующую сессию)
- **`rimiz.ru` отдаёт 404** на host-IIS. CMS-routing не знает этот host — нужно копнуть БД (таблица CMS-сайтов, mapping host header → site).
- **Snolla.IdentityManager** на хосте: папка перенесена, но IIS-сайт не создавался (по решению). Если CMS-код где-то дёргает `localhost:8089` или подобное — будет тихий fail. Симптомов пока нет.
- **Phase 5 (из старого плана)**: URL Rewrite + ARR для фикса `:4443` в редиректах, Application Initialization (warm-start), log rotation `C:\inetpub\logs\`. Не блокеры.
- **VM-cleanup**: если через ~неделю стабильной работы host'а проблем не будет — `VBoxManage unregistervm "snolla-recovery" --delete` освободит ~92 GB. NAT port forwards в OpenWRT (host:18080/18180/18181/18189) можно удалить.
## Связано
[[recovery-architecture-snapshot]] обновлён под полностью-host chain. [[snolla-recovery-vm]] — VMState=saved, все sites unused. [[windows-recovery-host]] — финальный layout `C:\sites\`. [[webconfig-password-xml-escape]] — новый concept про XML entity escape в conn-string'ах. [[cms-config-rewrite-pattern]] — UTF-8 BOM подтверждён ещё много раз.
---
## Phase 9 — RЕVERT (вечер 2026-05-19, после провала)
**Что случилось:** через ~10 минут после моего commit'a "done" пользователь открыл `https://www.pilorama98.ru/``502 Bad Gateway`. Дальше остальные домены показали `TOO_MANY_REDIRECTS`.
**Root cause** (расписан полностью в [[iis-migration-2026-05-19-postmortem]]):
- Phase 3 я patched traefik backend `host.docker.internal:18080``host.docker.internal:80`.
- Внутри traefik-контейнера `host.docker.internal:80` через Docker Desktop NAT резолвится **обратно в сам traefik** на его HTTP entrypoint :80 (potential Docker publish-port loopback gotcha).
- traefik `https.yml` имеет http-catchall middleware `redirect-to-https` → traefik отвечает 301 на свой же запрос → loop.
**Реактивная цепочка ошибок** (~1 час):
- Restart-WebAppPool, nuke workers, docker restart traefik → only sделали хуже (503).
- Patched `host.docker.internal:80 → 192.168.1.143:80` → тот же loop (LAN-IP через NAT тоже возвращается в traefik).
- Patched на `:8088` + добавил IIS binding → нужен `Stop+Start Website` чтобы binding applied, изначально забыл → IIS не listen → 503.
- Думал что 301 от CMS (Pitfall 5 X-Forwarded-Proto), копал CMS код — реальный источник был самим traefik (headers без `Server: Microsoft-IIS` подсказывали).
**Revert (выполнен):**
- `VBoxManage startvm "snolla-recovery"` — VM поднята из savestate (~10s + 90s warmup + recipe `ipconfig /release /renew` для застрявшего network adapter из [[vbox-windows-stability-tuning]]).
- 11 главных yml восстановлены из `*.bak-phase3-2026-05-19` (`host.docker.internal:18080` → VM).
- Stayer yml: `stostayer.yml.disabled / oldstostayer.yml.disabled` удалены, восстановлены из `*.bak-stayer-switch-2026-05-19` (`:18180/18181` → VM).
- `docker restart traefik` (file-provider не подхватил reload автоматом).
- Public smoke через VM-chain → 7/11 хостов отвечают (2 c 200, 5 с CMS-side редиректами на canonical — нормально для CMS-логики); проверено пользователем в браузере → работает.
## Финальное состояние (после revert)
Production снова на VM-chain (как было в начале сессии 2026-05-19). На хосте осталось:
- `C:\sites\{snolla, stostayer, stostayer.old, snolla-identity-manager}` — ~11 GB, **inert** (не на prod-пути)
- IIS sites/pools `snolla, stostayer, stostayer.old` — не получают трафика (traefik backend назад на VM)
- traefik backups: `*.bak-phase3-2026-05-19`, `*.bak-stayer-switch-2026-05-19`, `*.bak-hostip-2026-05-19`
- VM `snolla-recovery` — running, IIS активен, обслуживает 11 главных доменов + 2 stayer публично через port forwards.
**Артефакты для следующей попытки** (НЕ удалять):
- Web.config'и в `C:\sites\` пропатчены правильно (sitePath, conn-strings, stostayer на новый DB) — можно переиспользовать.
- IIS sites/pools уже настроены — переключение требует только traefik backend patch + tested correctly.
- Recipe для правильной миграции — в [[iis-migration-2026-05-19-postmortem]].