Ingress 與叢集 DNS:一個入口進來、一個名字相認

· tech

#kubernetes#networking

📑 目錄

第四篇給了短命 Pod 一個固定門牌 Service,但留了兩個尾巴:一是「外面怎麼用一個入口進來、再按網址分流到不同服務?」——那張對外類型圖裡最上面的 Ingress;二是叢集內服務彼此互打時,那個 http://web名字到底是誰在解析?這篇把兩件事收掉:對外靠 Ingress、對內靠叢集 DNS。它們是同一枚硬幣的兩面——都在解決「怎麼靠名字、而不是 IP 找到服務」。

先看對外這半邊,關鍵是 Service 與 Ingress 分工在不同的網路層:

使用者 一個網域 一個對外 IP GET shop.com/api Ingress L7:讀 HTTP 的 Host + path shop.com/ shop.com/api img.shop.com 順便在這裡卸 TLS(https → http) web-svc ClusterIP → web Pod api-svc ClusterIP → api Pod img-svc ClusterIP → img Pod 一個 IP、一個入口,按網址分流到多個內部 Service —— 這是 L4 的 Service 做不到的
Ingress 是 L7:它讀得懂 HTTP 的 Host 與 path,所以能用一個 IP 把不同網址分給不同 Service。相對地 Service(LoadBalancer)是 L4——只認 IP:port、看不到網址,每個對外服務就得配一個 IP

Ingress:一個入口,按網址分流

回想 Service 的 LoadBalancer 類型:每開一個對外服務,雲端就配一個對外 IP。十個服務就十個 IP、十筆帳單,而且它是 L4——只看目標 IP 與 port,看不到 HTTP 網址,沒辦法「同一個網域,/api 走這、/img 走那」。

Ingress 補的就是這層。 它是 L7(HTTP 層)的入口,一份 Ingress 規則長這樣:

apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
  rules:
    - host: shop.com
      http:
        paths:
          - path: /
            backend: { service: { name: web-svc, port: { number: 80 } } }
          - path: /api
            backend: { service: { name: api-svc, port: { number: 80 } } }
  tls:                       # https 在 Ingress 這一層卸掉,後端只收 http
    - hosts: [shop.com]
      secretName: shop-tls

一個對外 IP,靠 Host + path 把流量分給背後一堆 ClusterIP Service,還能順手在這層做 TLS 終結(把 https 解密成 http 再往後送,後端 Pod 不用各自處理憑證)。這正是 第四篇說的「Ingress 擺在前面 + 一堆內部 ClusterIP Service」那個最常見的組合。

一個坑:Ingress 只是「規則」,得有 Controller 來執行

這是最多人卡的地方:kubectl apply 一份 Ingress,什麼事都不會發生。 因為 Ingress 物件只是一張規則表——它需要一個真正在跑的 Ingress Controller(常見的是 ingress-nginx、Traefik)去讀這些規則、把自己內部的反向代理設好,才會真的有流量進來。

Ingress 物件 只是規則表(存 etcd) 監看 Ingress Controller 一個真在跑的 Pod 依規則設好反向代理 後端 Service 各個 ClusterIP 外部流量(前面通常還有一個 LoadBalancer) 又是 reconcile loop:Controller 把「代理設定」收斂成「規則的樣子」
Ingress 物件是「期望的路由規則」,Ingress Controller 才是真正監看規則、把反向代理設定收斂過去的那個 Pod。沒裝 Controller,Ingress 規則就只是躺在 etcd 裡沒人執行的一張紙

叢集裡可以同時跑好幾種 Controller,靠 IngressClass 指定某份 Ingress 該歸誰管。這又是那個熟悉的模式:你宣告期望(路由規則),一個 controller 持續把現實(代理設定)收斂過去——跟 DeploymentEndpoints 一模一樣的迴圈,只是這次收斂的是 nginx 的設定檔。

叢集 DNS:服務怎麼靠「名字」相認

換到對內這半邊。第四篇說服務之間用 http://web 這種名字互打就好,但誰把名字翻成 ClusterIP? 答案是叢集內建的 DNS——CoreDNS(以一個 Deployment 跑在 kube-system,再用一個 Service 對外)。每顆 Pod 建立時,它的 /etc/resolv.conf 都被指向 CoreDNS,所以 Pod 裡任何 DNS 查詢都會問到它。

每個 Service 自動有一個固定的 DNS 名字,規則是 <service>.<namespace>.svc.cluster.local:

web . default . svc . cluster.local Service 名 namespace 是個 Service 叢集網域 Pod 查「web」 同 namespace 用短名 補全名 CoreDNS kube-system 裡的 DNS ClusterIP 10.96.0.10 跨 namespace 就寫 web.other-ns;short name 只在同 namespace 有效
Service 的 DNS 名字四段各有意思。Pod 在同一個 namespace 用短名 web 就行——resolv.conf 的 search domain 會自動補成完整名,交給 CoreDNS 換出 ClusterIP;跨 namespace 才需要寫到 web.other-ns

實務上你幾乎只會用到短名:同 namespace 打 web、跨 namespace 打 web.payments 就夠了,後面那串 .svc.cluster.local 是 search domain 自動補的。有一個例外值得記:headless Service(clusterIP: None)不回傳單一虛擬 IP,而是回傳每一顆 Pod 的 IP,還讓每顆 Pod 有自己的 DNS 名字(pod-0.web.default.svc...)——這是 StatefulSet 那種「要點名到特定一顆」的有狀態服務才需要的,一般無狀態服務用普通 Service 就好。

反思

L4 與 L7 分清楚,「該用哪個」就不再猶豫

我剛接觸時,LoadBalancer 和 Ingress 常搞混——兩個看起來都是「對外」。分水嶺其實很利落:看不看得懂 HTTP。 Service 停在 L4,只認 IP:port,適合「就是要把這個 port 開出去」(資料庫、gRPC、非 HTTP 的東西);Ingress 站上 L7,讀得懂 Host 與 path,適合「一個網域下一堆 HTTP 服務、想在同一個入口收 TLS、按網址分流」。想通這條線之後,我的預設就變成:對外的 HTTP 一律走 Ingress、一個 IP 收全部;非 HTTP 或要獨立 IP 的才單開 LoadBalancer。 省 IP、省帳單,憑證也集中一處管。

「規則」和「執行規則的人」是兩回事——這觀念能救很多 debug

Ingress apply 下去卻沒反應,是新手最常見的鬼打牆,而它背後是一個更通用的道理:K8s 裡很多物件只是「期望」,得有一個 controller 在跑才會發生事情。 Ingress 要 Ingress Controller、NetworkPolicy 要 CNI 支援、CRD 要對應的 operator。我養成一個習慣:當某個資源「apply 了卻沒動靜」,第一反應不是懷疑寫錯,而是問——「執行這條規則的那個 controller,到底有沒有在跑?」 這一問常常直接命中要害。規則本身從不會自己動,動的永遠是那個盯著它的迴圈。

服務發現是 K8s 最被低估的送分題

從自建服務的年代一路走來,我對「服務發現」是有陰影的——Consul、Eureka、自己拼 etcd,光把「誰在哪」維護對就夠折騰。K8s 直接把這件事變成內建、零設定:建一個 Service 就自動有 DNS 名、CoreDNS 自動解析、Pod 換 IP 名字也不變。這跟 第四篇講的「依賴名字不依賴位置」是同一件事的底層支撐——正是因為有這套 DNS,「用名字相認」才從一個願望變成預設行為。 我現在會刻意提醒團隊別再自己造服務發現的輪子:K8s 已經把最難的那塊免費給你了,珍惜它。