Technology

C# Blazor Pod Scheduling

c blazor pod scheduling
C# Blazor Pod Scheduling | SiamCafe Blog
2026-03-10· อ. บอม — SiamCafe.net· 10,628 คำ

Blazor Pod Scheduling

C# Blazor Pod Scheduling Kubernetes Docker SignalR WebSocket Sticky Session HPA Health Check .NET 8 Razor Components

AspectBlazor ServerBlazor WASMBlazor United (.NET 8)
RenderServer (SignalR)Browser (WASM)ทั้งสอง (per component)
Sticky Sessionต้องใช้ไม่ต้องขึ้นกับ Component
Image Size80-120MB20-30MB (Nginx)80-120MB
ResourceCPU+RAM มากน้อย (Static)ปานกลาง
Scaleซับซ้อน (Session)ง่าย (Stateless)ปานกลาง
Offlineไม่ได้ได้บางส่วน

Docker Configuration

# === Blazor Docker Setup ===

# Dockerfile (Blazor Server)
# FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
# WORKDIR /src
# COPY *.csproj ./
# RUN dotnet restore
# COPY . .
# RUN dotnet publish -c Release -o /app/publish
#
# FROM mcr.microsoft.com/dotnet/aspnet:8.0
# WORKDIR /app
# COPY --from=build /app/publish .
# ENV ASPNETCORE_URLS=http://+:8080
# ENV ASPNETCORE_ENVIRONMENT=Production
# EXPOSE 8080
# HEALTHCHECK --interval=30s --timeout=3s \
#   CMD curl -f http://localhost:8080/health || exit 1
# ENTRYPOINT ["dotnet", "BlazorApp.dll"]

# Program.cs Health Checks
# var builder = WebApplication.CreateBuilder(args);
# builder.Services.AddHealthChecks()
#     .AddDbContextCheck("database")
#     .AddRedis(builder.Configuration["Redis:Connection"], "redis")
#     .AddUrlGroup(new Uri("https://api.example.com/health"), "external-api");
#
# var app = builder.Build();
# app.MapHealthChecks("/health");
# app.MapHealthChecks("/health/ready", new HealthCheckOptions {
#     Predicate = check => check.Tags.Contains("ready")
# });

from dataclasses import dataclass

@dataclass
class DockerImage:
    variant: str
    base_image: str
    size: str
    use_case: str

images = [
    DockerImage("Blazor Server",
        "mcr.microsoft.com/dotnet/aspnet:8.0",
        "80-120MB",
        "Server-side rendering SignalR WebSocket"),
    DockerImage("Blazor WASM (Nginx)",
        "nginx:alpine",
        "20-30MB",
        "Client-side WASM Static Files"),
    DockerImage("Blazor United",
        "mcr.microsoft.com/dotnet/aspnet:8.0",
        "80-120MB",
        "Mixed render modes Server + WASM"),
    DockerImage("SDK (Build Stage)",
        "mcr.microsoft.com/dotnet/sdk:8.0",
        "~700MB (build only)",
        "Compile Publish ไม่อยู่ใน Final"),
]

print("=== Docker Images ===")
for d in images:
    print(f"  [{d.variant}] {d.base_image}")
    print(f"    Size: {d.size} | Use: {d.use_case}")

Kubernetes Deployment

# === Blazor Server Kubernetes Config ===

# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: blazor-server
# spec:
#   replicas: 3
#   template:
#     spec:
#       containers:
#         - name: blazor
#           image: registry.example.com/blazor-app:v1.0
#           ports:
#             - containerPort: 8080
#           env:
#             - name: ConnectionStrings__Default
#               valueFrom:
#                 secretKeyRef:
#                   name: blazor-secrets
#                   key: db-connection
#           resources:
#             requests:
#               cpu: 250m
#               memory: 256Mi
#             limits:
#               cpu: "1"
#               memory: 512Mi
#           livenessProbe:
#             httpGet:
#               path: /health
#               port: 8080
#             initialDelaySeconds: 10
#             periodSeconds: 15
#           readinessProbe:
#             httpGet:
#               path: /health/ready
#               port: 8080
#             initialDelaySeconds: 5
#             periodSeconds: 10
# ---
# # Ingress with Sticky Session (for SignalR)
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
#   annotations:
#     nginx.ingress.kubernetes.io/affinity: "cookie"
#     nginx.ingress.kubernetes.io/session-cookie-name: "blazor-affinity"
#     nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
#     nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

@dataclass
class K8sConfig:
    resource: str
    key_setting: str
    blazor_server: str
    blazor_wasm: str

configs = [
    K8sConfig("Deployment",
        "replicas",
        "3 (ต้อง Sticky Session)",
        "3 (Stateless Scale ง่าย)"),
    K8sConfig("Ingress",
        "affinity",
        "cookie (SignalR WebSocket)",
        "ไม่ต้อง (Static Files)"),
    K8sConfig("Ingress",
        "proxy-timeout",
        "3600s (WebSocket long-lived)",
        "60s (ปกติ)"),
    K8sConfig("Service",
        "sessionAffinity",
        "ClientIP (Sticky Session)",
        "None (Round Robin)"),
    K8sConfig("HPA",
        "metric",
        "CPU > 70% (Min 2 Max 10)",
        "CPU > 70% (Min 2 Max 20)"),
    K8sConfig("Resources",
        "request/limit",
        "CPU 250m-1 Memory 256Mi-512Mi",
        "CPU 50m-200m Memory 64Mi-128Mi"),
]

print("=== K8s Configuration ===")
for c in configs:
    print(f"  [{c.resource}] {c.key_setting}")
    print(f"    Server: {c.blazor_server}")
    print(f"    WASM: {c.blazor_wasm}")

Scaling & Monitoring

# === Blazor Scaling Strategy ===

@dataclass
class ScaleStrategy:
    scenario: str
    challenge: str
    solution: str
    config: str

scaling = [
    ScaleStrategy("Blazor Server Scale Out",
        "SignalR WebSocket ต้อง Sticky Session ย้าย Pod ไม่ได้",
        "ใช้ Azure SignalR Service หรือ Redis Backplane",
        "AddSignalR().AddRedis(redisConnectionString)"),
    ScaleStrategy("Session Affinity Failover",
        "ถ้า Pod ตาย Session หาย User ต้อง Reconnect",
        "ตั้ง Circuit Reconnect UI แจ้ง User + Auto-reconnect",
        "Blazor.start({reconnectionOptions})"),
    ScaleStrategy("WASM CDN Distribution",
        "WASM Files 5-20MB ต้อง Download ครั้งแรก",
        "ใช้ CDN Cache + Brotli Compression + Service Worker",
        "Cache-Control: public, max-age=31536000"),
    ScaleStrategy("Database Connection Pool",
        "Pod เพิ่ม Connection Pool หมด",
        "ใช้ PgBouncer / ProxySQL + Connection Pool Limit",
        "MaxPoolSize=50 per Pod"),
]

@dataclass
class MonitorMetric:
    metric: str
    source: str
    alert: str

monitoring = [
    MonitorMetric("SignalR Connection Count",
        "dotnet_signalr_connections_current",
        "> 1000 connections/pod → Scale out"),
    MonitorMetric("Request Duration p99",
        "http_request_duration_seconds",
        "> 500ms → Check DB/External API"),
    MonitorMetric("Memory Usage",
        "container_memory_usage_bytes",
        "> 80% limit → Scale up/out"),
    MonitorMetric("Health Check Status",
        "/health endpoint",
        "Unhealthy → Alert + Auto-restart"),
    MonitorMetric("WebSocket Errors",
        "signalr_errors_total",
        "> 1% error rate → Check Network"),
]

print("=== Scaling ===")
for s in scaling:
    print(f"  [{s.scenario}]")
    print(f"    Challenge: {s.challenge}")
    print(f"    Solution: {s.solution}")

print("\n=== Monitoring ===")
for m in monitoring:
    print(f"  [{m.metric}] {m.source}")
    print(f"    Alert: {m.alert}")

เคล็ดลับ

Blazor คืออะไร

Web Framework C# Microsoft Razor Components Server SignalR WASM WebAssembly United .NET 8 NuGet Entity Framework Interactive UI

Docker ตั้งค่าอย่างไร

Multi-stage dotnet/sdk dotnet/aspnet nginx:alpine dotnet publish Release Health Check ENTRYPOINT 80-120MB Docker Compose SQL Server Redis

Kubernetes Deploy อย่างไร

Deployment Replicas Ingress Cookie Affinity SignalR WebSocket Timeout HPA Service sessionAffinity ConfigMap Secret Liveness Readiness Probe

Health Check ทำอย่างไร

ASP.NET Core AddHealthChecks DbContext Redis URL Disk MapHealthChecks /health /health/ready Liveness Readiness Startup Probe UI Dashboard

สรุป

C# Blazor Pod Scheduling Kubernetes Docker SignalR Sticky Session HPA Health Check .NET 8 WASM Redis Backplane Ingress Production

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

BigQuery Scheduled Query Pod Schedulingอ่านบทความ → ClickHouse Analytics Pod Schedulingอ่านบทความ → Fivetran Connector Pod Schedulingอ่านบทความ → GraphQL Federation Pod Schedulingอ่านบทความ → C# Blazor Career Development ITอ่านบทความ →

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