部署
框架 repo 的 deploy/ 附了可直接參考的 Dockerfile(multi-stage、兩個 stage 都以 digest 釘住;直接用已提交的 gen/ 建置而不在映像內重跑 proto 產生,runtime stage 是 distroless/static:nonroot 並以 USER nonroot:nonroot 執行)與 docker-compose(含 otelcol、Prometheus、Grafana、PgBouncer 設定)。以下三件事是框架刻意不代勞、部署者必須自己接的。
Scaffold 的 secret preflight
Section titled “Scaffold 的 secret preflight”新 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。
TLS 在前面終結
Section titled “TLS 在前面終結”服務的 port 是 cleartext h2c,伺服器本身不做 TLS。bearer token 經過這個 port 是明文的。把服務放在反向代理、service mesh 或 load balancer 後面終結 TLS,不要把 h2c port 直接暴露到公網。
Readiness 接真實依賴
Section titled “Readiness 接真實依賴”/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 埠的單埠多工不變。
預設會記哪些 log
Section titled “預設會記哪些 log”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)。
Authorization 是應用程式的事
Section titled “Authorization 是應用程式的事”框架只做 authentication:驗 JWT 簽章、把 claims 放進 context。誰能做什麼(role、permission policy)由應用程式在 handler 或自己的攔截器裡判斷:
claims, ok := auth.ClaimsFrom(ctx)if !ok || !slices.Contains(claims.Roles, "admin") { // 拒絕}Graceful shutdown
Section titled “Graceful shutdown”收到 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,見設定。