Certified Kubernetes Administrator (CKA) #6 クラスターアップグレード: kubeadm upgrade plan/apply、ノード別 drain

#5 HA クラスター で複数の control plane と外部 etcd によって可用性を高めたなら、この記事はそのクラスターを マイナーバージョン 1 つ上げる標準手順 です。CKA 実技でほぼ欠かさず出る定番の作業です。手順そのものは決まっているので、順番を覚えて手に馴染ませれば確実に点数を取れる領域です。

アップグレードが難しいのはコマンドが難しいからではなく、順番を 1 つでも違えるとクラスターが揺らぐから です。control plane を先に、その次にワーカーを。一度にマイナーバージョン 1 つだけ。そして kubeadm を先に上げて kubelet と kubectl を後に。この 3 つの原則を体に刻むのがこの記事の目標です。

アップグレードの 3 つの原則 #

コマンドを覚える前に原則から押さえます。この 3 つを違えると、どんなコマンドを使っても食い違います。

1) control plane を先に、ワーカーを後に。 control plane のコンポーネント (apiserver、controller-manager、scheduler) は、ワーカーの kubelet と同じか高いバージョンでなければなりません。だから常に control plane ノードを先に上げ、それが終わってからワーカーノードを 1 つずつ上げます。

2) 一度にマイナーバージョン 1 つだけ。 Kubernetes は 1.30 から 1.32 へ 2 段階を飛び越えるアップグレードをサポートしません。1.30 → 1.31 → 1.32 のように必ずマイナーバージョンを 1 つずつ経由しなければなりません。パッチバージョン (例: 1.31.0 → 1.31.4) は同じマイナー内なので自由に上げられます。

3) 1 つのノードの中では kubeadm → kubelet/kubectl の順。 ノード 1 つを上げるときにも順番があります。まず kubeadm パッケージを新しいバージョンに入れ替え、kubeadm upgrade でコンポーネントを上げてから、最後に kubeletkubectl パッケージを上げて kubelet を再起動します。

原則内容
ノードの順番control plane を先に → ワーカーノード
バージョンジャンプ一度にマイナーバージョン 1 つだけ (飛び越え禁止)
パッケージの順番kubeadm → (upgrade apply/node) → kubelet/kubectl

control plane のアップグレード #

control plane ノードから始めます。例として現在 1.31 から 1.32 に上げるとします。まず control plane ノードに接続します。

ssh controlplane

1) アップグレード計画の確認 #

kubeadm upgrade plan が現在のバージョンと上げられるバージョンを見せてくれます。どのコンポーネントがどのバージョンへ向かうかを表で出力するので、本格的な作業の前に必ず確認します。

kubeadm upgrade plan

2) kubeadm パッケージの入れ替え #

まずパッケージリポジトリで使うマイナーバージョンを有効化する必要があります。Kubernetes 1.28 からはマイナーバージョンごとにリポジトリが分かれているので、/etc/apt/sources.list.d/kubernetes.list のバージョン部分を新しいマイナーに変えなければなりません。

# リポジトリを新しいマイナーバージョンに入れ替え (1.31 → 1.32)
sed -i 's/v1.31/v1.32/' /etc/apt/sources.list.d/kubernetes.list

# パッケージ一覧の更新
apt update

# インストール可能な正確なバージョンの確認
apt-cache madison kubeadm

# kubeadm を目標バージョンに入れ替え
apt-mark unhold kubeadm
apt install -y kubeadm=1.32.0-1.1
apt-mark hold kubeadm

apt-mark hold はパッケージが意図せず自動アップグレードされるのを防ぐロックです。入れ替えの直前に unhold で外し、入れ替えの直後に再び hold でロックする習慣をつけます。

3) kubeadm で control plane コンポーネントをアップグレード #

入れ替えた kubeadm で control plane コンポーネントを実際に上げます。

# 入れ替えた kubeadm のバージョン確認
kubeadm version

# control plane コンポーネントのアップグレード実行
kubeadm upgrade apply v1.32.0

kubeadm upgrade apply は apiserver、controller-manager、scheduler の static Pod マニフェストを新しいバージョンのイメージに入れ替え、必要なら etcd も一緒に上げます。このステップが control plane アップグレードの核心です。control plane ノードが複数台の HA クラスターなら、残りの control plane ノードでは kubeadm upgrade node を実行 します (apply は最初のノードだけ)。

4) kubelet と kubectl の入れ替え #

control plane コンポーネントを上げたら、同じノードの kubelet と kubectl も上げます。このとき control plane ノードのワークロードを空ける drain (k drain controlplane --ignore-daemonsets) を先にしておく方が安全です。

# kubelet と kubectl の入れ替え
apt-mark unhold kubelet kubectl
apt install -y kubelet=1.32.0-1.1 kubectl=1.32.0-1.1
apt-mark hold kubelet kubectl

# kubelet 設定をリロードしてから再起動
systemctl daemon-reload
systemctl restart kubelet

# スケジューリングを再び許可
k uncordon controlplane

systemctl daemon-reload のあとに systemctl restart kubelet をしてこそ、新しいバージョンの kubelet が実際に立ち上がります。再起動を抜かすと、パッケージは上がったのに動作中の kubelet はそのままで、バージョンが反映されません。drain したノードは最後に k uncordon で戻します。

ワーカーノードのアップグレード #

control plane が終わったらワーカーノードを 1 つずつ上げます。一度に 1 ノードずつ 空けて上げて戻すのが原則です。複数のワーカーを同時に空けると、ワークロードを受け取るノードが足りなくなります。

1) ノードの drain #

アップグレードするノードの Pod を別のノードへ移し、新しい Pod が入ってこないように塞ぎます。このコマンドは通常 control plane ノードまたは kubectl が設定された場所で実行します。

k drain node01 --ignore-daemonsets --delete-emptydir-data

オプション 2 つを必ず覚えます。

  • --ignore-daemonsets: DaemonSet が立てた Pod はノードごとに 1 つずつ立つ必要があるので、drain で移せません。このオプションがないと drain は拒否されて止まります。
  • --delete-emptydir-data: emptyDir ボリュームを使う Pod があると、そのローカルデータが消えるという警告とともに drain が拒否されます。このオプションで「データが消えても構わない」という同意を明示してこそ進みます。

2) ノードで kubeadm と kubelet をアップグレード #

そのワーカーノードに接続し、control plane と同じ方法でパッケージを上げます。

ssh node01

# リポジトリを新しいマイナーバージョンに入れ替え
sed -i 's/v1.31/v1.32/' /etc/apt/sources.list.d/kubernetes.list
apt update

# kubeadm の入れ替え
apt-mark unhold kubeadm
apt install -y kubeadm=1.32.0-1.1
apt-mark hold kubeadm

# ワーカーノードの kubelet 設定をアップグレード
kubeadm upgrade node

ワーカーノードでは kubeadm upgrade apply ではなく kubeadm upgrade node を使います。このコマンドは control plane コンポーネントには手を付けず、そのノードの kubelet 設定だけを新しいバージョンに合わせて更新します。apply と node を混同するのが定番のミスです。

# kubelet と kubectl の入れ替え
apt-mark unhold kubelet kubectl
apt install -y kubelet=1.32.0-1.1 kubectl=1.32.0-1.1
apt-mark hold kubelet kubectl

# kubelet の再起動
systemctl daemon-reload
systemctl restart kubelet

3) ノードの uncordon #

アップグレードが終わったら、ノードを再びスケジューリング対象に戻します。

k uncordon node01

このコマンドを抜かすと、ノードは上がったのに SchedulingDisabled 状態で残り、新しい Pod を受け取れません。drain したノードは必ず uncordon で締めくくる対を覚えます。ワーカーが複数台なら node02、node03 についてステップ 1〜3 を同じように繰り返します。

cordon、drain、uncordon の違い #

3 つのコマンドは混同しやすいので 1 つの表で整理します。

コマンド新しい Pod のスケジューリング既存の Pod用途
k cordon <node>遮断そのまま残すノードに新しい Pod だけ入れないよう印を付ける
k drain <node>遮断別のノードへ evictアップグレード/点検の前にノードを空ける
k uncordon <node>許可影響なし作業が終わったノードを再び使用

核心は drain は cordon を含む という点です。drain は内部的にノードを cordon して新しい Pod を塞いだあと、既存の Pod を別のノードへ移します。だから drain のあとに cordon は別途必要なく、作業が終わったら uncordon だけすればよいのです。

検証 #

アップグレードがきちんと済んだかは k get nodes の VERSION 列で確認します。

k get nodes
NAME           STATUS   ROLES           AGE   VERSION
controlplane   Ready    control-plane   90d   v1.32.0
node01         Ready    <none>          90d   v1.32.0
node02         Ready    <none>          90d   v1.32.0

すべてのノードの STATUS が Ready で VERSION が目標バージョン (v1.32.0) に統一されていれば成功です。どこかのノードが SchedulingDisabled で残っているなら uncordon を抜かしたのであり、VERSION が更新されていないなら kubelet の再起動を抜かしたのです。

control plane コンポーネント自体のバージョンは static Pod でも確認できます。

# apiserver Pod のイメージタグ確認
k get pod -n kube-system kube-apiserver-controlplane \
  -o jsonpath='{.spec.containers[0].image}'

よく間違える落とし穴 #

試験で点数を削られるパターンをまとめておきます。

  • drain オプションの抜け。 --ignore-daemonsets なしで drain すると DaemonSet Pod のせいで拒否されます。--delete-emptydir-data なしで drain するとローカルデータの警告で拒否されます。2 つのオプションを一緒に付けるのを基本形として覚えます。
  • バージョンジャンプの試み。 1.30 から 1.32 へ一度に行こうとすると kubeadm upgrade が拒否します。必ずマイナーを 1 つずつ経由しなければなりません。
  • apply と node の混同。 最初の control plane は kubeadm upgrade apply <ver>、残りの control plane とワーカーは kubeadm upgrade node です。
  • kubelet 再起動の抜け。 パッケージだけ上げて systemctl restart kubelet を抜かすと VERSION が更新されません。
  • uncordon の抜け。 drain したノードを戻さないと新しい Pod を受け取れません。
  • 順番の逆転。 ワーカーを control plane より先に上げると kubelet が control plane より高くなり、非互換の状態になります。

試験ポイント #

  • アップグレードの順番は control plane → ワーカー、一度に マイナーバージョン 1 つ、ノードの中では kubeadm → kubelet/kubectl の順です。
  • control plane は kubeadm upgrade plan → パッケージ入れ替え → kubeadm upgrade apply <ver> → kubelet/kubectl 入れ替え → systemctl restart kubelet の順です。
  • ワーカーは k drain <node> --ignore-daemonsets --delete-emptydir-data → パッケージ入れ替え → kubeadm upgrade node → kubelet/kubectl 入れ替え → 再起動 → k uncordon <node> の順です。
  • HA で追加の control plane ノードは apply ではなく kubeadm upgrade node で上げます。
  • drain は cordon を含むので、終わったら uncordon だけすればよいのです。
  • 検証は k get nodes の STATUS と VERSION 列でします。

まとめ #

この記事で押さえたこと:

  • 3 つの原則。 control plane を先に、一度にマイナーバージョン 1 つ、ノードの中では kubeadm を先に。
  • control plane。 kubeadm upgrade plan で確認し、kubeadm を入れ替えてから kubeadm upgrade apply <ver> でコンポーネントを上げ、kubelet/kubectl を入れ替えて再起動します。
  • ワーカー。 drain で空けてパッケージ入れ替えと kubeadm upgrade node で上げてから、再起動して uncordon で戻します。
  • cordon/drain/uncordon。 drain は cordon を含み、終わったら uncordon で締めくくります。
  • 落とし穴。 drain オプション 2 つ、バージョンジャンプ禁止、apply と node の区別、再起動と uncordon の抜けに注意。

次へ — etcd バックアップとリストア #

アップグレードまででクラスターのライフサイクルを扱いました。次はクラスターのすべての状態が収められた etcd を守る仕事です。

#7 etcd バックアップとリストア では、etcdctl snapshot save でスナップショットを取り、障害状況を想定して etcdctl snapshot restore でクラスターの状態を戻す手順を扱います。証明書パスと endpoint の指定、復旧後に static Pod マニフェストを変えて新しいデータディレクトリを指すようにする過程まで手でなぞりながら整理します。

X