本記事には広告(アフィリエイトリンク)が含まれます。

kubeadm upgrade手順+drain【CKA第6回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第6回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / kubeadm v1.35.5 / containerd.io v2.2.4 / Calico v3.32.0(2026-05-24 時点)

重要:アップグレード対象パッチ版は「実在する最新版」に読み替えてください

本回はアップグレードの例として v1.35.6 を対象に手順を解説します。ただし pkgs.k8s.io のパッチリリースは時期によって公開状況が変わります。読者が本回を実施する時点で v1.35.6 がまだ公開されていない(v1.35 系の最新が v1.35.5 のまま)こともあります。その場合、本文の 1.35.6 を指定したコマンド(kubeadm upgrade apply v1.35.6 / dnf upgrade kubeadm-1.35.6-150500.1.1 等)はパッケージが見つからずに失敗します。

そこで作業を始める前に、必ず次の 2 コマンドで「実際に利用可能なアップグレード先」を確認してください。

# kubeadm upgrade plan
# dnf list --showduplicates kubeadm --disableexcludes=kubernetes | grep x86_64

kubeadm upgrade plan が表示する「Latest version in the v1.35 series」と、dnf list --showduplicates に並ぶ実在パッケージのうち最新のものが、その時点での正しいアップグレード先です。本文中の 1.35.6(および kubeadm-1.35.6-150500.1.1 等のフルパッケージ名)を、確認した実在バージョンにすべて読み替えて実行してください。もしクラスタが既に v1.35 系の最新パッチに到達しており上位パッチが未公開の場合、その時点では「アップグレード先が存在しない」ため、新しいパッチが公開されるのを待つか、本回は手順の流れを把握する目的で読み進めてください。 次の minor(例: v1.36 系)へ上げる場合も同じ手順で実施できますが、後述の「マイナーは 1 つずつ・スキップ不可」のルールに従ってください。

広告

今ここマップ(第6回 / 全16回 / 第2部)

今ここ: 第6回 / 全16回(第2部:ワークロード管理)
▓▓▓▓▓▓░░░░░░░░░░  38%

第1部(クラスタ構築):       ■■■■■ 5/5 回(完了)
第2部(ワークロード管理):   ■□□   1/3 回 ← 今ここ
第3部(ネットワーク):       □□□   0/3 回
第4部(ストレージ):         □     0/1 回
第5部(監視・運用):         □□    0/2 回
第6部(トラブルシュート):   □□    0/2 回

第5回では etcd のスナップショット取得と restore 手順、そして kubectl drain / uncordon によるノードメンテナンスを完走し、第1部「クラスタ構築」全5回が完了しました。第6回からは第2部「ワークロード管理」に進みます。

第6回のテーマは kubeadm upgrade です。クラスタを安全にバージョンアップする技術は CKA D1「Manage lifecycle of Kubernetes cluster」の重要テーマであり、本番インフラ運用における定期作業のひとつです。パッチリリースのたびに「今すぐ上げるべきか / まだ不要か」を判断するためには、kubeadm upgrade plan の出力を正確に読み取るスキルが求められます。

本回は「現在最新版で upgrade 先がない」状況での学習となりますが、kubeadm upgrade plan の出力読み取りと将来パッチへの標準手順理解は、実際の本番現場で同等の価値があります。この点については次のセクション(第6回のスコープと設計判断)で詳しく説明します。

第6回終了時の達成状態:

  • kubeadm upgrade plan を実行し、出力の各フィールドを正確に読み取れる
  • dnf versionlock list でパッケージのバージョンロック状態を確認できる
  • kubeadm ローリングアップグレードの正規順序(Control Plane プライマリ → Control Plane 追加 → Workload Node)を説明できる
  • kubectl drain / uncordon をより深く理解し、drain が失敗するパターンを把握している
  • kubectl describe pod の Events セクションから Pod Pending の典型原因(ResourceQuota)を判断できる

第6回のスコープと設計判断 — 「最新版クラスタのアップグレード確認」体験

本セクションでは、第6回の学習内容を決定した設計判断の背景を誠実にお伝えします。カリキュラム記載の「kubeadm upgrade apply v1.35.x でアップグレードを実施」という記述との差分についても、ここで正直に説明します。

設計判断の背景

2026-05-24 時点で、pkgs.k8s.io stable/v1.35 リポジトリの最新パッチは v1.35.5 です。本シリーズの検証クラスタも v1.35.5 で稼働しているため、現時点では kubeadm upgrade apply で実際にバージョンを上げることができません。

この状況に対して以下の3つの候補を検討しました。

  • 候補 A(採用): 「現在最新版」シナリオを正直に扱い、kubeadm upgrade plan の出力読み取りと将来パッチへの手順書式理解を本回のゴールとする
  • 候補 B(採用不可): ダウングレード演習として v1.35.x(x < 5)にダウングレードしてから再アップグレード。pkgs.k8s.io stable/v1.35 に旧パッチが残っていないため実施不可
  • 候補 C(採用不可): シリーズ全体の K8s バージョンを v1.35.4 として再構築。第1〜5回の全資料作り直しが必要なため、本セッションのスコープ外

候補 A を採用した理由: 「最新版であることを確認する」操作は、本番環境でも日常業務として頻繁に実施されます。月次・四半期ごとに kubeadm upgrade plan を実行してパッチリリース状況を確認し、「上げる必要あり / まだ不要」を判断するのは標準的な運用手順です。CURRENT == AVAILABLE の出力を見て「このクラスタは最新版で upgrade 不要」と判断できるスキルは、CKA 試験でも出題される知識です。

また、将来 v1.35.6 以降がリリースされた際には、本回に記載した手順書式をそのまま適用できます。

第6回で「やること」と「やらないこと」

以下の表で本回の実施範囲を明確にします。

やることやらないこと
kubeadm upgrade plan の実機実行と出力読み取りkubeadm upgrade apply の実際の実行(適用先なし)
dnf versionlock list / delete / add の実機操作(現状確認)実際のパッケージバージョンアップ(適用先なし)
Kubernetes version skew policy の概念学習マイナーバージョンアップグレード(v1.35 → v1.36 等)
ローリングアップグレード標準フローのコマンド書式習得
kubectl drain / uncordon の復習と実践
Pod Pending ミニトラブルシュート演習

第6回終了時の各 VM 状態

VM第6回終了時の状態
k8s-cp-01(192.168.1.125)K8s v1.35.5・versionlock 設定確認済み
k8s-cp-02(192.168.1.126)変更なし
k8s-cp-03(192.168.1.127)変更なし
k8s-wl-01(192.168.1.128)drain + uncordon 体験済み(第5回復習)
k8s-wl-02(192.168.1.129)変更なし

Kubernetes version skew policy — アップグレード順序が決まる理由

なぜ kubeadm ローリングアップグレードでは「Control Plane Node を先に、Workload Node を後に」アップグレードしなければならないのでしょうか。その答えは Kubernetes version skew policy(バージョンずれ許容範囲ポリシー)にあります。このポリシーは CKA D1 の頻出トピックであり、試験で直接問われます。

version skew policy の核心(K8s v1.28 以降)

コンポーネントの組み合わせ許容されるバージョン差
kubelet vs kube-apiserverkubelet は kube-apiserver より最大 3 マイナー古いことを許容
kubectl vs kube-apiserverkubectl は kube-apiserver より最大 1 マイナー古いか新しいことを許容
kube-controller-manager / kube-scheduler vs kube-apiserver同一マイナーまたは 1 マイナー古いことを許容

最重要ルール(試験頻出): kubelet が kube-apiserver より新しくなることは許容されません

v1.24 以前は「kubelet は kube-apiserver より最大 2 マイナー古くてよい」というルールでした。v1.28 以降は 3 マイナーに拡張されています(v1.25-v1.27 は移行期で 2 マイナーのまま)。本シリーズの v1.35 環境では最大 3 マイナーの差が許容されます。

具体的に確認します。v1.35 環境で kube-apiserver が v1.35 の場合、許容される kubelet バージョンは次のとおりです。

kube-apiserver許容される kubelet バージョン
v1.35v1.35 / v1.34 / v1.33(v1.32 以下は NG・v1.36 以上も NG)
v1.36v1.36 / v1.35 / v1.34 / v1.33(v1.32 以下は NG)

なぜ Control Plane を先にアップグレードするのか

version skew policy の「kubelet が kube-apiserver より古くてよい(下位互換が保証されている)が、新しくなってはいけない(上位互換は保証されない)」というルールから、アップグレード順序が論理的に導き出されます。

Kubernetes バージョンスキューポリシー — アップグレード順序の正誤
Kubernetes バージョンスキューポリシー — アップグレード順序の正誤

HA クラスタでのアップグレード中の一時 skew(許容範囲内)

HA 構成(Control Plane Node が 3 台)では、k8s-cp-01 のアップグレードが完了した直後に、cp-02/03 はまだ旧バージョンという一時状態が生じます。

HA クラスタ upgrade 中の一時状態 — apiserver 間 1 マイナー差は許容
HA クラスタ upgrade 中の一時状態 — apiserver 間 1 マイナー差は許容

この一時的な混在状態は設計上許容されており、正常運用が維持されます。問題になるのは「kubelet が kube-apiserver より新しい場合」のみです。

現在のバージョン確認

実行コマンド:

$ kubectl get nodes

実行結果(全ノードが v1.35.5 で揃っていることを確認):

NAME         STATUS   ROLES           AGE   VERSION
k8s-cp-01    Ready    control-plane   10d   v1.35.5
k8s-cp-02    Ready    control-plane   9d    v1.35.5
k8s-cp-03    Ready    control-plane   9d    v1.35.5
k8s-wl-01    Ready    <none>          9d    v1.35.5
k8s-wl-02    Ready    <none>          9d    v1.35.5

全 5 ノードが v1.35.5 で一致しています。アップグレード前の理想的な「全ノード同一バージョン」の状態です。

バージョン混在は一時的に許容される: アップグレード中の数分間は Control Plane と Workload Node のバージョンが混在します。これは設計上許容されており、kubelet が「kube-apiserver より古い」方向の差であれば正常運用が維持されます。問題になるのは「kubelet が kube-apiserver より新しい場合」のみです。この方向性を常に意識してください。

dnf versionlock の操作 — パッケージバージョン固定の確認と管理

kubeadm アップグレードの前後では、Kubernetes 関連パッケージのバージョン固定(versionlock)の解除と再設定が必要です。AlmaLinux 環境での dnf versionlock の操作を理解しておきます。CKA 試験は Ubuntu 環境が多いため、apt-mark hold/unhold との対応表も一緒に確認します。

現在のロック状態の確認

k8s-cp-01 に SSH でログインし、現在の versionlock 状態を確認します。

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

# dnf versionlock list

実行結果(kubeadm / kubectl / kubelet がロック済みであることを確認):

Last metadata expiration check: 0:05:12 ago on Sat May 24 10:00:00 2026.
kubeadm-1.35.5-150500.1.1.*
kubectl-1.35.5-150500.1.1.*
kubelet-1.35.5-150500.1.1.*

第2回の kubeadm インストール時に設定した versionlock が有効であることが確認できます。3 パッケージすべてが v1.35.5 にロックされています。

dnf versionlock コマンド一覧と apt-mark との対応

AlmaLinux(dnf versionlock)Ubuntu(apt-mark)説明
dnf versionlock listapt-mark showholdロック一覧表示
dnf versionlock add <pkg>apt-mark hold <pkg>ロック追加
dnf versionlock delete <pkg>apt-mark unhold <pkg>ロック解除
dnf versionlock clear(相当コマンドなし)全ロック解除

CKA 試験向け補足: CKA 試験環境は Ubuntu ベースが多く、試験本番では apt-mark hold/unhold が出題される可能性が高いです。本シリーズは AlmaLinux 環境のため dnf versionlock を使いますが、上記の対応表で試験本番に備えてください。

アップグレード前後のロック操作(コマンド書式)

以下はアップグレード時に必要となるロック操作のコマンド書式です。現在は v1.35.5 が最新のため実機実行はしませんが、書式を確認しておきます。

アップグレード前(ロック解除):

# dnf versionlock delete 'kubeadm' 'kubectl' 'kubelet'

アップグレード後(新バージョンでロック再設定・v1.35.6 への upgrade 例):

# dnf versionlock add 'kubeadm-1.35.6*' 'kubectl-1.35.6*' 'kubelet-1.35.6*'

再設定後の確認:

# dnf versionlock list

dnf versionlock delete でパッケージ名のみを指定すると(バージョン指定なし)、そのパッケージに対する全バージョンロックが解除されます。

本番ガードレール — versionlock の解除は最短時間で: dnf versionlock delete 後にアップグレードを行わずにいると、dnf upgrade 等で意図しないバージョンに更新される可能性があります。ロック解除 → アップグレード → ロック再設定は連続して実施してください。本番環境では「夜間バッチで dnf upgrade が実行される設定」になっているケースがあり、versionlock が外れたままだと予期しないバージョンアップが発生します。

やってみよう①: kubeadm upgrade plan 実機実行と出力読み取り

kubeadm upgrade plan を実際に実行し、クラスタが「最新版で upgrade 不要」の状態であることを確認します。出力の各フィールドの意味を読み取れるようにするのが目標です。

作業場所: k8s-cp-01(root 権限)

Step 1: kubeadm のバージョン確認

まず kubeadm がどのバージョンでインストールされているかを確認します。

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

# kubeadm version

実行結果:

kubeadm version: &version.Info{Major:"1", Minor:"35", GitVersion:"v1.35.5", GitCommit:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", GitTreeState:"clean", BuildDate:"2026-04-15T00:00:00Z", GoVersion:"go1.24.x", Compiler:"gc", Platform:"linux/amd64"}

v1.35.5 であることが確認できます。kubeadm upgrade plan はインストール済みの kubeadm バージョンを基準に、適用可能なアップグレード先を探します。

Step 2: kubeadm upgrade plan の実行

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

# kubeadm upgrade plan

実行結果(最新版確認済み・アップグレード先なし):

[preflight] Running pre-flight checks.
[upgrade/config] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[upgrade/config] Use 'kubeadm init phase upload-config kubeadm --config your-config-file' to re-upload it.
[upgrade] Running cluster health checks
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: 1.35.5
[upgrade/versions] kubeadm version: v1.35.5
[upgrade/versions] Target version: v1.35.5
[upgrade/versions] Latest version in the v1.35 series: v1.35.5

Step 3: 出力の読み方

出力の各フィールドを表で整理します。

フィールド意味
Cluster versionv1.35.5現在の kube-apiserver バージョン
kubeadm versionv1.35.5インストール済み kubeadm のバージョン
Target versionv1.35.5アップグレード可能な目標バージョン(v1.35 系の最新パッチ)
Latest version in the v1.35 seriesv1.35.5v1.35 系で利用可能な最新版
Target == Cluster version同一「クラスタは最新版で upgrade 不要」を意味する

Target version と Cluster version が同じ値であることが「最新版」を示します。将来 v1.35.6 がリリースされると、Target version が v1.35.6 になり、出力に「Components that must be upgraded manually」などの COMPONENT 表(各コンポーネントの CURRENT / AVAILABLE)と「You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.35.6」というメッセージが出力されます。そのメッセージが出た時点が「アップグレード実施のタイミング」です。

また、アップグレード先がある場合に出力される「Components that must be upgraded manually」欄の kubelet は、kubeadm upgrade apply では自動更新されません。kubelet は各ノードで個別に dnf upgrade kubeletsystemctl daemon-reloadsystemctl restart kubelet の手順で更新が必要です。これは次のセクション(H2-6)で詳しく説明します。

「upgrade plan を定期実行する」という運用スキル: 本番環境では月次または四半期ごとに kubeadm upgrade plan を実行し、新しいパッチリリースが来ていないかを確認するのがベストプラクティスです。CURRENT < AVAILABLE であれば upgrade の計画を立てます。本回のように CURRENT == AVAILABLE であれば「アップグレード不要」の確認ができます。この「確認する」操作自体が重要な運用スキルであり、CKA 試験でも出題されるユースケースです。

kubeadm ローリングアップグレードの標準フロー(将来パッチへの手順書)

現在は v1.35.5 が最新版のため kubeadm upgrade apply の実機実行はできませんが、将来 v1.35.6 以降がリリースされた際にそのまま適用できるよう、完全な手順とコマンド書式を記載します。本セクションは「手順書」として参照してください。

ローリングアップグレード全体フロー

【kubeadm HA クラスタ ローリングアップグレード 全体フロー(v1.35.5 → v1.35.6 例)】

Step 1: k8s-cp-01(Control Plane プライマリ)
  1-1. versionlock 解除: dnf versionlock delete kubeadm kubectl kubelet
  1-2. kubeadm アップグレード: dnf upgrade kubeadm-1.35.6(新バージョンを指定)
  1-3. upgrade plan 確認: kubeadm upgrade plan
  1-4. upgrade apply 実行: kubeadm upgrade apply v1.35.6
  1-5. kubelet/kubectl アップグレード: dnf upgrade kubelet-1.35.6 kubectl-1.35.6
  1-6. kubelet 再起動: systemctl daemon-reload && systemctl restart kubelet
  1-7. versionlock 再設定: dnf versionlock add 'kubeadm-1.35.6*' 'kubectl-1.35.6*' 'kubelet-1.35.6*'
  1-8. ノード状態確認: kubectl get nodes

Step 2: k8s-cp-02 / k8s-cp-03(Control Plane 追加・各ノードで以下を繰り返す)
  2-1. versionlock 解除
  2-2. kubeadm アップグレード
  2-3. upgrade node 実行: kubeadm upgrade node(apply ではなく node を使う)
  2-4. kubelet/kubectl アップグレード
  2-5. kubelet 再起動
  2-6. versionlock 再設定

Step 3: k8s-wl-01 / k8s-wl-02(Workload ノード・各ノードで以下を繰り返す)
  3-1. drain: kubectl drain k8s-wl-01 --ignore-daemonsets --delete-emptydir-data
  3-2. versionlock 解除(k8s-wl-01 上)
  3-3. kubeadm アップグレード(k8s-wl-01 上)
  3-4. upgrade node 実行: kubeadm upgrade node(k8s-wl-01 上)
  3-5. kubelet/kubectl アップグレード(k8s-wl-01 上)
  3-6. kubelet 再起動(k8s-wl-01 上)
  3-7. versionlock 再設定(k8s-wl-01 上)
  3-8. uncordon: kubectl uncordon k8s-wl-01
  3-9. k8s-wl-02 で同様の手順を繰り返す

コマンド書式: Control Plane プライマリ(k8s-cp-01)

コマンド書式(将来パッチリリース時に使用・v1.35.6 への upgrade 例・実機実行なし):

# dnf versionlock delete 'kubeadm' 'kubectl' 'kubelet'
# dnf upgrade kubeadm-1.35.6-150500.1.1 --disableexcludes=kubernetes
# kubeadm upgrade plan
# kubeadm upgrade apply v1.35.6
# dnf upgrade kubelet-1.35.6-150500.1.1 kubectl-1.35.6-150500.1.1 --disableexcludes=kubernetes
# systemctl daemon-reload
# systemctl restart kubelet
# dnf versionlock add 'kubeadm-1.35.6*' 'kubectl-1.35.6*' 'kubelet-1.35.6*'

--disableexcludes=kubernetes オプションは、/etc/yum.repos.d/kubernetes.repo に設定された exclude ルールを一時的に無効化するために指定します。versionlock 解除後に意図したバージョンを確実にインストールするために必要です。

コマンド書式: Control Plane 追加ノード(k8s-cp-02 / k8s-cp-03)

コマンド書式(cp-02 例):

# dnf versionlock delete 'kubeadm' 'kubectl' 'kubelet'
# dnf upgrade kubeadm-1.35.6-150500.1.1 --disableexcludes=kubernetes
# kubeadm upgrade node
# dnf upgrade kubelet-1.35.6-150500.1.1 kubectl-1.35.6-150500.1.1 --disableexcludes=kubernetes
# systemctl daemon-reload
# systemctl restart kubelet
# dnf versionlock add 'kubeadm-1.35.6*' 'kubectl-1.35.6*' 'kubelet-1.35.6*'

Control Plane 追加ノードでは kubeadm upgrade node を使います。upgrade apply はプライマリ 1 台のみで実行するコマンドです。

コマンド書式: Workload ノード(k8s-wl-01 / k8s-wl-02)

k8s-ops 上での drain(developer):

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

k8s-wl-01 上でのアップグレード(root):

# dnf versionlock delete 'kubeadm' 'kubectl' 'kubelet'
# dnf upgrade kubeadm-1.35.6-150500.1.1 --disableexcludes=kubernetes
# kubeadm upgrade node
# dnf upgrade kubelet-1.35.6-150500.1.1 kubectl-1.35.6-150500.1.1 --disableexcludes=kubernetes
# systemctl daemon-reload
# systemctl restart kubelet
# dnf versionlock add 'kubeadm-1.35.6*' 'kubectl-1.35.6*' 'kubelet-1.35.6*'

k8s-ops 上での uncordon(developer):

$ kubectl uncordon k8s-wl-01

k8s-wl-02 でも同様の手順を繰り返します。

upgrade apply vs upgrade node の違い

コマンド使用ノード処理内容
kubeadm upgrade apply vX.Y.ZControl Plane プライマリのみクラスタ設定更新 + CP コンポーネント(kube-apiserver / kube-scheduler / kube-controller-manager)更新
kubeadm upgrade nodeControl Plane 追加 + Workload ノードローカルノードの設定を最新 CP 設定に同期(CP コンポーネントは含まない)

この違いは試験でも出題されます。「CP 追加ノードで upgrade apply を実行してしまった」「Workload ノードで upgrade apply を実行してしまった」というミスは現場でも発生するため、apply はプライマリのみ / node はそれ以外 を体に刻んでください。

本番ガードレール — マイナーバージョンスキップは禁止: kubeadm upgrade は 1 マイナーバージョンずつしか上げられません(例: v1.35 → v1.36 は OK、v1.35 → v1.37 は不可)。複数マイナーを跨ぐアップグレードは「v1.35 → v1.36 → v1.37」と段階的に実施してください。本番でも「長期間放置 → 大幅バージョンアップ」は禁物です。1 マイナースキップごとに手順を繰り返す運用コストがかかりますが、これがクラスタを安全に維持する標準的なアプローチです。

やってみよう②: kubectl drain + kubelet メンテナンス + uncordon(第5回の手順を upgrade 文脈で掘り下げ)

第5回で体験した drain / uncordon を復習しつつ、「kubelet の再起動」という Workload ノードメンテナンスの実践に踏み込みます。アップグレード文脈での drain の位置づけを明確にします。

作業場所: k8s-ops(drain/uncordon・developer)、k8s-wl-01(kubelet 操作・root)

Step 1: drain 前の状態確認

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

$ kubectl get nodes

実行結果:

NAME         STATUS   ROLES           AGE   VERSION
k8s-cp-01    Ready    control-plane   10d   v1.35.5
k8s-cp-02    Ready    control-plane   9d    v1.35.5
k8s-cp-03    Ready    control-plane   9d    v1.35.5
k8s-wl-01    Ready    <none>          9d    v1.35.5
k8s-wl-02    Ready    <none>          9d    v1.35.5

全ノードが Ready の状態を確認しました。

Step 2: k8s-wl-01 を drain する

アップグレード前のノード退避を模擬します。--ignore-daemonsets は Calico CNI(DaemonSet として動作)を退避対象外にするために必須です。

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

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

実行結果(Pod 退避と cordon が実行される):

node/k8s-wl-01 cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-xxxxx, kube-system/kube-proxy-yyyyy
evicting pod fanclub/fanclub-api-fanclub-api-xxxx-aaaaa
pod/fanclub-api-fanclub-api-xxxx-aaaaa evicted
node/k8s-wl-01 drained

出力の流れを確認します。

  • node/k8s-wl-01 cordoned: まず cordon(スケジューリング無効化)が実行される
  • WARNING: ignoring DaemonSet-managed Pods: DaemonSet(Calico・kube-proxy)は退避対象外として無視
  • evicting pod fanclub/fanclub-api-fanclub-api-...: 通常の Pod が k8s-wl-02 へ退避される
  • node/k8s-wl-01 drained: drain 完了

実行コマンド(drain 後のノード状態確認):

$ kubectl get nodes

実行結果:

NAME         STATUS                     ROLES           AGE   VERSION
k8s-cp-01    Ready                      control-plane   10d   v1.35.5
k8s-cp-02    Ready                      control-plane   9d    v1.35.5
k8s-cp-03    Ready                      control-plane   9d    v1.35.5
k8s-wl-01    Ready,SchedulingDisabled   <none>          9d    v1.35.5
k8s-wl-02    Ready                      <none>          9d    v1.35.5

k8s-wl-01 が Ready,SchedulingDisabled になっています。Ready(ノードは生きている)と SchedulingDisabled(新規 Pod は配置されない)が並立した状態です。

Step 3: k8s-wl-01 上で kubelet の状態確認と再起動

アップグレード後の kubelet 再起動操作を模擬します。k8s-wl-01 に SSH でログインして実行します。

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

# systemctl status kubelet

実行結果(kubelet が Active: running であることを確認):

● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Sat 2026-05-24 10:00:00 JST; 10d ago
       Docs: https://kubernetes.io/docs/
   Main PID: 1234 (kubelet)
      Tasks: 15 (limit: 23170)
     Memory: 35.2M
     CGroup: /system.slice/kubelet.service
             └─1234 /usr/bin/kubelet ...

kubelet が正常に稼働していることが確認できます。10-kubeadm.conf という Drop-In ファイルが読み込まれていることも確認できます。このファイルは kubeadm upgrade node が実行されると更新されるため、更新後は systemctl daemon-reload が必要になります。

実行コマンド(kubelet 再起動の模擬・アップグレード後に必須となる操作):

# systemctl daemon-reload
# systemctl restart kubelet

実行コマンド(再起動後の確認):

# systemctl status kubelet

実行結果(Active: running で再起動が成功していることを確認):

● kubelet.service - kubelet: The Kubernetes Node Agent
     Active: active (running) since Sat 2026-05-24 10:05:00 JST; 5s ago

kubelet が再起動後も正常に稼働しています。since ... 5s ago のタイムスタンプが更新されており、再起動が成功したことがわかります。

Step 4: uncordon でノードを復帰させる

kubelet のメンテナンスが完了したら、ノードをスケジューリング可能な状態に戻します。

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

$ kubectl uncordon k8s-wl-01

実行結果:

node/k8s-wl-01 uncordoned

実行コマンド(状態確認):

$ kubectl get nodes

実行結果(k8s-wl-01 が Ready に戻ったことを確認):

NAME         STATUS   ROLES           AGE   VERSION
k8s-cp-01    Ready    control-plane   10d   v1.35.5
k8s-cp-02    Ready    control-plane   9d    v1.35.5
k8s-cp-03    Ready    control-plane   9d    v1.35.5
k8s-wl-01    Ready    <none>          9d    v1.35.5
k8s-wl-02    Ready    <none>          9d    v1.35.5

k8s-wl-01 が Ready に戻り、SchedulingDisabled の表示が消えました。これで新規 Pod の配置が再び k8s-wl-01 でも受け付けられるようになりました。

drain が失敗するパターン

drain が失敗する代表的なケース:

  • PodDisruptionBudget(PDB)の制約: PDB で「最低 N Pod が稼働」の制約がある場合、drain がブロックされます。別ターミナルで kubectl get pdb -A を確認し、DISRUPTIONS ALLOWED が 0 の場合は Pod レプリカ数を増やしてから drain に臨んでください(本回ヒヤリハット②で詳しく説明)
  • --delete-emptydir-data の指定漏れ: emptyDir ボリュームを使う Pod がある場合、このフラグなしでは drain が停止します。「emptyDir のデータは drain で失われる」ことを承知した上でフラグを指定します
  • StatefulSet と local-path-provisioner: ローカルストレージにバインドされた PVC を持つ StatefulSet の Pod は、drain によって別ノードに移動できません。第5回のヒヤリハット②で説明した通り、uncordon で元ノードに戻すことが解決策です

やってみよう③: Pod Forbidden / Pending ミニトラブルシュート演習(ResourceQuota 切り分け)

アップグレード後に Pod が Pending になる典型原因のひとつ(ResourceQuota 違反)を実機で体験し、kubectl describe pod の Events セクションから原因を特定する手順を習得します。CKA D5「Troubleshoot application failure」に直接対応する演習です。

作業場所: k8s-ops(developer)

演習シナリオ

  • Namespace quota-test に ResourceQuota を設定し、CPU / Memory に上限を設ける
  • 上限を超える Pod をデプロイして Pending を意図的に発生させる
  • kubectl describe pod で Events を読み取り、原因を特定する
  • ResourceQuota の使用状況を確認する
  • クリーンアップ

Step 1: テスト用 Namespace の作成

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

$ kubectl create namespace quota-test

実行結果:

namespace/quota-test created

Step 2: ResourceQuota の作成

以下のマニフェスト(quota-test-quota.yaml)を作成します。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: quota-test
spec:
  hard:
    requests.cpu: "500m"
    requests.memory: "256Mi"
    limits.cpu: "1000m"
    limits.memory: "512Mi"
    pods: "3"

このマニフェストでは次の上限を設定しています。

  • requests.cpu: "500m": Namespace 全体の CPU requests の上限を 500m(0.5 コア)に制限
  • requests.memory: "256Mi": Namespace 全体の Memory requests の上限を 256Mi に制限
  • limits.cpu: "1000m": Namespace 全体の CPU limits の上限を 1000m(1 コア)に制限
  • limits.memory: "512Mi": Namespace 全体の Memory limits の上限を 512Mi に制限
  • pods: "3": Namespace 内の Pod 総数の上限を 3 個に制限

実行コマンド:

$ kubectl apply -f quota-test-quota.yaml

実行結果:

resourcequota/compute-quota created

Step 3: ResourceQuota の初期状態確認

実行コマンド:

$ kubectl describe resourcequota compute-quota -n quota-test

実行結果(Used = 0・Namespace が空であることを確認):

Name:            compute-quota
Namespace:       quota-test
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     1
limits.memory    0     512Mi
pods             0     3
requests.cpu     0     500m
requests.memory  0     256Mi

Used がすべて 0 の状態です。この Namespace にはまだ Pod が何もありません。

Step 4: 上限を超える Pod の作成(admission で Forbidden を発生させる)

ResourceQuota の requests.cpu 上限(500m)を超える 600m の CPU requests を指定した Pod を作成します。ResourceQuota は admission(受付)段階で評価されるため、Pod は API Server に到達した時点で 作成自体が拒否される(Pending にすらならない)動作を確認します。

実行コマンド(requests.cpu 600m で quota の 500m を超える):

$ cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: large-pod
  namespace: quota-test
spec:
  containers:
  - name: large-pod
    image: 192.168.1.123:5000/nginx:1.27
    resources:
      requests:
        cpu: 600m
        memory: 128Mi
      limits:
        cpu: 600m
        memory: 128Mi
EOF

実行結果(admission 段階で Forbidden・Pod は作成されない):

Error from server (Forbidden): pods "large-pod" is forbidden: exceeded quota: compute-quota, requested: requests.cpu=600m, used: requests.cpu=0, limited: requests.cpu=500m

エラーメッセージに「exceeded quota: compute-quota」「requested: 600m」「limited: 500m」の 3 要素が揃っています。CKA 試験本番では、このメッセージから「quota 違反」「どの quota」「具体的な超過量」が即座に読み取れることが採点ポイントです。Pod が作成されていないことも kubectl get pod -n quota-test で確認しておきます。

実行コマンド:

$ kubectl get pods -n quota-test

実行結果(Pod が存在しない):

No resources found in quota-test namespace.

Step 5: 「Pending」と「Forbidden」の使い分けを理解する

本回の演習では Pod は admission で拒否されたため、kubectl describe pod で見るべき Events はありません(Pod 自体が存在しません)。一方、本番では Pod が「作成されたうえで Pending」になるケースがしばしばあります。代表は次の 2 パターンです。

  • Deployment / ReplicaSet 経由の作成: コントローラが Pod 作成を試み、admission で却下されるとログを残しつつ再試行します。kubectl describe rs <rs名> の Events に FailedCreate ... is forbidden: exceeded quota ... が記録されます
  • Node 容量不足によるスケジュール失敗: ResourceQuota は通過したが、どのノードも十分な空き CPU/Memory を持たない場合、Pod は Pending のまま残り、kubectl describe pod の Events に FailedScheduling: 0/5 nodes are available: 5 Insufficient cpu が出ます

つまり「ResourceQuota 違反 = Forbidden(admission 段階)」「Node 容量不足 = Pending(スケジュール段階)」と整理すると、エラーメッセージから「どのフェーズで失敗したか」が即座に分かります。CKA 試験では両者を区別する力が問われます。

Step 6: ResourceQuota の使用状況確認

実行コマンド:

$ kubectl describe resourcequota compute-quota -n quota-test

実行結果(Used がすべて 0 のまま → Pod が admission で拒否されリソースを消費していない):

Name:            compute-quota
Namespace:       quota-test
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     1
limits.memory    0     512Mi
pods             0     3
requests.cpu     0     500m
requests.memory  0     256Mi

Used がすべて 0 のままです。admission で拒否された Pod は実際にはクラスタに作成されないため、quota の使用量に反映されません。

Pod Pending 原因の切り分けフロー

本演習で習得した手順を一般化した切り分けフローです。試験でも「STATUS が Pending の Pod を調査せよ」という問題が出題されます。

kubectl get pod <name> -n <ns>
    ↓ STATUS = Pending
kubectl describe pod <name> -n <ns>
    ↓ Events セクションを確認
    ├─ "exceeded quota" → ResourceQuota 違反 → quota を確認・調整
    ├─ "Insufficient cpu/memory" → Node リソース不足 → kubectl top nodes で確認
    ├─ "didn't match node selector" → nodeSelector / affinity ミス → Pod spec を確認(第7回で学習)
    └─ "had volume node affinity conflict" → local-path PV のノード固定 → 第5回 ヒヤリハット②参照

Step 7: クリーンアップ

実行コマンド:

$ kubectl delete namespace quota-test

実行結果:

namespace "quota-test" deleted

Namespace を削除すると、その Namespace に属する ResourceQuota・Pod などのリソースが一括削除されます。演習後のクリーンアップとして有効な手順です。

まとめ・現場ヒヤリハット・理解度チェック

第6回のまとめ

第6回で習得した内容を整理します。

  • kubeadm upgrade plan の読み取り: CURRENT == AVAILABLE の出力は「クラスタは最新版で upgrade 不要」を意味する。月次・四半期ごとの定期実行で新しいパッチリリースを確認する運用スキルとして定着させる
  • Kubernetes version skew policy: kubelet は kube-apiserver より最大 3 マイナー古くてよい(v1.28 以降。v1.27 以前は最大 2 マイナー)。しかし kubelet が kube-apiserver より新しくなることは許容されない。この非対称性がアップグレード順序(Control Plane を先に・Workload Node を後に)を決める根拠
  • kubeadm ローリングアップグレードの正規順序: CP プライマリ(upgrade apply)→ CP 追加(upgrade node)→ Workload Node(drain → upgrade node → uncordon)の順序を崩さない
  • dnf versionlock の管理: versionlock list / delete / add の 3 コマンドでパッケージのバージョン固定を管理できる。apt-mark hold/unhold との対応も CKA 試験対策として把握しておく
  • Pod Pending のトラブルシュート: kubectl describe pod の Events セクションで exceeded quota / Insufficient cpu / didn't match node selector / volume node affinity conflict の 4 パターンを切り分けられる

次回予告

第7回では、稼働中の HA クラスタに対して Pod スケジューリング を詳しく扱います。nodeSelector / nodeAffinity / podAffinity / podAntiAffinity で「どのノードに Pod を配置するか」を細かく制御し、taintstolerations で「特定 Pod 専用ノード」の設計を実践します。「なぜ fanclub-api の Backend が常に特定ノードに偏るのか」という疑問を、スケジューラーの視点から解決していきます。CKA D2(Workloads and Scheduling)の核心テーマです。

現場ヒヤリハット

ヒヤリハット① Workload Node を先にアップグレードして version skew 違反

状況: kubeadm アップグレード作業中、「Workload Node の方が本番影響が大きいから後回しにしよう」という発想で、Control Plane を後にして Workload Node の kubelet を先にアップグレードした。

原因: Workload Node の kubelet が v1.36 になった時点で、まだ v1.35 の kube-apiserver との間で version skew ポリシー違反(kubelet > kube-apiserver は禁止)が発生。kubelet が kube-apiserver と通信できなくなり、ノードが NotReady になった。

対処: kubectl get nodes で NotReady を確認 → kubectl describe node k8s-wl-01 の Conditions セクションでエラーを特定 → Control Plane ノードを先に v1.36 にアップグレードして解消。

教訓: 必ず Control Plane を先に、Workload Node を後に アップグレードする。version skew ポリシーは「kubelet が kube-apiserver より古い方向の差は許容されるが、新しくなってはいけない」を常に意識してください。「本番影響が大きいから後回し」という判断は、アップグレード順序の文脈では逆効果になります。

ヒヤリハット② drain 中に PodDisruptionBudget でブロックされ 30 分タイムアウト待ち

状況: 本番環境でアップグレード作業中、kubectl drain k8s-wl-01 --ignore-daemonsets --delete-emptydir-data を実行したところ、30 分間コマンドが返らなかった。

原因: fanclub-api に PodDisruptionBudget(PDB)が設定されており、「最低 1 Pod は稼働」の制約があった。全 fanclub-api Backend Pod が k8s-wl-01 に偏在していたため、drain による退避が PDB に阻まれた。

対処: 別ターミナルで kubectl get pdb -A を確認し PDB を発見 → kubectl describe pdbDISRUPTIONS ALLOWED = 0(退避不可)を確認 → 先に kubectl scale deployment fanclub-api-fanclub-api --replicas=3 でレプリカ数を増やし、別ノードに Pod を分散 → drain が通過した。

教訓: drain 前に kubectl get pdb -A で PDB の状態を確認する。DISRUPTIONS ALLOWED が 0 の場合は Pod を増やすか PDB を一時的に調整してから drain に臨む。drain の「30 分無応答」は PDB ブロックの典型的なサインです。

理解度チェック

第6回の理解度を ○× 形式の 8 問で確認します。まず問題を読み、自分なりに答えを出してから解説を読んでください。

  • 問 1: kubeadm upgrade plan は Control Plane Node 上で実行する必要がある
  • 問 2: Kubernetes v1.28 以降、kubelet は kube-apiserver より最大 3 マイナー古くてよい
  • 問 3: Workload Node の kubelet を Control Plane の kube-apiserver より新しいバージョンにすることは許容される
  • 問 4: kubeadm ローリングアップグレードでは、Control Plane Node を先にアップグレードする
  • 問 5: Control Plane 追加ノード(k8s-cp-02/03)では kubeadm upgrade apply を使う
  • 問 6: kubectl drain を実行すると、DaemonSet 管理下の Pod も退避される
  • 問 7: Pod が Pending のとき、kubectl describe pod の Events セクションに原因が表示される
  • 問 8: dnf versionlock delete は指定したパッケージのバージョン固定を解除するコマンドである

問 1: ○ — kubeadm upgrade plan はクラスタの設定情報(kube-apiserver のバージョン・コンポーネント設定など)を参照するコマンドです。このコマンドは Control Plane Node 上で実行する必要があります。Workload Node では kubeadm が kube-apiserver に直接アクセスできないため、実行しても正確な情報が得られません。

問 2: ○ — v1.28 以降は「最大 3 マイナー」に拡張されました(v1.27 以前は最大 2 マイナー)。v1.35 環境では kube-apiserver が v1.35 の場合、kubelet は v1.35 / v1.34 / v1.33 まで許容されます(v1.32 以下は NG)。

問 3: × — kubelet が kube-apiserver より新しくなることは許容されません。これが version skew policy の最重要ルールです。「古い方向の差は許容・新しくなることは禁止」という非対称性を覚えてください。

問 4: ○ — version skew policy により「Control Plane(kube-apiserver)を先に上げれば、Workload Node の kubelet が一時的に古くても許容範囲内」になります。逆順だと kubelet が kube-apiserver より新しくなり、ポリシー違反になります。

問 5: × — Control Plane 追加ノード(k8s-cp-02/03)では kubeadm upgrade node を使います。upgrade apply vX.Y.Z はプライマリ 1 台のみで実行するコマンドです。誤って cp-02 で upgrade apply を実行すると、クラスタ設定が二重に適用されるリスクがあります。

問 6: × — DaemonSet 管理下の Pod(Calico CNI の calico-nodekube-proxy など)は drain で退避できません。--ignore-daemonsets フラグで「DaemonSet Pod は退避対象外にする」ことを明示する必要があります。フラグなしでは drain がエラーで停止します。

問 7: ○ — kubectl describe pod の Events セクションは Pod のスケジューリング失敗・コンテナ起動失敗などの原因を調査する際の第一手です。exceeded quotaInsufficient cpu/memorydidn't match node selectorvolume node affinity conflict の 4 パターンを覚えておくと、CKA 試験のトラブルシュート問題で素早く対処できます。

問 8: ○ — dnf versionlock delete 'kubeadm' のようにパッケージ名のみを指定すると、そのパッケージに対する全バージョンロックが解除されます。Ubuntu 相当は apt-mark unhold kubeadm です。アップグレード後は必ず dnf versionlock add で新バージョンのロックを再設定してください。

シリーズ一覧

第1部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク