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>
18 KiB
title, type, tags, ingested, raw_path, updated
| title | type | tags | ingested | raw_path | updated | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| IIS Host Migration Session 2026-05-19 | source |
|
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) уже установлены.WebAdministrationPS-module грузится из admin-PS. - В источниках на хосте (
grep): hardcodedC:\inetpub\wwwroot\в коде CMS нет — всё черезConfigurationManager.AppSettings["sitePath"]. НО в prod Web.config (из backupvm-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 MSSQL89.253.219.2,1433с userstostayer— это другая инфраструктура, не наш контейнер.
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:sitePathC:\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.0Integrated,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 rotationC:\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: & → & (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 rotationC:\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 middlewareredirect-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 + recipeipconfig /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.