Переносим первое legacy IIS-приложение в Windows Container: 5 ГБ образ и Jenkins-пайплайн для обновлений
Windows Containers на WS2016 позволяют гонять классические IIS-приложения без переписывания. Разбираемся с размером образов и выстраиваем pipeline обновлений через Jenkins.
Windows Containers в Windows Server 2016 позволяют запускать классические IIS-приложения в контейнерах без переписывания под Linux
В октябре мы уже щупали Windows Containers на Hyper-V 2016 - тогда это был тестовый стенд, IIS в Server Core и разбирательства с NAT-сетью. Теперь дошло до дела: конкретный клиент, конкретное legacy IIS-приложение, задача - вкатить в контейнер и не сломать.
Что за приложение
ASP.NET 4.5, несколько WebForms-форм, пара WCF-сервисов. Написано лет восемь назад, трогать код не хочет никто - работает, деньги приносит. Типичная история для Windows-стека. Линукс тут вообще не рассматривается - переписывать никто не будет. Windows Containers появились в правильный момент: слой совместимости с Windows API полный, приложение должно работать без изменений.
Забегая вперёд: оно и работает. Но путь к этому оказался с сюрпризами.
Размер - первый удар
microsoft/windowsservercore как базовый образ весит около 4 ГБ после pull. Добавляешь IIS и ASP.NET - ещё 1-1.5 ГБ сверху. Итог: образ приложения легко перешагивает отметку 5 ГБ.
Для тех кто привык к Alpine или ubuntu:latest это когнитивный разрыв. Но здесь другая физика: Windows не умеет в минималистичный runtime без потери совместимости. Nano Server умеет, но там нет полного .NET Framework - только .NET Core и CoreCLR, а наше приложение на полном фреймворке и трогать его нельзя.
Dockerfile получился примерно такой:
FROM microsoft/windowsservercore
RUN powershell -Command \
Add-WindowsFeature Web-Server, Web-Asp-Net45, Web-Net-Ext45, Web-ISAPI-Ext, Web-ISAPI-Filter
COPY ./publish /inetpub/wwwroot/app
RUN powershell -Command \
New-WebApplication -Name 'app' -Site 'Default Web Site' -PhysicalPath 'C:\inetpub\wwwroot\app' -ApplicationPool 'DefaultAppPool'
EXPOSE 80
CMD ["powershell", "Start-Service W3SVC; while ($true) { Start-Sleep 30 }"]
Сборка на первом прогоне - около 15 минут: скачать базовый образ, установить роли, скопировать артефакты. Дальше каждая пересборка при изменении кода - только последние слои, то есть несколько минут. Но первый docker pull на новой машине - жди.
Как приложение ведёт себя внутри
Запустили, открыли браузер - работает. Все WebForms рендерятся, WCF-сервисы отвечают, сессии живут. Слой совместимости с Windows API действительно работает - приложение не знает что оно в контейнере. Единственное что пришлось поправить: строки подключения к БД шли из web.config с именем SQL-сервера по NetBIOS-имени. Внутри контейнера DNS-резолюция по умолчанию идёт через NAT, и корпоративный NetBIOS там не работает. Переключили на FQDN - проблема ушла.
Ещё один момент с Event Log: приложение писало ошибки в Windows Event Log, а внутри контейнера он ведёт себя иначе - записи есть, но читать их через eventvwr снаружи неудобно. Пришлось добавить запись в файловый лог параллельно, чтобы видеть ошибки через обычные инструменты.
Пайплайн обновлений через Jenkins
Вот тут пришлось поработать головой. Каждый раз пересобирать 5 ГБ образ и пушить его в реестр - это долго и расточительно по трафику. Нужен рабочий pipeline.
Схема которая у нас получилась:
MSBuild -> Publish -> docker build -> docker push -> docker pull на хосте -> docker stop/start
В Jenkins это Pipeline с несколькими стейджами. Ключевые решения:
- Сборка артефактов происходит на отдельном Windows-агенте с MSBuild и Visual Studio Build Tools - не внутри Docker. Это быстрее и проще чем тащить MSBuild в образ.
- docker build запускается на том же Windows-агенте; Dockerfile просто копирует уже собранные артефакты из
./publish- слой с кодом пересобирается за секунды. - Реестр образов - пока корпоративный DTR (Docker Trusted Registry) на отдельной машине. Первый push с полным образом занял 20 минут, дальнейшие инкрементальные пуши - только изменённые слои, то есть 1-2 минуты.
- Обновление на продакшн-хосте - Jenkins по SSH дёргает PowerShell-скрипт:
docker pull,docker stop,docker run. Даунтайм - несколько секунд.
node('windows-builder') {
stage('Build') {
bat 'msbuild MyApp.sln /p:Configuration=Release /p:DeployOnBuild=true /p:PublishUrl=publish\\'
}
stage('Docker Build') {
bat 'docker build -t dtr.corp.local/myapp:latest .'
}
stage('Docker Push') {
bat 'docker push dtr.corp.local/myapp:latest'
}
stage('Deploy') {
bat 'powershell -File scripts\\deploy.ps1'
}
}
deploy.ps1 на хосте - тридцать строк: pull нового образа, остановить старый контейнер, запустить новый с теми же параметрами сети. Никаких оркестраторов, просто скрипт.
Что получилось
Приложение работает в контейнере без единого изменения в коде. Pipeline от коммита до продакшна занимает порядка 5-7 минут - из них 1-2 минуты MSBuild, 1-2 минуты docker build с копированием артефактов, 1-2 минуты push инкрементальных слоёв. Для этого класса приложений - нормально.
Для клиентов на managed-сопровождении с Windows-стеком это открывает понятный путь: legacy IIS без переписывания, изоляция приложений на одном хосте, управляемые обновления через Jenkins вместо RDP и ручных манипуляций.
Открытые вопросы которые пока висят: версионирование образов (сейчас latest, это плохо и надо менять), откат при проблемах (скрипт умеет запустить предыдущий тег, но надо автоматизировать), и мониторинг - Windows-контейнеры пока плохо видны в Zabbix без дополнительного агента внутри. Разбираемся.