Kubernetes 提供多種佈署模式以滿足不同應用程式需求。DaemonSet 適合佈署節點特定的系統服務,例如日誌收集、監控代理等,確保每個節點都執行一個 Pod 副本。單例服務模式則適用於需要確保唯一性與高用性的應用程式,例如主資料函式庫、分散式鎖定服務等。實作單例服務模式可選擇 StatefulSet 保證 Pod 順序性與持久化儲存,或使用 ReplicaSet 搭配外部鎖定機制,例如根據 etcd 或 ZooKeeper 的分散式鎖定。此外,應用程式內部也可實作鎖定機制,但需考量程式碼複雜度與維護成本。PodDisruptionBudget 則能限制因叢集維護而中斷的 Pod 數量,提升服務穩定性。
Daemon Service 模式詳解
Daemon Service 模式允許在特定的節點上執行優先順序高、與基礎設施相關的 Pod,主要由管理員用來執行節點特定的 Pod 以增強 Kubernetes 平台的功能。
問題背景
在軟體系統中,守護程式(daemon)的概念存在於多個層面。在作業系統層級,守護程式是一種長時間執行的、自還原的電腦程式,作為背景程式執行。在 Unix 系統中,守護程式的名稱通常以 d 結尾,如 httpd、named 和 sshd。在其他作業系統中,也使用諸如「服務啟動任務」或「幽靈作業」等術語來稱呼這些程式。
這些程式的共同特點是:它們作為程式執行,通常不與監視器、鍵盤和滑鼠互動,並且在系統啟動時啟動。同樣,在應用層級也有類別似的概念。例如,在 Java 虛擬機器中,守護執行緒在背景執行,為使用者執行緒提供支援服務。這些守護執行緒具有較低的優先順序,在背景執行,不參與應用程式的生命週期,並執行諸如垃圾回收或終結等任務。
Kubernetes 也具有 DaemonSet 的概念。考慮到 Kubernetes 是一個跨多個節點的分散式平台,其主要目標是管理應用程式 Pod,DaemonSet 由在叢集節點上執行的 Pod 表示,並為叢集的其他部分提供一些背景功能。
解決方案
ReplicaSet 及其前身 ReplicationController 是負責確保特定數量的 Pod 正在執行的控制結構。這些控制器不斷監視正在執行的 Pod 清單,並確保實際的 Pod 數量始終與期望的數量相符。在這方面,DaemonSet 是一種類別似的結構,負責確保一定數量的 Pod 始終在執行。不同之處在於,前兩者執行特定數量的 Pod,通常由應用程式對高用性和使用者負載的要求驅動,而不受節點數量的影響。
另一方面,DaemonSet 不受消費者負載的影響,不決定執行多少個 Pod 例項以及在哪裡執行。它的主要目的是在每個節點或特定節點上執行單個 Pod。讓我們來看看範例 9-1 中的 DaemonSet 定義。
範例 9-1:DaemonSet 資源定義
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: random-refresher
spec:
selector:
matchLabels:
app: random-refresher
template:
metadata:
labels:
app: random-refresher
spec:
nodeSelector:
feature: hw-rng
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
command: [ "java", "RandomRunner", "/numbers.txt", "10000", "30" ]
volumeMounts:
- mountPath: /host_dev
name: devices
volumes:
- name: devices
hostPath:
path: /dev
內容解密:
apiVersion和kind指定了 Kubernetes 資源的版本和型別,這裡是apps/v1版本的 DaemonSet。metadata部分定義了 DaemonSet 的後設資料,包括名稱random-refresher。spec.selector和spec.template.metadata.labels確保 DaemonSet 管理具有標籤app: random-refresher的 Pod。spec.template.spec.nodeSelector指定了只有帶有標籤feature: hw-rng的節點才會執行該 DaemonSet 的 Pod。containers部分定義了容器使用的映像檔、名稱、執行的命令以及掛載的磁碟區。volumes部分定義了一個名為devices的磁碟區,它掛載了節點上的/dev目錄到容器的/host_dev。
DaemonSets 通常會掛載節點檔案系統的一部分來執行維護操作,使用 hostPath 直接存取節點目錄。因此,DaemonSet 的主要候選者通常是與基礎設施相關的程式,例如叢集儲存提供者、記錄收集器、指標匯出器,甚至是執行叢集範圍操作的 kube-proxy。
DaemonSet 和 ReplicaSet 在管理上有許多不同,主要差異包括:
- DaemonSet 預設在每個節點上放置一個 Pod 例項,可以透過
nodeSelector或親和性欄位進行控制和限制。 - 由 DaemonSet 建立的 Pod 已經指定了
nodeName,因此不需要 Kubernetes 排程器的存在即可執行容器。 - 由 DaemonSet 建立的 Pod 可以在排程器啟動之前執行,這使得它們能夠在任何其他 Pod 被放置在節點上之前執行。
- 由於不使用排程器,DaemonSet 控制器不考慮節點的不可排程欄位。
- 由 DaemonSet 管理的 Pod 只能將
RestartPolicy設定為Always或保持未指定(預設為Always),以確保當活性探測失敗時,容器將被終止並始終重新啟動。 - 由 DaemonSet 管理的 Pod 被認為是在目標節點上執行,因此被許多控制器視為具有較高優先順序。
使用場景與存取方式
DaemonSet 的主要使用場景是在叢集中的某些節點上執行系統關鍵的 Pod。DaemonSet 控制器透過直接將 Pod 分配給節點來確保所有符合資格的節點都執行一個 Pod 的副本,從而使 DaemonSet Pods 能夠在預設排程器啟動之前被排程。
通常,DaemonSet 在每個節點或部分節點上建立單個 Pod。有幾種方法可以存取由 DaemonSet 管理的 Pod:
- 服務(Service):建立一個與 DaemonSet 具有相同 Pod 選擇器的服務,使用該服務可以負載平衡到隨機節點上的守護程式 Pod。
- DNS:建立一個無頭服務(headless Service),具有與 DaemonSet 相同的 Pod 選擇器,可以用於從 DNS 中檢索包含所有 Pod IP 和連線埠的多個 A 記錄。
- 節點 IP 與 hostPort:直接使用節點 IP 和指定的
hostPort存取特定節點上的 Pod。
Singleton Service 單例服務模式
Singleton Service 模式確保應用程式在任何時候只會有一個例項在執行,同時仍然保持高用性。此模式可以透過應用程式內部實作,也可以完全委託給 Kubernetes 來管理。
問題描述
Kubernetes 的一個主要功能是能夠輕鬆透明地擴充套件應用程式。Pod 可以透過單一命令(如 kubectl scale)進行命令式擴充套件,或者透過控制器定義(如 ReplicaSet)進行宣告式擴充套件,甚至可以根據應用程式負載動態擴充套件,如第 29 章「彈性擴充套件」所述。透過執行多個相同服務的例項(不是 Kubernetes Service,而是由 Pod 表示的分散式應用程式的元件),系統通常會增加吞吐量和可用性。當一個服務例項變得不健康時,請求分派器會將未來的請求轉發給其他健康的例項,從而提高用性。在 Kubernetes 中,多個例項是 Pod 的副本,而 Service 資源負責請求分發和負載平衡。
然而,在某些情況下,只允許一個服務例項執行。例如,如果服務中有一個定期執行的任務,並且有多個相同服務的例項,每個例項都會在排定的間隔觸發任務,導致重複任務,而不是預期的只有一個任務被觸發。另一個例子是服務對特定資源(檔案系統或資料函式庫)進行輪詢,我們希望確保只有一個例項,甚至只有一個執行緒執行輪詢和處理。第三種情況是,我們必須以保持順序的方式從訊息代理中消費訊息,使用單執行緒的消費者,這也是一種單例服務。
在所有這些和類別似的情況下,我們需要控制同一時間有多少個服務例項是活躍的(通常只需要一個),同時仍然確保高用性,無論啟動和保持執行了多少個例項。
解決方案
執行多個相同 Pod 的副本會建立一個 active-active 拓撲結構,其中所有服務例項都是活躍的。我們需要的是一個 active-passive 拓撲結構,其中只有一個例項是活躍的,而其他所有例項都是被動的。從根本上來說,這可以在兩個可能的層級實作:應用程式外部鎖定和應用程式內部鎖定。
應用程式外部鎖定
顧名思義,這種機制依賴於應用程式外部的管理程式,以確保只有一個應用程式例項在執行。應用程式實作本身並不知道這個約束,並且作為單例例項執行。從這個角度來看,它類別似於具有一個由管理執行時(如 Spring Framework)例項化的 Java 類別。類別實作不知道它是以單例方式執行,也不知道它包含任何防止例項化多個例項的程式碼結構。
圖 10-1 展示瞭如何藉助具有一個副本的 StatefulSet 或 ReplicaSet 控制器來實作應用程式外部鎖定。
此圖示展示了一個 StatefulSet 或 ReplicaSet 控制器管理著一個 Pod 副本,該 Pod 執行著應用程式,並受到外部鎖定機制的控制。
內容解密:
- StatefulSet/ReplicaSet:Kubernetes 中的控制器,用於管理 Pod 的數量和狀態。
- 1 replica:表示只有一個 Pod 副本在執行。
- Pod:Kubernetes 中的基本執行單元,封裝了一個或多個容器。
- Application:執行的應用程式,受控於外部鎖定機制以確保單例執行。
- External Locking Mechanism:外部鎖定機制,確保只有一個應用程式例項是活躍的。
這種方法使得開發人員可以在不修改應用程式內部邏輯的情況下實作單例服務,從而簡化了開發和維護工作。
在 Kubernetes 中實作單例模式的高用性
在分散式系統中,單例模式是一種常見的設計模式,用於確保某個服務或元件只有一個例項在執行。在 Kubernetes 中,可以透過多種方式實作單例模式的高用性。
使用 ReplicaSet 實作非嚴格單例
在 Kubernetes 中,可以使用 ReplicaSet 來實作非嚴格的單例模式。ReplicaSet 可以確保至少有一個 Pod 例項在執行,但不保證最多隻有一個例項。這種方法有利於可用性,但可能會出現多個例項的情況。
ReplicaSet 的優缺點
- 優點:簡單易用,能夠確保至少有一個例項在執行。
- 缺點:不能保證最多隻有一個例項,可能會出現多個例項的情況。
使用 StatefulSet 實作嚴格單例
如果需要實作嚴格的單例模式,可以使用 StatefulSet。StatefulSet 有利於一致性,能夠確保最多隻有一個例項在執行,但可能會出現沒有例項的情況。
StatefulSet 的優缺點
- 優點:能夠確保最多隻有一個例項在執行,有利於一致性。
- 缺點:可能會出現沒有例項的情況,組態和管理比 ReplicaSet 複雜。
使用 Headless Service 實作服務發現
對於 StatefulSet 管理的單例 Pod,可以使用 Headless Service 來實作服務發現。Headless Service 不會進行負載平衡,而是直接傳回 Pod 的 IP 地址。
Headless Service 的優點
- 能夠直接存取 Pod 的 IP 地址,避免了負載平衡的開銷。
- 有利於服務發現和直接存取單例 Pod。
應用內鎖定機制
除了使用 Kubernetes 的資源外,還可以在應用內部實作鎖定機制來控制服務例項的數量。這種方法需要應用程式本身支援分散式鎖定機制。
分散式鎖定機制的優缺點
- 優點:能夠精確控制服務例項的數量,適用於複雜的分散式系統。
- 缺點:需要應用程式本身支援,增加了開發和維護的複雜度。
程式碼解析:
上述 YAML 組態檔案定義了一個名為 singleton-example 的 StatefulSet 資源。它指定了 replicas: 1,確保只有一個 Pod 例項在執行。serviceName 指定了與之關聯的 Headless Service 名稱。範本部分定義了 Pod 的後設資料和容器規格,包括使用的 Docker 映象和容器埠。
詳細解說:
apiVersion和kind: 指定 Kubernetes 資源的 API 版本和型別。metadata: 定義資源的中繼資料,如名稱。spec: 定義資源的規格,包括副本數量、選擇器和服務名稱。replicas: 1: 確保只有一個 Pod 例項在執行,實作單例模式。selector和matchLabels: 用於比對具有特定標籤的 Pod。serviceName: 指定與 StatefulSet 相關聯的 Headless Service 名稱,用於服務發現。template: 定義 Pod 的範本,包括後設資料和容器規格。containers: 定義容器列表,包括名稱、Docker 映象和埠組態。
透過這種方式,可以在 Kubernetes 中實作嚴格的單例模式,並利用 Headless Service 進行服務發現,從而滿足特定的應用需求。
分散式鎖定與單例服務實作
在分散式系統中,確保服務的單一例項執行是至關重要的。常見的實作方式是使用分散式鎖定機制,如Apache ZooKeeper、HashiCorp’s Consul、Redis或etcd提供的鎖定服務。
使用ZooKeeper實作分散式鎖定
ZooKeeper使用短暫節點(ephemeral nodes)來實作鎖定機制。當客戶端會話結束時,這些節點會被自動刪除。第一個啟動的服務例項會在ZooKeeper伺服器上建立一個短暫節點,使其成為活躍例項。其他相同叢集中的服務例項則變為被動狀態,並等待短暫節點被釋放。
此圖示展示了ZooKeeper如何管理多個服務例項的活躍狀態。
內容解密:
- 服務例項1首先啟動並在ZooKeeper上建立短暫節點,使其成為活躍狀態。
- 其他服務例項(例項2和例項3)則進入被動狀態,並持續監控ZooKeeper上的短暫節點。
- 當活躍例項的會話結束或故障時,短暫節點被刪除,其他被動例項競爭建立新的短暫節點,從而變為活躍狀態。
在Kubernetes中使用etcd和Lease物件
在Kubernetes環境中,可以利用etcd的能力來實作鎖定機制。Kubernetes提供了Lease物件,用於節點心跳和元件級別的長官者選舉。每個節點都有一個對應的Lease物件,Kubelet持續更新Lease物件的renewTime欄位,以表明節點的可用性。
此圖示展示了Kubelet如何透過更新Lease物件的renewTime欄位來表明節點的可用性。
內容解密:
- Kubelet持續更新對應Lease物件的renewTime欄位,以保持心跳。
- Kubernetes控制平面監控這些Lease物件,以判斷節點的可用性。
使用Dapr實作分散式鎖定
Dapr提供了一個通用的分散式鎖定API,允許應用程式實作互斥存取分享資源。應用程式可以透過命名鎖來獨佔存取分享資源。當鎖被佔用時,其他例項無法取得鎖,直到鎖被釋放或超時。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Kubernetes Daemon Service 與單例服務模式詳解
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此圖示展示了Dapr如何管理多個應用程式例項對分享資源的存取。
內容解密:
- 應用程式例項1取得命名鎖後,可以獨佔存取分享資源。
- 應用程式例項2則需要等待命名鎖釋放後,才能存取分享資源。
Pod中斷預算(PodDisruptionBudget)
PodDisruptionBudget是Kubernetes提供的一種機制,用於限制同一時間內因維護而不可用的Pod數量。它確保了一定數量或百分比的Pod在任何時候都是可用的。
PodDisruptionBudget範例
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: random-generator-pdb
spec:
selector:
matchLabels:
app: random-generator
minAvailable: 2
內容解密:
- 此PodDisruptionBudget確保標籤為
app: random-generator的Pod中,至少有2個是可用的。 - 你也可以使用百分比(如80%)來組態最多允許多少百分比的Pod不可用。
.spec.maxUnavailable可以用來指定最多允許多少個Pod不可用,但它與.spec.minAvailable不能同時使用。