ADG Оставить заявку
Блог DevOps 5 мин чтения

Переносим первое 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 без дополнительного агента внутри. Разбираемся.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.