Blazor Pod Scheduling
C# Blazor Pod Scheduling Kubernetes Docker SignalR WebSocket Sticky Session HPA Health Check .NET 8 Razor Components
| Aspect | Blazor Server | Blazor WASM | Blazor United (.NET 8) |
|---|---|---|---|
| Render | Server (SignalR) | Browser (WASM) | ทั้งสอง (per component) |
| Sticky Session | ต้องใช้ | ไม่ต้อง | ขึ้นกับ Component |
| Image Size | 80-120MB | 20-30MB (Nginx) | 80-120MB |
| Resource | CPU+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}")
เคล็ดลับ
- SignalR: ใช้ Redis Backplane สำหรับ Blazor Server Multi-pod
- Sticky: ตั้ง Cookie Affinity สำหรับ Ingress WebSocket
- WASM: ใช้ Brotli Compression ลด WASM Download Size 60%
- Health: ตั้ง Health Check ตรวจ DB Redis External API
- .NET 8: ใช้ Blazor United เลือก Render Mode ต่อ Component
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
