Skip to content

Commit d4bf9bf

Browse files
Ruisi-Luclaude
andauthored
feat(flux): open the gateway data path on the demo cluster (#800)
Turn on the MQTT broker, mqtt-bridge, ingest and gateway-ops in the demo overlay, so the gateway hostname's /mqtt reaches the broker's gateway port through mqtt-bridge and telemetry lands in ClickHouse. - values-demo.yaml enables mqttBroker and the three services; the /mqtt route, broker accounts, passwords and the broker server certificate all come from the chart and the cluster, so no route or Secret is added. - flux.test.ts refuses these services on a pin at or below rc.11 (no ingest or gateway-ops images there), and checks the gateway routes, the broker objects and the connection budget. - The demo runbook and the Flux and chart READMEs describe the rollout, the post-merge checks (including the unregistered-certificate refusal script) and the rollback order. Refs #795 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
1 parent 3d080f3 commit d4bf9bf

5 files changed

Lines changed: 179 additions & 35 deletions

File tree

‎deploy/chart/README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ proto 的 shim 偶爾會把自己的訊息寫進 stdout(例如另一個 proto
6868
| Postgres(CloudNativePG 加 AGE) | `postgres.mode` | `bundled`:單一 instance 的 CloudNativePG Cluster、AGE extension 與 NetworkPolicy(見「叢集內的 Postgres」);CloudNativePG operator 與 CRD 是叢集層級的平台元件,由 [deploy/cloudnative-pg](../cloudnative-pg/README.md) 安裝,不在 chart 裡。`external` 已接到平台設定。含 AGE 的映像在 [deploy/images/postgres](../images/postgres/README.md),由 release.yml 發佈。每個服務的角色與 migration 由 migration Job 處理 | Laysi/trustiot-v2#134(備份與 PITR) |
6969
| migration Job | `migrate.enabled` | 完成(預設開):每個服務的 Postgres migration、ClickHouse DDL、服務角色與 collector 的 ClickHouse 帳號;每個服務的 pod 以 init container 等它完成才啟動(見「migration Job」) | Laysi/trustiot-v2#60;第一個 outbox 生產端在 Laysi/trustiot-v2#65,每租戶的 AGE 角色在 Laysi/trustiot-v2#176 |
7070
| ClickHouse | `clickhouse.mode` | `bundled`:單節點 StatefulSet,bootstrap 管理帳號的密碼在叢集內產生(見「叢集內的 ClickHouse」);`external` 已接到平台設定。schema、帳號與 row policy 由 migration Job 以 SQL 套用 | 管理帳號密碼的輪替見 docs/runbooks/secrets.md |
71-
| MQTT broker(Mosquitto) | `mqttBroker.enabled` | 完成(預設關,只有 nightly 的 `tests/values/smoke.yaml` 開啟):單一副本的 StatefulSet,閘道器埠 8883(TLS 1.3、只信任我們的 CA、CRL)只放行 mqtt-bridge,服務埠 1883 只放行有 broker 帳號的服務;dynamic security 預設拒絕,服務的密碼在叢集內產生;伺服器憑證的私鑰由 CronJob 在叢集內產生、經 certs 服務簽發,過了效期一半就換發(Laysi/trustiot-v2#535);SQLite 持久化(見「MQTT broker」) | 閘道器帳號的登記與撤銷在 Laysi/trustiot-v2#82 |
71+
| MQTT broker(Mosquitto) | `mqttBroker.enabled` | 完成(預設關;nightly 的 `tests/values/smoke.yaml` 與展示叢集開啟,展示叢集自 Laysi/trustiot-v2#795):單一副本的 StatefulSet,閘道器埠 8883(TLS 1.3、只信任我們的 CA、CRL)只放行 mqtt-bridge,服務埠 1883 只放行有 broker 帳號的服務;dynamic security 預設拒絕,服務的密碼在叢集內產生;伺服器憑證的私鑰由 CronJob 在叢集內產生、經 certs 服務簽發,過了效期一半就換發(Laysi/trustiot-v2#535);SQLite 持久化(見「MQTT broker」) | 閘道器帳號的登記與撤銷在 Laysi/trustiot-v2#82 |
7272
| Qdrant | `qdrant.mode` | `external` 已接到平台設定;`bundled` 尚未加入 | Laysi/trustiot-v2#194 |
7373
| 物件儲存、SMTP、AI 端點 | `objectStorage.*`、`smtp.*`、`ai.*` | 一律外部,設定已接到平台設定 | Laysi/trustiot-v2#133、Laysi/trustiot-v2#169、Laysi/trustiot-v2#139 |
7474
| OpenTelemetry collector 與 OTLP 轉送 | `observability.collector.enabled` | 完成(預設開):收服務的 OTLP、遮蔽後把日誌與 trace 寫進 ClickHouse 的 `trustiot_logs`,可轉送到 SIEM(見「OpenTelemetry collector」)。帳號由 migration Job 以 SQL 建立;指標先收下丟棄 | Laysi/trustiot-v2#183(指標存放與 Grafana)、Laysi/trustiot-v2#63(服務間授權要放行 collector) |
@@ -508,7 +508,7 @@ chart 產生下列物件:
508508
| `clickhouse.external.migrationSecret.name` | `""`,`external` 且 Job 開啟時必填 | 有 access management 權限的帳號的帳密所在的既有 Secret(叢集內 ClickHouse 的管理帳號在叢集內產生,見「叢集內的 ClickHouse」),只給 migration Job |
509509
| `clickhouse.external.migrationSecret.usernameKey` | `username` | |
510510
| `clickhouse.external.migrationSecret.passwordKey` | `password` | |
511-
| `mqttBroker.enabled` | `false` | 部署叢集內的 MQTT broker(Mosquitto,決策 6),見「MQTT broker」。兩份 values 與 `deploy/flux` 都不開(flux 的測試檢查兩個叢集),目前只有 nightly 的 `tests/values/smoke.yaml` 開啟。伺服器憑證不必事先準備:chart 宣告空的 Secret,憑證 CronJob 在 certs 服務能簽章之後填入,在那之前 broker 不開閘道器埠(見「MQTT broker」的「伺服器憑證」) |
511+
| `mqttBroker.enabled` | `false` | 部署叢集內的 MQTT broker(Mosquitto,決策 6),見「MQTT broker」。兩份 values 與 SaaS 叢集都不開;nightly 的 `tests/values/smoke.yaml` 與展示叢集的 `values-demo.yaml`(Laysi/trustiot-v2#795)開啟,flux 的測試檢查每個叢集由哪個檔設定它。伺服器憑證不必事先準備:chart 宣告空的 Secret,憑證 CronJob 在 certs 服務能簽章之後填入,在那之前 broker 不開閘道器埠(見「MQTT broker」的「伺服器憑證」) |
512512
| `mqttBroker.image.repository` | `docker.io/library/eclipse-mosquitto` | |
513513
| `mqttBroker.image.tag` | `2.1.2-alpine` | 實際版本由 digest 決定 |
514514
| `mqttBroker.image.digest` | 見 `values.yaml` | 必填(版本固定),multi-arch index 的 digest,含 arm64;tag 與 digest 必須與 `crates/protocol/tests/broker.rs`、`tools/migrate/tests/mqtt_broker.rs` 和 `scripts/lib/stack.ts` 相同(chart 測試比對)。換版本前先讀新版的 SIGHUP、CRL 與 dynamic security 行為,「MQTT broker」一節的做法依賴 2.1.2 的原始碼 |
@@ -696,9 +696,11 @@ curl -sS -D - -X POST "http://127.0.0.1:18080/internal/roles/main/v1/messages" \
696696
- **關閉**:`services.gateway-ops.enabled: false` 時下一次 Job 讓 `gateway_ops_app`、`gateway_ops_platform` 不能登入,broker 移除 `service:gateway-ops` 這個帳號(它離開了帳號持有者的清單);Secret 與資料留著(`keep`);broker 上已經 retained 的設定訊息不因此清除。
697697
- nightly 的 smoke 測試開啟 gateway-ops 與 broker,確認 pod 就緒(它的 readiness 包括以 `service:gateway-ops` 連上 broker 並獲准共享訂閱,所以這也證明 chart 交給它的 broker 帳號能用)、兩個 Postgres 角色都有連線(`scripts/nightly/smoke.ts`)。nightly 的程式不讀任何 pod 的日誌(`scripts/nightly/diagnostics.test.ts`),所以 broker 端的登入紀錄不在 nightly 驗證。叢集裡沒有綁定的閘道器,所以它不下發任何設定。
698698
- **展示叢集**:chart 只提供開關。建議分兩次換版,讓每次只有一種中斷:
699-
1. 依 [deploy/flux/README.md](../flux/README.md) 的換版流程換到含這一版的 chart,gateway-ops 先不開。這一步叢集內的 Postgres 因 `max_connections` 改成 220 重啟一次。
699+
1. 依 [deploy/flux/README.md](../flux/README.md) 的換版流程換到含這一版的 chart,gateway-ops 先不開。這一步叢集內的 Postgres 因 `max_connections` 改變重啟一次(加入 gateway-ops 時調到 220,之後的版本是 240;從 `0.0.0-rc.11` 的 200 換上來也一樣)。
700700
2. 確認第 1 步穩定後,另一次在 `values-demo.yaml` 打開 `services.gateway-ops.enabled`(broker 還沒開時一起打開 `mqttBroker.enabled`,否則渲染失敗)。這一步 broker 的帳號持有者多了 gateway-ops,broker StatefulSet 的 pod template 改變、broker 重啟一次,連著的閘道器與 ingest 斷線後重連(見「MQTT broker」的「服務的帳號」);migration Job 重跑以建立 `gateway_ops_app`、`gateway_ops_platform`,用 Postgres 的服務跟著 Job 滾動更新。
701701

702+
Laysi/trustiot-v2#795 照這個順序做:第 1 步是 `0.0.0-rc.12` 的換版,第 2 步在同一個變更打開 broker、mqtt-bridge、ingest 與 gateway-ops([deploy/flux/README.md](../flux/README.md)「打開閘道器的資料路徑」)。
703+
702704
## migration Job
703705

704706
`migrate.enabled: true`(預設,SaaS 與地端相同)部署 `templates/migrate.yaml`,程式是 [tools/migrate](../../tools/migrate/README.md)(Laysi/trustiot-v2#60,決策 4、14、29,清單 M8)。每次執行都是冪等的:設定沒變時再跑一次,資料庫裡什麼都不變(測試比對整份 catalog)。
@@ -874,7 +876,7 @@ bootstrap 管理帳號只給 migration Job 用,密碼在叢集內產生,沒
874876
875877
## MQTT broker
876878
877-
`mqttBroker.enabled: true` 部署 `templates/mqtt-broker.yaml`(決策 6,Laysi/trustiot-v2#84),雲端唯一的 MQTT broker:閘道器經入口 gateway 與 mqtt-bridge 連到它,雲端服務在叢集內連它。預設關:兩份 values 與 `deploy/flux` 都不開(`deploy/flux` 的測試檢查 SaaS 與展示叢集都沒有設定 `mqttBroker`),目前只有 nightly 的 `tests/values/smoke.yaml` 開啟。
879+
`mqttBroker.enabled: true` 部署 `templates/mqtt-broker.yaml`(決策 6,Laysi/trustiot-v2#84),雲端唯一的 MQTT broker:閘道器經入口 gateway 與 mqtt-bridge 連到它,雲端服務在叢集內連它。預設關:兩份 values 與 SaaS 叢集都不開;開啟的是 nightly 的 `tests/values/smoke.yaml`,以及展示叢集為了閘道器的資料路徑在 `values-demo.yaml` 開啟(Laysi/trustiot-v2#795,連同 mqtt-bridge、ingest 與 gateway-ops)。`deploy/flux` 的測試檢查 SaaS 叢集沒有設定 `mqttBroker`、展示叢集只在 `values-demo.yaml` 設定它,而且沒有私有 values 指到它。
878880
879881
- 一個副本的 StatefulSet `<release>-mqtt-broker`,Mosquitto 2.1(`mqttBroker.image`,以 digest 固定)。資料在 PVC `data-<release>-mqtt-broker-0`(`/mosquitto/data`):SQLite 的 session、排隊與 retained 訊息、dynamic security 檔與信任檔;StatefulSet 刪除或縮容時保留 PVC。
880882
- ClusterIP Service `<release>-mqtt-broker` 只開 `mqtt-gateways`(8883)與 `mqtt-services`(1883),兩種部署形態相同,沒有 nodePort,任何入口路由都不能指向它(`ingress.routes` 寫了就渲染失敗)。chart 測試檢查兩份 values 的渲染結果;nightly 在 k3d 上檢查部署後的每個 Service 都是沒有 node port、外部 IP 與 load balancer 位址的 ClusterIP,也沒有任何 Route 指向 broker 的 Service。

‎deploy/flux/README.md‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ bun scripts/flux/components.ts # 重寫 gotk-components.yaml 與控制器映
235235
| chart 版本 | 自己的 `chart.yaml` 與 `values-saas.yaml`;deploy PR 把它與 SaaS 一起移到新版,只動它時用 `pin-chart.ts` | 換版與 SaaS 一樣是「合併即部署、revert 即回滾」,簽章驗證也相同;代價是一個 deploy PR 可能同時動兩個叢集 |
236236
| values | HelmRelease 依序讀 `values-saas.yaml`、`values-demo.yaml`,最後是 SOPS 加密的 Secret `trustiot-demo-values` 的四個 key,各自以 `targetPath` 與 `literal: true` 寫進一個值:`app-host` → `hosts.app`、`gateway-host` → `hosts.gateway`、`ota-host` → `hosts.ota`、`smtp-from` → `smtp.from` | 主機名稱與寄件人不公開([密鑰與權限](../../docs/architecture/secrets.md)「公開 repo 的資訊規則」),只在加密的 Secret 裡。加密的內容在 PR 裡看不到,所以它只能設這四個值:整份 values 的寫法能改任何設定(映像、ingress、環境變數),沒有 `literal` 時 Flux 以 Helm 的 `--set` 語法讀值,逗號就能接上另一個 `路徑=值`。Secret 帶 `reconcile.fluxcd.io/watch: Enabled`,改了就升級 |
237237
| 入口的 ClusterIP | 固定:`values-demo.yaml` 以 `ingress.tunnel.clusterIP` 設一個展示機 Service 網段(`10.43.0.0/16`)靜態區段裡沒被使用的位址,tunnel 的路由指向這個位址(Laysi/trustiot-v2#478)。設為 `10.43.0.80`(Laysi/trustiot-v2#480),與換到含這個欄位的 chart 版本在同一個 PR,而且在展示環境第一次部署(Laysi/trustiot-v2#467)之前;較舊的 chart 不認得這個欄位,schema 拒絕整份 values,HelmRelease 就失敗 | 入口 Service 重建後拿回同一個位址,路由不失效,舊位址也不會被別的 Service 拿走而經 tunnel 公開;ClusterIP 建立後不能改,所以要在 Service 第一次建立前設好。選法與改位址的步驟見 [chart 的 README](../chart/README.md)「固定的入口 ClusterIP」 |
238-
| 服務 | `values-demo.yaml` 的 `services` 逐一打開切片的服務(目前 `identity`、`assets`、`certs`),`ingress.routes` 列出 app 主機的 cookie 路由(目前 `/api/identity`、`/api/certs`) | 只開部署中那一版 chart 有映像的服務;切片的 issue 進了 release 後,在這裡打開(certs、AI 閘道與 telemetry-query 要另設的值見下方「打開 certs」「打開 AI 閘道」「打開 telemetry-query」) |
238+
| 服務 | `values-demo.yaml` 的 `services` 逐一打開切片的服務(目前 `identity`、`assets`、`certs`,以及閘道器資料路徑的 `mqtt-bridge`、`ingest`、`gateway-ops` 與 `mqttBroker`),`ingress.routes` 列出 app 主機的 cookie 路由(目前 `/api/identity`、`/api/certs`) | 只開部署中那一版 chart 有映像的服務;切片的 issue 進了 release 後,在這裡打開(certs、閘道器的資料路徑、AI 閘道與 telemetry-query 要另設的值見下方「打開 certs」「打開閘道器的資料路徑」「打開 AI 閘道」「打開 telemetry-query」) |
239239
| 外部端點 | Qdrant 的網址在 `.invalid`(RFC 6761,不會解析);Sentry 關閉 | 切片不用知識庫,SaaS 型態的 schema 又要求外部網址;沒有展示用的 Sentry 專案,錯誤留在 ClickHouse 的日誌 |
240240
| 備份與磁碟 | 沒有備份;Postgres 10Gi、ClickHouse 20Gi | 里程碑「展示切片」接受展示資料遺失;一台閘道器加幾具電表的量 |
241241

@@ -322,6 +322,15 @@ age-keygen 的輸出不是終端機時,私鑰寫到管線、`Public key: …`
322322
323323
`ingress.routes` 是清單,Helm 會整個取代:之後加別的路由(例如下方的 telemetry-query)要保留這兩條。
324324

325+
- **打開閘道器的資料路徑**(Laysi/trustiot-v2#795):閘道器經 gateway 主機名稱的 `/mqtt` 連進來,到雲端服務要四樣東西,在 `values-demo.yaml` 一起打開:`mqttBroker.enabled`(broker 本身)、`services.mqtt-bridge`(把 WebSocket 轉成到 broker 閘道器埠的 TCP)、`services.ingest`(遙測寫進 ClickHouse)與 `services.gateway-ops`(在線狀態與設定下發)。理由與取捨:
326+
- `/mqtt` 的路由不寫在 `ingress.routes`:它是 chart 固定的例外路徑(`ingress.publicPaths.mqtt`,完整路徑比對、不需要 cookie),mqtt-bridge 打開時 chart 才掛上指向它的路由,沒開時這個路徑既不路由也不免驗證([chart 的 README](../chart/README.md)「入口 gateway」)。broker 自己沒有任何入口路由,閘道器只經 mqtt-bridge 連它。
327+
- 不用加 Secret:broker 的服務帳號密碼由 broker pod 的 init container 產生、服務角色與 ClickHouse 寫入帳號的密碼由 migration Job 產生,broker 的伺服器憑證由 chart 的 CronJob 產生私鑰、經 certs 簽發(2026-10-28 的儀式之後 certs 就能簽),都不經過人([chart 的 README](../chart/README.md)「MQTT broker」)。
328+
- 四樣一起開,不分次:ingest 與 gateway-ops 沒有 broker 時 chart 拒絕渲染;broker 沒有 mqtt-bridge 時閘道器連不到它,只剩雲端服務的連線;mqtt-bridge 沒有 broker 時不啟動。
329+
- certs 已經打開,broker 開啟後它拿到 `service:certs` 帳號,依 Postgres 同步閘道器在 broker 的 client 與角色(Laysi/trustiot-v2#633);broker 的 trust sidecar 從 certs 取 CA 憑證與 CRL。gateway-ops 的 API 路由(`/api/gateway-ops`)不在這一步:它不在資料路徑上,給前端用,由用到它的 issue 加。
330+
- chart 版本:要比 `0.0.0-rc.11` 晚。rc.11 沒有 ingest 與 gateway-ops 的映像,它的 certs 也還沒有 broker 帳號、不同步閘道器的 client(Laysi/trustiot-v2#737、Laysi/trustiot-v2#727),閘道器過不了 broker。`chart.yaml` 釘的版本不晚於 rc.11 時 `bun test deploy/flux` 失敗。依 chart README「gateway-ops」的「展示叢集」,換版與打開分兩次合併,每次只有一種中斷:先合併換版的 PR(叢集內的 Postgres 因 `max_connections` 從 200 調到 240 重啟一次),穩定後再合併打開的 PR(broker 第一次啟動、migration Job 重跑、certs 為掛上 broker 帳號滾動更新)。
331+
- 換到含換發端點(Laysi/trustiot-v2#81)的版本之前,先完成[展示環境 runbook](../../docs/runbooks/demo-environment.md)「換 chart 版本」的「換發端點上線」:Cloudflare 的速率限制涵蓋 `/certs/renew`,並在換版的 issue 審查這個暴露。`/mqtt` 的速率限制要在 mqtt-bridge 部署之前就套用(runbook「`/mqtt` 的速率限制」)。
332+
- 打開後:新的 ingest、gateway-ops 與 certs pod 停在等待掛載,直到 broker 的 init container `accounts` 寫入它們的密碼(chart README「服務的帳號」),之後自己繼續。broker 一開始沒有閘道器埠 8883,等憑證 CronJob 的下一次執行(最多 15 分鐘)填入伺服器憑證,trust sidecar 讓 broker 重啟一次帶上 8883(chart README「伺服器憑證」的「儀式之前與第一次簽發」)。之後閘道器的 agent 在自己的退避間隔內(最多 60 秒)連上。確認的步驟在 runbook「打開閘道器的資料路徑」。
333+
325334
- **打開 AI 閘道**(Laysi/trustiot-v2#536):展示叢集部署的 chart 含 `deploy/images/ai-gateway` 之後,`values-demo.yaml` 加上下面的值,`trustiot-anthropic.sops.yaml` 已在 main 上並列在 `kustomization.yaml`(上表)。`credentials: [ai]` 是 chart 的預設,仍寫出來:`scripts/demo/flux.ts` 只從展示叢集的 values 判斷要哪些 Secret,看不到 chart 的預設。展示環境只有 Anthropic 的金鑰,所以角色只能用 `anthropic/` 的模型;角色名稱與模型由呼叫端的 issue 決定(AI 助理是 Laysi/trustiot-v2#145),沒有角色時 chart 拒絕渲染。`baseUrl` 留空是 Anthropic 自己的 API,要經 Cloudflare AI Gateway 時才設。閘道沒有呼叫端時誰都連不到它(`mesh.calls` 由呼叫端的 issue 加上),打開後確認 pod 就緒;金鑰能不能用,以 chart README「AI 閘道」的 port-forward 送一次最小的呼叫確認。
326335

327336
```yaml

‎deploy/flux/clusters/demo/trustiot/values-demo.yaml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ services:
5454
# (docs/runbooks/demo-environment.md, 「簽發 CA 的儀式」).
5555
certs:
5656
enabled: true
57+
# The gateway's data path (Laysi/trustiot-v2#795): mqtt-bridge takes the gateway's MQTT over
58+
# WebSocket on the gateway host's /mqtt (the chart's cookie-free public path `mqtt`, routed only
59+
# while mqtt-bridge is on) and relays it to the broker below; ingest writes the telemetry to
60+
# ClickHouse; gateway-ops tracks presence and delivers the configurations. No cookie route and no
61+
# Secret: the broker accounts and the database passwords are generated in the cluster.
62+
mqtt-bridge:
63+
enabled: true
64+
ingest:
65+
enabled: true
66+
gateway-ops:
67+
enabled: true
68+
69+
# The MQTT broker (deploy/chart/README.md, 「MQTT broker」). Its server certificate is issued in
70+
# the cluster by the certs service's issuing CA above, and certs keeps the gateways' broker
71+
# clients in step with Postgres (Laysi/trustiot-v2#633).
72+
mqttBroker:
73+
enabled: true
5774

5875
# The demo's own gateway CA (Laysi/trustiot-v2#619), set by the ceremony on 2026-10-28
5976
# (docs/runbooks/demo-environment.md, 「簽發 CA 的儀式」). Both are public: each is the

0 commit comments

Comments
 (0)