Technology

C# Blazor Zero Downtime Deployment

C# Blazor Zero Downtime Deployment | SiamCafe Blog
2025-08-02· อ. บอม — SiamCafe.net· 10,499 คำ

Blazor Zero Downtime

C# Blazor Zero Downtime Deployment Rolling Update Blue-Green Canary SignalR Health Check Graceful Shutdown Kubernetes Production

StrategyDowntimeRollbackResourceComplexity
Rolling UpdateZeroRolling Back+1 Podต่ำ
Blue-GreenZeroInstant (สลับ Traffic)2x Resourceปานกลาง
CanaryZeroส่ง 100% กลับเวอร์ชันเก่า+10-20% Resourceสูง
Recreateมี DowntimeDeploy เวอร์ชันเก่าปกติต่ำ

Rolling Update Configuration

# === Blazor Server Rolling Update ===

# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: blazor-app
# spec:
#   replicas: 3
#   strategy:
#     type: RollingUpdate
#     rollingUpdate:
#       maxSurge: 1
#       maxUnavailable: 0
#   template:
#     spec:
#       terminationGracePeriodSeconds: 60
#       containers:
#         - name: blazor
#           image: registry.example.com/blazor-app:v2.0
#           ports:
#             - containerPort: 8080
#           lifecycle:
#             preStop:
#               exec:
#                 command: ["/bin/sh", "-c", "sleep 15"]
#           readinessProbe:
#             httpGet:
#               path: /health/ready
#               port: 8080
#             initialDelaySeconds: 5
#             periodSeconds: 5
#             failureThreshold: 3
#           livenessProbe:
#             httpGet:
#               path: /health
#               port: 8080
#             initialDelaySeconds: 15
#             periodSeconds: 10
#       minReadySeconds: 10

# Program.cs - Graceful Shutdown
# var builder = WebApplication.CreateBuilder(args);
# builder.Services.AddHealthChecks()
#     .AddDbContextCheck("database")
#     .AddCheck("signalr", () => HealthCheckResult.Healthy());
#
# var app = builder.Build();
# app.MapHealthChecks("/health");
# app.MapHealthChecks("/health/ready");
#
# app.Lifetime.ApplicationStopping.Register(() => {
#     Log.Information("Shutting down gracefully...");
#     Thread.Sleep(10000); // Wait for connections to drain
# });

from dataclasses import dataclass

@dataclass
class RollingConfig:
    setting: str
    value: str
    purpose: str
    blazor_note: str

configs = [
    RollingConfig("maxSurge", "1",
        "เพิ่ม Pod ใหม่ 1 ตัวก่อนลบ Pod เก่า",
        "Pod ใหม่ต้อง Ready ก่อนรับ SignalR Connections"),
    RollingConfig("maxUnavailable", "0",
        "ห้ามลด Pod จนกว่า Pod ใหม่ Ready",
        "ป้องกัน Connection Drop ระหว่าง Deploy"),
    RollingConfig("minReadySeconds", "10",
        "รอ 10 วินาทีหลัง Ready ก่อนดำเนินการต่อ",
        "ให้ SignalR Hub เริ่มต้นเสร็จสมบูรณ์"),
    RollingConfig("terminationGracePeriodSeconds", "60",
        "รอ 60 วินาทีก่อน Force Kill",
        "ให้เวลา SignalR Connection Drain"),
    RollingConfig("preStop sleep 15", "sleep 15",
        "รอ 15 วินาทีก่อน SIGTERM",
        "ให้ Endpoint ถูกถอดจาก Service ก่อน"),
]

print("=== Rolling Update Config ===")
for c in configs:
    print(f"  [{c.setting}] = {c.value}")
    print(f"    Purpose: {c.purpose}")
    print(f"    Blazor: {c.blazor_note}")

Blue-Green Deployment

# === Blue-Green Deployment Script ===

# # deploy-blue-green.sh
# #!/bin/bash
# NEW_VERSION=$1
# CURRENT=$(kubectl get svc blazor-app -o jsonpath='{.spec.selector.version}')
#
# if [ "$CURRENT" = "blue" ]; then
#   TARGET="green"
# else
#   TARGET="blue"
# fi
#
# # Deploy new version to target
# kubectl set image deployment/blazor-$TARGET \
#   blazor=registry.example.com/blazor-app:$NEW_VERSION
#
# # Wait for rollout
# kubectl rollout status deployment/blazor-$TARGET --timeout=300s
#
# # Run smoke tests
# ENDPOINT=$(kubectl get svc blazor-$TARGET -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# curl -f "http://$ENDPOINT/health/ready" || exit 1
#
# # Switch traffic
# kubectl patch svc blazor-app -p "{\"spec\":{\"selector\":{\"version\":\"$TARGET\"}}}"
#
# echo "Switched to $TARGET (v$NEW_VERSION)"

@dataclass
class BlueGreenStep:
    step: int
    action: str
    command: str
    duration: str
    rollback: str

steps = [
    BlueGreenStep(1, "Deploy to Green",
        "kubectl set image deployment/blazor-green",
        "2-5 นาที",
        "ไม่ต้อง (Blue ยังทำงาน)"),
    BlueGreenStep(2, "Wait for Rollout",
        "kubectl rollout status deployment/blazor-green",
        "1-3 นาที",
        "kubectl rollout undo"),
    BlueGreenStep(3, "Smoke Test Green",
        "curl /health/ready + E2E tests",
        "1-5 นาที",
        "ไม่ Switch ถ้า Test Fail"),
    BlueGreenStep(4, "Switch Traffic",
        "kubectl patch svc (change selector)",
        "< 1 วินาที",
        "Switch กลับ Blue ทันที"),
    BlueGreenStep(5, "Monitor Green",
        "Watch error rate, latency, logs",
        "15-30 นาที",
        "Switch กลับ Blue ถ้าผิดปกติ"),
    BlueGreenStep(6, "Cleanup Blue",
        "Scale down Blue (optional)",
        "ทำเมื่อมั่นใจ",
        "เก็บ Blue ไว้ 24 ชม. ก่อนลบ"),
]

print("=== Blue-Green Steps ===")
for s in steps:
    print(f"  Step {s.step}: {s.action} ({s.duration})")
    print(f"    Command: {s.command}")
    print(f"    Rollback: {s.rollback}")

SignalR Connection Handling

# === SignalR Zero Downtime Handling ===

@dataclass
class SignalRStrategy:
    scenario: str
    problem: str
    solution: str
    config: str

signalr_strategies = [
    SignalRStrategy("Pod Termination",
        "SignalR WebSocket Connection หลุดเมื่อ Pod ถูกลบ",
        "preStop hook + Graceful Shutdown รอ Connection Drain",
        "terminationGracePeriodSeconds: 60, preStop sleep 15"),
    SignalRStrategy("Client Reconnection",
        "Client ต้อง Reconnect หลัง Connection หลุด",
        "Blazor Circuit Reconnect UI + Auto-reconnect",
        "Blazor.start({circuit: {reconnectionOptions}})"),
    SignalRStrategy("Session Affinity",
        "SignalR ต้องกลับ Pod เดิม (Sticky Session)",
        "Ingress Cookie Affinity + Redis Backplane",
        "nginx affinity: cookie + AddSignalR().AddRedis()"),
    SignalRStrategy("State Preservation",
        "Circuit State หายเมื่อ Pod ตาย",
        "เก็บ State ใน Redis/Database ไม่ใช่ Memory",
        "IDistributedCache + Redis State Store"),
    SignalRStrategy("Load Balancer Drain",
        "New Connection ยังถูกส่งมา Pod ที่กำลังจะตาย",
        "Readiness Probe Fail → ถอดจาก Service → Drain",
        "readinessProbe + preStop + SIGTERM handler"),
]

print("=== SignalR Strategies ===")
for s in signalr_strategies:
    print(f"\n  [{s.scenario}]")
    print(f"    Problem: {s.problem}")
    print(f"    Solution: {s.solution}")
    print(f"    Config: {s.config}")

เคล็ดลับ

Zero Downtime Deployment คืออะไร

Deploy ไม่หยุดบริการ Rolling Update Blue-Green Canary SignalR Graceful Shutdown Circuit Reconnect Kubernetes ผู้ใช้ไม่รู้สึก

Rolling Update ทำอย่างไร

maxSurge 1 maxUnavailable 0 minReadySeconds 10 terminationGracePeriod 60 preStop sleep Readiness Probe PDB SignalR Drain

Blue-Green Deployment ทำอย่างไร

2 Environment Blue Green Deploy Green Test Switch Traffic Service Selector Instant Rollback 2x Resource Smoke Test Monitor

Health Check ตั้งอย่างไร

ASP.NET Core AddHealthChecks Liveness /health Readiness /health/ready Startup Probe Database Redis SignalR Graceful Shutdown SIGTERM

สรุป

C# Blazor Zero Downtime Rolling Update Blue-Green Canary SignalR preStop Graceful Shutdown Health Check Redis Backplane Kubernetes Production

📖 บทความที่เกี่ยวข้อง

C# Minimal API Zero Downtime Deploymentอ่านบทความ → GCP Vertex AI Zero Downtime Deploymentอ่านบทความ → A/B Testing ML Zero Downtime Deploymentอ่านบทความ → Databricks Unity Catalog Zero Downtime Deploymentอ่านบทความ → Falco Runtime Security Zero Downtime Deploymentอ่านบทความ →

📚 ดูบทความทั้งหมด →