Clawd Clawd 內心戲:

AI 時代的學習哲學:你不需要背 YAML 怎麼縮排、nginx.conf 的每一行怎麼寫。那些東西 AI 30 秒就能生出來,你背它幹嘛?

人類真正該掌握的是兩件事:概念(每一層是什麼、為什麼存在)和 trade-off(什麼時候該用什麼、用錯了會怎樣)。

所以這篇文章不會教你一行行設定 Ingress YAML 或 Istio VirtualService。我只教你「每層是什麼」和「什麼時候該用」。Implementation details?丟給 AI 就好。你是 Tech Lead,不是 YAML 工程師。

歡迎來到 Level-Up 系列第三篇。

情境:你剛進一間新公司,第一天 Tech Lead 就丟給你一個任務:

「我們有三個 FastAPI service —— user-serviceorder-servicenotification-service。現在它們各自跑在不同 port,我需要你把它們全部放到 api.mycompany.com 這個 domain 底下。」

你心想:三個 service、一個 domain?聽起來簡單吧?

然後你開始 Google,發現牽涉到 DNS、Ingress、Istio、URL prefix、Swagger… 每個關鍵字都是一個兔子洞。

別慌。這篇文章就是那棟大樓的藍圖。我們從地基(DNS)開始,一層一層往上蓋到屋頂(Swagger),蓋完你就知道整個 request 怎麼從使用者的瀏覽器跑到你的 FastAPI endpoint。

Let’s build 🏗️


🏗️ Floor 0:起點 — 三個 service,三個 port

⚔️ Level 0 / 7 一個 Domain,多個 Service
0% 完成

在一切開始之前,先看看你手上有什麼。

你有三個 FastAPI service,各自跑在不同的 port:

  • user-servicelocalhost:8001
  • order-servicelocalhost:8002
  • notification-servicelocalhost:8003

開發環境一切正常。三個 terminal 分別跑 uvicorn,想測哪個就打哪個 port。爽。

但是,你總不能叫使用者記住三個 port 吧?

「欸,你要查訂單的話打 api.mycompany.com:8002,要收通知的話打 api.mycompany.com:8003,記得不要打錯喔!」

這聽起來就像一間餐廳有三個門,吃牛排走左邊那個門、吃義大利麵走中間、喝飲料走右邊。客人走錯門就吃不到東西。

正常的做法是:一個大門進去,裡面再分流

現狀:使用者要記住三個 port,很痛苦

所以我們的目標是:

api.mycompany.com/users/...          → user-service
api.mycompany.com/orders/...         → order-service
api.mycompany.com/notifications/...  → notification-service

一個 domain,用 URL path 分流到不同 service。

要達成這件事,我們需要蓋好幾層樓。從最底層的 DNS 開始。

小測驗

為什麼不能直接讓使用者用不同 port 存取不同 service?


🏗️ Floor 1:DNS — 門牌號碼

⚔️ Level 1 / 7 一個 Domain,多個 Service
14% 完成

蓋房子第一步:你的地址要能被找到。

你跟公司申請了 api.mycompany.com 這個 domain。但 domain name 只是一串人類看得懂的文字,電腦不認識。電腦認識的是 IP 位址,像 34.120.56.78

DNS(Domain Name System) 就是負責把 domain name 翻譯成 IP 位址的系統。

當使用者在瀏覽器打 api.mycompany.com/users/123,第一件事不是連到你的 server,而是去問 DNS:

api.mycompany.com 的 IP 是什麼?」

DNS 回答:34.120.56.78

然後瀏覽器才真正連到 34.120.56.78 這台 server。

DNS 把 domain name 翻譯成 IP,瀏覽器才知道要連去哪

你需要做什麼?

去你的 DNS 管理介面(Cloudflare、Route 53、GoDaddy 等),新增一筆 A Record

api.mycompany.com  →  A  →  34.120.56.78

就這樣。DNS 這一層的工作就是「讓 domain 指向你的 server」,它不管你 server 上跑幾個 service,也不管 URL path 是什麼。它只負責把人導到正確的大樓門口。

Clawd Clawd 偷偷講:

DNS 還有一種叫 CNAME 的記錄,是「指向另一個 domain」而不是直接指向 IP。常見於用 CDN 或 cloud provider 的時候。但概念一樣 —— 最終都是為了讓 domain name 能被解析成一個 IP。

小測驗

DNS 在整個架構中負責什麼?


🏗️ Floor 2:Ingress — 大樓的大門警衛

⚔️ Level 2 / 7 一個 Domain,多個 Service
29% 完成

OK,DNS 把使用者帶到你的大樓門口了(IP 位址)。

但大樓門口不能只有一扇門直通一個房間。你有三個 service,使用者打不同的 URL path 應該進不同的房間。

這就是 Ingress 的工作。

在 Kubernetes 的世界裡,Ingress 是一個 API 物件,定義了「哪些外部 request 要被導到哪個 internal service」。

它的邏輯大概長這樣(概念版,不是真的 YAML):

如果 path 開頭是 /users      → 轉給 user-service
如果 path 開頭是 /orders     → 轉給 order-service
如果 path 開頭是 /notifications → 轉給 notification-service

Ingress 根據 URL path prefix 把 request 分配到不同 service

Ingress 不是自己跑的

一個很重要的概念:Ingress 本身只是一個「規則定義」,它需要一個 Ingress Controller 來真正執行這些規則。

常見的 Ingress Controller:

  • NGINX Ingress Controller — 最老牌、最多人用
  • Traefik — 自動化程度高、設定比較直覺
  • HAProxy — 效能取向

Ingress Controller 就是那個「真的站在門口指路的警衛」,Ingress 是「警衛手上的名單」。

Clawd Clawd 嘀咕一下:

很多人第一次接觸 Kubernetes 的時候都搞混 Ingress 和 Ingress Controller。Ingress 是你寫的規則(YAML),Ingress Controller 是執行規則的那個程式。你不能只寫規則不裝 Controller,就像你不能只寫員工手冊但不請警衛一樣。

Ingress 也處理 TLS

Ingress 通常也負責 TLS termination —— 就是 HTTPS 的那個 S。

使用者用 https://api.mycompany.com 連進來,Ingress 負責解密(因為 SSL 憑證掛在 Ingress 上),然後用 HTTP 把 request 轉給內部的 service。

所以你的 FastAPI service 不需要自己處理 SSL,交給 Ingress 就好。

小測驗

以下哪個描述最正確?


🏗️ Floor 3:Istio — 大樓的智慧管理系統

⚔️ Level 3 / 7 一個 Domain,多個 Service
43% 完成

Ingress 讓你可以根據 URL path 分流。很好,夠用了嗎?

如果你的需求只是「path A 去 service A、path B 去 service B」,那 Ingress 就夠了。收工回家。

但如果你的需求是這些呢?

  • 新版 API 只先開放 10% 的 traffic(canary deployment
  • service A 呼叫 service B 的時候自動重試 3 次(retry policy
  • service 之間的通訊全部加密(mTLS
  • 我想看每個 request 的延遲、錯誤率(observability
  • 某個 service 掛了,自動把 traffic 導走(circuit breaking

這些事情 Ingress 做不到。你需要更強大的東西:Istio

Istio 的核心概念:Sidecar

Istio 最特別的設計是 sidecar proxy

它在每個 service 旁邊塞一個小小的 proxy(通常是 Envoy),所有進出這個 service 的 traffic 都會經過這個 proxy。

不是這樣:  User → Service A → Service B

而是這樣:  User → [Proxy A] → Service A → [Proxy A] → [Proxy B] → Service B

這些 proxy 會自動做加密、重試、記錄延遲… 你的 code 完全不用改。

Istio 在每個 service 旁邊放一個 Envoy proxy,管理所有 traffic

Istio 取代 Ingress?

嚴格來說,如果你用了 Istio,Istio Gateway + VirtualService 可以取代 Kubernetes Ingress 的功能。它做的事一樣(根據 host 和 path 分流),但功能更強大。

不過「可以取代」不代表「一定要取代」。這個 trade-off 我們留到 Boss Floor 再講。

Clawd Clawd 碎碎念:

Istio 的學習曲線是出了名的陡。很多團隊裝了 Istio 之後才發現,其實他們 80% 的需求用 Ingress 就搞定了。剩下 20% 的 canary deployment,用 Argo Rollouts 也能做。所以裝 Istio 之前請三思。

小測驗

Istio sidecar proxy 的運作方式是?


🏗️ Floor 4:FastAPI × Swagger × root_path — 每個 service 的「室內裝潢」

⚔️ Level 4 / 7 一個 Domain,多個 Service
57% 完成

好,外部的大樓結構蓋好了(DNS → Ingress/Istio)。從這層開始,我們要處理每個 service 內部的事情。

這一層東西比較多,所以我們拆成四個小節慢慢來。


🔹 Floor 4-0:FastAPI 是什麼?— 每個 service 的骨架

在蓋「室內裝潢」之前,先搞清楚你的 service 是用什麼蓋的。

FastAPI 是一個 Python web framework,專門用來寫 API。你的三個 service —— user-serviceorder-servicenotification-service —— 每一個都是用 FastAPI 寫的。

一個最簡單的 FastAPI service 長這樣:

from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"id": user_id, "name": "Alice"}

就這樣。五行 code,你就有一個 API endpoint:GET /users/123 會回傳 {"id": 123, "name": "Alice"}

但 FastAPI 最殺手級的功能不是「寫 API 很快」—— 而是它自動幫你生成一份互動式 API 文件

啟動 service 之後,打開 http://localhost:8001/docs,你會看到這個畫面:

FastAPI 看你的 code,自動生成互動式 Swagger UI 文件

Swagger UI 是開發者的好朋友 —— 前端工程師看它知道要怎麼串 API、你自己開發時可以直接在上面測試。

到目前為止一切美好。但當你的 service 被放到 Ingress 後面(也就是我們 Floor 2 和 3 建的那個大樓結構),問題就來了。


🔹 Floor 4-1:路徑錯位 — Ingress 轉進來的 path,FastAPI 認不認得?

你的 user-service 在本地跑的時候,endpoint 是:

http://localhost:8001/users/123    ✅ 正常運作

現在掛到 Ingress 後面,使用者打的是:

https://api.mycompany.com/users/123

Ingress 看到 /users 開頭,知道要轉給 user-service。但問題來了:

Ingress 轉給 FastAPI 的 path 到底是 /users/123 還是 /123

這取決於 Ingress 有沒有做 path rewrite(路徑改寫)。

情況一:不改寫 path

Ingress 把 /users/123 原封不動地轉給 user-service

FastAPI 收到的是 /users/123,那你的 code 要這樣寫:

@app.get("/users/{user_id}")  # ← 帶 /users prefix
def get_user(user_id: int):
    return {"id": user_id}

情況二:改寫 path(strip prefix)

Ingress 把 /users/123/users prefix 剝掉,只把 /123 轉給 user-service

FastAPI 收到的是 /123,code 可以寫得比較乾淨:

@app.get("/{user_id}")  # ← 不需要 /users prefix
def get_user(user_id: int):
    return {"id": user_id}

Ingress 有沒有做 path rewrite,決定了 FastAPI endpoint 怎麼寫

大多數團隊的做法是「改寫」(strip prefix),因為這樣每個 service 不用知道自己掛在哪個 path 底下,更好維護。

到這裡 API routing 已經通了。但故事還沒完 —— 你打開 Swagger UI 試了一下,發現怪怪的…

小測驗

Ingress 做 path rewrite(strip prefix)的好處是?


🔹 Floor 4-2:root_path — 「嘿 FastAPI,你被掛在 /users 底下」

你選了「Ingress 做 path rewrite」,API routing 一切正常。

然後你打開 https://api.mycompany.com/users/docs 看 Swagger UI,點了 GET /{user_id}“Try it” 按鈕,填了 user_id = 123,按下 Execute

❌ 404 Not Found

蛤?API 明明能用,為什麼 Swagger 的 “Try it” 壞了?

看一下 Swagger UI 實際送出的 request:

GET https://api.mycompany.com/123    ← 😱 少了 /users!

正確應該是:

GET https://api.mycompany.com/users/123    ← ✅ 這才對

問題在哪?

FastAPI 生成 Swagger UI 的時候,它不知道自己被 Ingress 掛在 /users 底下。它以為自己就是住在根目錄 /。所以 Swagger 的 “Try it” 會直接打 /123,而不是 /users/123

解法:設定 root_path

app = FastAPI(root_path="/users")  # ← 告訴 FastAPI:你被掛在 /users 底下

@app.get("/{user_id}")
def get_user(user_id: int):
    return {"id": user_id}

加了 root_path="/users" 之後:

  • API routing 完全不受影響@app.get("/{user_id}") 還是只 match /123
  • Swagger UI 知道了 — “Try it” 會正確地送 GET /users/123

root_path 讓 Swagger UI 知道真實的 base URL,Try it 才會打對地方

延伸閱讀

Clawd Clawd 認真說:

root_path 是 ASGI 標準(不是 FastAPI 獨有的)。它只影響 OpenAPI schema 裡的 servers 欄位,不影響 routing。簡單說:root_path 是給文件看的,不是給 router 看的。

技術上,root_path 會讓 FastAPI 生成的 openapi.json 裡出現 "servers": [{"url": "/users"}]。Swagger UI 讀到這個就知道所有 endpoint 都要加上 /users prefix。


🔹 Floor 4-3:完整拼圖 — 推薦做法 + 常見雷

把前面幾小節串起來,大多數團隊的推薦做法是:

  1. Ingress 做 path rewrite(strip /users prefix)
  2. FastAPI 設 root_path="/users"(讓 Swagger 知道真實路徑)
  3. Endpoint 不帶 prefix(乾淨好維護)
# user-service/main.py
from fastapi import FastAPI

app = FastAPI(root_path="/users")

@app.get("/")          # 外面打 /users/        → Ingress strip → FastAPI 收到 /
@app.get("/{user_id}") # 外面打 /users/123     → Ingress strip → FastAPI 收到 /123
def get_user(user_id: int):
    return {"id": user_id}

# Swagger UI:https://api.mycompany.com/users/docs
# Try it GET /users/123 → ✅ 正確!

常見雷:忘記設 root_path

症狀原因
API 正常但 Swagger “Try it” 404root_path 沒設,Swagger 送錯 URL
Swagger 頁面打得開但 endpoint 列表是空的openapi.json 的路徑也被 prefix 影響了
本地開發沒問題、部署後 Swagger 壞掉本地沒有 Ingress,部署後才有 path prefix 差異
Clawd Clawd 嘀咕一下:

這個坑真的非常經典。幾乎每個第一次把 FastAPI 放到 reverse proxy 後面的人都會踩到。如果你 Google “FastAPI behind nginx swagger not working”,會看到幾百個 Stack Overflow 問題。答案永遠是:設 root_path

小測驗

你的 FastAPI service 被 Ingress 掛在 /users 底下(有 strip prefix)。API 正常但 Swagger 的 Try it 回 404。最可能的原因是?


🏗️ Floor 5:Swagger / OpenAPI — 屋頂上的導覽地圖

⚔️ Level 5 / 7 一個 Domain,多個 Service
71% 完成

大樓蓋好了,使用者可以正確地打到每個 service。但還有一個問題:

「欸,你們家 API 有文件嗎?我怎麼知道有哪些 endpoint、要帶什麼參數?」

FastAPI 內建 Swagger UI —— 每個 service 都自動生成一份互動式 API 文件:

  • api.mycompany.com/users/docs → user-service 的 Swagger
  • api.mycompany.com/orders/docs → order-service 的 Swagger
  • api.mycompany.com/notifications/docs → notification-service 的 Swagger

但你有沒有覺得哪裡怪怪的?

三個 Swagger,三個入口。 每次要查 API 都要記不同的網址。前端工程師會想打你。

統一 Swagger 的做法

核心概念是:每個 FastAPI service 都會在 /openapi.json 自動生成一份 OpenAPI spec(JSON 格式的 API 描述檔)。

你可以寫一個 API Gateway service 或用工具把這些 spec 合併成一份,然後用統一的 Swagger UI 呈現:

api.mycompany.com/docs
  ├── 包含 user-service 的所有 endpoint
  ├── 包含 order-service 的所有 endpoint
  └── 包含 notification-service 的所有 endpoint

統一 Swagger UI 從每個 service 拉取 OpenAPI spec,合併呈現

常見做法

  1. 各自獨立:每個 service 有自己的 /docs,不合併。適合 service 之間差異大、團隊獨立運作的情況。
  2. 手動合併:寫一個 gateway 去拉各 service 的 openapi.json 然後合併。
  3. 用工具:像 Swagger UI 的 urls 選項 可以在一個頁面切換多個 spec。
  4. API Gateway 整合:Kong、AWS API Gateway 等自帶統一文件功能。
Clawd Clawd 碎碎念:

說實話,很多團隊根本不合併 Swagger,就讓每個 service 各自有 /docs。因為合併的維護成本不低,而且 spec 格式不一致的時候合併會爆炸。如果你的團隊只有 3-5 個 service,各自獨立就好,不用硬合併。人生苦短。

小測驗

統一 Swagger UI 的核心做法是?


🏗️ Boss Floor:Trade-off 分析

⚔️ Level 6 / 7 一個 Domain,多個 Service
86% 完成

恭喜你蓋到頂樓了!現在你知道每一層是什麼。

但 Tech Lead 真正在意的不是「這個東西是什麼」,而是「什麼時候該用什麼」。

這就是 trade-off 的世界。

Trade-off 1:Ingress vs Istio

純 IngressIstio
路由✅ path / host 分流✅ 更細緻(header、weight)
TLS✅ 外部 TLS✅ 外部 + 內部 mTLS
Canary❌ 要搭配其他工具✅ 原生支援 weight-based routing
Observability❌ 自己接✅ 內建 metrics、tracing
學習曲線🟢 低🔴 高
資源消耗🟢 低🔴 每個 Pod 多一個 sidecar
Debug 難度🟢 單純🔴 多了一層 proxy 要查

結論

  • 3-5 個 service、小團隊 → 純 Ingress。別裝 Istio,你會後悔。
  • 10+ 個 service、需要 canary、mTLS、統一 observability → 上 Istio,但要有專人維護。
  • 中間地帶 → 用 Ingress + Argo Rollouts(canary)+ Linkerd(比 Istio 輕量的 service mesh)。
Clawd Clawd 的 murmur:

我看過太多團隊「因為 Istio 很潮」就裝了,結果 debug 一個 503 要查 Envoy proxy 的 log、Istio control plane 的設定、destination rule 的 mTLS mode… 原本 5 分鐘能解決的問題變成 2 小時。技術選型不是追流行,是解決問題。

Trade-off 2:URL prefix 設計原則

你的 URL 要怎麼切?這裡有幾個常見的 pattern:

Pattern A:按 resource 切(最常見)

/users/...
/orders/...
/notifications/...

每個 prefix 對應一個 service。直覺、好懂。

Pattern B:按版本切

/v1/users/...
/v2/users/...

version prefix 在最前面。適合需要同時維護多版本 API 的情況。

Pattern C:按 domain 切

users.api.mycompany.com/...
orders.api.mycompany.com/...

用 subdomain 而不是 path prefix。好處是每個 service 完全獨立(不同 DNS、不同 TLS cert),壞處是 DNS 和 cert 管理變複雜。

怎麼選?

  • 大多數情況 → Pattern A。簡單、一個 Ingress 搞定。
  • 需要版本管理 → Pattern B。但考慮用 header(Accept: application/vnd.myco.v2+json)可能比 URL 版本更優雅。
  • service 團隊完全獨立、想各自管理部署 → Pattern C

Trade-off 3:要不要統一 Swagger?

各自獨立統一合併
維護成本🟢 零🔴 需要維護合併邏輯
使用者體驗🟡 要記多個網址🟢 一個入口看所有 API
適合內部用、service 少對外 API、service 多

完整架構回顧

最後,讓我們把整棟大樓從下到上看一遍:

完整架構:DNS → Ingress → FastAPI → Swagger,每一層各司其職

小測驗

你的團隊有 3 個 FastAPI service,5 個工程師。Tech Lead 問你要不要用 Istio。你的建議是?


🎓 恭喜通關!

⚔️ Level 7 / 7 一個 Domain,多個 Service
100% 完成

你從 Floor 0 的「三個 port」一路蓋到了 Boss Floor 的「trade-off 分析」。回顧一下每一層學了什麼:

Floor學到了什麼
Floor 0多 port 暴露 → 不好管理,需要統一入口
Floor 1DNS 把 domain 翻譯成 IP,是最底層的地基
Floor 2Ingress 根據 URL path 分流到不同 service
Floor 3Istio 是進階的 service mesh,管 traffic、mTLS、observability
Floor 4FastAPI 的 root_path 讓 Swagger 路徑正確顯示
Floor 5OpenAPI spec 可以統一或各自獨立呈現
Boss Floor小團隊用 Ingress 就好,Istio 等真的需要再上

記住這篇的核心心法:

概念搞懂、trade-off 想清楚,implementation details 讓 AI 幫你寫。

下一篇 Level-Up 見 🍄 ( •̀ ω •́ )✧