Kubernetes 為每個 Pod 分配獨立 IP,解決了容器間的連線埠衝突。Pod 內的容器分享網路名稱空間,可直接透過 localhost 互相通訊。然而,Pod 的生命週期短暫,IP 位址可能變動,因此 Kubernetes 引入 Service 作為穩定的存取入口。Service 透過 Label Selector 選擇後端 Pod,並提供虛擬 IP。kube-proxy 則負責將流量從 Service 轉發至 Pod,並支援 iptables、IPVS 等多種代理模式。iptables 模式透過設定 netfilter 規則轉發流量,IPVS 則在核心層提供負載平衡,效能更佳。服務發現方面,Kubernetes 提供環境變數和 DNS 兩種機制,方便應用程式定位服務。

Kubernetes 網路模型概述

在 Kubernetes 叢集中執行的應用程式,預期可以從叢集內部或外部存取。從網路的角度來看,這意味著可能會有一個統一資源識別符(URI)或網際網路通訊協定(IP)位址與應用程式相關聯。多個應用程式可以在同一個 Kubernetes 工作節點上執行,但它們如何暴露自己而不會相互衝突?讓我們一起來看看這個問題,然後探討 Kubernetes 網路模型。

連線埠共用問題

傳統上,如果兩個不同的應用程式在同一台機器上執行,而機器的 IP 是公開的,並且這兩個應用程式都是公開可存取的,那麼這兩個應用程式就不能在機器上監聽同一個連線埠。如果它們都嘗試在同一台機器上監聽同一個連線埠,其中一個應用程式將無法啟動,因為該連線埠正在被使用。下圖提供了一個簡單的說明:

此圖顯示了在節點上(應用程式)發生連線埠共用衝突的問題。

為瞭解決連線埠共用衝突的問題,這兩個應用程式需要使用不同的連線埠。顯然,這裡的限制是這兩個應用程式必須共用相同的 IP 位址。如果它們有自己的 IP 位址,但仍位於同一台機器上呢?這是純 Docker 的方法。如果應用程式不需要對外暴露自身,這種方法是有幫助的,如下圖所示:

此圖顯示了在節點上(容器)發生連線埠共用衝突的問題。

在上圖中,兩個應用程式都有自己的 IP 位址,因此它們都可以監聽連線埠 80。它們可以相互通訊,因為它們位於相同的子網路中(例如,Docker 網橋)。但是,如果兩個應用程式都需要透過將容器連線埠繫結到主機連線埠來對外暴露自身,則它們無法繫結到相同的連線埠 80。至少其中一個連線埠繫結將失敗。如上圖所示,容器 B 無法繫結到主機連線埠 80,因為主機連線埠 80 已被容器 A 佔用。連線埠共用衝突問題仍然存在。

動態連線埠組態為系統在連線埠分配和應用程式發現方面帶來了許多複雜性;然而,Kubernetes 並未採用這種方法。讓我們來討論一下 Kubernetes 如何解決這個問題。

Kubernetes 網路模型

在 Kubernetes 叢集中,每個 Pod 都會獲得自己的 IP 位址。這意味著應用程式可以在 Pod 級別相互通訊。這種設計的美妙之處在於,它提供了一個乾淨、向後相容的模型,其中 Pod 從連線埠分配、命名、服務發現、負載平衡、應用程式組態和遷移的角度來看,就像虛擬機器(VM)或實體主機一樣。同一個 Pod 中的容器共用相同的 IP 位址。通常,不同應用程式使用相同的預設連線埠(Apache 和 nginx)在同一個 Pod 中執行的情況非常少見。實際上,封裝在同一個容器中的應用程式通常具有依賴關係或服務於不同的目的,由應用程式開發人員決定是否將它們封裝在一起。一個簡單的例子是,在同一個 Pod 中,有一個超文字傳輸協定(HTTP)伺服器或 nginx 容器來提供靜態檔案,還有主要的 Web 應用程式來提供動態內容。

Kubernetes 利用 CNI 外掛程式來實作 IP 位址分配、管理和 Pod 通訊。然而,所有外掛程式都需要遵循以下兩個基本要求:

  1. 節點上的 Pod 可以與所有節點上的所有 Pod 通訊,而無需使用網路位址轉換(NAT)。
  2. 諸如 kubelet 之類別的代理程式可以與同一節點上的 Pod 通訊。

這兩個前述要求強制簡化了將應用程式從 VM 遷移到 Pod 的過程。

分配給每個 Pod 的 IP 位址是一個私有 IP 位址或叢集 IP 位址,該位址不可公開存取。那麼,如何才能使應用程式在不與叢集中的其他應用程式衝突的情況下公開存取?Kubernetes 服務是將內部應用程式暴露給公眾的關鍵。我們將在後面的章節中更深入地探討 Kubernetes 服務的概念。目前,將本章的內容總結成一張圖是有用的,如下所示:

此圖顯示了向網際網路公開的服務。

Pod 內部的通訊

在前面的圖表中,有一個 k8s 叢集,其中有四個應用程式在兩個 Pod 中執行:應用程式 A 和應用程式 B 在 Pod X 中執行,它們共用相同的 Pod IP 位址——100.97.240.188——同時分別監聽連線埠 8080 和 9090。同樣,應用程式 C 和應用程式 D 在 Pod Y 中執行,分別監聽連線埠 8000 和 9000。這四個應用程式都可以透過以下導向公眾的 Kubernetes 服務公開存取:svc.a.com、svc.b.com、svc.c.com 和 svc.d.com。Pod(此圖中的 X 和 Y)可以佈署在單一工作節點上,也可以跨越 1,000 個節點複製。但是,從使用者或服務的角度來看,這沒有任何區別。儘管圖中的佈署相當不尋常,但仍然需要在同一個 Pod 中佈署多個容器。是時候深入瞭解 Pod 內部的容器之間的通訊了。

Pod 內部的容器之間的通訊

同一個 Pod 中的容器共用相同的 Pod IP 位址。通常,由應用程式開發人員決定將容器映像檔封裝在一起,並解決任何可能的資源使用衝突,例如連線埠監聽。在本文中,我們將探討 Pod 內部容器之間的通訊技術細節,並強調超出網路層級的通訊。

Linux 名稱空間和暫停容器

Linux 名稱空間是 Linux 核心的一個功能,用於隔離資源以實作隔離目的。分配名稱空間後,一組行程會看到一組資源,而另一組行程會看到另一組資源。名稱空間是現代容器技術的一個主要基本方面。讀者瞭解這個概念對於深入瞭解 Kubernetes 至關重要。因此,我們闡述了所有 Linux 名稱空間及其解釋。自 Linux 核心版本 4.7 起,有七種型別的名稱空間,分別列出如下:

  • cgroup:隔離 cgroup 和根目錄。cgroup 名稱空間虛擬化了行程的 cgroup 檢視。每個 cgroup 名稱空間都有自己的 cgroup 根目錄集。
  • IPC:隔離 System V 行程間通訊(IPC)物件或可移植作業系統介面(POSIX)訊息佇列。
  • Network:隔離網路裝置、協定堆積疊、連線埠、IP 路由表、防火牆規則等。
  • Mount:隔離掛載點。因此,每個掛載名稱空間例項中的行程將看到不同的單一目錄層次結構。

相關解說:

此處使用了 Linux 名稱空間的概念,主要目的是為了實作資源的隔離。其中,Network 名稱空間用於隔離網路裝置、協定堆積疊、連線埠等,這對於容器之間的通訊至關重要。另外,Mount 名稱空間則用於隔離掛載點,使得每個掛載名稱空間中的行程看到不同的目錄層次結構,這對於容器的檔案系統隔離非常重要。

此圖示展示了同一個 Pod 中的容器如何共用相同的網路名稱空間,並透過 localhost 相互通訊。

相關解說:

此圖示清晰地展示了 Pod 內部容器之間的通訊方式。透過共用相同的網路名稱空間,容器可以直接透過 localhost 相互通訊,無需經過外部網路,大大簡化了容器之間的通訊流程。

Kubernetes 網路架構深度解析

Kubernetes(K8s)作為現代化的容器協調系統,其網路架構是確保容器間高效通訊的關鍵。本篇將探討 K8s 的網路模型、容器間通訊機制,以及服務(Service)與 kube-proxy 的運作原理。

名稱空間與容器間通訊

Kubernetes 利用 Linux 名稱空間(Namespace)實作資源隔離,主要包含以下幾種名稱空間:

  • PID 名稱空間:隔離行程 ID,不同名稱空間中的行程可擁有相同的 PID。
  • 使用者名稱空間:隔離使用者 ID 和群組 ID,允許容器內外的使用者身份不同。
  • UTS 名稱空間:隔離主機名稱和 NIS 網域名稱。

在同一 Pod 內的容器分享相同的 IPC 和網路名稱空間,這使得容器間的通訊變得更加高效。然而,這也需要 K8s 解決潛在的連線埠衝突問題。每個 Pod 會被分配一個 IP 位址,並透過虛擬網路介面進行通訊。

Pod 內部通訊機制

Pod 內的容器可透過多種方式進行通訊:

  1. 網路通訊:分享網路名稱空間,容器可直接使用 localhost 進行通訊。
  2. IPC 通道:透過共用的 IPC 名稱空間,使用 IPC 物件或 POSIX 訊息佇列進行通訊。
  3. 共用掛載卷:容器可掛載相同的儲存卷,進行檔案讀寫操作。
  4. 訊號通訊:在啟用 shareProcessNamespace 的情況下,容器可透過訊號(如 SIGTERM、SIGKILL)進行通訊。

程式碼範例:組態 shareProcessNamespace

apiVersion: v1
kind: Pod
metadata:
  name: shared-pid-pod
spec:
  shareProcessNamespace: true
  containers:
  - name: container-a
    image: ubuntu
    command: ["sleep", "infinity"]
  - name: container-b
    image: ubuntu
    command: ["sleep", "infinity"]

內容解密:

  • shareProcessNamespace: true 啟用共用 PID 名稱空間,使容器間可互相看見對方的行程。
  • containers 下定義了兩個容器,分別執行 sleep 命令以保持執行狀態。

Pod 之間的通訊

由於 Pod 是動態且短暫的實體,其 IP 位址可能會頻繁變更。因此,K8s 引入了 Service 抽象層來解決 Pod 之間的通訊問題。

Kubernetes Service

Service 是對一組 Pod 的抽象,提供穩定的存取介面。Service 透過 Label Selector 選擇對應的 Pod,並分配一個虛擬 IP 位址。由於 Service 的 IP 位址較為穩定,因此可用於解決 Pod IP 位址變更的問題。

kube-proxy

kube-proxy 是 K8s 中的網路代理元件,負責將流量從 Service 虛擬 IP 轉發至後端 Pod。其運作模式包括:

  1. 使用者空間代理模式:kube-proxy 在節點上監聽一個隨機連線埠,並將流量轉發至後端 Pod。
  2. iptables 規則:設定 iptables 規則,將目標為 Service 虛擬 IP 的流量轉發至 kube-proxy 監聽的連線埠。

iptables 與流量轉發

在使用者空間代理模式下,kube-proxy 使用 iptables 將流量轉發至代理連線埠。預設情況下,kube-proxy 使用輪詢演算法選擇後端 Pod。

kube-proxy 使用者空間代理模式

此圖示說明瞭 kube-proxy 如何將客戶端的請求透過 Service 虛擬 IP 轉發至後端的多個 Pod。

Kubernetes 網路服務與 kube-proxy 運作模式

Kubernetes 的網路架構中,kube-proxy 是關鍵元件,負責實作服務(Service)的網路代理功能。kube-proxy 有多種運作模式,包括 iptables、IPVS 等,每種模式都有其特點和適用場景。

iptables 代理模式

在 iptables 代理模式下,kube-proxy 將轉發流量的工作交給 netfilter,並透過 iptables 規則進行管理。kube-proxy 只負責維護和更新 iptables 規則。當流量到達服務 IP 時,netfilter 會根據 kube-proxy 管理的 iptables 規則,將流量轉發到後端 Pod。

圖示:kube-proxy iptables 代理模式

此圖示說明瞭 iptables 代理模式的工作流程。

內容解密:

  1. Client 發出請求到 Service IP。
  2. kube-proxy 透過 iptables 規則將流量轉發給 netfilter。
  3. netfilter 根據 iptables 規則將流量轉發到後端 Pod。

相比於 userspace 代理模式,iptables 模式的優勢在於流量不再需要在核心空間和使用者空間之間來回傳遞,而是直接在核心空間進行轉發,從而降低了開銷。然而,這種模式的缺點是需要處理錯誤情況。例如,如果第一個選中的 Pod 沒有回應,連線就會失敗。

IPVS 代理模式

IPVS(IP Virtual Server)代理模式是另一種 kube-proxy 的運作模式。IPVS 是根據 netfilter 的,並實作了 Linux 核心中的傳輸層負載平衡。IPVS 可以將 TCP 或 UDP 流量轉發到後端真實伺服器。

圖示:kube-proxy IPVS 代理模式

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Kubernetes 網路模型深度解析

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

此圖示說明瞭 IPVS 代理模式的工作流程。

內容解密:

  1. Client 發出請求到 Service IP。
  2. IPVS 將流量轉發到後端 Pod。
  3. IPVS 使用雜湊表來儲存封包的目的地,從而實作更快的規則同步和更低的延遲。

相比於 iptables 代理模式,IPVS 模式具有更快的規則同步和更低的延遲,並且提供了更多的負載平衡選項。然而,使用 IPVS 模式需要確保節點上具有 IPVS Linux。

Kubernetes 服務簡介

Kubernetes Deployment 會動態地建立和銷毀 Pod,這對於前端和後端分離的應用架構來說是一個挑戰。Kubernetes 的服務抽象解決了這個問題,使得前端 Pod 可以存取後端 Pod。

Kubernetes 服務定義

Kubernetes 服務透過 YAML 檔案定義,例如:

apiVersion: v1
kind: Service
metadata:
  name: service-1
spec:
  type: NodePort
  selector:
    app: app-1
  ports:
  - nodePort: 29763
    protocol: TCP
    port: 80
    targetPort: 9376

程式碼解析:

apiVersion: v1
kind: Service
metadata:
  name: service-1
spec:
  type: NodePort
  selector:
    app: app-1
  ports:
  - nodePort: 29763
    protocol: TCP
    port: 80
    targetPort: 9376

內容解密:

  1. type屬性定義了服務如何暴露在網路上。
  2. selector屬性定義了 Pod 的標籤。
  3. port屬性定義了叢集內部暴露的埠。
  4. targetPort屬性定義了容器監聽的埠。

無選擇器的服務

服務通常使用選擇器來定義相關的 Pod。然而,也可以定義沒有選擇器的服務,用於存取外部服務或不同名稱空間中的服務。沒有選擇器的服務透過 Endpoints 物件對映到網路地址和埠。

Endpoints 物件範例:

apiVersion: v1
kind: Endpoints
subsets:
- addresses:
  - ip: 192.123.1.22
  ports:
  - port: 3909

程式碼解析:

apiVersion: v1
kind: Endpoints
subsets:
- addresses:
  - ip: 192.123.1.22
  ports:
  - port: 3909

內容解密:

  1. Endpoints 物件定義了服務的網路地址和埠。
  2. addresses欄位指定了服務的 IP 地址。
  3. ports欄位指定了服務的埠。

服務發現

Kubernetes 中的服務發現可以透過環境變數或 DNS(Domain Name System)實作。

環境變數

當建立一個服務時,Kubernetes 會在節點上建立一組環境變數,例如 [NAME]_SERVICE_HOST[NAME]_SERVICE_PORT。這些環境變數可以用於其他 Pod 或應用程式存取服務。

環境變數範例:

DB_SERVICE_HOST=192.122.1.23
DB_SERVICE_PORT=3909

DNS

Kubernetes 支援兩種 DNS 外掛:CoreDNS 和 Kube-DNS。DNS 服務包含服務名稱到 IP 地址的對映。Pod 和應用程式可以使用這個對映來連線服務。

使用者端可以透過環境變數或 DNS 查詢來定位服務 IP,並且有多種型別的服務可以滿足不同型別的使用者端需求。