TRIAL&RetailAI Advent Calendar 2025的第一篇文章。
今天的主題是“Kubernetes 深度解析(etcd + Raft)”。我在開發中一直使用 Kubernetes(k8s)。希望更深入地了解 k8s,並再次從基本的事項開始整理 etcd 和 Raft 的相關內容。
目前在基礎系統部門工作,主要負責後端開發。
在2024年也寫過Advent Calendar 的文章,希望有機會能讓您看到。
k8s 是自動管理容器的容器編排系統。
其特點在於將應用程序保持在「聲明式(desired state)」的狀態。

引用來源:Cluster Architecture
| 組件 | 角色 |
|---|---|
| API 伺服器 | 所有操作的入口點 REST API。 |
| 排程器 | 決定 Pod 配置在哪個 Node 上。 |
| 控制器管理器 | 透過各控制器將集群狀態調整至 desired state。 |
| etcd | Kubernetes 唯一的資料儲存庫(儲存所有設定和狀態)。 |
| 組件 | 角色 |
|---|---|
| Kubelet | 管理 Node 內的 Pod,並與 API Server 通信。 |
| Kube-Proxy | 透過服務實現負載均衡和網路控制。 |
| 容器執行環境 | 實際執行容器(containerd / CRI-O 等)。 |
k8s 用於儲存內部狀態的分散式 Key-Value 儲存系統。
控制平面的所有設定與狀態均儲存在 etcd 中。
因此,當 etcd 停止運作時,Kubernetes 整體會失去功能。
# 因為使用 Mac,透過 Homebrew 安裝 etcd
brew install etcd
# 確認安裝
which etcd
# 啟動 etcd
export ETCDCTL_API=3
etcd
# 寫入值
# 在另一個終端機執行以下命令
etcdctl put foo "hello etcd"
> OK
# 取得值
etcdctl get foo
> OK
> foo
> hello etcd
# 監控變更
# 在另一個終端機執行以下命令
etcdctl watch foo
# 在更另一個終端機執行以下命令
etcdctl put foo "new value"
> OK
# 在執行 etcdctl watch foo 的終端機中實時確認變更
> PUT
> foo
> new value
您應該能體會到 Kubernetes 控制平面的組件(如 API Server 等)如何對 etcd 進行 "PUT / GET / WATCH" 操作。
Raft 是 etcd、Consul、TiKV 和 Kubernetes(內部的 etcd)所使用的
分散式共識演算法。這樣可以產生強一致性。
| 要點 | 說明 |
|---|---|
| Leader 僅有一台 | 為了防止寫入衝突並保持一致性。 |
| 所有寫入皆由 Leader 處理 | 分散式系統將數據線性化(Linearizable)的基本原則。 |
| 過半數(Majority)持有日誌後才會提交 | 為了在多個節點故障的情況下仍能正確恢復。 |
| Leader 死後會自動選舉 | 透過故障轉移實現高可用性,集群不會停止。 |
| etcd 強一致性的基礎 | etcd 的讀/寫為何是線性化的原因在於 Raft 的日誌複製和選舉。 |
| 角色 | 說明 |
|---|---|
| Leader | 處理所有寫入請求的唯一節點,並將日誌複製至 Follower。 |
| Follower | 接收來自 Leader 的日誌複製,並應用於狀態機。 |
| Candidate | 若 Follower 在一段時間內無法與 Leader 通信則可參加選舉。 |
# 在三個終端機上分別執行以下命令
# 第一個終端機
etcd \
--name n1 \
--listen-peer-urls http://127.0.0.1:2380 \
--listen-client-urls http://127.0.0.1:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--initial-advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster-token etcd-cluster \
--initial-cluster n1=http://127.0.0.1:2380,n2=http://127.0.0.1:2382,n3=http://127.0.0.1:2384 \
--initial-cluster-state new
# 第二個終端機
etcd \
--name n2 \
--listen-peer-urls http://127.0.0.1:2382 \
--listen-client-urls http://127.0.0.1:2377 \
--advertise-client-urls http://127.0.0.1:2377 \
--initial-advertise-peer-urls http://127.0.0.1:2382 \
--initial-cluster-token etcd-cluster \
--initial-cluster n1=http://127.0.0.1:2380,n2=http://127.0.0.1:2382,n3=http://127.0.0.1:2384 \
--initial-cluster-state new
# 第三個終端機
etcd \
--name n3 \
--listen-peer-urls http://127.0.0.1:2384 \
--listen-client-urls http://127.0.0.1:2378 \
--advertise-client-urls http://127.0.0.1:2378 \
--initial-advertise-peer-urls http://127.0.0.1:2384 \
--initial-cluster-token etcd-cluster \
--initial-cluster n1=http://127.0.0.1:2380,n2=http://127.0.0.1:2382,n3=http://127.0.0.1:2384 \
--initial-cluster-state new
# 在三者中的一個終端機上確認
{"level":"info","ts":"2025-11-30T15:14:15.626738+0900","logger":"raft","caller":"[email protected]/raft.go:970","msg":"b71f75320dc06a6c became leader at term 2"}
# 關閉三者中的 Leader 的終端機
# 在另一個終端機上確認 Leader 變更
"level":"info","ts":"2025-11-30T15:24:09.243345+0900","logger":"raft","caller":"[email protected]/raft.go:912","msg":"9e85cc091d4d15bb became candidate at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.243374+0900","logger":"raft","caller":"[email protected]/raft.go:1064","msg":"9e85cc091d4d15bb [logterm: 2, index: 9] sent MsgVote request to 42b4c95db84f2660 at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.243410+0900","logger":"raft","caller":"[email protected]/raft.go:1064","msg":"9e85cc091d4d15bb [logterm: 2, index: 9] sent MsgVote request to b71f75320dc06a6c at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.257516+0900","logger":"raft","caller":"[email protected]/raft.go:1077","msg":"9e85cc091d4d15bb received MsgVoteResp from 9e85cc091d4d15bb at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.257597+0900","logger":"raft","caller":"[email protected]/raft.go:1693","msg":"9e85cc091d4d15bb has received 1 MsgVoteResp votes and 0 vote rejections"}
{"level":"info","ts":"2025-11-30T15:24:09.262962+0900","logger":"raft","caller":"[email protected]/raft.go:1077","msg":"9e85cc091d4d15bb received MsgVoteResp from 42b4c95db84f2660 at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.263028+0900","logger":"raft","caller":"[email protected]/raft.go:1693","msg":"9e85cc091d4d15bb has received 2 MsgVoteResp votes and 0 vote rejections"}
{"level":"info","ts":"2025-11-30T15:24:09.263059+0900","logger":"raft","caller":"[email protected]/raft.go:970","msg":"9e85cc091d4d15bb became leader at term 3"}
{"level":"info","ts":"2025-11-30T15:24:09.263086+0900","logger":"raft","caller":"[email protected]/node.go:370","msg":"raft.node: 9e85cc091d4d15bb elected leader 9e85cc091d4d15bb at term 3"}
我想您會體驗到 k8s 的 API Server 如何通過這個機制實現 100% 安全的狀態保存。
下次由 @mizoguchi_ryosuke 帶來的主題是『從遺傳性和曼斯基學習的「○○決定論」陷阱:在生成 AI 中實踐誠實的分析』。
敬請期待!!
RetailAI 和 TRIAL 正在招募工程師。
我們處理 Kotlin、Flutter、Go、Python等多種語言。
有興趣的人可以透過以下方式了解或發送訊息給我們。