# 💾 etcd backup và restore

## Mục tiêu bài học

Sau bài này, bạn phải làm được 3 việc sau, không cần mở tài liệu:

1. Tạo snapshot etcd bằng `etcdctl snapshot save` với đúng endpoint và bộ cert.
2. Verify snapshot bằng `etcdutl snapshot status`.
3. Restore thành công từ snapshot trên cluster lab, sửa đúng cả `--data-dir` và `hostPath` trong static pod manifest.

Đây là dạng bài gần như chắc chắn xuất hiện trong CKA và là một trong những câu "ăn điểm nhanh" nhất. Mình đã chứng kiến rất nhiều bạn mất điểm chỉ vì quên sửa `hostPath`, nên bài này sẽ tập trung vào đúng cái bẫy đó.

## Ôn nhanh

Ở bài trước, cluster của bạn được dựng bằng `kubeadm init`, và control plane chạy dưới dạng **static pod** với manifest nằm trong `/etc/kubernetes/manifests/`. etcd cũng là một static pod như vậy, tên `etcd-<tên-node>`, trong namespace `kube-system`.

Điểm mấu chốt cần nhớ: kubelet quét thư mục `/etc/kubernetes/manifests/`. File nào còn ở đó thì pod còn chạy, file nào bị move ra thì pod bị xoá. Chúng ta sẽ lợi dụng đúng cơ chế này để "tắt" etcd khi restore.

## Bảng tham chiếu nhanh

| Thành phần | Giá trị mặc định trên cluster kubeadm |
| --- | --- |
| Client endpoint | `https://127.0.0.1:2379` |
| Peer port | `2380` |
| CA | `/etc/kubernetes/pki/etcd/ca.crt` |
| Server cert | `/etc/kubernetes/pki/etcd/server.crt` |
| Server key | `/etc/kubernetes/pki/etcd/server.key` |
| Data dir | `/var/lib/etcd` |
| Static pod manifest | `/etc/kubernetes/manifests/etcd.yaml` |

etcd lắng nghe client request trên port {{c1::2379}} và trao đổi dữ liệu giữa các member trên port {{c2::2380}}.

## Ví dụ mẫu: backup rồi restore trên cluster lab

Luồng tổng quát của một lần restore trông như sau:

```mermaid
flowchart TD
    A["💾<br/>etcdctl snapshot save"] --> B["🔍<br/>etcdutl snapshot status"]
    B --> C["🛑<br/>Move etcd.yaml ra khỏi manifests"]
    C --> D["♻️<br/>etcdutl snapshot restore vào data-dir mới"]
    D --> E["✏️<br/>Sửa --data-dir và hostPath"]
    E --> F["🚀<br/>Move manifest trở lại"]
    F --> G["✅<br/>kubectl get nodes và kiểm tra object"]
```

### Bước 1: Xác nhận thông tin etcd đang chạy

```bash
kubectl get nodes
kubectl -n kube-system get pod -l component=etcd -o wide
```

Lấy nhanh các tham số quan trọng mà không cần mở file bằng tay:

```bash
kubectl -n kube-system get pod etcd-controlplane -o yaml | grep -E "data-dir|listen-client-urls|name="
```

Bạn sẽ thấy `--data-dir=/var/lib/etcd` và `--listen-client-urls=https://127.0.0.1:2379,...`. Hai dòng này chính là thứ bạn cần cho lệnh backup.

### Bước 2: Tạo snapshot

```bash
sudo -i
mkdir -p /opt/backup

ETCDCTL_API=3 etcdctl snapshot save /opt/backup/etcd-snapshot.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key
```

Kết quả mong đợi:

```
{"level":"info","msg":" fetched snapshot ...","totalKey":...
Snapshot saved at /opt/backup/etcd-snapshot.db
```

> [!NOTE]
> Từ etcd 3.4 trở đi, API v3 là mặc định nên `ETCDCTL_API=3` không còn bắt buộc. Cứ đặt vào cho chắc, nó không gây hại và giúp bạn không phải nhớ phiên bản etcd trên cluster thi.

### Bước 3: Verify snapshot

Đừng bao giờ restore từ một file chưa kiểm tra. Chạy:

```bash
etcdutl snapshot status /opt/backup/etcd-snapshot.db -w table
```

Kết quả mong đợi có đủ 4 cột: `hash`, `revision`, `totalKey`, `totalSize`. Nếu lệnh báo lỗi đọc file thì snapshot hỏng, phải làm lại từ Bước 2.

### Bước 4: Tạo "dữ liệu sau snapshot" để chứng minh restore có tác dụng

Đây là mẹo mình luôn dùng khi luyện thi: tạo một object **sau** khi snapshot, restore xong object đó phải biến mất. Đó là bằng chứng restore thật sự chạy.

```bash
kubectl create namespace demo-restore
kubectl create deployment web --image=nginx -n demo-restore
kubectl get all -n demo-restore
```

### Bước 5: Dừng etcd static pod

```bash
mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/etcd.yaml.bak
```

Chờ khoảng 15 đến 30 giây, rồi kiểm tra bằng `crictl` (không dùng `kubectl`, vì lúc này API server không còn backend):

```bash
crictl ps | grep etcd
```

Không còn container etcd nào là đúng. Từ giờ đến khi etcd lên lại, mọi lệnh `kubectl` sẽ treo hoặc báo lỗi `etcdserver: request timed out`. Đó là bình thường, đừng hoảng.

### Bước 6: Restore vào data-dir MỚI

```bash
etcdutl snapshot restore /opt/backup/etcd-snapshot.db \
  --data-dir=/var/lib/etcd-restore
```

Kết quả mong đợi:

```
2024-xx-xx ... restored snapshot ... to /var/lib/etcd-restore
```

Từ etcd 3.5 trở đi, lệnh restore được khuyến nghị là {{c3::etcdutl snapshot restore}} thay cho `etcdctl snapshot restore` (đã deprecated nhưng vẫn chạy được).

> [!WARNING]
> Tuyệt đối không restore đè lên `/var/lib/etcd` khi thư mục đó đang là data-dir của etcd. Hãy luôn restore ra một thư mục mới, ví dụ `/var/lib/etcd-restore`. Giữ nguyên `/var/lib/etcd` làm bản dự phòng tại chỗ, nếu hỏng còn đường quay lại.

### Bước 7: Sửa manifest, nhớ sửa HAI chỗ

Mở file backup ra sửa:

```bash
vi /etc/kubernetes/etcd.yaml.bak
```

Chỗ thứ nhất, trong `command`:

```yaml
    - --data-dir=/var/lib/etcd-restore
```

Chỗ thứ hai, trong `volumes`:

```yaml
  - hostPath:
      path: /var/lib/etcd-restore
      type: DirectoryOrCreate
    name: etcd-data
```

> [!IMPORTANT]
> Đây là bẫy số 1 của dạng bài này. Sửa `--data-dir` mà quên `hostPath.path` thì container vẫn mount `/var/lib/etcd` cũ vào trong pod, etcd khởi động bình thường nhưng dữ liệu là dữ liệu cũ. Bạn sẽ tưởng mình restore hỏng và đi loanh quanh rất lâu.

Flag quyết định thư mục dữ liệu mới khi restore là {{c4::--data-dir}}. Và trong manifest, phần cần sửa thứ hai nằm ở `volumes`, cụ thể là {{c5::hostPath.path}}.

### Bước 8: Đưa manifest trở lại và kiểm tra

```bash
mv /etc/kubernetes/etcd.yaml.bak /etc/kubernetes/manifests/etcd.yaml
sleep 20
crictl ps | grep etcd
kubectl get nodes
```

Khi `kubectl get nodes` trả về danh sách node là etcd đã sống lại. Kiểm tra bằng chứng restore:

```bash
kubectl get ns demo-restore
# Error from server (NotFound): namespaces "demo-restore" not found
kubectl get pods -A
kubectl get ns
```

Namespace `demo-restore` biến mất nghĩa là dữ liệu đã quay về đúng thời điểm snapshot. Cluster của bạn đã restore thành công.

## Thực hành có hướng dẫn

Làm lần lượt 4 bài tập dưới đây trên cluster lab. Bài 4 mô phỏng sát đề thi nhất.

### Bài tập 1: Trích xuất thông số etcd mà không mở file

Yêu cầu: chỉ dùng `kubectl`, in ra `--data-dir` và endpoint client mà etcd đang dùng.

<details><summary>Xem gợi ý</summary>

Dùng `kubectl -n kube-system get pod etcd-<node> -o yaml`, sau đó lọc bằng `grep`. Hoặc `describe` và đọc phần `Command`.

</details>

<details><summary>Xem đáp án</summary>

```bash
kubectl -n kube-system get pod etcd-controlplane -o yaml \
  | grep -E "data-dir|listen-client-urls"
```

Kết quả mong đợi:

```
    - --data-dir=/var/lib/etcd
    - --listen-client-urls=https://127.0.0.1:2379,https://10.0.0.10:2379
```

</details>

### Bài tập 2: Backup và verify

Yêu cầu: tạo snapshot tại `/opt/backup/etcd-snapshot.db`, sau đó in ra `revision` và `totalKey` dưới dạng bảng.

<details><summary>Xem đáp án</summary>

```bash
sudo -i
mkdir -p /opt/backup
ETCDCTL_API=3 etcdctl snapshot save /opt/backup/etcd-snapshot.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

etcdutl snapshot status /opt/backup/etcd-snapshot.db -w table
```

Ghi lại số `revision`. Bạn sẽ dùng nó để đối chiếu sau khi restore.

</details>

### Bài tập 3: Restore và chứng minh bằng dữ liệu phát sinh

Yêu cầu:

1. Tạo namespace `post-snapshot` sau khi đã có snapshot.
2. Restore từ `/opt/backup/etcd-snapshot.db`.
3. Chứng minh `post-snapshot` không còn tồn tại, còn các namespace cũ vẫn nguyên.

<details><summary>Xem đáp án</summary>

```bash
kubectl create namespace post-snapshot

mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/etcd.yaml.bak
crictl ps | grep etcd   # phải rỗng

etcdutl snapshot restore /opt/backup/etcd-snapshot.db \
  --data-dir=/var/lib/etcd-restore

# sửa --data-dir và hostPath.path thành /var/lib/etcd-restore
vi /etc/kubernetes/etcd.yaml.bak

mv /etc/kubernetes/etcd.yaml.bak /etc/kubernetes/manifests/etcd.yaml
sleep 20

kubectl get ns
```

`post-snapshot` phải biến mất, `default`, `kube-system` vẫn còn.

</details>

### Bài tập 4: Mô phỏng đề thi

Đề bài giả lập:

> A cluster is running on node `controlplane`. A snapshot of etcd exists at `/opt/backup/etcd-snapshot-previous.db`. Restore the cluster to this snapshot. Do not change the etcd data directory used by the static pod beyond what is required to complete the restore.

Yêu cầu: hoàn thành trong 10 phút, sau đó tự chấm theo 3 tiêu chí: etcd pod Running, `kubectl get nodes` trả về `Ready`, dữ liệu đúng thời điểm snapshot.

<details><summary>Xem hướng làm nhanh</summary>

1. `crictl ps | grep etcd` để xác nhận chưa có gì bất thường.
2. `mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/etcd.yaml.bak`
3. `etcdutl snapshot restore /opt/backup/etcd-snapshot-previous.db --data-dir=/var/lib/etcd-restore`
4. Sửa 2 chỗ trong manifest: `--data-dir` và `hostPath.path`.
5. `mv` manifest trở lại, chờ 20 đến 30 giây.
6. Verify: `crictl ps | grep etcd`, `kubectl get nodes`, `kubectl get ns`.

</details>

> [!TIP]
> Trong phòng thi, hãy làm câu etcd backup trước tiên. Nó chỉ mất 2 đến 3 phút, gần như không thể sai nếu bạn thuộc bộ cert, và mang lại cảm giác an toàn cho phần còn lại của bài.

## Lỗi thường gặp và cách xử lý

**1. Quên `--cacert`, `--cert`, `--key`**

`etcdctl` báo `context deadline exceeded` hoặc `transport: authentication handshake failed`. etcd luôn yêu cầu TLS, không có ngoại lệ. Thiếu một trong ba flag là không chạy được.

**2. Quên `hostPath.path`**

Đây là lỗi phổ biến nhất. etcd pod lên Running, `kubectl` hoạt động, nhưng dữ liệu vẫn là dữ liệu cũ. Dấu hiệu nhận biết: object bạn tạo sau snapshot vẫn còn tồn tại. Cách sửa: kiểm tra lại phần `volumes` trong manifest, đảm bảo `path` trùng với `--data-dir`.

**3. Restore đè lên data-dir đang dùng**

etcd rơi vào crash loop, log báo lỗi đọc WAL hoặc `snapshot file ... exists`. Luôn restore ra thư mục mới.

**4. Quên move manifest ra trước khi restore**

Restore vẫn chạy xong, nhưng etcd đang chạy không hề biết. Khi bạn `mv` manifest trở lại, etcd vẫn dùng data-dir cũ. Luôn tắt etcd trước.

**5. Dùng `kubectl` để kiểm tra khi etcd đã tắt**

`kubectl get pods` sẽ treo rất lâu rồi trả về `etcdserver: request timed out`. Đừng chờ. Dùng `crictl ps` và `journalctl -u kubelet -f` để biết chuyện gì đang xảy ra.

**6. Nhầm port 2379 và 2380**

`--endpoints` phải dùng port client 2379. Port 2380 là peer traffic, dùng sai sẽ bị timeout.

**7. Restore xong nhưng etcd không lên**

Xem log: `crictl logs <container-id>` hoặc `journalctl -u kubelet -n 100`. Nếu log báo lỗi liên quan tới member name hoặc cluster config, chạy lại lệnh restore với bộ flag khớp manifest hiện tại:

```bash
etcdutl snapshot restore /opt/backup/etcd-snapshot.db \
  --data-dir=/var/lib/etcd-restore \
  --name=controlplane \
  --initial-cluster=controlplane=https://127.0.0.1:2380 \
  --initial-advertise-peer-urls=https://127.0.0.1:2380 \
  --initial-cluster-token=etcd-cluster-1
```

Thay `controlplane` bằng đúng giá trị `--name` trong manifest của bạn.

**8. Snapshot lấy từ máy khác, version etcd lệch**

Restore báo lỗi hash. Thêm `--skip-hash-check` để bỏ qua bước kiểm tra, nhưng chỉ dùng khi bạn chắc file không hỏng.

**9. Ghi snapshot vào thư mục không có quyền**

Chạy `sudo -i` trước, hoặc dùng `sudo` cho từng lệnh. Lỗi `permission denied` ở `/opt/backup` là do chưa lên root.

**10. Tưởng restore hỏng vì mất dữ liệu**

Restore đưa cluster về đúng thời điểm snapshot. Mọi object tạo sau đó sẽ mất. Đó là hành vi đúng, không phải lỗi.

Sau khi restore, dữ liệu trong etcd sẽ ở đúng trạng thái tại thời điểm {{c6::snapshot được tạo}}, nên mọi object tạo sau đó đều biến mất.

## Góc chuyên gia

**Tạo alias cho bộ cert để gõ nhanh.** Trong phòng thi, bạn sẽ dùng lại bộ flag này nhiều lần:

```bash
export E="--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key"

etcdctl snapshot save /opt/backup/etcd-snapshot.db $E
```

Lưu ý: biến `$E` phải để **không có dấu nháy** khi gọi, nếu không shell sẽ coi cả chuỗi là một tham số.

**Đừng đoán đường dẫn cert.** Trên cluster kubeadm thì đường dẫn luôn nằm trong `/etc/kubernetes/pki/etcd/`, nhưng nếu đề cho cluster lạ, hãy kiểm tra bằng `kubectl -n kube-system describe pod etcd-<node>` và đọc phần `Mounts`.

**Giữ lại data-dir cũ.** Sau khi restore thành công, đừng xoá `/var/lib/etcd`. Nếu manifest mới có vấn đề, bạn chỉ cần sửa `hostPath` và `--data-dir` về lại giá trị cũ là cluster sống lại ngay. Đây là "bảo hiểm" miễn phí.

**Thứ tự việc làm khi restore, thuộc lòng 5 bước:**

```steps
1. Move `/etc/kubernetes/manifests/etcd.yaml` ra ngoài, chờ etcd container biến mất (`crictl ps | grep etcd`).
2. Chạy `etcdutl snapshot restore <file> --data-dir=/var/lib/etcd-restore`.
3. Sửa `--data-dir` trong `command` của manifest.
4. Sửa `hostPath.path` trong `volumes` của manifest.
5. Move manifest trở lại, chờ 20 đến 30 giây, verify bằng `crictl ps` rồi `kubectl get nodes`.
```

**Backup định kỳ trong thực tế.** Không ai chạy tay lệnh này mỗi ngày. Người ta đặt cron trên control plane node, ghi snapshot ra `/opt/backup` rồi đẩy lên object storage nằm ngoài cluster. Điểm cần nhớ: snapshot phải nằm ở nơi khác node control plane, nếu không thì mất node là mất luôn bản backup.

**Đừng quên restore là bước bắt buộc trước khi upgrade.** Bạn sẽ thấy điều này ở bài sau.

## Thẻ ôn tập

```flashcard
[
  { "front": "etcd lưu những gì của cluster?", "back": "Toàn bộ state của cluster dưới dạng key-value, gồm mọi object, Secret, ConfigMap và trạng thái RBAC." },
  { "front": "etcd chạy dưới dạng gì trên control plane của cluster kubeadm?", "back": "Static pod, manifest tại /etc/kubernetes/manifests/etcd.yaml." },
  { "front": "Lệnh tạo snapshot etcd?", "back": "etcdctl snapshot save /path/snap.db kèm --endpoints, --cacert, --cert, --key." },
  { "front": "Port client của etcd là bao nhiêu?", "back": "2379 (port 2380 là peer traffic giữa các member)." },
  { "front": "Ba file cert cần cho etcdctl nằm ở đâu?", "back": "/etc/kubernetes/pki/etcd/ca.crt, server.crt và server.key." },
  { "front": "Công cụ được khuyến nghị để restore từ etcd 3.5 trở đi?", "back": "etcdutl snapshot restore (etcdctl snapshot restore đã deprecated)." },
  { "front": "Bước đầu tiên bắt buộc trước khi restore etcd?", "back": "Move /etc/kubernetes/manifests/etcd.yaml ra khỏi thư mục manifests để kubelet dừng static pod." },
  { "front": "Flag chỉ định thư mục dữ liệu mới khi restore?", "back": "--data-dir." },
  { "front": "Sau khi restore phải sửa những chỗ nào trong etcd.yaml?", "back": "Hai chỗ: --data-dir trong command và hostPath.path trong volumes." },
  { "front": "Lệnh kiểm tra snapshot có hợp lệ không?", "back": "etcdutl snapshot status /path/snap.db -w table." },
  { "front": "Khi etcd đã dừng thì kiểm tra bằng gì thay cho kubectl?", "back": "crictl ps, crictl logs và journalctl -u kubelet, vì kubectl cần etcd làm backend." },
  { "front": "Xem nhanh cấu hình etcd đang chạy bằng kubectl?", "back": "kubectl -n kube-system get pod etcd-<node> -o yaml, lọc theo data-dir và listen-client-urls." },
  { "front": "ETCDCTL_API=3 có còn bắt buộc không?", "back": "Không bắt buộc từ etcd 3.4 vì API v3 là mặc định, nhưng đặt vào vẫn an toàn." },
  { "front": "Object tạo sau thời điểm snapshot sẽ ra sao sau khi restore?", "back": "Biến mất, vì cluster quay về đúng trạng thái tại thời điểm snapshot." }
]
```

## Tóm tắt và bài tiếp theo

Tóm gọn 4 dòng:

- Backup: `etcdctl snapshot save` với endpoint `https://127.0.0.1:2379` và bộ ba cert trong `/etc/kubernetes/pki/etcd/`.
- Verify: `etcdutl snapshot status <file> -w table`.
- Restore: tắt static pod bằng cách move manifest, `etcdutl snapshot restore --data-dir=<thư mục mới>`, sửa **cả** `--data-dir` và `hostPath.path`, rồi move manifest trở lại.
- Bẫy lớn nhất: quên `hostPath`. Bẫy thứ hai: dùng `kubectl` khi etcd đã tắt.

Bài tiếp theo là **Upgrade cluster bằng kubeadm và drain node**. Ở đó bạn sẽ dùng lại chính kỹ năng này: quy trình chuẩn là backup etcd trước, upgrade control plane, rồi `drain` và upgrade từng worker node. Nếu bạn làm thành thạo bài hôm nay, bài upgrade sẽ nhẹ đi rất nhiều.
