インフラエンジニアの羅針盤

インフラエンジニア1〜3年目のための技術ガイド

CKA編⑤ etcdバックアップ復元とdrain

公開更新

新卒インフラエンジニア向け「Kubernetes 実践教科書 ② CKA クラスタ構築・運用編」(全16回)の第5回です。第4回で 5 ノードの HA クラスタが立ち、fanclub-api も本番へ移りました。ですがインフラは「作って終わり」ではありません。壊れても戻せること、そしてノードを安全に入れ替えられることが本番運用の要です。今回は、クラスタの全状態を保持する etcd をバックアップ・復元し、Workload Node を安全に外して戻し、最後に etcd や API Server の正体でもある Static Pod を自分で作ります。いずれも CKA で頻出の実技です。

本回で使う VM を整理します。プロンプトの # は root、$ は一般ユーザー developer を表し、各 VM へは手元のマシンから developer で SSH ログインし、root が必要な作業は sudo -i で root シェルに切り替えます。

  • k8s-cp-01192.168.1.125)/k8s-cp-02192.168.1.126)/k8s-cp-03192.168.1.127)― Control Plane Node。etcd が同居します。バックアップ・復元はここで行います。
  • k8s-wl-01192.168.1.128)/k8s-wl-02192.168.1.129)― Workload Node。drain の対象です。
  • k8s-ops192.168.1.122)― 作業端末。kubectl はここから実行します(第4回で ~/.kube/config を昇格済みなので KUBECONFIG の指定は不要です)。
目次
  1. 今ここマップ(全 16 回中の現在地)
  2. この回のゴール
  3. なぜ etcd のバックアップが最重要なのか
  4. etcdctl / etcdutl を用意する
  5. etcd をバックアップする(snapshot save)
  6. 壊して直す ― スナップショットから復元する
  7. Workload Node を安全に外す・戻す(drain / uncordon)
  8. Static Pod を自分で作る
  9. やってみよう
  10. まとめ
  11. 理解度チェック(○×形式・全 9 問)
  12. 次回予告

今ここマップ(全 16 回中の現在地)

本シリーズは 6 部構成です。現在地は第1部「クラスタ構築」の第5回です。

  • 第1部 クラスタ構築(第1〜5回)← 今ここ
  • 第2部 ワークロード管理(第6〜8回)
  • 第3部 ネットワーク(第9〜11回)
  • 第4部 ストレージ(第12回
  • 第5部 監視・運用(第13〜14回)
  • 第6部 トラブルシュート(第15〜16回)
  • AlmaLinux 10.2(kernel 6.12.0-211.22.1.el10_2)
  • kubeadm・kubectl v1.35.6
  • etcd・etcdctl・etcdutl v3.6.6
  • 確認日 2026-07-11

この回のゴール

本回を終えると、次のことができるようになります。到達できたかは記事末の「やってみよう」と「理解度チェック」で確認します。

  • etcdctl snapshot save で etcd のバックアップを取得できる
  • 既存の data-dir を退避し、etcdutl snapshot restore --data-dir /var/lib/etcd で標準の場所へ復元してクラスタ状態を復旧できる(manifest は変更不要・退避は rm ではなく mvと理解する)
  • kubectl drain / uncordon で Workload Node を安全に外し・戻せる
  • Static Pod(/etc/kubernetes/manifests/)の仕組みを説明し、自分で作れる

なぜ etcd のバックアップが最重要なのか

Kubernetes のあらゆるオブジェクト ― Pod・Deployment・Service・ConfigMap・Secret・Namespace ― の状態は、すべて etcd という 1 つの分散データベースに保存されています。kubectl で何かを作ると、最終的にその内容は etcd に書き込まれます。つまり etcd はクラスタの唯一の「真実の源」であり、etcd を失うことはクラスタの状態そのものを失うことを意味します。ノードや Pod が動いていても、etcd が壊れれば、そこに記録された「あるべき姿」は取り戻せません。だから etcd のバックアップは、本番運用で最も重要な備えの 1 つです。

本巻の etcd は、第3回・第4回で見たとおり 3 台の Control Plane に同居する stacked etcd で、3 メンバーが Raft という合意アルゴリズムで常に同期しています(過半数=quorum 2 で書き込みを確定)。この「常に同期し合う」性質は、後の復元手順で重要になります。バックアップ(スナップショット)は、その時点の etcd の中身をファイル 1 つに固める操作で、quorum とは別の「時点を丸ごと保存しておく保険」です。

etcdctl / etcdutl を用意する

etcd を操作する CLI が etcdctl(稼働中の etcd に接続して使う)と etcdutl(スナップショットファイルを直接扱う)です。kubeadm クラスタの etcd は Static Pod として動いていますが、これらの CLI はホストには入っていないため、cp-01 に導入します。etcd 本体(実機は 3.6.6)とバージョンを合わせ、リリースの tarball から取り出します。Control Plane Node は最小構成の AlmaLinux で tar も入っていないため、先に dnf で導入します。また配置先は /usr/local/bin ではなく /usr/bin にします。本環境では root(sudo)の secure_path/usr/local/bin が含まれず、そこへ置くと #(root)で etcdctl が見つからないためです。

実行コマンド(k8s-cp-01・root):

# dnf install -y tar
# cd /tmp
# curl -x http://192.168.1.121:3128 -sSLO https://github.com/etcd-io/etcd/releases/download/v3.6.6/etcd-v3.6.6-linux-amd64.tar.gz
# tar xzf etcd-v3.6.6-linux-amd64.tar.gz
# cp etcd-v3.6.6-linux-amd64/etcdctl etcd-v3.6.6-linux-amd64/etcdutl /usr/bin/
# etcdctl version

実行結果:

etcdctl version: 3.6.6
API version: 3.6

1 点、先に済ませておきます。バックアップ(次節)は cp-01 だけで行いますが、復元(後述)は 3 台の etcd すべてで etcdutl を実行します。そこで、上とまったく同じ導入手順(tar の導入・tarball のダウンロード・展開・/usr/bin への配置)を cp-02・cp-03 でも実施しておいてください。3 台とも /usr/bin/etcdctl/usr/bin/etcdutl(root 所有)が入った状態にします。

稼働中の etcd に接続するには、認証のための証明書が要ります。kubeadm は etcd 用の証明書を /etc/kubernetes/pki/etcd/ に置いています。接続先(endpoint)は localhost の 2379 です。毎回この長いオプションを打つのは大変なので、cp-01 の root シェルにエイリアスを定義しておきます。なお、このエイリアスは今の root シェル内だけで有効です。ログインし直すと消えるので、その場合は再度定義してください(cp-02・cp-03 では etcd への接続は行わないため、このエイリアスは cp-01 だけで使います)。

実行コマンド(k8s-cp-01・root):

# alias e="etcdctl --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"
# e member list -w table

実行結果:

+------------------+---------+-----------+----------------------------+----------------------------+------------+
|        ID        | STATUS  |   NAME    |         PEER ADDRS         |        CLIENT ADDRS        | IS LEARNER |
+------------------+---------+-----------+----------------------------+----------------------------+------------+
| 1b420186ef9811ae | started | k8s-cp-02 | https://192.168.1.126:2380 | https://192.168.1.126:2379 |      false |
| 519e6016796020c0 | started | k8s-cp-01 | https://192.168.1.125:2380 | https://192.168.1.125:2379 |      false |
| 72c512512c15a9b5 | started | k8s-cp-03 | https://192.168.1.127:2380 | https://192.168.1.127:2379 |      false |
+------------------+---------+-----------+----------------------------+----------------------------+------------+

3 メンバー(cp-01〜03)がすべて started です。これが第4回で構築した HA etcd の姿です。なお ETCDCTL_API=3 という環境変数を昔は付けましたが、etcd 3.4 以降は v3 が既定なので不要です(付けても害はありません)。

etcd をバックアップする(snapshot save)

バックアップの保存先ディレクトリを作り、snapshot save でスナップショットを取得します。ファイル名には日付を入れておくと運用時に管理しやすくなります。

実行コマンド(k8s-cp-01・root):

# mkdir -p /backup
# e snapshot save /backup/etcd-20260711.db

実行結果:

{"level":"info","msg":"created temporary db file","path":"/backup/etcd-20260711.db.part"}
{"level":"info","msg":"fetching snapshot","endpoint":"https://127.0.0.1:2379"}
{"level":"info","msg":"fetched snapshot","endpoint":"https://127.0.0.1:2379"}
{"level":"info","msg":"saved","path":"/backup/etcd-20260711.db"}
Snapshot saved at /backup/etcd-20260711.db
Server version 3.6.0

取得したスナップショットの中身(リビジョン・キー数・ハッシュ)は etcdutl snapshot status で確認できます。ファイルを直接読むだけなので、こちらは証明書も endpoint も要りません。

実行コマンド(k8s-cp-01・root):

# etcdutl snapshot status /backup/etcd-20260711.db -w table

実行結果:

+----------+----------+------------+------------+---------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE | VERSION |
+----------+----------+------------+------------+---------+
| 5d2bb248 |    87802 |        449 |     4.7 MB |   3.6.0 |
+----------+----------+------------+------------+---------+

スナップショットは etcdctl ではなく etcdutl で扱う点に注意してください。古い etcd(3.4 系)ではこれらも etcdctl snapshot status で行えましたが、3.5 以降「稼働中の etcd を操作する道具(etcdctl)」と「ファイルを操作する道具(etcdutl)」に分かれ、3.6 では statusrestoreetcdutl 側に集約されています。本番のガードレールとして、取得したスナップショットは cp が全滅しても取り出せるようクラスタの外(別ホストやオブジェクトストレージ)へ退避し、cron などで定期取得するのが定石です。

壊して直す ― スナップショットから復元する

復元を体験するために、まず「消えたら分かる目印」を作ります。テスト用の Namespace を作り、その状態を含んだスナップショットを取り、わざと削除してから、スナップショットで復活させます。復元は「スナップショットを取った瞬間の状態」に戻す操作なので、目印はスナップショットを取る前に作っておく必要があります。

実行コマンド(k8s-ops・developer):

$ kubectl create namespace restore-test
$ kubectl get namespace restore-test

実行結果:

namespace/restore-test created
NAME           STATUS   AGE
restore-test   Active   5s

この restore-test を含んだスナップショットを cp-01 で取り直します。これが「戻したい正しい状態」のバックアップです。

実行コマンド(k8s-cp-01・root):

# e snapshot save /backup/etcd-demo.db

実行結果:

Snapshot saved at /backup/etcd-demo.db

では「事故」を起こします。k8s-ops から restore-test を削除します。

実行コマンド(k8s-ops・developer):

$ kubectl delete namespace restore-test
$ kubectl get namespace restore-test

実行結果:

namespace "restore-test" deleted
Error from server (NotFound): namespaces "restore-test" not found

目印が消えました。これをスナップショットから復元します。ここで HA 特有の重要な注意があります。etcd は 3 メンバーが常に同期し合っているため、1 台だけを古いスナップショットに戻しても、残り 2 台の「現在の状態(削除後)」に同期し直されて、復元が帳消しになってしまいます。したがって復元は、3 台すべての etcd を止め、3 台とも同じスナップショットから復元するのが正しい手順です。全体の流れを図にまとめます。

etcd スナップショットからの復元フロー図。取得済みバックアップと restore-test 削除を前提に、①3 台の etcd と API Server を止める→②3 台とも既存の /var/lib/etcd を mv で退避し、etcdutl で標準の /var/lib/etcd へ復元(rm ではなく mv で退避するのが本番のガードレール)→③manifest は変更せずそのまま戻して再起動→restore-test 復活、という順序を示す。あわせて、3 メンバーは同期し合うため 3 台とも同じスナップショットから復元する理由を示す図
図1:etcd スナップショットからの復元フロー(3 台同時・mv で退避)

手順は 3 段階です。(1) 3 台の etcd と API Server を止める → (2) 3 台とも既存の data-dir を退避し、標準の /var/lib/etcd へ復元する → (3) manifest を(変更せず)戻して起動し直す。まず (1)、cp-01・cp-02・cp-03 の各ノードで、etcd と API Server の Static Pod manifest を /etc/kubernetes/manifests/ の外へ退避します。ファイルを外へ動かすと kubelet が対応する Pod を停止します。

実行コマンド(k8s-cp-01・k8s-cp-02・k8s-cp-03 それぞれ・root):

# mkdir -p /etc/kubernetes/manifests-stopped
# mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests-stopped/

次に (2)、同じスナップショットを 3 台に配って、各ノードで復元します。復元先は etcd の標準の場所 /var/lib/etcd で、既存データは後ほど mv で退避してから上書きします。まずスナップショットを cp-02・cp-03 へ配ります。/backup/etcd-demo.db は root 所有で、VM 同士は直接 SSH できないため、手元のマシンを経由して配ります第2回で admin.conf を配ったのと同じ 2 ホップです)。はじめに cp-01 で、developer が読める場所へ複製します。

実行コマンド(k8s-cp-01・root):

# cp /backup/etcd-demo.db /tmp/etcd-demo.db
# chown developer:developer /tmp/etcd-demo.db

手元のマシンへいったん取り寄せ、cp-02・cp-03 へ送ります。

実行コマンド(手元のマシン):

$ scp developer@192.168.1.125:/tmp/etcd-demo.db /tmp/etcd-demo.db
$ scp /tmp/etcd-demo.db developer@192.168.1.126:/tmp/etcd-demo.db
$ scp /tmp/etcd-demo.db developer@192.168.1.127:/tmp/etcd-demo.db

cp-02・cp-03 では、届いたファイルを /backup/ へ置きます。

実行コマンド(k8s-cp-02・k8s-cp-03 それぞれ・root):

# mkdir -p /backup
# cp /tmp/etcd-demo.db /backup/etcd-demo.db

これで 3 台とも /backup/etcd-demo.db が揃いました。ここからが復元の本体です。大事な作法が 2 つあります。1 つは、復元先を etcd の標準の場所 /var/lib/etcd にすること。もう 1 つは、既存の /var/lib/etcd をいきなり消して上書きせず、先に mv で退避してから復元することです(万一失敗しても元へ戻せる安全策)。まず 3 台それぞれで、既存の data-dir を退避します。

実行コマンド(k8s-cp-01・k8s-cp-02・k8s-cp-03 それぞれ・root):

# mv /var/lib/etcd /var/lib/etcd.bak

退避して空いた /var/lib/etcd へスナップショットを復元します。etcdutl snapshot restore に、各メンバー固有の名前(--name)とピア URL(--initial-advertise-peer-urls)、3 メンバー全員を並べた --initial-cluster、そして復元先 --data-dir /var/lib/etcd を指定します。ノードごとに --name--initial-advertise-peer-urls だけが異なります。cp-01 の例を示します。

実行コマンド(k8s-cp-01・root):

# etcdutl snapshot restore /backup/etcd-demo.db \
    --name k8s-cp-01 \
    --initial-advertise-peer-urls https://192.168.1.125:2380 \
    --initial-cluster k8s-cp-01=https://192.168.1.125:2380,k8s-cp-02=https://192.168.1.126:2380,k8s-cp-03=https://192.168.1.127:2380 \
    --data-dir /var/lib/etcd

cp-02 では --name k8s-cp-02--initial-advertise-peer-urls https://192.168.1.126:2380、cp-03 では --name k8s-cp-03--initial-advertise-peer-urls https://192.168.1.127:2380 に変えて実行します(--initial-cluster--data-dir /var/lib/etcd は 3 台とも同じです)。実行結果は次のように、/var/lib/etcd へメンバーが復元された旨が出ます。

実行結果(cp-01 の例・抜粋):

info	bbolt	Opening bbolt db (/var/lib/etcd/member/snap/db) successfully
info	snapshot/v3_snapshot.go:333	restored snapshot	{"path": "/backup/etcd-demo.db", "wal-dir": "/var/lib/etcd/member/wal", "data-dir": "/var/lib/etcd", "snap-dir": "/var/lib/etcd/member/snap"}

この /var/lib/etcd は etcdutl が root 所有で作り直します(退避した /var/lib/etcd.bak と同じ所有・権限なので、追加の chown は要りません)。そしてここが本方式の勘所です。復元先を etcd がもともと使う標準の /var/lib/etcd にしたので、Static Pod の manifest(etcd.yaml)はいっさい書き換える必要がありませんetcd.yaml--data-dir も hostPath も元から /var/lib/etcd を指しているため、そのまま起動すれば復元したデータを読みます(もし別のディレクトリへ復元すると manifest 側のパスも合わせる必要が生じ、さらに次回のアップグレード時に kubeadm が manifest を既定パスで作り直して不整合になります。標準の /var/lib/etcd へ戻すのが結局いちばん安全です)。

本番のガードレールとして、先ほど rm ではなく mv で退避した点が効いてきます。万一復元に失敗しても、rm -rf /var/lib/etcd してから mv /var/lib/etcd.bak /var/lib/etcd で元の状態へ戻せます。逆に、いきなり rm -rf /var/lib/etcd してから復元すると、失敗したときに戻す先がありません。復元がうまくいって稼働を確認できたら、退避した /var/lib/etcd.bak は削除して構いません。

3 台とも復元したら (4)、退避しておいた etcd.yamlkube-apiserver.yaml を(中身は変更せず、そのまま/etc/kubernetes/manifests/ へ戻します。kubelet が manifest を検知して etcd と API Server を起動し直し、標準の /var/lib/etcd に置いた復元データを読み込みます。3 台すべてで戻すことが大切です。

実行コマンド(k8s-cp-01・k8s-cp-02・k8s-cp-03 それぞれ・root):

# mv /etc/kubernetes/manifests-stopped/etcd.yaml /etc/kubernetes/manifests-stopped/kube-apiserver.yaml /etc/kubernetes/manifests/

しばらく待つと etcd と API Server が起動し、クラスタが復帰します(起動しきるまでの 1〜2 分は、kubectl が一時的にエラーを返すことがありますが、異常ではありません)。k8s-ops から、削除したはずの restore-test が復活していることを確認します。

実行コマンド(k8s-ops・developer):

$ kubectl get namespace restore-test

実行結果:

NAME           STATUS   AGE
restore-test   Active   3m

スナップショットを取った時点の状態に戻り、restore-test が蘇りました。逆に言えば、スナップショット取得よりに作ったものは失われます(復元は「その時点」に巻き戻す操作だからです)。この 3 メンバー同時復元は手順が多く緊張します。第15回のトラブルシュートで、実際の障害シナリオとしてもう一度扱います。確認が済んだら、不要になった restore-test は削除しておいて構いません。

Workload Node を安全に外す・戻す(drain / uncordon)

ノードをメンテナンスするときは、いきなり止めるのではなく、そのノードの Pod を他のノードへ退避させてから外します。これを行うのが kubectl drain です。ここで 1 つ注意があります。fanclub-db(PostgreSQL)は第4回で導入した local-path のボリュームを使っていますが、local-path はボリュームを Pod が載ったノードのローカルディスクに作るため、db の Pod は事実上そのノードに固定され、別ノードへは移せません。そこで drain の対象は、db-0 が載っていない側の Workload Node にします。まず配置を確認します。

実行コマンド(k8s-ops・developer):

$ kubectl get pods -n fanclub -o wide

実行結果(例):

NAME                                READY   STATUS    RESTARTS   AGE   IP               NODE
fanclub-backend-6896d48bd9-7j9bf    2/2     Running   0          40m   10.244.119.130   k8s-wl-02
fanclub-db-0                        1/1     Running   0          40m   10.244.215.197   k8s-wl-01
fanclub-frontend-7d9b4667f-hjs2p    1/1     Running   0          40m   10.244.215.195   k8s-wl-01
fanclub-logcollector-2vtlr          1/1     Running   0          40m   10.244.215.194   k8s-wl-01
fanclub-logcollector-zs66g          1/1     Running   0          40m   10.244.119.129   k8s-wl-02

この例では fanclub-db-0 が k8s-wl-01 に載っています。したがって drain 対象は k8s-wl-02 にします(配置は環境ごとに変わるので、必ずご自身の出力で db-0 のいない側を選んでください)。--ignore-daemonsets は DaemonSet(logcollector・calico-node・kube-proxy)を退避対象から除外するオプションで、DaemonSet は全ノードに 1 つずつ常駐する仕組みのため drain では動かせません。--delete-emptydir-data は emptyDir を使う Pod がある場合にその削除を明示的に許可します。

実行コマンド(k8s-ops・developer):

$ kubectl drain k8s-wl-02 --ignore-daemonsets --delete-emptydir-data

実行結果(抜粋):

node/k8s-wl-02 cordoned
Warning: ignoring DaemonSet-managed Pods: fanclub/fanclub-logcollector-zs66g, kube-system/calico-node-8bbwf, kube-system/kube-proxy-jp4lf
evicting pod fanclub/fanclub-frontend-7d9b4667f-hjs2p
evicting pod fanclub/fanclub-backend-6896d48bd9-7j9bf
pod/fanclub-frontend-7d9b4667f-hjs2p evicted
pod/fanclub-backend-6896d48bd9-7j9bf evicted
node/k8s-wl-02 drained

drain は 2 つのことを同時に行います。1 つは cordon(新規スケジュールの停止)、もう 1 つは既存 Pod の 退避(eviction)です。退避された frontend と backend は、もう一方の k8s-wl-01 へ移って起動し直します。ノードの状態を確認すると、k8s-wl-02 が SchedulingDisabled になっています。

実行コマンド(k8s-ops・developer):

$ kubectl get nodes
$ kubectl get pods -n fanclub -o wide | grep backend

実行結果:

NAME        STATUS                     ROLES           AGE    VERSION
k8s-cp-01   Ready                      control-plane   2d     v1.35.6
k8s-cp-02   Ready                      control-plane   1d     v1.35.6
k8s-cp-03   Ready                      control-plane   1d     v1.35.6
k8s-wl-01   Ready                      <none>          1d     v1.35.6
k8s-wl-02   Ready,SchedulingDisabled   <none>          1d     v1.35.6
fanclub-backend-7c9f8b6d55-q4m8p    2/2   Running   0   50s   10.244.215.203   k8s-wl-01

backend が k8s-wl-01 へ移りました。メンテナンスが終わったら uncordon でスケジュールを再開します。

実行コマンド(k8s-ops・developer):

$ kubectl uncordon k8s-wl-02

実行結果:

node/k8s-wl-02 uncordoned

これで k8s-wl-02 が Ready に戻り、新しい Pod を再び受け入れられます。なお、ノードを完全に取り外す場合は drain のあとに kubectl delete node k8s-wl-02 でクラスタから外し、そのノード側で kubeadm reset を実行します。再び参加させたいときは第4回と同じ kubeadm join を使います。local-path とノード固定の話は、第12回で分散ストレージ Longhorn に移すことで解消します(どのノードからでも同じボリュームを使えるようになります)。

Static Pod を自分で作る

本回で扱った etcd も、API Server もスケジューラも、Static Pod という特別な Pod です。通常の Pod は kubectl が API Server に依頼し、スケジューラが配置先を決めて動きますが、Static Pod は 各ノードの kubelet が /etc/kubernetes/manifests/ を監視し、そこに置かれた manifest を API Server を介さず直接起動します。だから API Server 自身(の Static Pod)も、この仕組みで起動できるわけです。CKA でも「指定ノードに Static Pod を作れ」はよく問われます。仕組みを試します。wl-01 の /etc/kubernetes/manifests/ に、nginx の Pod manifest を置きます。

実行コマンド(k8s-wl-01・root):

# cat <<'EOF' > /etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: static
spec:
  containers:
  - name: web
    image: nginx:1.27-alpine
    ports:
    - containerPort: 80
EOF

ファイルを置くだけで、kubelet がこれを検知して Pod を起動します。kubectl apply は要りません。k8s-ops から確認すると、Pod 名が static-web-k8s-wl-01 のように<名前>-<ノード名>」の形で現れます。これは、kubelet が起動した Static Pod を API Server 側から読み取り専用で見えるようにした「mirror Pod(ミラー)」です。

実行コマンド(k8s-ops・developer):

$ kubectl get pod -o wide | grep static-web

実行結果:

static-web-k8s-wl-01   1/1   Running   0   30s   10.244.215.205   k8s-wl-01

この mirror Pod は kubectl delete pod では消せません(消してもすぐ kubelet が作り直します)。Static Pod を止めるには、manifest ファイルそのものを削除します。wl-01 でファイルを消すと、kubelet が Pod を停止します。

実行コマンド(k8s-wl-01・root):

# rm /etc/kubernetes/manifests/static-web.yaml

少し待って k8s-ops で確認すると、static-web-k8s-wl-01 が一覧から消えています。通常の Pod は API Server 経由・スケジューラ配置・kubectl で管理しますが、Static Pod はノード固定・kubelet 直管理・manifest ファイルで管理、という違いを押さえておきましょう。

やってみよう

バックアップを取り、中身を確認する

cp-01 で etcdctl ... snapshot save により /backup/ にスナップショットを取り、etcdutl snapshot status -w table でリビジョンとキー数を読み取ってください。証明書オプションが必要なのは save(稼働中の etcd に接続)だけで、status(ファイルを読むだけ)には不要である理由も説明できるようにしましょう。

壊して直す

テスト用 Namespace を作り、それを含んだスナップショットを取り、Namespace を削除してから、3 メンバー同時復元で復活させてください。既存の /var/lib/etcdrm ではなく mv で退避し、標準の /var/lib/etcd へ復元する(manifest は変更しない)ところまでが 1 セットである点を、手を動かして体に入れましょう。作業前に本回開始時点へ戻せるスナップショットを取っておくと安心です。

ノードを外して戻す

kubectl get pods -n fanclub -o wide で db-0 の載っているノードを確認し、db-0 のいない側の Workload Node を kubectl drain ... --ignore-daemonsets --delete-emptydir-data で drain してください。backend がもう一方のノードへ移ることを -o wide で確認し、kubectl uncordon で戻します。

Static Pod を作る

wl-01 の /etc/kubernetes/manifests/static-web(nginx)の manifest を置き、kubectl get pod -o widestatic-web-k8s-wl-01 が現れることを確認してください。kubectl delete pod では消えないこと、manifest を削除すると止まることも確かめましょう。

まとめ

本回では、クラスタの唯一の真実の源である etcd を etcdctl snapshot save でバックアップし、既存の data-dir を mv で退避してから etcdutl snapshot restore --data-dir /var/lib/etcd で標準の場所へ復元し、クラスタ状態を戻しました。3 メンバー HA では 3 台とも同じスナップショットから復元する点、復元先を標準の /var/lib/etcd にすれば manifest を書き換えずに済む点、そして rm ではなく mv で退避して失敗時に戻せるようにする点が肝でした。あわせて kubectl drain / uncordon でノードを安全に外して戻し、etcd や API Server の正体である Static Pod を自分でも作れることを確認しました。「壊れても戻せる・ノードを安全に入れ替えられる」運用の第一歩です。

理解度チェック(○×形式・全 9 問)

次の各文が正しいか(○)誤りか(×)を判断してください。下の「解答と解説」を開くと答え合わせができます。

  1. Kubernetes の全リソースの状態は etcd に保存されている。
  2. etcd スナップショットからの復元は、既存の /var/lib/etcd をそのまま上書きすればよい。
  3. 復元先を標準の /var/lib/etcd にすれば、etcd.yaml(manifest)は書き換えなくてよい。
  4. 3 メンバー HA の etcd は、1 台だけをスナップショットに復元すれば全体が復元される。
  5. etcdutl snapshot status は稼働中の etcd への接続(証明書)を必要とする。
  6. kubectl drain--ignore-daemonsets が要るのは、DaemonSet の Pod があると drain が止まるためである。
  7. kubectl cordon はノードをクラスタから削除する。
  8. Static Pod は /etc/kubernetes/manifests/ に manifest を置くと kubelet が起動する。
  9. Static Pod(の mirror Pod)は kubectl delete pod で恒久的に削除できる。
解答と解説

1=○/2=×(いきなり上書き・削除せず、先に mv で退避してから標準の /var/lib/etcd へ復元する。失敗時に戻せるようにするため)/3=○(復元先を標準の /var/lib/etcd にすれば manifest は変更不要。別ディレクトリだと manifest 調整が要り、次回アップグレードで不整合になる)/4=×(3 台とも止めて 3 台とも同じスナップショットから復元する。1 台だけだと残りに同期し直される)/5=×(status はファイルを読むだけで証明書も endpoint も不要)/6=○(DaemonSet は --ignore-daemonsets で除外する)/7=×(cordon はスケジュール停止のみ・削除ではない)/8=○(kubelet が manifest を直接起動)/9=×(manifest ファイルを削除して止める。kubectl delete pod では作り直される)

次回予告

次回・第6回では、この HA クラスタを計画的に育てる運用へ進みます。kubeadm upgrade によるクラスタのバージョンアップ、証明書の期限確認と kubeadm certs renew による更新、そしてノードの drain / cordon を組み合わせた無停止アップグレードの手順を学びます。バージョンは上げっぱなしにできず、証明書も 1 年で切れます。止めずに育てる技術が、本番運用では重要になります。

前の記事
次の記事