Windows Container'ları: .NET Framework 4.8 ve IIS Uygulamasını Container'a Almak, Server Core, Nano Server ve Isolation Modları
Bir .NET Framework 4.8 ve IIS uygulaması Windows container'ına girer, ama yalnız Windows host'ta ve image'ın Windows sürümü host'unkine uyduğunda çalışır. Bookstore'un eski back office'i için Server Core tabanlı Dockerfile'ı, web.config'i ortam değişkenleriyle güncelleyen configuration builder'ları, ServiceMonitor ve LogMonitor'ü, process ve Hyper-V isolation'ı, 2,6 GB'lık image'ı, çok işletim sistemli tag'leri ve AKS'teki Windows node pool'u ele alıyor, .NET 10'a taşıma kararını tartışıyoruz.
Sürüm notu: Bu yazıdaki Linux tarafı adımlar Docker Engine 29.9.3, .NET SDK 10.0.404 (.NET Framework 4.8 reference assembly'leriyle derleme), hadolint 2.15.1 ve kubeconform 0.8.0 (Kubernetes 1.35 şeması) ile denenmiştir. Windows image'ları (mcr.microsoft.com/dotnet/framework/aspnet:4.8.1, Windows Server 2025 ve 2022, LogMonitor 2.2.1) registry'den okunarak incelenmiştir.
Önce Tahmin Et
Bookstore'un back office'i için Windows Server 2025 tabanlı (ltsc2025) bir image build ettin. Process isolation ile hangi makinelerde çalışır: (a) Windows Server 2022 sunucusu, (b) Windows 11 24H2 geliştirici makinesi, (c) Windows Server 2025 sunucusu? İkinci soru: bu image'ın indirme boyutu, Linux'taki Bookstore.Api image'ının kabaca kaç katı: 3, 10, 35?
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) back office'i, yani stok girişleri, fiyat yönetimi ve raporlar, senaryomuzda 2016'da .NET Framework 4.8 ile yazılmış ve hâlâ bir Windows sunucusunda IIS (Internet Information Services; Windows'un yerleşik web sunucusu) üzerinde çalışıyor. Ekip onu .NET 10'a taşımadan önce container'a almak istiyor. Windows'ta build ve run adımları Microsoft'un ve Docker'ın dokümanından. Registry'den okunabilen her şeyi (image config'leri, boyutlar, manifest'ler) ve Linux'ta yapılabilen her şeyi (derleme, lint, manifest list) gerçek çıktılarla gösteriyorum.
Windows Container'ı Farklı Kılan: Kernel, Sürüm Uyumu ve Isolation
1. yazıda container'ın host'un kernel'ini paylaşan bir process olduğunu gördük. Windows container'ı da aynı fikir, ama paylaşılan kernel Windows'unki; bu yüzden yalnız Windows host'ta çalışır. Linux engine'den .NET Framework image'ını çekmeye çalışınca registry'deki listede Linux için bir girdi bulunamıyor:
docker pull mcr.microsoft.com/dotnet/framework/aspnet:4.8.1
Error response from daemon: no matching manifest for linux/amd64 in the manifest list entries: no match for platform in manifest: not found
Windows'ta Docker Desktop iki engine taşır: WSL (Windows Subsystem for Linux; Windows içinde Linux kernel'i çalıştıran katman) üzerindeki Linux engine (2. yazı) ve Windows'un kendi container özelliğini kullanan Windows engine. Menüdeki "Switch to Windows containers" ile CLI'ın hangisine konuşacağını seçersin. Dokümana göre Windows container'ları yalnız Windows 10/11 Pro ya da Enterprise'da ve Docker Desktop "all users" moduyla kurulduysa kullanılabiliyor; varsayılan kullanıcı başına kurulumda bu seçenek yok.
Asıl fark sürüm uyumu. Microsoft'un dokümanına göre Windows'ta user mode ile kernel mode birbirine sıkı bağlı ve bu yüzden image'ın Windows sürümü, yani build numarası, host'unkine uymak zorunda. Aynı tag'in arkasında her Windows Server sürümü için ayrı bir image var:
docker buildx imagetools inspect mcr.microsoft.com/dotnet/framework/aspnet:4.8.1
Name: mcr.microsoft.com/dotnet/framework/aspnet:4.8.1
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:afccb81a22b0...
Manifests:
Name: mcr.microsoft.com/dotnet/framework/aspnet:4.8.1@sha256:e4d434f9e261...
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: windows/amd64
OSVersion: 10.0.20348.5870
Name: mcr.microsoft.com/dotnet/framework/aspnet:4.8.1@sha256:d76c82cca0cc...
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: windows/amd64
OSVersion: 10.0.26100.33822
10.0.20348 Windows Server 2022'nin, 10.0.26100 Windows Server 2025'in (ve Windows 11 24H2'nin) build numarası. Windows host bu listeden kendi sürümüne uyanı seçer. Hangi host'un hangi image'ı nasıl çalıştırabildiğini Microsoft'un uyumluluk tablosu söylüyor:
| Host | ltsc2025 image | ltsc2022 image | ltsc2019 image |
|---|---|---|---|
| Windows Server 2025 | Process ve Hyper-V | Process ve Hyper-V | Yalnız Hyper-V |
| Windows Server 2022 | Çalışmaz | Process ve Hyper-V | Yalnız Hyper-V |
| Windows 11 (2025 image'ı için 24H2 ve sonrası) | Process ve Hyper-V | Process ve Hyper-V | Yalnız Hyper-V |
Önce Tahmin Et'in cevabı: (a) hayır, Windows Server 2022 host'u 2025 image'ını hiçbir modda çalıştıramaz; (b) ve (c) evet. Uymayan bir çiftte container başlarken dokümandaki şu hata gelir: The operating system of the container does not match the operating system of the host. (0xc0370101).
Tablodaki iki mod isolation modları. Process isolation'da container host'un kernel'ini paylaşır; Linux container'ına en yakın model bu. Hyper-V isolation'da her container küçük bir utility VM içinde, kendi kernel'iyle çalışır; böylece daha eski bir image'ı da çalıştırabilir ve container ile host arasında daha güçlü bir sınır olur. Bedeli her container için ayrı bir VM. Microsoft'un dokümanına göre Windows Server'da varsayılan process, Windows 10 Pro ve Enterprise istemcilerinde Hyper-V; mod --isolation ile seçilir:

# Windows host'ta
docker run --rm --isolation=process mcr.microsoft.com/windows/nanoserver:ltsc2025 cmd /c ver
docker run --rm --isolation=hyperv mcr.microsoft.com/windows/nanoserver:ltsc2022 cmd /c ver
Bu bölümde resmî belgelerden yararlanılmıştır.
Base Image'lar: Server Core, Nano Server ve Boyut
Microsoft dört base image yayımlıyor. Nano Server en küçüğü; PowerShell, WMI (Windows'un yönetim arayüzü) ve .NET Framework içermez, .NET (Core) uygulamaları için tasarlanmış. Server Core orta boyutlu ve mevcut Windows Server uygulamalarını olduğu gibi taşımak için; .NET Framework ve IIS burada çalışır. Windows ve Windows Server image'ları tam Windows API'sini taşır (ör. ekran kartı hızlandırması gerekiyorsa). .NET Framework uygulaması için seçim belli: Server Core üzerine kurulmuş dotnet/framework/aspnet image'ı, içinde IIS ve ASP.NET ile. Boyutları registry'deki manifest'lerden, image indirmeden topladım (labs/17-windows/image-sizes.sh, katmanların sıkıştırılmış boyutu):
mcr.microsoft.com/windows/nanoserver:ltsc2025 layers=1 compressed=194 MB
mcr.microsoft.com/windows/servercore:ltsc2025 layers=2 compressed=2345 MB
mcr.microsoft.com/dotnet/framework/aspnet:4.8.1-windowsservercore-ltsc2025 layers=10 compressed=2600 MB
mcr.microsoft.com/dotnet/framework/sdk:4.8.1-windowsservercore-ltsc2025 layers=9 compressed=5009 MB
mcr.microsoft.com/dotnet/aspnet:10.0-nanoserver-ltsc2025 layers=8 compressed=250 MB
mcr.microsoft.com/dotnet/aspnet:10.0 layers=6 compressed=97 MB
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra layers=5 compressed=72 MB
İkinci sorunun cevabı: yaklaşık 35 kat. Bookstore.Api'nin kullandığı noble-chiseled-extra 72 MB indirilirken back office'in base'i 2,6 GB, build image'ı 5 GB; diskte açıldıklarında daha da büyürler. Aynı tablo .NET 10'un Windows'ta da çok daha küçük olduğunu gösteriyor: Nano Server üzerindeki aspnet:10.0-nanoserver-ltsc2025 250 MB.
Image config'inde bir şey daha dikkat çekiyor:
docker buildx imagetools inspect mcr.microsoft.com/dotnet/framework/aspnet:4.8.1-windowsservercore-ltsc2025 \
--format '{{json .Image}}' | jq -c '.config | {Entrypoint, ExposedPorts, User}'
{"Entrypoint":["C:\\ServiceMonitor.exe","w3svc"],"ExposedPorts":{"80/tcp":{}},"User":null}
Server Core image'larında kullanıcı tanımlı değil, yani container ContainerAdministrator ile başlar; Nano Server'da varsayılan ContainerUser. IIS'in worker process'i yine de kendi application pool kimliğiyle çalışır. Port 80; 15. yazıdaki 8080 geçişi .NET 8 Linux image'larına ait. Lifecycle de bir karar girdisi: Microsoft'un tablosuna göre Windows Server 2022 base image'larının ana desteği 13 Ekim 2026'da bitti (uzatılmış destek Ekim 2031'e kadar), 2025'inki Ekim 2029'a kadar sürüyor. .NET Framework 4.8.1 image'ları yalnız 2022 ve 2025 için var.
Back Office'in Dockerfile'ı
Örneği kısa tutmak için back office'i iki HTTP handler'a indirdim: /prices.ashx sabah alınan fiyat listesini CSV (virgül ya da noktalı virgülle ayrılmış düz metin tablo) olarak döner, /health.ashx veritabanına bağlanabiliyorsa "Healthy" der. ASP.NET MVC (Model-View-Controller yapısındaki web framework'ü) ya da Web API uygulaması da aynı Dockerfile'la container'a girer. Kod legacy/Bookstore.BackOffice/ altında:
// GET /prices.ashx: mağaza ekibinin her sabah aldığı fiyat listesi
public sealed class PriceListHandler : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
{
string connectionString = ConfigurationManager.ConnectionStrings["BookstoreDb"].ConnectionString;
StringBuilder csv = new StringBuilder("Isbn;Title;PriceTry\r\n");
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(
"SELECT Isbn, Title, PriceTry FROM dbo.Books ORDER BY Title", connection))
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
csv.Append(reader.GetString(0)).Append(';')
.Append(reader.GetString(1)).Append(';')
.Append(reader.GetDecimal(2).ToString(CultureInfo.InvariantCulture))
.Append("\r\n");
}
}
}
context.Response.ContentType = "text/csv; charset=utf-8";
context.Response.Write(csv.ToString());
}
}
Proje SDK stili bir csproj (TargetFramework net48, çıktı IIS'in beklediği bin\ klasörüne). .NET SDK Linux'ta .NET Framework reference assembly'lerini kendisi ekliyor; uygulamayı burada çalıştıramasam da en azından derlendiğini doğrulayabildim:
docker run --rm -v "$PWD:/work" -w /work mcr.microsoft.com/dotnet/sdk:10.0 \
dotnet build legacy/Bookstore.BackOffice/Bookstore.BackOffice.csproj -c Release
Bookstore.BackOffice -> /work/legacy/Bookstore.BackOffice/bin/Bookstore.BackOffice.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Dockerfile (Windows'ta build edilmedi):
# escape=`
# Windows image'larını klasik builder build eder: "# syntax", heredoc, RUN --mount ve ADD --checksum yok
ARG WINDOWS_VERSION=ltsc2025
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8.1-windowsservercore-${WINDOWS_VERSION} AS build
WORKDIR C:\src
COPY legacy/Bookstore.BackOffice/Bookstore.BackOffice.csproj legacy/Bookstore.BackOffice/
RUN msbuild legacy\Bookstore.BackOffice\Bookstore.BackOffice.csproj -t:Restore -v:minimal
COPY legacy/Bookstore.BackOffice/ legacy/Bookstore.BackOffice/
RUN msbuild legacy\Bookstore.BackOffice\Bookstore.BackOffice.csproj -p:Configuration=Release -v:minimal
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8.1-windowsservercore-${WINDOWS_VERSION} AS final
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
# LogMonitor, IIS log'larını ve Application event log hatalarını stdout'a (docker logs) taşır
RUN New-Item -ItemType Directory C:\LogMonitor | Out-Null; `
Invoke-WebRequest -Uri https://github.com/microsoft/windows-container-tools/releases/download/v2.2.1/LogMonitor.exe -OutFile C:\LogMonitor\LogMonitor.exe; `
if ((Get-FileHash C:\LogMonitor\LogMonitor.exe -Algorithm SHA256).Hash -ne '9f2c6dc34d363595afefddc4d4f21a5ecea332c30578c2b89e0f9ed7a4412191') { throw 'LogMonitor.exe checksum mismatch' }
COPY legacy/Bookstore.BackOffice/LogMonitor/LogMonitorConfig.json C:\LogMonitor\
WORKDIR C:\inetpub\wwwroot
COPY --from=build C:\src\legacy\Bookstore.BackOffice\bin\ bin\
COPY legacy/Bookstore.BackOffice/Web.config legacy/Bookstore.BackOffice/prices.ashx legacy/Bookstore.BackOffice/health.ashx ./
EXPOSE 80
ENTRYPOINT ["C:\\LogMonitor\\LogMonitor.exe", "C:\\ServiceMonitor.exe", "w3svc"]
Docker'ın BuildKit dokümanına göre Windows container'ları build edilirken klasik builder kullanılıyor (BuildKit'in Windows desteği deneysel); heredoc, cache mount ve ADD --checksum burada yok. Checksum'ı PowerShell'deki Get-FileHash ile kendimiz doğruluyoruz. # escape=` direktifi kaçış karakterini ters tırnak yapıyor ki C:\src gibi yollar olduğu gibi yazılabilsin. SHELL PowerShell'e geçiyor ve $ErrorActionPreference = 'Stop', 16. yazıdaki set -e ve pipefail'in karşılığı: ilk hatada adım durur. Final image'a kaynak kod değil yalnız bin\, Web.config ve iki .ashx giriyor.
Linux'taki lint araçları bu dosyayı Linux Dockerfile'ı gibi okuyor, sonuçlarına dikkatli bakmak gerek:
docker build --check --platform linux/amd64 -f legacy/Bookstore.BackOffice/Dockerfile .
docker run --rm -i hadolint/hadolint:v2.15.1 hadolint --no-color - < legacy/Bookstore.BackOffice/Dockerfile
Check complete, 4 warnings have been found!
WARNING: InvalidBaseImagePlatform
Base image mcr.microsoft.com/dotnet/framework/sdk:4.8.1-windowsservercore-ltsc2025 was pulled with platform "windows(10.0.26100.33822)/amd64", ...
WARNING: WorkdirRelativePath - https://docs.docker.com/go/dockerfile/rule/workdir-relative-path/
Relative workdir "C:\\src" can have unexpected results if the base image changes
...
-:8 SC1001 info: This \B will be a regular 'B' in this context.
-:10 SC1001 info: This \B will be a regular 'B' in this context.
Hiçbiri gerçek bir sorun değil: C:\src mutlak bir Windows yolu, image da bilerek Windows için; ShellCheck bulguları msbuild satırındaki ters bölüleri Linux shell'i gibi okumaktan geliyor.
Windows'ta build ve çalıştırma:
# PowerShell, Docker Desktop Windows container modunda
docker build -f legacy/Bookstore.BackOffice/Dockerfile -t dockerseries-bookstore-backoffice:1.4.0-ltsc2025 .
docker run -d --name dockerseries-backoffice -p 8090:80 `
-e "BookstoreDb=Server=sql.bookstore.test;Database=BookstoreDb;User Id=bookstore_app;Password=$env:BOOKSTORE_DB_PASSWORD;TrustServerCertificate=True" `
dockerseries-bookstore-backoffice:1.4.0-ltsc2025
curl.exe http://localhost:8090/prices.ashx
docker logs dockerseries-backoffice
Bu bölümde resmî belgelerden yararlanılmıştır.
Configuration ve Log'lar: web.config, ServiceMonitor ve LogMonitor
Linux'taki Bookstore.Api, connection string'i ConnectionStrings__Default ortam değişkeninden alıyordu (8. ve 12. yazı). .NET Framework'te ayarlar web.config'te durur ve image'a gömülüdür. Ortama göre değiştirmek için .NET Framework 4.7.1'le gelen configuration builder'lar var; Microsoft.Configuration.ConfigurationBuilders.Environment paketi web.config'teki değerleri aynı adlı ortam değişkenleriyle değiştiriyor:
<configBuilders>
<builders>
<!-- Strict: yalnız aşağıda zaten var olan anahtarlar, aynı adlı ortam değişkeniyle değiştirilir -->
<add name="Env" mode="Strict" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment" />
</builders>
</configBuilders>
<connectionStrings configBuilders="Env">
<!-- geliştirici varsayılanı; container'da docker run -e BookstoreDb=... ile değişir -->
<add name="BookstoreDb" connectionString="Server=localhost;Database=BookstoreDb;Integrated Security=true" providerName="System.Data.SqlClient" />
</connectionStrings>
Değişkenin IIS'in worker process'ine ulaşması ayrı bir konu, çünkü IIS bir Windows servisi ve container'ın ortamını kendiliğinden görmez. Bunu image'ın entrypoint'i olan ServiceMonitor çözüyor: README'sine göre w3svc servisini izlerken container'ın ortam değişkenlerini (bir dışlama listesindekiler hariç) DefaultAppPool'a kopyalıyor. docker run -e BookstoreDb=... bu yüzden çalışıyor.
Eski uygulamalarda sık görülen Integrated Security=true container'da kendiliğinden çalışmaz: container bir domain'e üye değil. Microsoft'un çözümü gMSA (group Managed Service Account; domain'de parolası otomatik yönetilen servis hesabı) ve docker run --security-opt "credentialspec=file://..." ile verilen bir credential spec; kurulumu Active Directory tarafında iş gerektiriyor. Parolalı bağlantıda ise 13. yazıdaki kural geçerli: ortam değişkeni docker inspect'te görünür. Aynı paket ailesindeki KeyPerFileConfigBuilder, Kubernetes'in secret'ları gibi dosya olarak bağlanan değerleri okuyabiliyor.
Log'lar da farklı. docker logs yalnız entrypoint'in stdout'unu gösterir; IIS ise isteklerini C:\inetpub\logs altındaki dosyalara, uygulama hataları event log'a gider ve docker logs boş kalır. Microsoft'un LogMonitor aracı bu kaynakları okuyup stdout'a yazıyor. Dockerfile'daki ENTRYPOINT onu ServiceMonitor'ün önüne koyuyor ve yapılandırması şu kadar:
{
"LogConfig": {
"logFormat": "json",
"sources": [
{
"type": "EventLog",
"startAtOldestRecord": true,
"eventFormatMultiLine": false,
"channels": [
{
"name": "application",
"level": "Error"
}
]
},
{
"type": "File",
"directory": "c:\\inetpub\\logs",
"filter": "*.log",
"includeSubdirectories": true
}
]
}
}
Tek Tag, Birden Çok İşletim Sistemi
4. yazıda bir tag'in arkasında birden çok mimari için image'lar olabileceğini gördük. Aynı liste farklı işletim sistemlerini de taşıyabilir. Docker'ın resmi golang image'ı bunun iyi bir örneği:
docker buildx imagetools inspect golang:1.25 \
--format '{{range .Manifest.Manifests}}{{with .Platform}}{{.OS}}/{{.Architecture}} {{.OSVersion}}{{end}}{{"\n"}}{{end}}' \
| grep -v unknown | grep .
linux/amd64
linux/arm
linux/arm64
linux/386
linux/ppc64le
linux/riscv64
linux/s390x
windows/amd64 10.0.26100.33296
windows/amd64 10.0.20348.5499
Linux sunucu Linux girdisini, Windows Server 2025 host'u 26100'lü girdiyi çeker. Tuzak, Linux ve Windows image'larını ayrı makinelerde build edip aynı tag'e ayrı ayrı push etmek: her push öncekinin üstüne yazar ve tag son push'u gösterir. Bu makinede Windows image'ı build edemediğim için aynı mekanizmayı iki Linux platformuyla, 127.0.0.1'de çalışan kendi registry container'ımızda gösteriyorum:
docker run -d --name dockerseries-registry -p 127.0.0.1:52817:5000 registry:3
R=127.0.0.1:52817/bookstore/api
platforms() {
docker buildx imagetools inspect "$1" \
--format '{{range .Manifest.Manifests}}{{with .Platform}}{{.OS}}/{{.Architecture}}{{end}}{{"\n"}}{{end}}' | grep -v unknown | grep .
}
for arch in amd64 arm64; do
docker build -q --platform linux/$arch -f src/Bookstore.Api/Dockerfile -t $R:1.3.0 .
docker push -q $R:1.3.0
done
platforms $R:1.3.0
linux/arm64
amd64 image'ı kayboldu. Doğrusu her biri için ayrı bir tag push edip sonra bunları tek bir listede birleştirmek:
for arch in amd64 arm64; do
docker build -q --platform linux/$arch -f src/Bookstore.Api/Dockerfile -t $R:1.3.0-linux-$arch .
docker push -q $R:1.3.0-linux-$arch
done
docker buildx imagetools create -t $R:1.3.0 $R:1.3.0-linux-amd64 $R:1.3.0-linux-arm64
platforms $R:1.3.0
#1 0.000 copying sha256:0b7aa69c1d1b... from 127.0.0.1:52817/bookstore/api:1.3.0-linux-arm64 to 127.0.0.1:52817/bookstore/api
...
#1 0.006 pushing sha256:37a511660155... to 127.0.0.1:52817/bookstore/api:1.3.0
linux/amd64
linux/arm64
imagetools create image'ları çalıştırmıyor, registry'deki manifest'leri okuyup yeni bir liste yazıyor. Back office için yol aynı: Windows build makinesinde 1.4.0-ltsc2025 ve 1.4.0-ltsc2022 push edilir, sonra 1.4.0 tag'i bu ikisinden oluşturulur.
Kubernetes'te Windows Node Pool
Kubernetes Windows container'larını yalnız Windows node'larında çalıştırır; cluster'ın control plane'i yine Linux'tadır. AKS'te (Azure Kubernetes Service; Azure'un yönetilen Kubernetes servisi) bu ayrı bir node pool demek. Dokümana göre Kubernetes 1.36 ve sonrasında varsayılan Windows Server 2025 ve bu node pool'lar FIPS (Federal Information Processing Standards; ABD'nin kriptografi modülleri için koyduğu standart) uyumlu bir image istiyor; Windows Server 2022 ise 1.37'den itibaren yok ve Haziran 2028'de emekliye ayrılıyor:
az aks nodepool add --resource-group bookstore-rg --cluster-name bookstore-aks \
--os-type Windows --os-sku Windows2025 --enable-fips-image --name npwin --node-count 2
Pod'un Windows node'una ve doğru Windows sürümüne gitmesini nodeSelector sağlıyor. Kubernetes her Windows node'una build numarasını node.kubernetes.io/windows-build label'ı olarak ekliyor; .spec.os.name ise yalnız bilgi amaçlı, zamanlamayı etkilemiyor:
apiVersion: apps/v1
kind: Deployment
metadata:
name: bookstore-backoffice
spec:
replicas: 2
selector:
matchLabels:
app: bookstore-backoffice
template:
metadata:
labels:
app: bookstore-backoffice
spec:
os:
name: windows
nodeSelector:
kubernetes.io/os: windows
node.kubernetes.io/windows-build: "10.0.26100"
containers:
- name: backoffice
image: registry.example.com/bookstore/backoffice:1.4.0
ports:
- containerPort: 80
env:
- name: BookstoreDb
valueFrom:
secretKeyRef:
name: bookstore-backoffice-db
key: connection-string
readinessProbe:
httpGet:
path: /health.ashx
port: 80
initialDelaySeconds: 20
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "1"
memory: 1Gi
kubeconform -strict -summary -kubernetes-version 1.35.0 labs/17-windows/backoffice-windows.yaml
Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0
Manifest yalnız şemaya göre doğrulandı, bir cluster'a uygulanmadı. Managed Kubernetes'e geçişin geri kalanı Kubernetes serisinin "Uçtan Uca: .NET + Angular Uygulamasını Managed Kubernetes'e Almak (AKS, EKS, GKE) ve Production Checklist'i" yazısında.
Bu bölümde resmî belgelerden yararlanılmıştır.
Karar: Container'a Almak mı, .NET 10'a Taşımak mı?
Container'a almanın kazancı hızlı gelir: sunucu kurulumu bir Dockerfile'a dönüşür, test ve production aynı image'ı çalıştırır, CI her commit'te build eder. Maliyetleri de net:
- Image boyutu: 2,6 GB base, 5 GB build image'ı; her yeni node'da ve her base güncellemesinde bu indirme tekrar eder.
- Lisans: Microsoft'un base image lisansına göre Windows image'ları yalnız lisanslı bir Windows host'ta kullanılabilir ve Windows istemci sürümlerinde yalnız geliştirme ve test içindir. Production, Windows Server lisansı ya da bulutta lisansı fiyata dahil Windows VM'leri demek.
- Ekosistem: chiseled image, BuildKit özellikleri, Linux araçlarının çoğu burada yok; host ile image sürümünü birlikte yönetmek gerekiyor.
.NET 10'a taşımak bu maliyetleri kaldırır (72 MB'lık Linux image'ı, 8. yazı), ama System.Web'e bağlı kodun ASP.NET Core'a yeniden yazılması demek; Web Forms'un doğrudan karşılığı yok. Pratik yol çoğu zaman ikisini sırayla yapmak: önce container'a al ve eski sunucudan kurtul, sonra özellikleri birer birer Bookstore.Api'ye taşı ve back office'i küçült. Container'sız bir ara yol da var: ASP.NET Core uygulamasını yine IIS'te çalıştırmak, Web Server'lar ve Hosting serisinin "IIS'te ASP.NET Core: Hosting Bundle, ASP.NET Core Module, In-Process ve Out-of-Process" yazısında.
Üretimde Dikkat
Windows base image'ları her ay güvenlik güncellemeleriyle yeniden yayımlanıyor ve .NET Framework image'ları dokümana göre base güncellendikten sonraki 12 saat içinde yenileniyor. Image'ı her ay yeniden build et; tekrarlanabilirlik istiyorsan tarihli tag'lerden birini (4.8.1-20261013-windowsservercore-ltsc2025) ya da digest'i kullan. Windows Server 2019 ve sonrasında host ile image'ın revision numarasının aynı olması gerekmiyor, build numarasının uyması yeterli.
Host'ları yükseltmeden önce image'ları hazırla. Windows Server 2022 node'larını 2025'e geçirirken önce iki sürümü de içeren tag'i yayımla, sonra node'ları değiştir; ters sırada pod'lar uyumsuz sürüm yüzünden başlamaz.
İlk çekme süresini hesaba kat. 2,6 GB'lık bir image yeni bir node'da dakikalar sürebilir; readiness probe'u ve deploy timeout'larını buna göre ayarla.
Sık Düşülen Tuzaklar
"The operating system of the container does not match the operating system of the host" hatasını isolation'la geçiştirmek. Hyper-V isolation çalıştırır ama her container'a bir VM ekler; kalıcı çözüm host'a uyan image'ı build etmek.
Linux ve Windows image'larını aynı tag'e ayrı ayrı push etmek. Son push kazanır; ayrı tag'ler ve imagetools create kullan.
Windows Dockerfile'ında BuildKit özellikleri kullanmak. Heredoc, RUN --mount ve ADD --checksum klasik builder'da çalışmaz.
Docker Desktop'ı kullanıcı başına kurup Windows container menüsünü aramak. Bu mod yalnız "all users" kurulumunda var.
docker logs'un boş olmasını uygulamanın log yazmadığına yormak. IIS dosyaya yazar; LogMonitor olmadan stdout'a hiçbir şey gelmez.
Küçük Bir Deneme
★☆☆ Windows 11 Pro ya da Enterprise bir makinede Docker Desktop'ı Windows container moduna al ve mcr.microsoft.com/windows/nanoserver:ltsc2025'i önce --isolation=process, sonra --isolation=hyperv ile çalıştır. Başlama sürelerini Measure-Command ile karşılaştır.
★★☆ Back office'i --build-arg WINDOWS_VERSION=ltsc2022 ile de build et, iki tag'i kendi registry'ne push et ve imagetools create ile tek bir 1.4.0 tag'i oluştur. Windows host'un hangisini çektiğini docker image inspect ile doğrula.
★★★ /prices.ashx'in aynısını Bookstore.Api'de bir minimal API endpoint'i olarak yaz ve iki image'ı boyut, başlama süresi ve bellek açısından karşılaştır. Back office'in kalan parçalarını taşımak kaç endpoint demek?
Bu yazıyla Docker serisi bitti. Container'ın bir process olduğundan başlayıp Bookstore'un üç parçasını (Bookstore.Api, bookstore-web ve SQL Server) image'a aldık, volume ve network'le birbirine bağladık, güvenli çalıştırdık, bozulduklarında okumayı öğrendik, eski kurulumları 2026'ya taşıdık, Dockerfile'ın az bilinen bayraklarını kullandık ve back office'in eski Windows hâlini de container'a aldık. Bunları tek bir dosyada tanımlayıp birlikte yönetmek Docker Compose serisinin, aynı image'ları bir cluster'da çalıştırmak Kubernetes serisinin, cluster'a paketleyip dağıtmak Helm ve GitOps ve Argo CD serilerinin konusu.