etcd: Quorum, Raft và Dựng Cluster Ba Node

K
Kai··8 min read

Từ bài này ta bắt đầu bật các thành phần thật, và điểm khởi đầu hợp lý nhất là etcd — vì mọi thứ khác đều ghi trạng thái vào nó. Chưa có etcd thì api-server không có chỗ lưu, controller không có gì để đối chiếu. Nên ta dựng nó trước, ngay trên ba controller, dùng đúng cert etcdetcd-ca đã tạo ở Bài 4.

etcd là gì và lưu gì

etcd là một cơ sở dữ liệu key-value phân tán, nhất quán mạnh. Trong Kubernetes nó giữ toàn bộ trạng thái cluster: mọi đối tượng bạn từng tạo — Pod, Deployment, Service, ConfigMap, Secret, cả các Lease dùng cho leader election — đều nằm ở đây dưới dạng key-value. Như đã nói ở Bài 1, chỉ api-server được nói chuyện với etcd; mọi thành phần khác đọc/ghi trạng thái đều qua api-server.

Vì etcd giữ tất cả, hai tính chất của nó quyết định độ tin cậy của cả cluster: dữ liệu phải nhất quán (mọi node thấy cùng một sự thật) và phải còn sống kể cả khi một máy chết. Cả hai đến từ thuật toán đồng thuận Raft.

Raft, leader và quorum

Trong một cluster etcd, các node bầu ra một leader; mọi thao tác ghi đều đi qua leader, leader sao chép xuống các follower. Một thao tác ghi chỉ được xem là thành công khi đa số (majority) node đã xác nhận đã ghi. "Đa số" này gọi là quorum, và hiểu nó là hiểu vì sao số node lại quan trọng.

   Ghi một key:
      client ──► LEADER ──┬──► follower 1  (xác nhận)
                          └──► follower 2  (xác nhận)
      Leader chờ ĐA SỐ xác nhận (gồm cả chính nó) → báo thành công

Quorum của một cluster N node là floor(N/2) + 1. Cluster vẫn hoạt động chừng nào còn đủ quorum; mất quorum thì etcd dừng nhận ghi để không sinh ra hai phiên bản sự thật mâu thuẫn. Lập bảng cho dễ thấy:

   Số node (N)   Quorum cần   Chịu được mất tối đa
   ───────────   ──────────   ────────────────────
        1             1               0
        2             2               0      ← tệ hơn cả 1!
        3             2               1
        4             3               1      ← bằng 3, mà tốn hơn
        5             3               2

Bảng này giải thích vì sao luôn dùng số node lẻ. Hai node cần cả hai đồng ý (quorum 2), nên một node chết là mất quorum — tức là dở hơn một node đơn lẻ, lại tốn gấp đôi. Bốn node chịu được đúng một node chết, y như ba node, nhưng tốn thêm một máy mà không thêm khả năng chịu lỗi. Vậy nên ba là lựa chọn nhỏ nhất có ý nghĩa cho HA (chịu được một node chết), và đó là số ta chọn cho series này.

Bước 1 — Cài binary etcd trên ba controller

etcd phát hành dưới dạng một file nén gồm hai binary: etcd (server) và etcdctl (client dòng lệnh). Ta dùng bản v3.6.11 (3.6.x mới nhất khi viết). Trên mỗi controller, tải về và đặt vào /usr/local/bin, đồng thời tạo các thư mục etcd cần:

# chạy trên TỪNG controller (controller-0, controller-1, controller-2)
cd /tmp
VER=v3.6.11
curl -sL -o etcd.tar.gz \
  https://github.com/etcd-io/etcd/releases/download/${VER}/etcd-${VER}-linux-amd64.tar.gz
tar -xzf etcd.tar.gz
sudo cp etcd-${VER}-linux-amd64/etcd etcd-${VER}-linux-amd64/etcdctl /usr/local/bin/
sudo mkdir -p /etc/etcd /var/lib/etcd
sudo chmod 700 /var/lib/etcd
etcd --version | head -1
etcd Version: 3.6.11

/var/lib/etcd là nơi etcd ghi dữ liệu thật, để quyền 700 cho riêng root đọc. Trong series tôi chạy các lệnh này lần lượt trên cả ba controller qua SSH; bạn có thể làm tay từng máy hoặc dùng một vòng lặp ssh như tôi.

Bước 2 — Đưa cert etcd lên mỗi controller

etcd dùng TLS ở hai mặt trận: với client (api-server gọi vào cổng 2379) và giữa các peer với nhau (cổng 2380). Cả hai dùng chung cặp cert etcd đã ký bởi etcd-ca ở Bài 4. Copy ba file từ workstation lên /etc/etcd của mỗi controller:

# từ workstation, trong thư mục ~/k8s-scratch/pki
for h in controller-0 controller-1 controller-2; do
  scp etcd-ca.pem etcd.pem etcd-key.pem ${h}:/tmp/
  ssh $h 'sudo cp /tmp/etcd-*.pem /etc/etcd/ && rm /tmp/etcd-*.pem'
done

(Tôi dùng file ssh_config của Bài 3 nên gõ tên node trực tiếp; bạn thêm -F ~/k8s-scratch/ssh_config hoặc -i key.pem ubuntu@<ip> tùy cách bạn đã cấu hình.)

Bước 3 — Viết systemd unit cho etcd

Mỗi controller chạy etcd như một service systemd. File unit gần giống nhau giữa ba máy, chỉ khác hai chỗ: tên node (--name) và IP của chính nó. Quan trọng nhất là phải hiểu các cờ:

  • --listen-peer-urls / --initial-advertise-peer-urls (cổng 2380): kênh nói chuyện giữa các etcd với nhau (Raft).
  • --listen-client-urls / --advertise-client-urls (cổng 2379): kênh cho client (api-server). Ta cho lắng nghe cả IP nội bộ lẫn 127.0.0.1 để gọi cục bộ trên controller cũng được.
  • --initial-cluster: danh sách cả ba thành viên — đây là cách mỗi etcd biết hai bạn còn lại ở đâu để bắt tay thành cluster.
  • --initial-cluster-state new: báo đây là cluster lập mới (khác với việc thêm node vào cluster có sẵn).
  • --client-cert-auth / --peer-client-cert-auth: bắt buộc cả client lẫn peer phải trình cert hợp lệ — không cert thì không vào.

Đây là unit cho controller-0 (IP 10.0.1.11); hai máy kia đổi --name và các IP tương ứng:

[Unit]
Description=etcd
Documentation=https://github.com/etcd-io/etcd
After=network.target

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \
  --name controller-0 \
  --cert-file=/etc/etcd/etcd.pem \
  --key-file=/etc/etcd/etcd-key.pem \
  --peer-cert-file=/etc/etcd/etcd.pem \
  --peer-key-file=/etc/etcd/etcd-key.pem \
  --trusted-ca-file=/etc/etcd/etcd-ca.pem \
  --peer-trusted-ca-file=/etc/etcd/etcd-ca.pem \
  --client-cert-auth \
  --peer-client-cert-auth \
  --initial-advertise-peer-urls https://10.0.1.11:2380 \
  --listen-peer-urls https://10.0.1.11:2380 \
  --listen-client-urls https://10.0.1.11:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.11:2379 \
  --initial-cluster-token etcd-cluster-0 \
  --initial-cluster controller-0=https://10.0.1.11:2380,controller-1=https://10.0.1.12:2380,controller-2=https://10.0.1.13:2380 \
  --initial-cluster-state new \
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target

Ghi file này vào /etc/systemd/system/etcd.service trên mỗi máy (nhớ đổi tên + IP), rồi nạp lại systemd và bật service tự khởi động:

sudo systemctl daemon-reload
sudo systemctl enable etcd

Bước 4 — Khởi động và kiểm chứng

Có một điểm cần lưu ý khi khởi động: với Type=notify, lệnh systemctl start etcd sẽ chờ cho tới khi etcd báo sẵn sàng — mà etcd chỉ sẵn sàng khi đủ quorum, tức là khi đã thấy các peer khác. Nếu bạn start tuần tự và chờ từng máy, máy đầu sẽ treo chờ hai máy sau. Cách gọn là start cả ba gần như cùng lúc, dùng --no-block để lệnh trả về ngay:

for h in controller-0 controller-1 controller-2; do
  ssh $h 'sudo systemctl start --no-block etcd'
done

Sau vài giây, ba etcd tìm thấy nhau, bầu leader và cluster hình thành. Kiểm tra danh sách thành viên — chạy etcdctl với bộ cert TLS (không có cert thì etcd từ chối, vì ta đã bật --client-cert-auth):

sudo etcdctl member list \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/etcd-ca.pem \
  --cert=/etc/etcd/etcd.pem \
  --key=/etc/etcd/etcd-key.pem -w table
+------------------+---------+--------------+------------------------+------------------------+------------+
|        ID        | STATUS  |     NAME     |       PEER ADDRS       |      CLIENT ADDRS      | IS LEARNER |
+------------------+---------+--------------+------------------------+------------------------+------------+
| 33eed1f752c2defa | started | controller-2 | https://10.0.1.13:2380 | https://10.0.1.13:2379 |      false |
| bbeedf10f5bbaa0c | started | controller-1 | https://10.0.1.12:2380 | https://10.0.1.12:2379 |      false |
| eecdfcb7e79fc5dd | started | controller-0 | https://10.0.1.11:2380 | https://10.0.1.11:2379 |      false |
+------------------+---------+--------------+------------------------+------------------------+------------+

Ba thành viên, đều started. Để khỏi gõ lại bộ cert dài, đặt nó vào một biến rồi xem sức khỏe và trạng thái cả cluster:

E="--cacert=/etc/etcd/etcd-ca.pem --cert=/etc/etcd/etcd.pem --key=/etc/etcd/etcd-key.pem"
sudo etcdctl endpoint health --cluster $E -w table
+------------------------+--------+-------------+-------+
|        ENDPOINT        | HEALTH |    TOOK     | ERROR |
+------------------------+--------+-------------+-------+
| https://10.0.1.11:2379 |   true | 12.525445ms |       |
| https://10.0.1.12:2379 |   true | 17.632423ms |       |
| https://10.0.1.13:2379 |   true | 22.387437ms |       |
+------------------------+--------+-------------+-------+

endpoint status cho thấy ai đang làm leader (rút gọn vài cột cho dễ đọc):

sudo etcdctl endpoint status --cluster $E -w table
+------------------------+------------------+---------+---------+-----------+-----------+
|        ENDPOINT        |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM |
+------------------------+------------------+---------+---------+-----------+-----------+
| https://10.0.1.13:2379 | 33eed1f752c2defa |  3.6.11 |   20 kB |     false |         2 |
| https://10.0.1.12:2379 | bbeedf10f5bbaa0c |  3.6.11 |   20 kB |     false |         2 |
| https://10.0.1.11:2379 | eecdfcb7e79fc5dd |  3.6.11 |   20 kB |      true |         2 |
+------------------------+------------------+---------+---------+-----------+-----------+

controller-0 đang là leader (IS LEADER true); hai máy kia là follower. RAFT TERM 2 là số "nhiệm kỳ" — mỗi lần bầu lại leader thì term tăng. Nếu giờ bạn tắt controller-0, cluster sẽ mất leader một thoáng rồi hai máy còn lại (vẫn đủ quorum 2/3) bầu ra leader mới, term tăng lên 3, và cluster tiếp tục nhận ghi. Đó chính là khả năng chịu một node chết mà bảng quorum ở trên hứa hẹn — bạn có thể tự thử ở cuối bài.

🧹 Dọn dẹp

etcd giờ chạy thường trú trên ba controller như một phần của control plane, nên ta không tắt nó — các bài sau cần. Nếu tạm nghỉ học, cứ stop-instances cả cụm như Bài 3; etcd sẽ tự khởi động lại (đã systemctl enable) và hợp lại thành cluster khi máy bật lên, vì dữ liệu nằm trong /var/lib/etcd trên ổ EBS không mất khi stop.

Tổng kết

Ta đã có một kho trạng thái phân tán, chịu được một node chết, bảo vệ bằng TLS hai chiều. Quan trọng hơn con số "ba node", bạn đã hiểu vì sao là ba: quorum và Raft quyết định mọi lựa chọn về số lượng và topology của etcd. Đây là hiểu biết theo bạn đi xa, vì etcd là thứ cần chăm sóc cẩn thận nhất ở mọi cluster production — và là thứ ta sẽ sao lưu, khôi phục ở Bài 21.

Giờ đã có chỗ lưu, Bài 7 dựng thành phần ngồi ngay trước nó và là cổng vào duy nhất của cả cluster: kube-apiserver. Ta sẽ cấu hình nó nói chuyện với cluster etcd vừa dựng (qua cert apiserver-etcd-client), bật mã hóa Secret bằng file encryption-config.yaml ở Bài 5, và hiểu sâu hơn dây chuyền authn → authz → admission mà mọi request phải đi qua.

Related Posts