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

18 KiB
Raw Blame History

title, type, tags, ingested, raw_path, updated
title type tags ingested raw_path updated
IIS Host Migration Session 2026-05-19 source
migration
iis
windows
recovery
traefik
2026-05-19 ../../.tasks/iis-on-host-migration.md 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.2Data 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:18080host.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.WebC:\sites\snolla (rename)
  • C:\stayer\MoreThenCms.WebC:\sites\stostayer (move)
  • C:\stayer\stostayer.oldC:\sites\stostayer.old (move)
  • C:\stayer\Snolla.IdentityManagerC:\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.Webrename невозможен 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.2localhost. После — :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 — приватны.
                        Mёртвые: 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:18080host.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.