網路基礎 (Networking Fundamentals)¶
本章定位:在學習 Kubernetes / EKS / eBPF 之前,先把網路的核心觀念與直覺建立起來。 我們的目標不是「背指令」,而是理解「為什麼網路要這樣設計」,這樣之後看 Pod 網路、CNI、kube-proxy、Service Mesh 時才不會迷路。
當你日後排查「Pod 連不到 Service」「DNS 解析失敗」「LoadBalancer 沒流量進來」這類問題時,真正派得上用場的不是 K8s 指令,而是你對封包 (Packet) 在網路上如何移動的理解。所以這一章值得你慢慢讀。
0. 為什麼學 K8s 要先學網路?¶
Kubernetes 本質上是一套「在許多台機器上,自動安排容器,並讓它們彼此能透過網路溝通」的系統。它幾乎所有的核心抽象都跟網路有關:
| K8s 概念 | 背後其實是什麼網路技術 |
|---|---|
| Pod 有自己的 IP | 虛擬網路介面 + 路由 (Routing) |
| Service (ClusterIP) | 第四層負載平衡 (L4 Load Balancing) + 網路位址轉譯 (NAT) |
| Service 怎麼導流 | iptables / IPVS / eBPF 規則 |
| Ingress | 第七層 (L7) 反向代理 |
| DNS 名稱解析 | CoreDNS,本質是一台 DNS 伺服器 |
| NetworkPolicy | 防火牆規則 (Firewall Rules) |
換句話說,K8s 沒有發明新的網路,它只是把傳統網路技術「自動化、宣告式化」。所以先懂底層,K8s 的一切才會變得理所當然。
1. OSI 七層與 TCP/IP 模型 (OSI Model & TCP/IP Model)¶
1.1 兩種分層模型的對照¶
網路為了「分工」與「解耦」,把通訊拆成好幾層。每一層只需要關心自己的工作,並信任下一層會把資料送出去。這就像寄信:你只管寫信封 (應用層),郵差怎麼開車送 (實體層) 你不用管。
graph TD
subgraph OSI 七層模型
A7[L7 應用層 Application]
A6[L6 表現層 Presentation]
A5[L5 會議層 Session]
A4[L4 傳輸層 Transport]
A3[L3 網路層 Network]
A2[L2 資料連結層 Data Link]
A1[L1 實體層 Physical]
end
A7 --> A6 --> A5 --> A4 --> A3 --> A2 --> A1
| OSI 層 | TCP/IP 模型 | 代表協定 / 物件 | 你會在 K8s 哪裡遇到 |
|---|---|---|---|
| L7 應用層 (Application) | 應用層 | HTTP, gRPC, DNS, TLS | Ingress、Service Mesh、CoreDNS |
| L6 表現層 (Presentation) | 應用層 | TLS 加解密、編碼 | TLS termination |
| L5 會議層 (Session) | 應用層 | 連線狀態管理 | 多半被合併 |
| L4 傳輸層 (Transport) | 傳輸層 | TCP, UDP、連接埠 (Port) | Service (ClusterIP/NodePort) |
| L3 網路層 (Network) | 網際網路層 | IP, ICMP, 路由 | Pod IP、CNI、路由表 |
| L2 資料連結層 (Data Link) | 連結層 | MAC, ARP, 乙太網路 | bridge、veth pair |
| L1 實體層 (Physical) | 連結層 | 網路線、電訊號 | 雲端的底層網路 |
1.2 實務上你最常碰的是 L2 / L3 / L4 / L7¶
不要被七層嚇到。在 K8s 與雲原生的世界,99% 的時間你只需要掌握這四層:
- L2 (資料連結層):同一個網段 (Subnet) 內,靠 MAC 位址 與 ARP 找到對方。K8s 的 Pod 透過 veth pair 接到節點的虛擬橋接器,就是 L2 的事。
- L3 (網路層):不同網段之間,靠 IP 位址 與路由 (Routing) 轉送封包。「每個 Pod 一個 IP」就是 L3 的設計。
- L4 (傳輸層):用連接埠 (Port) 區分同一台主機上的不同服務,TCP 保證可靠、UDP 追求快。K8s Service 工作在這層。
- L7 (應用層):看得懂 HTTP 的網址、Header、Cookie。K8s Ingress 工作在這層。
直覺記法:層數越高,越「懂」應用內容;層數越低,越「只管搬運」。 L3 只看得到「IP 對 IP」;L7 看得到「使用者要存取
/api/login」。
1.3 封裝 (Encapsulation):資料是怎麼一層層包起來的¶
當你用瀏覽器送出一個 HTTP 請求,資料會由上往下「一層包一層」,每層加上自己的標頭 (Header):
graph LR
HTTP[HTTP 資料] --> TCP[加上 TCP 標頭<br/>含來源/目的 Port]
TCP --> IP[加上 IP 標頭<br/>含來源/目的 IP]
IP --> ETH[加上乙太網路標頭<br/>含來源/目的 MAC]
ETH --> WIRE[變成電訊號送出]
接收端則反過來「一層層拆開」。理解這個「洋蔥結構」很重要 —— 當你之後用 tcpdump 抓封包時,看到的就是這些一層層的標頭。
動手練習:觀察你機器上的網路分層¶
# 查看本機的網路介面 (L2/L3 資訊:MAC 位址與 IP 位址)
ip addr show
# 查看路由表 (L3:封包要往哪個網段送)
ip route show
# 查看目前監聽中的連接埠 (L4:哪些服務正在等連線)
ss -tlnp
# 用 curl 觀察一次完整的 L7 (HTTP) 請求與回應標頭
curl -v https://example.com
2. IP 位址、子網路與 CIDR (IP Address, Subnet & CIDR)¶
2.1 IP 位址是什麼¶
IPv4 位址 是一個 32 位元的數字,習慣寫成四段十進位 (點分十進位),例如 192.168.1.10。每段 0~255,佔 8 位元 (1 byte),四段共 32 位元。
一個 IP 位址在邏輯上分成兩部分:
- 網路部分 (Network):決定你在「哪個社區 (網段)」。
- 主機部分 (Host):決定你是「社區裡的哪一戶」。
到底前面幾位元是網路、後面幾位元是主機?這就是子網路遮罩 (Subnet Mask) 要回答的。
2.2 子網路遮罩 (Subnet Mask) 與 CIDR¶
子網路遮罩用來「切」出網路部分。它也是 32 位元,規則是:前面連續的 1 對應網路部分,後面連續的 0 對應主機部分。
| 表示法 | 子網路遮罩 | CIDR | 可用主機數 (大約) |
|---|---|---|---|
| Class C 等級 | 255.255.255.0 | /24 | 256 - 2 = 254 |
| 半個 C | 255.255.255.128 | /25 | 128 - 2 = 126 |
| Class B 等級 | 255.255.0.0 | /16 | 65536 - 2 |
| 單一主機 | 255.255.255.255 | /32 | 1 |
CIDR (Classless Inter-Domain Routing,無類別域間路由) 就是「斜線記法」,例如 192.168.1.0/24,斜線後的數字代表「網路部分佔幾個位元」。/24 代表前 24 位元是網路,剩下 8 位元 (256 個位址) 是主機。
你之後設定 K8s 叢集時,會看到
--pod-network-cidr=10.244.0.0/16或--service-cidr=10.96.0.0/12,現在你就知道這是在「規劃 Pod 與 Service 各自能用的 IP 範圍」。
2.3 網段切割 (Subnetting) 的計算直覺¶
關鍵公式:
- 主機位元數 = 32 − CIDR 前綴
- 可容納的位址總數 = 2^(主機位元數)
- 可用主機數 = 位址總數 − 2(扣掉「網路位址」與「廣播位址」)
範例:10.0.0.0/24
- 主機位元 = 32 − 24 = 8
- 位址總數 = 2^8 = 256(10.0.0.0 ~ 10.0.0.255)
- 網路位址 = 10.0.0.0(代表整個網段)
- 廣播位址 = 10.0.0.255(送給網段內全部主機)
- 可用主機 = 10.0.0.1 ~ 10.0.0.254,共 254 個
為什麼 K8s 要關心這個? 因為「每個 Pod 一個 IP」,如果你的 Pod CIDR 切太小,Pod 數量一多就會「IP 用完」,Pod 卡在
ContainerCreating。網段規劃是叢集容量規劃的一部分。
2.4 私有網段 (Private IP Ranges)¶
有三段 IP 是 RFC 1918 保留給「私有網路」用的,它們不會出現在公網上,可以在你家、公司、雲端 VPC 內自由使用:
| CIDR | 範圍 | 常見用途 |
|---|---|---|
10.0.0.0/8 |
10.0.0.0 ~ 10.255.255.255 | 雲端 VPC、K8s Pod/Service 網段最常用 |
172.16.0.0/12 |
172.16.0.0 ~ 172.31.255.255 | Docker 預設 bridge (172.17.0.0/16) |
192.168.0.0/16 |
192.168.0.0 ~ 192.168.255.255 | 家用 / 小型辦公室路由器 |
另外 127.0.0.0/8(127.0.0.1 localhost)是回送位址 (Loopback),永遠指向自己。在 K8s 中,同一個 Pod 內的多個容器共享網路命名空間,所以它們之間可以用 localhost 互通 —— 這是個非常重要的設計,後面會再提。
動手練習:計算與觀察網段¶
# 查看自己 IP 與所屬網段 (看 inet 後面的 /xx 就是 CIDR)
ip -4 addr show
# 用 ipcalc 計算網段資訊 (若沒安裝:sudo apt install ipcalc)
ipcalc 10.0.0.0/24
# 觀察輸出的 Network / Broadcast / HostMin / HostMax
# 自己心算驗證:10.244.5.0/24 這個網段
# - 網路位址?廣播位址?可用主機數?
3. L4 (TCP/UDP) vs L7 (HTTP)¶
3.1 連接埠 (Port):同一個 IP 上的「房間號碼」¶
IP 位址 帶你到「正確的主機」,但一台主機上可能同時跑著 Web 伺服器、資料庫、SSH……要怎麼區分?答案是連接埠 (Port),一個 16 位元的數字 (0~65535)。
- IP + Port 合起來,才能唯一定位「某主機上的某個服務」,這個組合叫 Socket。
- 常見的「公認埠 (Well-known Ports)」:HTTP=80、HTTPS=443、SSH=22、DNS=53。
3.2 TCP vs UDP¶
| 特性 | TCP (傳輸控制協定) | UDP (使用者資料報協定) |
|---|---|---|
| 連線 | 連線導向 (先交握) | 無連線 (直接送) |
| 可靠性 | 保證送達、會重傳、有順序 | 不保證、不重傳 |
| 速度/開銷 | 較慢、開銷大 | 較快、開銷小 |
| 適用情境 | HTTP、資料庫、SSH | DNS 查詢、即時影音、遊戲 |
3.3 三向交握 (Three-way Handshake)¶
TCP 在傳資料前,必須先「握手三次」確認雙方都準備好:
sequenceDiagram
participant C as 用戶端 (Client)
participant S as 伺服器 (Server)
C->>S: 1. SYN (我想連線,序號 x)
S->>C: 2. SYN + ACK (好,我收到了,我的序號 y)
C->>S: 3. ACK (確認,開始傳資料)
Note over C,S: 連線建立,開始傳輸資料
為什麼要懂這個?當你
telnet或curl一個服務「卡住沒回應」,問題往往就出在三向交握 —— 可能是防火牆規則 (iptables) 把 SYN 丟掉了,或目的 Port 根本沒人監聽。理解交握能讓你精準判斷「卡在哪一步」。
3.4 為什麼 K8s Service 是 L4,而 Ingress 是 L7?¶
這是本章最重要的觀念之一,請務必弄懂:
-
Service (ClusterIP / NodePort / LoadBalancer) 工作在 L4。 它只認得「IP + Port」與「TCP/UDP」。它把進來的封包,依照規則轉送 (NAT) 到後端某個 Pod 的 IP+Port。它看不懂 HTTP 的網址路徑。你給它一個 TCP 連線,它就幫你導到某個 Pod,如此而已。 → 優點:快、通用 (任何 TCP/UDP 協定都能轉,不限 HTTP)。
-
Ingress 工作在 L7。 它看得懂 HTTP,所以可以根據「網址路徑 (
/apivs/web)」或「主機名稱 (a.example.comvsb.example.com)」做不同的導流,還能處理 TLS 終結、HTTP 標頭改寫。 → 優點:聰明、能做應用層路由;代價:只懂 HTTP/HTTPS,開銷較大。
graph TD
U[使用者請求 a.com/api] --> ING[Ingress L7<br/>看得懂網址路徑與主機名]
ING -->|/api| SVC1[Service A L4]
ING -->|/web| SVC2[Service B L4]
SVC1 --> P1[Pod]
SVC1 --> P2[Pod]
SVC2 --> P3[Pod]
一句話總結:Ingress 負責「聰明地分流到正確的 Service」,Service 負責「把流量公平地分給後端 Pod」。 兩者是上下游關係,不是二選一。
動手練習:觀察 L4 與 L7¶
# 觀察三向交握 (抓本機對 443 的 TCP 封包,看 SYN/ACK 旗標)
sudo tcpdump -i any -n 'tcp port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
# 開另一個終端機執行 curl https://example.com,回來看 tcpdump 輸出
# 看 L4:目前有哪些已建立的 TCP 連線 (State=ESTAB)
ss -tnp
# 看 L7:用 curl 取得 HTTP 應用層的完整互動 (狀態碼、標頭)
curl -sv https://example.com -o /dev/null
4. DNS 解析流程 (DNS Resolution)¶
4.1 為什麼需要 DNS¶
人記得住 google.com,記不住 142.250.x.x。DNS (Domain Name System,網域名稱系統) 就是「網路的電話簿」,負責把好記的網域名稱翻譯成 IP 位址。
4.2 一次查詢的旅程¶
sequenceDiagram
participant APP as 應用程式
participant R as 解析器 (Resolver)
participant Root as 根伺服器 (.)
participant TLD as 頂級網域 (.com)
participant AUTH as 權威伺服器 (example.com)
APP->>R: example.com 的 IP 是?
R->>Root: 我問 .com 去哪找?
Root-->>R: 去問 .com 的 TLD 伺服器
R->>TLD: example.com 去哪找?
TLD-->>R: 去問 example.com 的權威伺服器
R->>AUTH: example.com 的 A 紀錄?
AUTH-->>R: 93.184.216.34
R-->>APP: 答案是 93.184.216.34 (並快取)
重點觀念:
- 快取 (Cache) 無所不在:解析器、作業系統、瀏覽器都會快取,所以多數查詢不會真的跑完整趟。這也是為什麼「改了 DNS 要等 TTL 過期才生效」。
- 常見紀錄類型:A(域名→IPv4)、AAAA(→IPv6)、CNAME(別名)、SRV(服務位置)。
4.3 預告:K8s 內部 DNS 與 CoreDNS¶
在 Kubernetes 裡,每個 Service 都會自動得到一個 DNS 名稱,例如:
負責回答這些「叢集內部域名」的,就是跑在叢集裡的 CoreDNS(一台 DNS 伺服器,本身也是用 Pod 跑的)。當你的 Pod 想連 my-service,它會:
- 向 CoreDNS 查詢
my-service...svc.cluster.local - CoreDNS 回傳該 Service 的 ClusterIP
- Pod 把封包送往 ClusterIP,接著就交給 L4 (Service) 與 NAT/iptables 接手
所以日後遇到「Pod 連不到別的服務」,第一個要懷疑的往往就是 DNS。你會非常頻繁地在 Pod 裡執行
nslookup/dig來除錯。本章先把 DNS 流程記熟,K8s 的 Service Discovery 就只是「把這套搬進叢集」。
動手練習:解析 DNS¶
# 用 dig 查 A 紀錄,觀察 ANSWER SECTION 與 TTL
dig example.com
# 只看最後答案 (簡潔輸出)
dig +short example.com
# 追蹤完整遞迴過程:根 -> TLD -> 權威 (理解上面那張圖)
dig +trace example.com
# 查 CNAME / MX 等不同紀錄
dig www.github.com CNAME
dig gmail.com MX
# 查看本機用哪台 DNS 伺服器
cat /etc/resolv.conf
5. 路由與預設閘道 (Routing & Default Gateway)¶
5.1 封包要怎麼決定往哪走?¶
當主機要送一個封包,它會問自己一個關鍵問題:
「目的 IP 跟我在同一個網段嗎?」
- 在同一個網段 → 直接透過 L2 (ARP 找到對方 MAC) 送過去,不需要路由器。
- 不在同一個網段 → 我自己到不了,只好把封包交給「預設閘道 (Default Gateway)」,請它幫忙轉送。
預設閘道 (Default Gateway) 通常就是你的路由器,它連著外面更大的網路,知道怎麼把封包往外送。
graph TD
A[主機要送封包到目的 IP] --> B{目的 IP 在我的網段內?}
B -->|是| C[ARP 找 MAC,L2 直送]
B -->|否| D[送給預設閘道 Default Gateway]
D --> E[閘道依自己的路由表繼續轉送]
E --> F[一站一站轉送直到目的地]
5.2 路由表 (Routing Table)¶
每台主機都有一張路由表,記錄「要去某個網段,該往哪個介面/下一跳 (next hop) 送」。它是「由上往下、最精確 (最長前綴) 優先」比對的。
# 查看路由表
ip route show
# 典型輸出解讀:
# default via 192.168.1.1 dev eth0 <- 預設路由:不認得的目的地都丟給 192.168.1.1
# 192.168.1.0/24 dev eth0 ... <- 直連網段:這段我自己就能送達
# 10.244.0.0/24 dev cni0 ... <- (K8s 節點上常見) 本機 Pod 網段走 cni0
K8s 的伏筆:節點上會有一堆「往各個 Pod 網段的路由」。CNI 外掛 (如 Calico、Flannel) 的核心工作之一,就是在每個節點上「設定正確的路由,讓 A 節點的 Pod 封包能找到 B 節點的 Pod」。所以看懂
ip route,就看懂了一半的 CNI。
動手練習:追蹤封包的路徑¶
# 查路由表:封包去 8.8.8.8 會走哪條路由?
ip route get 8.8.8.8
# 看封包實際經過哪些路由器 (每一跳 hop)
traceroute 8.8.8.8 # 或 tracepath 8.8.8.8
# 觀察 ARP 表 (本網段內 IP 對 MAC 的對應)
ip neigh show
# 找出你的預設閘道是誰
ip route | grep default
6. NAT 與連接埠轉發 (NAT & Port Forwarding)¶
6.1 NAT 解決什麼問題?¶
私有網段 (如 10.0.0.0/8) 的 IP 不能在公網上路由。那一台私有 IP 的 Pod 要怎麼連到外面的 8.8.8.8?答案是 網路位址轉譯 (NAT, Network Address Translation)。
NAT 由閘道 (路由器 / K8s 節點) 執行:它在封包出去時,把「私有來源 IP」改寫成「自己的公有 IP」,並記住這個對應關係;回應封包回來時,再改寫回原本的私有 IP,送回正確的內部主機。
graph LR
P["Pod 10.244.1.5"] -->|來源=10.244.1.5| NAT[節點/閘道<br/>做 SNAT]
NAT -->|來源改寫成 節點公有IP| NET[外部網際網路]
NET -->|回應送回節點公有IP| NAT
NAT -->|改寫回 10.244.1.5| P
- SNAT (來源 NAT):改寫「來源位址」。Pod 連外用的就是這種 —— 對外界而言,所有 Pod 看起來都像是「節點」在連線。
- DNAT (目的 NAT):改寫「目的位址」。這正是 K8s Service 的核心 —— 你連 ClusterIP,iptables 用 DNAT 把目的改寫成某個實際 Pod 的 IP。
6.2 連接埠轉發 (Port Forwarding)¶
連接埠轉發是 DNAT 的一種:「把送到 閘道IP:8080 的流量,轉送到 內部IP:80」。
你一定用過它:
- Docker 的 -p 8080:80,就是把主機的 8080 埠轉發到容器的 80 埠。
- K8s 的 NodePort Service,就是「把每個節點的某個高位埠 (如 30080),DNAT 轉發到後端 Pod」。
- kubectl port-forward,則是建立一條從你本機到 Pod 的通道。
觀念串接:Service(ClusterIP)= 對內的 DNAT 負載平衡;NodePort = 對外開一個節點埠做轉發;Pod 連外 = SNAT。整個 K8s 的網路,大量建立在 NAT 之上。
動手練習:觀察 NAT¶
# 查看 NAT 表的規則 (需 root;Docker/K8s 節點上會看到大量規則)
sudo iptables -t nat -L -n -v
# 觀察 conntrack 連線追蹤表 (NAT 靠它記住對應關係)
# 需安裝 conntrack-tools
sudo conntrack -L 2>/dev/null | head
# 在有 Docker 的機器上,跑一個含 port forwarding 的容器再回頭看 nat 表
# docker run -d -p 8080:80 nginx
# sudo iptables -t nat -L DOCKER -n
7. iptables / netfilter 基礎¶
這一節非常重要。理解 iptables,你才會懂 kube-proxy 在做什麼,以及為什麼 eBPF (如 Cilium) 要取代 iptables。
7.1 netfilter 是引擎,iptables 是設定工具¶
- netfilter 是 Linux 核心裡的封包處理框架,它在封包進出的幾個「掛勾點 (hook)」上,讓你決定封包要放行、丟棄、還是改寫。
- iptables 是你用來「設定 netfilter 規則」的使用者空間工具。
它的結構是三層:表 (Table) → 鏈 (Chain) → 規則 (Rule)。
7.2 表 (Table):依「目的」分類規則¶
| 表 (Table) | 用途 |
|---|---|
filter |
過濾:放行 (ACCEPT) 或丟棄 (DROP),是防火牆的核心 |
nat |
位址轉譯:做 SNAT / DNAT(K8s Service 主要用這張) |
mangle |
改寫封包標頭欄位 (如 TTL、TOS) |
raw |
連線追蹤前的特殊處理 |
7.3 鏈 (Chain):依「封包經過的時機」分類¶
鏈對應 netfilter 的掛勾點,代表「封包旅程中的某個時間點」:
| 鏈 (Chain) | 觸發時機 |
|---|---|
PREROUTING |
封包剛進來、還沒做路由決策前(DNAT 在這裡) |
INPUT |
封包目的是「本機」 |
FORWARD |
封包「路過」本機,要轉送出去 |
OUTPUT |
本機自己產生的封包 |
POSTROUTING |
封包即將離開、路由決策後(SNAT 在這裡) |
graph LR
IN[封包進入] --> PRE[PREROUTING<br/>DNAT]
PRE --> RD{路由決策}
RD -->|給本機| INP[INPUT] --> LOCAL[本機程序]
LOCAL --> OUT[OUTPUT]
RD -->|要轉送| FWD[FORWARD]
FWD --> POST[POSTROUTING<br/>SNAT]
OUT --> POST
POST --> EXIT[封包離開]
7.4 規則 (Rule):條件 + 動作¶
每條鏈裡有一串規則,由上往下比對。每條規則是「符合某條件 → 執行某動作」,動作 (target) 常見有:ACCEPT(放行)、DROP(丟棄)、DNAT/SNAT(改寫)、RETURN(跳出)、或跳到「自訂鏈」。
# 例:把送到 10.96.0.1:443 的封包,DNAT 改送到某個 Pod (這就是 Service 的本質)
# iptables -t nat -A PREROUTING -d 10.96.0.1 -p tcp --dport 443 -j DNAT --to 10.244.1.5:6443
7.5 為什麼這對 K8s / eBPF 這麼重要?¶
傳統 kube-proxy 的 iptables 模式,做的事就是:把「Service → 後端 Pod」的對應,翻譯成一大堆 iptables 的 nat 規則(用 DNAT 做負載平衡,隨機挑一個 Pod)。
問題來了: - 規則是線性比對的,Service 與 Pod 一多(成千上萬條規則),封包要從頭比對到尾,效能與更新延遲都會惡化。 - 規則更新要整批重寫,大叢集下會卡頓。
這就是 eBPF (如 Cilium)、或 IPVS 模式想解決的事: - IPVS 用核心內的雜湊表 (hash table) 做查找,從「線性」變「常數時間」。 - eBPF 更進一步,把封包處理邏輯「直接編譯成程式,掛在核心的網路路徑上」,可以在很早的階段就做完轉送決策,繞過冗長的 iptables 鏈,效能與可觀測性都大幅提升。
所以當你聽到「Cilium 用 eBPF 取代 kube-proxy / iptables」,現在你能理解它的動機:不是 iptables 不能用,而是在大規模、高動態的叢集下,線性規則鏈成了瓶頸。
動手練習:閱讀 iptables 規則¶
# 列出 filter 表的所有鏈與規則 (-n 不做 DNS 反解,快很多)
sudo iptables -L -n -v
# 專看 nat 表 (在 K8s 節點上,這裡會看到 KUBE-SERVICES 等自訂鏈)
sudo iptables -t nat -L -n -v
# 列出所有自訂鏈名稱
sudo iptables -t nat -S | head
# (進階) 在 minikube/kind 節點上找找 K8s 產生的鏈
sudo iptables -t nat -L KUBE-SERVICES -n 2>/dev/null | head
8. 負載平衡 (Load Balancing)¶
8.1 為什麼需要負載平衡¶
一個服務若只有一個後端,它會是單點故障,也撐不了大流量。負載平衡 (Load Balancing) 把進來的流量「分散」到多個後端,提供擴展性與高可用。
8.2 L4 負載平衡 vs L7 負載平衡¶
| 面向 | L4 負載平衡 | L7 負載平衡 |
|---|---|---|
| 依據 | IP + Port (TCP/UDP) | HTTP 內容 (網址、Header、Cookie) |
| 懂應用內容嗎? | 不懂,只搬 TCP/UDP | 懂,可依路徑/主機名分流 |
| 能做的事 | 連線層級分流 | 路徑路由、TLS 終結、重試、改寫標頭 |
| 開銷 | 低、快 | 高、功能多 |
| K8s 對應 | Service | Ingress / Gateway API |
| 雲端對應 | NLB (Network LB) | ALB (Application LB) |
直覺:L4 像郵局只看地址轉信,不拆信;L7 像秘書會拆信讀內容,再決定轉給哪個部門。
8.3 對應到 K8s 與雲端¶
graph TD
USER[外部使用者] --> CLB[雲端 LB]
CLB -->|L4: NLB 對應| SVCLB[Service type=LoadBalancer L4]
CLB -->|L7: ALB 對應| ING[Ingress Controller L7]
ING --> SVC[Service ClusterIP L4]
SVCLB --> PODS1[Pods]
SVC --> PODS2[Pods]
- Service (ClusterIP):叢集內部的 L4 負載平衡(靠 iptables/IPVS/eBPF 把流量分到後端 Pod)。
- Service (type=LoadBalancer):向雲端要一個 L4 負載平衡器 (如 AWS NLB),把外部流量導進來。
- Ingress:叢集邊緣的 L7 反向代理,依 HTTP 規則分流到不同 Service;在雲上常對應 ALB。
EKS 的伏筆:AWS Load Balancer Controller 會根據你的 Service / Ingress,自動去 AWS 開 NLB (L4) 或 ALB (L7)。理解 L4/L7 的差別,你才知道「什麼情況該用哪一種,以及為什麼」。
動手練習:理解負載平衡行為¶
# 模擬:用 curl 連續打同一個服務,觀察回應是否來自不同後端 (若後端會回主機名)
for i in $(seq 1 10); do curl -s http://localhost:8080/ ; echo; done
# 在 K8s 節點上,觀察一個 ClusterIP 背後對應到幾個 Pod (DNAT 的負載平衡規則)
# sudo iptables -t nat -L KUBE-SVC-XXXX -n
9. 與 K8s 網路模型的銜接 (Bridging to the K8s Network Model)¶
恭喜你撐到這裡。現在把前面所有觀念串成一張 K8s 網路的全景圖。
9.1 K8s 網路的四大基本要求¶
Kubernetes 對網路只提出幾條「規則」,不規定怎麼實作(實作交給 CNI):
- 每個 Pod 有自己獨立的 IP(L3 設計)。
- 同節點 / 跨節點的 Pod 彼此可直接用 IP 互通,過程中不需 NAT。
- 節點上的 agent (如 kubelet) 能和該節點上的 Pod 互通。
- 同一個 Pod 內的容器共享網路命名空間,彼此用
localhost溝通(還記得 §2.4 嗎?)。
9.2 為什麼要「每個 Pod 一個 IP」?¶
傳統做法是「容器共用主機 IP,靠不同 Port 區分」,但這會帶來 Port 衝突、應用要關心自己被映射到哪個 Port 等麻煩。
K8s 選擇 「IP-per-Pod」:每個 Pod 都像一台獨立的小主機,有自己完整的 IP 與 Port 空間。好處是:
- 應用程式可以用「它原本習慣的 Port」(例如就是 80),不必擔心衝突。
- Pod 之間溝通的心智模型,跟「兩台機器用 IP 溝通」一模一樣,簡單直覺。
代價是:你需要一個夠大的 Pod 網段 (還記得 §2.3 的網段規劃嗎?),以及一套機制讓這些 Pod IP「在叢集裡到處都路由得到」——這套機制就是 CNI。
9.3 CNI:把這些網路規則「實作出來」的外掛¶
CNI (Container Network Interface) 是 K8s 與網路外掛之間的標準介面。當一個 Pod 被建立,CNI 外掛 (Calico / Cilium / Flannel / VPC CNI…) 負責:
- 幫 Pod 建立網路介面 (通常是一對 veth pair,一端在 Pod 內,一端接到節點)。
- 從 Pod 網段分配一個 IP 給它。
- 設定路由 (§5) 與必要的 NAT / iptables / eBPF 規則 (§6, §7),讓這個 Pod 的封包能正確進出,並能找到其他節點的 Pod。
graph TD
subgraph 節點 A
P1[Pod A1<br/>10.244.1.2] --- B1[節點網路 / 路由]
P2[Pod A2<br/>10.244.1.3] --- B1
end
subgraph 節點 B
P3[Pod B1<br/>10.244.2.2] --- B2[節點網路 / 路由]
end
B1 ---|跨節點路由 由 CNI 設定| B2
9.4 一條請求的完整旅程(把全章串起來)¶
當叢集內一個 Pod 想存取 my-service:
- DNS (§4):向 CoreDNS 查
my-service...svc.cluster.local→ 得到 ClusterIP。 - L4 + DNAT (§3, §6, §7):封包送往 ClusterIP,被 iptables/IPVS/eBPF 用 DNAT 改寫目的,負載平衡 (§8) 選中某個後端 Pod IP。
- 路由 (§5):依路由表把封包送到目標 Pod(可能跨節點,由 CNI 設定的路由完成)。
- 回程:conntrack 記得對應關係,回應封包循原路改寫回來。
你會發現:K8s 網路沒有黑魔法,它就是把你這一章學到的 DNS、L4、NAT、iptables、路由、負載平衡 自動化地組裝起來。當你日後 debug,就是回到這幾層逐一檢查。
動手練習:預習 K8s 網路觀察(若已有 minikube / kind)¶
# 查看每個 Pod 的 IP (印證「IP-per-Pod」)
kubectl get pods -o wide
# 查看 Service 的 ClusterIP 與對應的後端 Endpoints
kubectl get svc
kubectl get endpoints
# 進到 Pod 裡,觀察它眼中的網路 (它有自己的 IP 與 localhost)
kubectl run netshoot --rm -it --image=nicolaka/netshoot -- bash
# 進去後可玩:ip addr / ip route / dig kubernetes.default / curl <service>
本章檢核點 (Checklist)¶
讀完並動手後,確認你能對自己回答以下問題:
- [ ] 我能說出 L2 / L3 / L4 / L7 各自負責什麼,並各舉一個 K8s 對應物。
- [ ] 我理解封裝 (Encapsulation),知道封包是一層層包標頭。
- [ ] 給我一個 CIDR(如
10.244.0.0/24),我能算出網路位址、廣播位址與可用主機數。 - [ ] 我能背出三段私有網段 (Private IP),並知道
localhost/ loopback 的意義。 - [ ] 我能解釋 TCP 三向交握 (Three-way Handshake) 的三個步驟。
- [ ] 我能說清楚「為什麼 Service 是 L4、Ingress 是 L7」,以及它們的上下游關係。
- [ ] 我能描述一次 DNS 解析的流程,並知道 CoreDNS 在 K8s 扮演的角色。
- [ ] 我能解釋封包遇到「同網段 vs 跨網段」時的不同處理,以及預設閘道 (Default Gateway) 的作用。
- [ ] 我能區分 SNAT 與 DNAT,並指出 K8s 哪裡用到它們。
- [ ] 我能說出 iptables 的「表 (Table) / 鏈 (Chain) / 規則 (Rule)」結構,以及 PREROUTING/POSTROUTING 與 DNAT/SNAT 的關係。
- [ ] 我能解釋為什麼大規模叢集下 iptables 模式的 kube-proxy 會有效能瓶頸,以及 IPVS / eBPF 想解決什麼。
- [ ] 我能區分 L4 與 L7 負載平衡,並對應到 Service、Ingress、NLB、ALB。
- [ ] 我能說明 K8s「IP-per-Pod」的設計理念,以及 CNI 的職責。
- [ ] 我能用
ip addr/ip route/ss/dig/tcpdump/iptables做基本觀察與除錯。
下一站:有了這些網路直覺,接下來就能進入 Kubernetes 的 Pod 網路、Service、Ingress 與 CNI 的實作細節。你會發現,你已經懂了一大半。