Gümüşhane merkezli, 14 yıllık üretim ERP’si olan 65 çalışanlı bir maden ekipmanı şirketi, “Windows Server 2012 R2 EOL geldi, sunucu çöküyor, biz bu uygulamayı taşımak zorundayız” gerçeğiyle uygulamayı .NET Framework 4.8’den .NET 8 + Linux container’a 6 ayda taşıdı. Bu yazı sahadaki notlar.
Container Nedir? Avantajları
| Avantaj |
Açıklama |
| Portability |
“Çalıştığı yerde çalışır” — local, test, prod |
| Density |
VM’den 10x daha çok container 1 host’ta |
| Hızlı startup |
Saniyeler (VM dakikalar) |
| Immutable infra |
Container = artifact, modify değil replace |
| Microservice friendly |
Bağımsız scale, deploy |
| Resource isolation |
cgroup + namespace |
Geleneksel App’i Container’a Almak: Yollar
| Yaklaşım |
Açıklama |
Bu vaka |
| Lift & Shift to Windows Container |
.NET Framework 4.x Windows container’da çalışır |
Önce denendi (büyük image, ağır) |
| Migrate to .NET 8 + Linux container |
Modernize + küçük image + ucuz |
✓ Seçildi |
| Refactor + microservice |
Tam yeniden tasarla |
— (bütçe yok) |
Discovery: Bağımlılık Analizi
.NET Framework 4.8 monolit:
- 280 .csproj
- ~~620K LOC
- 18 dış DLL referansı
- SQL Server (intel CLR functions)
- Crystal Reports (Windows-only!)
- 4 Windows Service
.NET 8 uyumluluk araç: .NET Upgrade Assistant
- Compatible: %~~78 paket
- Replacement gerek: 12 paket (3rd party API yok)
- Block: 3 paket (Crystal Reports, Windows-only DLL, COM interop)
Karar:
- Crystal Reports → web tabanlı raporlama (FastReports .NET 8)
- Windows Service → Worker Service (.NET 8 cross-platform)
- COM interop → REST API wrapper (eski Windows VM'de canlı)
Migration Yol Haritası
| Hafta |
Aşama |
| 1-2 |
Discovery + .NET Upgrade Assistant rapor |
| 3-6 |
Engelleyici dependency’leri çöz (Crystal alternatif, vs) |
| 7-12 |
Code migration (ProjectName.csproj sdk-style) |
| 13-16 |
Compile + unit test (Linux’a) |
| 17-20 |
Dockerfile + multi-stage |
| 21-22 |
Local Docker test |
| 23-24 |
AKS deploy + smoke test |
| 25-26 |
Paralel canlı + cutover |
Code Migration Örnekleri
Eski (.NET Framework 4.8)
// HttpClient yok, WebClient kullanım
using (var client = new WebClient())
{
var data = client.DownloadString(url);
}
// System.Configuration.ConfigurationManager
var conn = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
Yeni (.NET 8)
// HttpClient + IHttpClientFactory (DI)
public class MyService(IHttpClientFactory http)
{
public async Task GetData(string url)
{
var client = http.CreateClient();
return await client.GetStringAsync(url);
}
}
// IConfiguration (Microsoft.Extensions.Configuration)
public class MyService(IConfiguration config)
{
private readonly string conn = config.GetConnectionString("Default");
}
Dockerfile (Multi-Stage)
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.sln .
COPY src/*/*.csproj ./
RUN for file in *.csproj; do mkdir -p src/${file%.csproj} && mv $file src/${file%.csproj}/; done
RUN dotnet restore
COPY . .
RUN dotnet publish src/Erp.Web -c Release -o /app/publish /p:UseAppHost=false
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
RUN groupadd -r erp && useradd -r -g erp erp
USER erp
COPY --from=build --chown=erp:erp /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
HEALTHCHECK --interval=30s --timeout=5s
CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1
ENTRYPOINT ["dotnet", "Erp.Web.dll"]
Image boyutu: 2.4 GB (Windows container) → 220 MB (Linux .NET 8) — %~~91 küçülme.
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: erp-web
namespace: erp
spec:
replicas: 2
selector: { matchLabels: { app: erp-web } }
template:
metadata: { labels: { app: erp-web } }
spec:
containers:
- name: erp
image: acr.azurecr.io/erp-web:1.0
ports: [{ containerPort: 8080 }]
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "1500m", memory: "1Gi" }
readinessProbe:
httpGet: { path: /health/ready, port: 8080 }
initialDelaySeconds: 5
livenessProbe:
httpGet: { path: /health/live, port: 8080 }
initialDelaySeconds: 30
env:
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef: { name: erp-secrets, key: db-conn }
Sonuçlar (6 Ay)
| Metrik |
Önce |
Sonra |
| Sunucu sayısı |
4 Windows Server (2012 R2) |
1 AKS cluster (3 node) |
| Image boyutu |
— |
220 MB (Linux container) |
| Deploy süresi |
~~~~3 saat (manuel) |
~~5 dk (CI/CD) |
| App startup |
~~90 sn |
~~12 sn |
| RAM kullanım (avg) |
~~~~6.8 GB |
~~~~520 MB |
| Aylık compute fatura |
~~$1.800 (4 server) |
~~$640 (AKS shared) |
| Patch yönetim |
~~aylık manuel |
~~yıllık (managed K8s) |
Sahada Düşülen Üç Tuzak
- Windows Container’ı son hedef sanmak: Windows container çalışır ama image büyük + lisans pahalı. Linux .NET 8’e geçiş ek emek ama uzun vadede karlı.
- Test coverage olmadan refactor: Unit test yazmadan modernize → regression bulutu. Önce test yaz, sonra modernize.
- Container = sihirli kutu sanmak: Health probe + resource limit + secret management eksikse production’da kâbus.
CloudSpark olarak .NET Framework → .NET 8 modernization, Linux container migration, Dockerfile multi-stage optimization, AKS deployment ve GitOps CI/CD için danışmanlık veriyoruz.