![Deployment-domination]()
Previous article: Real-Time Magic: SignalR and gRPC - Bring Apps Alive with Interactive Wonders! (Part - 31 of 40)
Table of Contents
Introduction to Containerization
Docker Fundamentals
ASP.NET Core Dockerization
Multi-Stage Docker Builds
Docker Compose for Development
Kubernetes Core Concepts
Kubernetes Deployment Strategies
Production-Grade Configuration
CI/CD Pipeline Implementation
Monitoring and Logging
Security Best Practices
Real-World Case Study
1. Introduction to Containerization
The Evolution of Application Deployment
Traditional deployment methodologies often led to the infamous "it works on my machine" syndrome. Containerization revolutionizes this by packaging applications with their dependencies, ensuring consistency across environments.
Real-World Analogy: Think of containers as shipping containers in the logistics industry. Just as standardized containers can be transported via ship, train, or truck without opening, software containers run consistently regardless of the underlying infrastructure.
Why Containerization Matters for ASP.NET Core?
// Traditional deployment challenges
public class DeploymentPainPoints
{
public List<string> CommonIssues = new()
{
"Dependency version conflicts",
"Environment configuration mismatches",
"Inconsistent runtime behavior",
"Difficult scaling operations",
"Long deployment cycles"
};
}
Benefits Overview
Consistency: Identical environments from development to production
Isolation: Applications run in isolated environments
Portability: Run anywhere Docker is supported
Scalability: Easy horizontal scaling
Resource Efficiency: Better utilization than virtual machines
2. Docker Fundamentals
Docker Architecture Deep Dive
# Understanding Docker components
Docker Ecosystem:
Docker Engine:
- Docker Daemon
- Docker Client
- REST API
Docker Images: Immutable templates
Docker Containers: Runnable instances
Docker Registry: Image storage (Docker Hub, Azure Container Registry)
Docker Compose: Multi-container applications
Essential Docker Commands
# Image management
docker build -t myapp:latest .
docker images
docker rmi <image_id>
# Container operations
docker run -d -p 8080:80 --name myapp myapp:latest
docker ps
docker stop <container_id>
docker logs <container_id>
# System management
docker system prune
docker stats
Dockerfile Anatomy
# Base image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN dotnet restore "MyApp/MyApp.csproj"
COPY . .
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build
# Publish stage
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish
# Final stage
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
3. ASP.NET Core Dockerization
Basic Docker Configuration
Let's create a real-world e-commerce application and containerize it step by step.
Project Structure
ECommerceApp/
├── src/
│ ├── ECommerce.API/
│ ├── ECommerce.Services/
│ └── ECommerce.Data/
├── tests/
├── docker-compose.yml
└── Dockerfile
Complete Docker Implementation
// Program.cs - Modern minimal API approach
using ECommerce.API;
using ECommerce.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to container
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Database configuration with environment flexibility
builder.Services.AddDbContext<ECommerceContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
if (builder.Environment.IsProduction())
{
options.UseSqlServer(connectionString,
sqlOptions => sqlOptions.EnableRetryOnFailure());
}
else
{
options.UseSqlServer(connectionString);
}
});
// Health checks
builder.Services.AddHealthChecks()
.AddDbContextCheck<ECommerceContext>();
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health");
app.Run();
dockerfile
# Multi-stage Dockerfile for ASP.NET Core
# Stage 1: Base
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Stage 2: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy project files
COPY ["src/ECommerce.API/ECommerce.API.csproj", "src/ECommerce.API/"]
COPY ["src/ECommerce.Services/ECommerce.Services.csproj", "src/ECommerce.Services/"]
COPY ["src/ECommerce.Data/ECommerce.Data.csproj", "src/ECommerce.Data/"]
# Restore dependencies
RUN dotnet restore "src/ECommerce.API/ECommerce.API.csproj"
# Copy everything else
COPY . .
# Build
WORKDIR "/src/src/ECommerce.API"
RUN dotnet build "ECommerce.API.csproj" -c Release -o /app/build
# Stage 3: Publish
FROM build AS publish
RUN dotnet publish "ECommerce.API.csproj" -c Release -o /app/publish
# Stage 4: Final
FROM base AS final
WORKDIR /app
# Create a non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
COPY --from=publish /app/publish .
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/health || exit 1
ENTRYPOINT ["dotnet", "ECommerce.API.dll"]
Environment-Specific Configurations
// appsettings.Production.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=sql-server;Database=ECommerce;User Id=sa;Password=${SA_PASSWORD};TrustServerCertificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://+:80"
}
}
}
}
4. Multi-Stage Docker Builds
Advanced Optimization Techniques
# Ultra-optimized multi-stage build
# Stage 1: Base with security scanning
FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:abc123... AS base
USER root
WORKDIR /app
# Security hardening
RUN apt-get update && \
apt-get upgrade -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Stage 2: Build with caching optimization
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy only project files for better layer caching
COPY ["Directory.Build.props", "./"]
COPY ["src/ECommerce.API/ECommerce.API.csproj", "src/ECommerce.API/"]
COPY ["src/ECommerce.Services/ECommerce.Services.csproj", "src/ECommerce.Services/"]
COPY ["src/ECommerce.Data/ECommerce.Data.csproj", "src/ECommerce.Data/"]
# Restore with no cache for deterministic builds
RUN dotnet restore "src/ECommerce.API/ECommerce.API.csproj" --no-cache
# Copy source code
COPY . .
# Build with optimization
WORKDIR "/src/src/ECommerce.API"
RUN dotnet build "ECommerce.API.csproj" -c Release -o /app/build \
--no-restore \
-p:ContinuousIntegrationBuild=true
# Stage 3: Test
FROM build AS test
WORKDIR "/src/tests"
RUN dotnet test --logger "trx" --results-directory /testresults
# Stage 4: Publish with trimming
FROM build AS publish
RUN dotnet publish "ECommerce.API.csproj" -c Release -o /app/publish \
--no-build \
-p:PublishReadyToRun=true \
-p:PublishTrimmed=true \
-p:TrimMode=link
# Stage 5: Final optimized image
FROM base AS final
WORKDIR /app
# Non-root user for security
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
RUN chown -R appuser:appgroup /app
USER appuser
COPY --from=publish /app/publish .
# Enhanced health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:80/health || exit 1
ENTRYPOINT ["dotnet", "ECommerce.API.dll"]
Build Arguments and Environment Variables
# Parameterized Dockerfile
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0
ARG CONFIGURATION=Release
FROM ${RUNTIME_IMAGE} AS base
# ... base layer setup
FROM ${SDK_IMAGE} AS build
ARG CONFIGURATION
# ... build with ${CONFIGURATION}
# Build command with arguments
# docker build --build-arg CONFIGURATION=Debug -t myapp:debug .
5. Docker Compose for Development
Complete Development Environment
# docker-compose.yml - Full development stack
version: '3.8'
services:
ecommerce.api:
image: ecommerce-api:latest
build:
context: .
dockerfile: Dockerfile
target: build # Use build stage for development
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Server=sql-server;Database=ECommerce;User Id=sa;Password=YourPassword123!;TrustServerCertificate=true;
ports:
- "5000:80"
volumes:
- .:/src
- ~/.nuget/packages:/root/.nuget/packages:ro
depends_on:
- sql-server
- redis
networks:
- ecommerce-network
sql-server:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
SA_PASSWORD: "YourPassword123!"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
ports:
- "1433:1433"
volumes:
- sql-data:/var/opt/mssql
networks:
- ecommerce-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- ecommerce-network
seq:
image: datalust/seq:latest
environment:
- ACCEPT_EULA=Y
ports:
- "5341:5341"
- "8081:80"
volumes:
- seq-data:/data
networks:
- ecommerce-network
volumes:
sql-data:
redis-data:
seq-data:
networks:
ecommerce-network:
driver: bridge
Development-Specific Dockerfile
# Dockerfile.dev - Development optimized
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS development
# Install tools for development
RUN dotnet tool install -g dotnet-ef
RUN dotnet tool install -g dotnet-watch
ENV PATH="$PATH:/root/.dotnet/tools"
WORKDIR /app
# Copy project files
COPY . .
# Expose ports
EXPOSE 80
EXPOSE 443
# Development entry point
CMD ["dotnet", "watch", "run", "--urls", "http://0.0.0.0:80"]
Database Migration Strategy
// Database migrator service
public static class DatabaseMigrator
{
public static async Task MigrateDatabaseAsync(this WebApplication app)
{
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ECommerceContext>();
await context.Database.MigrateAsync();
app.Logger.LogInformation("Database migrated successfully");
}
catch (Exception ex)
{
app.Logger.LogError(ex, "An error occurred while migrating the database");
throw;
}
}
}
// In Program.cs
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
await app.MigrateDatabaseAsync();
}
6. Kubernetes Core Concepts
Kubernetes Architecture Overview
# Understanding Kubernetes components
Kubernetes Cluster:
Control Plane:
- API Server: Frontend for Kubernetes
- etcd: Key-value store for cluster data
- Scheduler: Assigns pods to nodes
- Controller Manager: Regulates cluster state
Worker Nodes:
- Kubelet: Agent running on each node
- Container Runtime: Docker, containerd
- Kube-proxy: Network proxy
Essential Kubernetes Objects
# Pod - Smallest deployable unit
apiVersion: v1
kind: Pod
metadata:
name: ecommerce-api-pod
labels:
app: ecommerce-api
tier: backend
spec:
containers:
- name: ecommerce-api
image: ecommerce-api:latest
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
# Service - Network abstraction
apiVersion: v1
kind: Service
metadata:
name: ecommerce-api-service
spec:
selector:
app: ecommerce-api
ports:
- port: 80
targetPort: 80
type: LoadBalancer
# Deployment - Declarative updates
apiVersion: apps/v1
kind: Deployment
metadata:
name: ecommerce-api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: ecommerce-api
template:
metadata:
labels:
app: ecommerce-api
spec:
containers:
- name: ecommerce-api
image: ecommerce-api:latest
ports:
- containerPort: 80
Real-World E-Commerce Kubernetes Setup
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ecommerce-production
labels:
name: ecommerce-production
environment: production
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ecommerce-config
namespace: ecommerce-production
data:
appsettings.json: |
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: ecommerce-secrets
namespace: ecommerce-production
type: Opaque
data:
connection-string: <base64-encoded-connection-string>
api-key: <base64-encoded-api-key>
7. Kubernetes Deployment Strategies
Complete Deployment Configuration
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ecommerce-api
namespace: ecommerce-production
labels:
app: ecommerce-api
version: v1.0.0
spec:
replicas: 3
minReadySeconds: 30
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
revisionHistoryLimit: 3
selector:
matchLabels:
app: ecommerce-api
template:
metadata:
labels:
app: ecommerce-api
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "80"
prometheus.io/path: "/metrics"
spec:
containers:
- name: ecommerce-api
image: ecommerce-api:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: ecommerce-secrets
key: connection-string
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 1
startupProbe:
httpGet:
path: /health/startup
port: 80
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
restartPolicy: Always
terminationGracePeriodSeconds: 60
Service and Ingress Configuration
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: ecommerce-api-service
namespace: ecommerce-production
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "false"
spec:
selector:
app: ecommerce-api
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
type: LoadBalancer
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ecommerce-ingress
namespace: ecommerce-production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.ecommerce.com
secretName: ecommerce-tls
rules:
- host: api.ecommerce.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ecommerce-api-service
port:
number: 80
Horizontal Pod Autoscaler
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ecommerce-api-hpa
namespace: ecommerce-production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ecommerce-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
8. Production-Grade Configuration
Advanced Health Checks
// Advanced health monitoring
public static class HealthCheckExtensions
{
public static IHealthChecksBuilder AddProductionHealthChecks(
this IServiceCollection services,
IConfiguration configuration)
{
return services.AddHealthChecks()
.AddDbContextCheck<ECommerceContext>(
name: "database",
tags: new[] { "ready", "live" })
.AddRedis(
redisConnectionString: configuration.GetConnectionString("Redis"),
name: "redis",
tags: new[] { "ready", "live" })
.AddUrlGroup(
new Uri("https://api.paymentgateway.com/health"),
name: "payment-gateway",
tags: new[] { "ready" })
.AddDiskStorageHealthCheck(s =>
s.AddDrive("C:\\", 1024),
name: "storage",
tags: new[] { "live" })
.AddApplicationInsightsPublisher();
}
}
// Custom health check for business logic
public class OrderProcessingHealthCheck : IHealthCheck
{
private readonly ECommerceContext _context;
public OrderProcessingHealthCheck(ECommerceContext context)
{
_context = context;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
// Check if orders can be processed
var recentOrders = await _context.Orders
.Where(o => o.CreatedAt > DateTime.UtcNow.AddHours(-1))
.CountAsync(cancellationToken);
return recentOrders >= 0
? HealthCheckResult.Healthy("Order processing is operational")
: HealthCheckResult.Degraded("Order processing is experiencing issues");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Order processing health check failed", ex);
}
}
}
Configuration Management
// Configuration builder with multiple sources
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json",
optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddUserSecrets<Program>(optional: true);
if (context.HostingEnvironment.IsProduction())
{
config.AddAzureKeyVault(
"https://mykeyvault.vault.azure.net/",
new DefaultAzureCredential());
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseSerilog((context, configuration) =>
{
configuration.ReadFrom.Configuration(context.Configuration);
});
Resilience and Circuit Breaker Patterns
// Polly resilience policies
public static class ResiliencePolicies
{
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => !msg.IsSuccessStatusCode)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
var logger = context.GetLogger();
logger?.LogWarning(
"Retry {RetryCount} after {Delay}ms for {OperationKey}",
retryCount, timespan.TotalMilliseconds, context.OperationKey);
});
}
public static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (outcome, breakDelay, context) =>
{
var logger = context.GetLogger();
logger?.LogError(
"Circuit breaker opened for {BreakDelay}ms",
breakDelay.TotalMilliseconds);
},
onReset: (context) =>
{
var logger = context.GetLogger();
logger?.LogInformation("Circuit breaker reset");
});
}
}
// Usage in service registration
services.AddHttpClient<IPaymentService, PaymentService>()
.AddPolicyHandler(ResiliencePolicies.GetRetryPolicy())
.AddPolicyHandler(ResiliencePolicies.GetCircuitBreakerPolicy());
9. CI/CD Pipeline Implementation
GitHub Actions Pipeline
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --verbosity normal --logger trx
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build . \
--file Dockerfile \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
- name: Push Docker image
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v4
with:
namespace: ecommerce-production
manifests: |
k8s/namespace.yaml
k8s/configmap.yaml
k8s/secret.yaml
k8s/deployment.yaml
k8s/service.yaml
k8s/ingress.yaml
k8s/hpa.yaml
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
kubectl-version: 'latest'
- name: Verify deployment
run: |
kubectl rollout status deployment/ecommerce-api -n ecommerce-production
kubectl get pods -n ecommerce-production
Azure DevOps Pipeline
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
variables:
dockerRegistryServiceConnection: 'AzureContainerRegistry'
imageRepository: 'ecommerceapi'
containerRegistry: 'myacr.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
stages:
- stage: Build
displayName: Build and test
jobs:
- job: Build
displayName: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DotNetCoreCLI@2
displayName: 'Restore dependencies'
inputs:
command: 'restore'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
arguments: '--no-restore --configuration Release'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
arguments: '--no-build --verbosity normal --logger trx'
- task: Docker@2
displayName: 'Build Docker image'
inputs:
command: 'build'
repository: '$(imageRepository)'
dockerfile: '$(dockerfilePath)'
tags: |
$(tag)
latest
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: 'push'
repository: '$(imageRepository)'
tags: |
$(tag)
latest
- stage: DeployToStaging
displayName: Deploy to staging
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: Deploy
environment: 'staging'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes'
inputs:
action: 'deploy'
namespace: 'ecommerce-staging'
manifests: |
$(Build.SourcesDirectory)/k8s/**/*.yaml
containers: |
$(containerRegistry)/$(imageRepository):$(tag)
- task: Kubernetes@1
displayName: 'Verify deployment'
inputs:
command: 'rollout'
arguments: 'status deployment/ecommerce-api -n ecommerce-staging'
Database Migration in CI/CD
// Database migration job in Kubernetes
apiVersion: batch/v1
kind: Job
metadata:
name: database-migration
namespace: ecommerce-production
spec:
template:
spec:
containers:
- name: migrator
image: ecommerce-api:latest
command: ["dotnet", "ECommerce.API.dll", "migrate"]
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: ecommerce-secrets
key: connection-string
restartPolicy: Never
backoffLimit: 2
10. Monitoring and Logging
Structured Logging with Serilog
// Program.cs with advanced logging
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Elasticsearch;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "ECommerce.API")
.Enrich.WithMachineName()
.Enrich.WithEnvironmentName()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.File(
"logs/ecommerce-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://elasticsearch:9200"))
{
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
IndexFormat = "ecommerce-logs-{0:yyyy.MM}",
NumberOfShards = 2,
NumberOfReplicas = 1
})
.CreateLogger();
try
{
Log.Information("Starting web application");
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
// ... rest of configuration
var app = builder.Build();
// ... app configuration
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
Application Insights Integration
// Advanced telemetry configuration
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = configuration["ApplicationInsights:ConnectionString"];
options.EnableAdaptiveSampling = false;
});
services.AddApplicationInsightsKubernetesEnricher();
// Custom telemetry initializer
services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
}
public class CustomTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry != null)
{
var context = _httpContextAccessor.HttpContext;
if (context != null)
{
requestTelemetry.Properties["User"] = context.User.Identity?.Name;
requestTelemetry.Properties["ClientIP"] = context.Connection.RemoteIpAddress?.ToString();
}
}
}
}
Kubernetes Monitoring Stack
# k8s/monitoring.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: monitoring
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'ecommerce-api'
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- ecommerce-production
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:latest
ports:
- containerPort: 9090
volumeMounts:
- name: prometheus-config
mountPath: /etc/prometheus/
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
11. Security Best Practices
Security Context and Policies
# k8s/security.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ecommerce-api-sa
namespace: ecommerce-production
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: ecommerce-production
name: ecommerce-api-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ecommerce-api-rolebinding
namespace: ecommerce-production
subjects:
- kind: ServiceAccount
name: ecommerce-api-sa
namespace: ecommerce-production
roleRef:
kind: Role
name: ecommerce-api-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ecommerce-api-network-policy
namespace: ecommerce-production
spec:
podSelector:
matchLabels:
app: ecommerce-api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
- to:
- namespaceSelector:
matchLabels:
name: ecommerce-production
ports:
- protocol: TCP
port: 1433
- protocol: TCP
port: 6379
Container Security
# Security-hardened Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
# Security updates
RUN apt-get update && \
apt-get upgrade -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appgroup && \
useradd -r -g appgroup -s /bin/false appuser
WORKDIR /app
# Set secure permissions
RUN chown -R appuser:appgroup /app && \
chmod -R 755 /app
USER appuser
# Copy application
COPY --from=publish /app/publish .
# Security headers via environment
ENV ASPNETCORE_URLS=http://+:80
ENV DOTNET_RUNNING_IN_CONTAINER=true
ENV COMPlus_EnableDiagnostics=0
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/health || exit 1
ENTRYPOINT ["dotnet", "ECommerce.API.dll"]
Secret Management
// Secure configuration management
public static class SecurityExtensions
{
public static IServiceCollection AddSecureConfiguration(
this IServiceCollection services,
IConfiguration configuration)
{
// Azure Key Vault integration
if (configuration.GetValue<bool>("UseKeyVault"))
{
var keyVaultEndpoint = configuration["KeyVault:Endpoint"];
services.AddAzureKeyVault(keyVaultEndpoint);
}
// Database encryption
services.AddDbContext<ECommerceContext>(options =>
{
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure();
sqlOptions.CommandTimeout(30);
});
});
// Secure HTTP clients
services.AddHttpClient("SecureClient")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
// Custom certificate validation
return errors == System.Net.Security.SslPolicyErrors.None;
}
});
return services;
}
}
12. Real-World Case Study
E-Commerce Platform Deployment
Scenario: Deploying a high-traffic e-commerce platform with 1 million+ products and 10,000+ concurrent users.
Production Stack:
Frontend:
- React SPA (CDN hosted)
- Azure Front Door for global distribution
Backend:
- ASP.NET Core API (Kubernetes)
- Redis Cluster for caching
- SQL Server Always On Availability Groups
Infrastructure:
- Azure Kubernetes Service (AKS)
- Azure Application Gateway
- Azure Monitor + Application Insights
Performance Optimization
// Caching strategy
public class DistributedCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly ILogger<DistributedCacheService> _logger;
public DistributedCacheService(IDistributedCache cache, ILogger<DistributedCacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
var cachedData = await _cache.GetStringAsync(key);
if (cachedData != null)
{
_logger.LogDebug("Cache hit for {Key}", key);
return JsonSerializer.Deserialize<T>(cachedData);
}
_logger.LogDebug("Cache miss for {Key}", key);
var data = await factory();
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromMinutes(30)
};
await _cache.SetStringAsync(key, JsonSerializer.Serialize(data), options);
return data;
}
}
// Database performance
public class OptimizedProductRepository : IProductRepository
{
private readonly ECommerceContext _context;
public OptimizedProductRepository(ECommerceContext context)
{
_context = context;
}
public async Task<List<Product>> GetFeaturedProductsAsync(int count)
{
return await _context.Products
.Where(p => p.IsFeatured && p.IsActive)
.OrderByDescending(p => p.CreatedAt)
.Take(count)
.AsNoTracking() // Read-only optimization
.ToListAsync();
}
public async Task<Product> GetProductWithDetailsAsync(int productId)
{
return await _context.Products
.Include(p => p.Category)
.Include(p => p.Inventory)
.Include(p => p.Reviews)
.AsSplitQuery() // Performance optimization for multiple includes
.FirstOrDefaultAsync(p => p.Id == productId);
}
}
Disaster Recovery Plan
# k8s/backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
namespace: ecommerce-production
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mcr.microsoft.com/mssql-tools:latest
command:
- /bin/bash
- -c
- |
/opt/mssql-tools/bin/sqlcmd -S $(DB_SERVER) -U $(DB_USER) -P $(DB_PASSWORD) \
-Q "BACKUP DATABASE [ECommerce] TO DISK = '/backup/ecommerce_$(date +%Y%m%d_%H%M%S).bak'"
volumeMounts:
- name: backup-volume
mountPath: /backup
env:
- name: DB_SERVER
valueFrom:
secretKeyRef:
name: ecommerce-secrets
key: db-server
- name: DB_USER
valueFrom:
secretKeyRef:
name: ecommerce-secrets
key: db-user
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ecommerce-secrets
key: db-password
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backup-pvc
namespace: ecommerce-production
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
Final Deployment Checklist
# Production Deployment Checklist
## Pre-Deployment
- [ ] Security scanning completed
- [ ] Performance testing passed
- [ ] Database migrations tested
- [ ] Rollback plan documented
- [ ] Team communication sent
## Deployment
- [ ] Blue-green deployment configured
- [ ] Health checks passing
- [ ] Monitoring dashboards updated
- [ ] Log aggregation working
- [ ] Alert rules configured
## Post-Deployment
- [ ] Smoke tests completed
- [ ] Performance metrics verified
- [ ] Error rate monitored
- [ ] User feedback collected
- [ ] Documentation updated
This comprehensive guide covers the entire journey from local Docker development to production Kubernetes deployment for ASP.NET Core applications. The real-world examples, complete code samples, and production best practices provide a solid foundation for mastering cloud-native deployment strategies.