跳到內容

部署

框架 repo 的 deploy/ 附了可直接參考的 Dockerfile(multi-stage、兩個 stage 都以 digest 釘住;直接用已提交的 gen/ 建置而不在映像內重跑 proto 產生,runtime stage 是 distroless/static:nonroot 並以 USER nonroot:nonroot 執行)與 docker-compose(含 otelcol、Prometheus、Grafana、PgBouncer 設定)。以下三件事是框架刻意不代勞、部署者必須自己接的。

新 scaffold 會帶有本機開發用設定;在第一次 git add、Docker build 或交給 CI 前,先檢查 secret 是否已外部化。不要把可用 JWT signing secret 留在版控或 image layer。

  • 將 real secret 交給 secret manager、mounted file 或 runtime environment,以 GORTEXA_AUTH__JWT_SECRET 注入。
  • 讓 repo 只保留不可用的 placeholder 或不含 secret 的範例設定;local override 應被忽略。
  • 讓 Docker build context 與 runtime image 都不含 local secret config,並驗證 image 只由 runtime injection 啟動。參考用的 .dockerignore 已把 .git、.env、*.local.yaml 擋在 build context 外——fork 時請保留。
  • 規劃 rotation、issuer 與 audience;共享 secret 的服務應設定不同 audience。

這是 deployment gate,不是可選的「稍後再做」。同樣地,scaffold 的 in-memory resource 只適合 demo;在 readiness 之前先建立真實的 storage、cache、MQ lifecycle。

服務的 port 是 cleartext h2c,伺服器本身不做 TLS。bearer token 經過這個 port 是明文的。把服務放在反向代理、service mesh 或 load balancer 後面終結 TLS,不要把 h2c port 直接暴露到公網。

/readyz 預設只反映行程本身活著。用 app.Health().Register(...) 註冊 DB、cache、MQ 的真實檢查,readiness 才有參考價值:

app.Health().Register("postgres", func(ctx context.Context) health.State {
if err := pool.Ping(ctx); err != nil {
return health.Unhealthy
}
return health.Healthy
})

每個 check 有 5 秒逾時,單一慢依賴不會拖垮整個查詢。

health probe 可綁在私有 ops 埠、與對外埠分離:kernel.WithAdminListener(addr) 在獨立的 plain-HTTP 埠只服務 /healthz 與 /readyz,讓 orchestrator 探測健康時不必碰到公開的 h2c 埠(app.AdminAddr() 可查實綁位址)。要在獨立埠上掛 pprof 或自訂 ops handler,用 kernel.WithExtraListener(lis, h)。兩者皆 opt-in,主 h2c 埠的單埠多工不變。

metric 與 span 在沒有設定 OTLP endpoint 時都會被直接丟掉,所以下面這幾件事各自會寫一行 WARN 到你的 logger,不需要任何遙測後端:

事件 訊息 附帶欄位
/readyz 回 503 gortexa: readiness not serving status、checks
開機時 health registry 是空的 gortexa: no health checks registered; /readyz reports ready without probing anything —
斷路器開/關 gortexa: circuit breaker state change method、from、to
MCP 拒絕 origin 或超量 batch mcp: rejected request from a disallowed origin / mcp: rejected an oversized JSON-RPC batch origin/elements+cap、remote

其中最值得盯的是空 registry 那行:它代表 /readyz 正在回報「就緒」但實際上什麼都沒探測。斷路器那行只有在用 interceptor.NewSet 時才會自動出現(它會用 Config.Logger 補上 CBConfig.Logger);直接自建 breaker 的話要自己設。MCP 那兩行例外,不寫到「你的 logger」:bridge 寫到 slog.Default(),要讓它們走你設定的格式與 OTLP 管線,就呼叫 slog.SetDefault(log)。

框架只做 authentication:驗 JWT 簽章、把 claims 放進 context。誰能做什麼(role、permission policy)由應用程式在 handler 或自己的攔截器裡判斷:

claims, ok := auth.ClaimsFrom(ctx)
if !ok || !slices.Contains(claims.Roles, "admin") {
// 拒絕
}

收到 SIGTERM 後,伺服器在 server.shutdown_timeout(預設 20s)內排空 in-flight 請求再退出。排空逾時則強制關閉剩餘連線並硬停 gRPC server;逾時以 warning 記錄而非回傳錯誤,行程仍以 0 退出、telemetry flush hooks 照跑——rolling deploy 時開著的 MCP SSE 或 Health.Watch 串流不會被當成 crash。負載中中斷的行為驗證見範例 05。

  • server.reflection 保持 false:不對外暴露服務結構。
  • observ.otlp_insecure 連遠端 collector 時必須 false。
  • 真實 secret 走 runtime injection,且不得存在於 Git 或 image layer,見設定。