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

kubeadm initでシングルノード起動【CKA第2回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第2回です。
動作確認バージョン: AlmaLinux 10.1 (Heliotrope Lion・kernel 6.12.0-124.56.5.el10_1) / K8s v1.35.5 / kubeadm・kubelet・kubectl v1.35.5 / containerd.io v2.2.4 / Calico v3.32.0 / HAProxy 3.0.5(2026-05-21 時点・k8s-cp-01 / k8s-lb 実機検証済)。

第1回ではオリエンテーションとして第2巻の全体像・CKA 試験形式・kubeadm の概要を把握し、第1巻で使った kind クラスタを削除しました。第2回からは本番想定クラスタの構築が始まります。
今回は Control Plane Node #1(k8s-cp-01)に kubeadm v1.35 をインストールし、シングルノードクラスタを起動します。alma-proxy の whitelist 拡張から始まり、OS 前提設定・containerd 設定・kubeadm init・Calico CNI インストールまでを通しで構築します。
この回が終わると kubectl get nodes でシングルノードが Ready 表示されます。

広告

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

今ここマップ(第2巻 16 回中の現在位置):

今ここ: 第2回 / 全16回(第1部:クラスタ構築)
▓▓░░░░░░░░░░░░░░  12%

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

第2回で学ぶことは次の 5 点です。これがそのまま今回の学習目標になります。

  • alma-proxy の Squid whitelist に pkgs.k8s.ioprojectcalico.docs.tigera.io を追加し、k8s-cp-01 が Kubernetes パッケージを取得できる状態を作る
  • k8s-cp-01 に proxy 設定・swap 無効化・カーネルモジュール・sysctl・containerd(SystemdCgroup=true)を設定し、kubeadm init の前提条件を整える
  • pkgs.k8s.io から kubelet / kubeadm / kubectl v1.35 をインストールし、dnf versionlock でバージョンをピン留めする
  • k8s-lb に最小 HAProxy パススルー設定(6443 frontend → k8s-cp-01:6443 backend)を入れ、kubeadm init --config kubeadm-config.yaml --upload-certs でシングルノードクラスタを起動する
  • /etc/kubernetes/admin.conf を k8s-ops に転送し、Calico CNI をインストールして kubectl get nodes で Ready 確認する。alias k=kubectl を導入する

第2回終了時の達成状態を整理します。次回(第3回)以降の前提になる状態です。

  • alma-proxy の whitelist に pkgs.k8s.io / projectcalico.docs.tigera.io が追加されている
  • k8s-cp-01 に kubeadm v1.35 / kubelet v1.35 / kubectl v1.35 がインストール済み、かつ versionlock でピン留め済み
  • k8s-lb に最小 HAProxy パススルー設定(6443 → k8s-cp-01:6443)が入っている
  • k8s-cp-01 でシングルノードクラスタが起動し、Calico CNI が稼働している
  • k8s-ops から kubectl get nodes で k8s-cp-01 が Ready 表示される

今回は本番運用で踏み外してはいけないガードレールが 5 か所登場します。swap・SystemdCgroup・バージョンピン留め・controlPlaneEndpoint・カーネルモジュールの 5 つです。それぞれ「なぜこの設定が必要か」を本文中で必ず明示します。手順を機械的にコピーするだけでは、本番環境で再起動した瞬間にクラスタが壊れます。理由まで理解しながら進めてください。

第2回のスコープと前提 — k8s-lb・k8s-cp-01 の初期状態

具体的なコマンドに入る前に、今回「何を・どの VM で・どの順番で構築するのか」を整理します。フル構築回は手順が長くなるため、全体像を先に把握しておくと迷いません。

第2回で作業する VM 一覧

第2巻は全 9 VM 構成ですが、第2回で触るのは次の 4 VM です。残りの k8s-registry・k8s-cp-02/03・k8s-wl-01/02 は第2回では操作しません。

VMIP役割第2回での作業
alma-proxy192.168.1.121DNS / NTP / Squid プロキシwhitelist 拡張
k8s-ops192.168.1.122作業端末admin.conf 受け取り・alias 設定・Calico apply
k8s-lb192.168.1.124HAProxy LB最小パススルー設定
k8s-cp-01192.168.1.125Control Plane Node #1フル構築(OS 設定・containerd・kubeadm・init)

作業の起点は k8s-ops です。k8s-ops から SSH で各 VM に接続して作業します。SSH 経路はワークステーション → k8s-ops(192.168.1.122)→ 各 VM という 2 段構成です。k8s-cp-01 へは ssh developer@192.168.1.125 で接続します。developer ユーザーは全 cp/wl ノードで NOPASSWD の sudo が設定済みのため、root 作業は sudo を付けて実行します。

k8s-cp-01 の初期状態

k8s-cp-01 は素の AlmaLinux Minimal Install 直後の状態です。Kubernetes 関連は何も入っていません。第2回の前提スナップショット SP_vol2-pre-02 時点の初期値と、今回の作業で何をどう変更するかを表に整理します。

項目初期値第2回での変更
OSAlmaLinux 10.0(Purple Lion)dnf update で 10.1(Heliotrope Lion)に更新
kubeadm / kubelet / kubectl未導入pkgs.k8s.io から v1.35 をインストール
containerd未導入dnf でインストール + SystemdCgroup=true
swap有効(約 3.9 GB)swapoff -a + /etc/fstab 編集
カーネルモジュールoverlay / br_netfilter 未ロード/etc/modules-load.d/k8s.conf で設定
NICeth0 192.168.1.125/24 + eth1 10.0.10.125/24(2 NIC)そのまま使用
SELinuxEnforcingEnforcing のまま維持
プロキシ設定なし/etc/profile.d/proxy.sh / /etc/dnf/dnf.conf に設定

初期 OS が 10.0 になっているのは、第2巻環境の構築時点が 10.0 だったためです。本巻の動作確認基準は AlmaLinux 10.1 のため、今回の前提設定の中で dnf update を実施して 10.1 に揃えます。

第2回の構築フロー全体図

今回触る 4 VM と、k8s-cp-01 の構築レイヤーの関係を図で示します。下から順に積み上げる構築です。

第2回 構築フロー全体図 — alma-proxy から k8s-ops Ready まで
第2回 構築フロー全体図 — alma-proxy から k8s-ops Ready まで

この図のとおり、k8s-cp-01 の構築は OS 前提設定 → containerd → kubeadm パッケージ → kubeadm init という積み上げ式です。下のレイヤーが完成していないと上のレイヤーは動きません。たとえば containerd の SystemdCgroup が誤っていると、その上の kubeadm init が途中で失敗します。順番を守ることが重要です。

第2回〜第4回の役割分担

第2回はシングルノードクラスタの起動までを行います。HA クラスタ(CP×3 + WL×2)の完成は第4回です。第2回から第4回までの役割分担を整理します。

[第2回] k8s-lb:    最小パススルー設定(6443 → k8s-cp-01:6443 のみ)
        k8s-cp-01: kubeadm init でシングルノードクラスタ起動
         ↓
[第3回] k8s-lb:    HAProxy HA 設計(CP×3 backend・stats・ヘルスチェック)
        kubeadm-config.yaml の HA 設計
         ↓
[第4回] k8s-cp-02/03: kubeadm join --control-plane
        k8s-wl-01/02:  kubeadm join(Workload Node 参加)
        → 5 ノード HA クラスタ完成・fanclub-api HA 移行

第2回は k8s-lb に「最小限」の設定しか入れません。stats ページや CP×3 の backend は第3回(HAProxy HA 設計の専門回)で作り込みます。第2回で k8s-lb を最小限だけ触る理由は次の項目で説明します。

controlPlaneEndpoint を今から設定する理由

これが今回 1 つ目の本番ガードレールです。kubeadm init では controlPlaneEndpoint というフィールドで「外部から API Server に接続する際のエンドポイント」を指定できます。今回はこれを k8s-lb:6443 に設定します。

第2回時点では Control Plane が k8s-cp-01 の 1 台しかありません。であれば controlPlaneEndpoint を指定せず、k8s-cp-01 の IP を直接エンドポイントにする方が一見シンプルに見えます。しかし、それは本番アンチパターンです。

controlPlaneEndpoint は kubeadm init の実行時にしか素直に設定できません。後から変更するには、API Server の TLS 証明書に含まれる SAN(Subject Alternative Name)を作り直す必要があります。SAN にはクラスタが受け付けるホスト名や IP が列挙されており、ここに新しいエンドポイントを追加するには証明書の再生成・各コンポーネントの再起動が必要です。本番稼働中のクラスタでこれを行うのは大掛かりな作業で、ダウンタイムのリスクも伴います。

第4回で k8s-cp-02/03 を kubeadm join --control-plane で追加するとき、join 先のエンドポイントは controlPlaneEndpoint です。最初から k8s-lb:6443 をエンドポイントにしておけば、第4回の HA 化は証明書を作り直さずに進められます。CKA でも「HA クラスタを構築する」際は controlPlaneEndpoint を最初から付けるのが定石です。第2回時点では k8s-lb の向き先が CP 1 台だけですが、エンドポイントを LB に向けておくこと自体に意味があります。

この設計判断のために、第2回で k8s-lb に最小パススルー設定を入れます。kubeadm initcontrolPlaneEndpoint: k8s-lb:6443 を解決できるよう、k8s-lb の 6443 が k8s-cp-01 の 6443 に転送される状態を先に作っておく必要があるからです。

事前準備 — k8s-ops から各ノードへ SSH 公開鍵を配布する

第2回以降では k8s-ops(作業端末)から alma-proxy・k8s-cp-01〜03・k8s-wl-01〜02・k8s-lb の各ノードへ ssh developer@<ホスト> 形式で接続し、コマンドを実行します。第1巻時点では k8s-ops は kind クラスタの操作端末として使うだけで、他ノードへの SSH 鍵は配布されていません。第2回の各ステップに入る前に、k8s-ops で鍵ペアを生成し、各ノードの ~developer/.ssh/authorized_keys に公開鍵を登録します。この作業は第2巻全体で 1 回だけ実施すれば、第3回以降は鍵認証で透過的に接続できます。

実行コマンド(k8s-ops 上・developer 権限・鍵が未生成の場合のみ):

$ test -f ~/.ssh/id_ed25519 || ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 -C "developer@k8s-ops"
$ cat ~/.ssh/id_ed25519.pub

続いて公開鍵を 7 ノードへ配布します。各ノードに対して ssh-copy-id を実行し、初回はパスワード(環境構築時に設定した developer のパスワード)を入力します。StrictHostKeyChecking=accept-new で初回ホスト鍵を自動承認します。実行コマンド:

$ for ip in 192.168.1.121 192.168.1.124 192.168.1.125 192.168.1.126 192.168.1.127 192.168.1.128 192.168.1.129; do
    ssh-copy-id -o StrictHostKeyChecking=accept-new developer@$ip
  done

各ノードで「Number of key(s) added: 1」が表示されれば成功です。動作確認として 7 ノードに無パスワード SSH で hostname を叩きます。実行コマンド:

$ for ip in 192.168.1.121 192.168.1.124 192.168.1.125 192.168.1.126 192.168.1.127 192.168.1.128 192.168.1.129; do
    echo -n "$ip: "; ssh -o BatchMode=yes developer@$ip hostname
  done

全 7 ノードのホスト名が返れば SSH 鍵セットアップ完了です。以降の演習はパスワード入力なしで進められます。

やってみよう① — alma-proxy whitelist 拡張(pkgs.k8s.io / projectcalico.docs.tigera.io)

最初の演習です。alma-proxy の Squid whitelist に Kubernetes パッケージリポジトリと Calico マニフェスト配信元の 3 ドメインを追加します。本シリーズの検証環境は企業ネットワークを再現するため、すべての外部通信が Squid プロキシの whitelist 方式を通ります。whitelist に登録されていないドメインへのアクセスは遮断されます。kubeadm パッケージを取得するには、まず通り道を開ける必要があります。

作業場所は alma-proxy(192.168.1.121)です。k8s-ops から ssh developer@192.168.1.121 で接続し、root 作業を行います。

ステップ 1:現在の whitelist を確認する

まず現状の whitelist を確認します。Squid の設定では /etc/squid/whitelist.txt を許可ドメインリストとして読み込んでいます。

実行コマンド:

# cat /etc/squid/whitelist.txt

実行結果(抜粋・既存登録ドメインを確認):

.almalinux.org
repo.almalinux.org
download.docker.com
github.com
raw.githubusercontent.com
dl.k8s.io
.quay.io
.ghcr.io
registry.k8s.io
get.helm.sh

第1巻と第2巻の準備で登録済みのドメインが並んでいます。注目すべきは registry.k8s.iodl.k8s.io.quay.io.ghcr.io がすでにあることです。registry.k8s.io は Kubernetes のコンテナイメージ配信元で、kubeadm が pull する kube-apiserver・etcd 等のイメージはここから取得します。つまりイメージの通り道はすでに開いています。一方、kubeadm 本体の RPM パッケージを配る pkgs.k8s.io がリストにありません。これが今回追加するドメインです。

ステップ 2:pkgs.k8s.io と関連ドメインを追記する

3 ドメインを whitelist ファイルに追記します。pkgs.k8s.io は HTTP リダイレクトで実体である CDN ホスト prod-cdn.packages.k8s.io に転送されるため、両方を許可する必要があります。CDN ホストを忘れると dnf が CONNECT tunnel failed, response 403 でメタデータ取得に失敗します。

実行コマンド:

# echo 'pkgs.k8s.io' >> /etc/squid/whitelist.txt
# echo 'prod-cdn.packages.k8s.io' >> /etc/squid/whitelist.txt
# echo 'projectcalico.docs.tigera.io' >> /etc/squid/whitelist.txt

追記されたことを確認します。

実行コマンド:

# grep -E 'pkgs.k8s.io|packages.k8s.io|projectcalico' /etc/squid/whitelist.txt

実行結果:

pkgs.k8s.io
prod-cdn.packages.k8s.io
projectcalico.docs.tigera.io

ステップ 3:squid を設定リロードする

whitelist ファイルを書き換えただけでは Squid に反映されません。設定リロードが必要です。squid -k reconfigure はサービスを停止せずに設定を読み直すコマンドです。稼働中の接続を切断しないため、本番でも安全に実行できます。

実行コマンド:

# squid -k reconfigure

実行結果:

2026/05/20 23:03:38| Processing Configuration File: /etc/squid/squid.conf (depth 0)
2026/05/20 23:03:38| Set Current Directory to /var/spool/squid

2 行(Processing Configuration File / Set Current Directory)が出れば正常です。既存 whitelist にサブドメイン重複(例: repo.almalinux.org.almalinux.org の併存)がある場合は WARNING 行が追加で表示されますが、今回追加した 2 行とは無関係なので無視して構いません。

Squid が正常に稼働していることを確認します。

実行コマンド:

# systemctl status squid

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

● squid.service - Squid caching proxy
     Loaded: loaded (/usr/lib/systemd/system/squid.service; enabled; preset: disabled)
     Active: active (running) since Mon 2026-05-04 18:53:45 JST; 2 weeks 2 days ago
 Invocation: ce02835cd71a4d59aa85350ad79d1965
       Docs: man:squid(8)

Active が active (running) であれば whitelist 拡張は完了です。

補足(実機で遭遇しやすい落とし穴):Squid のバージョンや設定によっては、squid -k reconfigure だけでは whitelist.txt(ファイル参照型の dstdomain ACL)に追記したドメインが反映されないことがあります。追加したドメイン(例: pkgs.k8s.io)への curlHTTP/1.1 403 Forbidden になり、dnf makecache が失敗する場合は、sudo systemctl restart squid で完全再起動してから再確認してください。access.logTCP_DENIED/403 CONNECT pkgs.k8s.io:443 が記録されていれば ACL 未反映のサインです(既存ドメインは許可されるのに新規追加分だけ拒否される、という症状で切り分けられます)。再起動は稼働中の接続を一時切断しますが、本回の構築フェーズでは影響は限定的です。

追加した 3 ドメインの役割

今回追加した 3 ドメインの役割を整理します。

  • pkgs.k8s.io — kubeadm / kubelet / kubectl の公式 RPM リポジトリ。2024年3月に Google ホストの旧リポジトリ(packages.cloud.google.com)が完全廃止されたため、現在は唯一の公式 RPM 取得元。この回の構築はこのドメインが通らないと始まらない
  • prod-cdn.packages.k8s.iopkgs.k8s.io がリダイレクトする CDN 配信ホスト。リポジトリのメタデータとパッケージ実体はここから配信されるため、pkgs.k8s.io と必ずセットで whitelist 登録する
  • projectcalico.docs.tigera.io — Calico のドキュメントとマニフェスト配信元。Calico CNI のインストールマニフェスト(calico.yaml)に関連するドメイン

Calico の実体である calico.yaml は GitHub の raw.githubusercontent.com から取得し、Calico コンテナイメージは .quay.io または .ghcr.io から取得されます。これらはすべて whitelist に登録済みのため、CNI インストールも問題なく進みます。kubeadm がコントロールプレーンコンポーネント(kube-apiserver 等)を pull する registry.k8s.io も登録済みです。今回の追加は pkgs.k8s.io という 1 つの抜けを埋める作業がメインです。

alma-proxy の whitelist 拡張が完了しました。次のセクションから k8s-cp-01 の前提設定に入ります。

k8s-cp-01 の前提設定(proxy・dnf update・swap・カーネルモジュール・sysctl)

ここから k8s-cp-01 の構築に入ります。このセクションは「kubeadm init を実行できる状態まで OS を整える」工程です。手順は多いですが、一つひとつに kubeadm が正しく動くための理由があります。作業場所は k8s-cp-01 です。k8s-ops から ssh developer@192.168.1.125 で接続します。

プロキシ設定(/etc/profile.d/proxy.sh・/etc/dnf/dnf.conf)

k8s-cp-01 はインターネットへ直接出られません。すべての外部通信を alma-proxy(192.168.1.121:3128)経由にする必要があります。シェル環境変数として http_proxy / https_proxy / no_proxy を設定します。

ここが今回の最重要ガードレールです。no_proxy には「プロキシを経由しないアドレス」を列挙します。社内 IP レンジ(192.168.1.0/24・10.0.10.0/24)、Kubernetes の Service CIDR(10.96.0.0/12)・Pod CIDR(10.244.0.0/16)、クラスタ内ドメイン(.svc・.cluster.local)に加え、クラスタを構成する全ノードのホスト名(k8s-lb・k8s-cp-01〜03・k8s-wl-01〜02 など)を必ず含めます

ホスト名を含めることが、なぜ IP レンジを含めるだけでは不十分なのか。理由は kubeadm init の挙動にあります。kubeadm init をプロキシ環境変数付き(sudo -E)で実行すると、kubeadm は http_proxy / https_proxy / no_proxy を kube-apiserver・kube-controller-manager・kube-scheduler の Static Pod、および kube-proxy DaemonSet の環境変数として自動的に埋め込みます。これらのコンポーネントは API Server へ controlPlaneEndpoint に設定したホスト名 k8s-lb:6443 で接続します。プログラムのプロキシ判定はホスト名の文字列で no_proxy を照合するため、no_proxy に IP レンジ 192.168.1.0/24 があっても、ホスト名 k8s-lb がマッチせず接続が alma-proxy 経由になります。alma-proxy は whitelist 外の内部ホストへの接続を 403 Forbidden で拒否するため、kube-proxy がサービスルーティングを構成できず、CNI も起動せず、クラスタが Ready になりません。

この障害は「kube-proxy のログに Forbidden が出続ける」「ノードが NotReady のまま」という形で現れますが、原因が Kubernetes の RBAC ではなくプロキシ設定にあるため発見が難しい落とし穴です。no_proxy に全ノードのホスト名を入れておけば確実に回避できます。

実行コマンド(k8s-cp-01 で root 作業):

# cat > /etc/profile.d/proxy.sh << 'EOF'
export http_proxy=http://192.168.1.121:3128
export https_proxy=http://192.168.1.121:3128
export no_proxy=localhost,127.0.0.1,192.168.1.0/24,10.0.10.0/24,10.96.0.0/12,10.244.0.0/16,.svc,.cluster.local,k8s-lb,k8s-ops,k8s-registry,k8s-cp-01,k8s-cp-02,k8s-cp-03,k8s-wl-01,k8s-wl-02,alma-proxy
EOF
# source /etc/profile.d/proxy.sh

dnf もプロキシを経由させます。/etc/dnf/dnf.conf に proxy 行を追記します。

実行コマンド:

# echo 'proxy=http://192.168.1.121:3128' >> /etc/dnf/dnf.conf

プロキシ設定が効いているか、whitelist 登録済みのドメインへの疎通で確認します。

実行コマンド:

# curl -sI https://pkgs.k8s.io/ | head -n 1

実行結果(プロキシ経由の CONNECT 確立を示す行が返れば疎通成功):

HTTP/1.1 200 Connection established

これは Squid(alma-proxy)が HTTPS の CONNECT メソッドに対して返す応答です。HTTP/1.1 200 Connection established は「プロキシが target ホスト(pkgs.k8s.io)への TCP トンネルを確立した」という意味で、これが返れば proxy 経由で pkgs.k8s.io に到達できる状態が確認できます。

dnf update(AlmaLinux 10.0 → 10.1 へ更新)

k8s-cp-01 の初期 OS は AlmaLinux 10.0 です。本巻の動作確認基準である 10.1 に揃えるため、dnf update を実行します。

実行コマンド:

# dnf update -y

更新が完了したら OS バージョンを確認します。

実行コマンド:

# cat /etc/almalinux-release

実行結果:

AlmaLinux release 10.1 (Heliotrope Lion)

※ 2026年5月下旬以降に dnf update を実行した場合、AlmaLinux 10.2(Lavender Lion)に更新される場合があります。本シリーズは AlmaLinux 10.1 / 10.2 のいずれでも同手順で動作確認しています。

AlmaLinux 10.1 のコードネームは「Heliotrope Lion」です。マイナーバージョンごとにコードネームが付きます(10.0 は Purple Lion)。

dnf update ではカーネルも更新される場合があります。新しいカーネルを有効化するには再起動が必要です。再起動が必要かどうかを確認するコマンドを実行します。

実行コマンド:

# dnf needs-restarting -r

AlmaLinux 10 Minimal Install には独立した needs-restarting コマンド(dnf-utils 提供)は含まれないため、dnf needs-restarting サブコマンド形式で実行します。

実行結果(再起動が必要な場合・更新内容により対象は変動):

起動以降にコアライブラリーまたはサービスがアップデートされました:
  * linux-firmware
  * microcode_ctl
  * systemd

これらのアップデートを完全に活用するには、再起動が必要です。
詳細情報: https://access.redhat.com/solutions/27943

カーネル更新を含む場合は reboot で再起動し、再ログイン後に作業を続行します。動作確認バージョンに記載するカーネル番号は、再起動後の実機値で確定します。本記事の動作確認バージョン欄が「kernel 6.12.x」のように記載されている場合、x は実機検証で確定した値です。

swap 無効化(kubeadm / kubelet 起動の必須要件)

これが今回 2 つ目の本番ガードレールです。Kubernetes の kubelet は swap が有効な状態だと正常に動作しません。kubelet は Pod に割り当てたメモリ量を正確に管理する必要があり、swap によってメモリがディスクへ退避されると、その前提が崩れるためです。kubeadm の preflight check も swap を検出するとエラーにします。したがって、kubeadm init の前に swap を無効化する必要があります。

まず実行中の swap を無効化します。

実行コマンド:

# swapoff -a

ここで重要なのは、swapoff -a だけでは不十分という点です。swapoff -a は「今この瞬間の swap を無効化する」コマンドであり、再起動すると /etc/fstab の設定に従って swap が復活します。再起動後も swap を無効に保つには、/etc/fstab の swap 行をコメントアウトする必要があります。

実行コマンド(/etc/fstab の swap エントリをコメントアウト・再起動後も無効化を維持):

# sed -i.bak '/swap/s/^/#/' /etc/fstab

このコマンドは /etc/fstab 内の swap を含む行の先頭に # を付けてコメントアウトします。-i.bak により編集前のファイルが /etc/fstab.bak として保存されるため、誤った場合は復元できます。

AlmaLinux 10.x では systemd-fstab-generator が起動時に /etc/fstab の swap エントリから動的に dev-disk-by\x2duuid-xxx.swap ユニットを生成する仕組みを持っています。/etc/fstab をコメントアウトすればこの generate も止まるはずですが、まれに古い generated unit が残って次回起動時に swap が再有効化される事例があります。確実を期すなら以下の daemon-reload を実行して generated unit を更新し、現在 active な swap unit があれば mask します。実行コマンド:

# systemctl daemon-reload
# systemctl list-unit-files --type=swap --no-pager
# SWAP_UNIT=$(systemctl list-units --type=swap --no-pager --no-legend | awk '{print $1}' | head -1)
# [ -n "$SWAP_UNIT" ] && systemctl mask "$SWAP_UNIT"

swap が無効化されたことを確認します。

実行コマンド:

# free -h

実行結果(Swap の total が 0 になることを確認):

               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       XXXMi       XXXMi       XXXMi       XXXMi       XXXGi
Swap:             0B          0B          0B

Swap 行の total が 0B であれば無効化成功です。swapoff -a/etc/fstab の編集はセットで行います。片方だけでは、再起動した瞬間に kubelet が起動しなくなります。この落とし穴は今回の現場ヒヤリハットで詳しく扱います。

カーネルモジュール設定(overlay / br_netfilter)

これが今回 3 つ目の本番ガードレールです。Kubernetes ノードには 2 つのカーネルモジュールが必要です。

  • overlay — containerd がコンテナイメージのレイヤを重ね合わせる OverlayFS で使用する。これがないとコンテナイメージの展開が不安定になる
  • br_netfilter — Linux ブリッジを通過するパケットを iptables で処理できるようにする。Kubernetes の Service・NetworkPolicy は iptables(または ipvs)に依存するため、ブリッジ経由の Pod 間トラフィックを iptables で扱えないとネットワークが正しく機能しない

これらのモジュールがロードされていないと、containerd や Pod ネットワークが不安定になります。再起動後も自動でロードされるよう /etc/modules-load.d/k8s.conf に登録します。

実行コマンド:

# cat > /etc/modules-load.d/k8s.conf << 'EOF'
overlay
br_netfilter
EOF

このファイルは再起動時のロード設定です。今この瞬間にもロードするため、modprobe を実行します。

実行コマンド:

# modprobe overlay
# modprobe br_netfilter

ロードされたことを確認します。

実行コマンド:

# lsmod | grep -E 'overlay|br_netfilter'

実行結果:

br_netfilter           36864  0
bridge                417792  1 br_netfilter
overlay               245760  0

br_netfilter をロードすると依存モジュールの bridge も自動でロードされます。3 つの行が表示されればモジュールの準備は完了です。

sysctl 設定(iptables・ip_forward)

カーネルモジュールをロードしただけでは不十分で、対応するカーネルパラメータ(sysctl)を有効にする必要があります。3 つのパラメータを設定します。

  • net.bridge.bridge-nf-call-iptables = 1 — ブリッジを通過する IPv4 パケットを iptables で処理する。Service・NetworkPolicy の動作に必須
  • net.bridge.bridge-nf-call-ip6tables = 1 — 同じく IPv6 パケット用
  • net.ipv4.ip_forward = 1 — ノードを通過する IP パケットの転送を許可する。Pod ネットワークのルーティングに必須

これらを /etc/sysctl.d/k8s.conf に記述します。/etc/sysctl.d/ 配下のファイルは再起動後も自動で読み込まれます。

実行コマンド:

# cat > /etc/sysctl.d/k8s.conf << 'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

設定を即座に反映します。sysctl --system/etc/sysctl.d/ 配下を含むすべての設定ファイルを読み直します。

実行コマンド:

# sysctl --system

実行結果(適用確認の抜粋):

* Applying /etc/sysctl.d/k8s.conf ...
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1

3 つのパラメータが = 1 で適用されていれば成功です。net.bridge.bridge-nf-call-iptables が表示されない場合は、前のステップで br_netfilter モジュールがロードされていない可能性があります。lsmod | grep br_netfilter で再確認してください。

firewalld ポート開放(Control Plane Node 向け)

本シリーズは firewalld を有効のまま運用します。Control Plane Node が必要とするポートを開放します。各ポートの用途は次のとおりです。

ポート用途
6443/tcpkube-apiserver(クラスタ API のエンドポイント)
2379-2380/tcpetcd(クライアント通信 2379・ピア通信 2380)
10250/tcpkubelet API
10257/tcpkube-controller-manager
10259/tcpkube-scheduler

実行コマンド:

# firewall-cmd --permanent --add-port=6443/tcp
# firewall-cmd --permanent --add-port=2379-2380/tcp
# firewall-cmd --permanent --add-port=10250/tcp
# firewall-cmd --permanent --add-port=10257/tcp
# firewall-cmd --permanent --add-port=10259/tcp
# firewall-cmd --reload

--permanent は再起動後も設定を保持するオプション、--reload は永続設定を実行中の firewalld に反映するコマンドです。開放されたポートを確認します。

実行コマンド:

# firewall-cmd --list-ports

実行結果:

2379-2380/tcp 6443/tcp 10250/tcp 10257/tcp 10259/tcp

SELinux 確認(Enforcing 維持)

SELinux は Enforcing のまま維持します。kubeadm の古いインストール手順には SELinux を Permissive にする例が登場しますが、本シリーズではセキュリティを保つため Enforcing を維持します。まず現状を確認します。

実行コマンド:

# getenforce

実行結果:

Enforcing

AlmaLinux 10 + kubeadm v1.35 では、container-selinux パッケージがコンテナランタイム向けの SELinux ポリシーを提供します。このパッケージは containerd のインストール時に依存パッケージとして自動導入されます。containerd インストール後に rpm -q container-selinux で導入を確認できます。container-selinux が入っていれば、SELinux を Enforcing のまま kubeadm init が通ります。SELinux を Permissive に下げる必要はありません。

k8s-lb の名前解決確認

後の kubeadm init では controlPlaneEndpoint: k8s-lb:6443 を指定します。k8s-cp-01 が k8s-lb というホスト名を IP に解決できないと、init がエラーになります。名前解決は alma-proxy 上の dnsmasq(192.168.1.121)が担います。

実行コマンド:

# getent hosts k8s-lb

AlmaLinux 10 Minimal Install には nslookup(bind-utils 提供)が含まれません。代わりに glibc 標準の getent hosts を使えば NSS(/etc/nsswitch.conf)の解決順に従って名前解決を確認できます。

実行結果(dnsmasq または /etc/hosts で解決できる場合):

192.168.1.124   k8s-lb

k8s-lb192.168.1.124 に解決されれば問題ありません。もし dnsmasq に k8s-lb のエントリがなく解決できない場合は、保険として /etc/hosts に直接エントリを追加します。

実行コマンド(名前解決できない場合のみ実施):

# echo '192.168.1.124 k8s-lb' >> /etc/hosts

/etc/hosts は DNS より優先して参照されます。dnsmasq で解決できている場合に追記しても、同じ IP を指すため害はありません。kubeadm initcontrolPlaneEndpoint が解決できないとクラスタ起動が失敗するため、ここで確実に解決できる状態にしておきます。

これで k8s-cp-01 の OS 前提設定が完了しました。プロキシ・OS バージョン・swap・カーネルモジュール・sysctl・ファイアウォール・名前解決のすべてが整いました。次のセクションでコンテナランタイムである containerd を導入します。

作業端末 k8s-ops 側でも /etc/hosts を整備

後で admin.conf を k8s-ops に転送して kubectl を使う際、k8s-ops 側でも k8s-lb の名前解決が必要になります。k8s-ops の /etc/resolv.conf は外部 DNS(8.8.8.8 等)を参照しているため、内部ホスト名 k8s-lb は解決できません。kubectl 実行時に dial tcp: lookup k8s-lb on 8.8.8.8:53: no such host エラーで止まらないよう、ここで /etc/hosts に内部ホスト名 7 件をまとめて追加します。

実行コマンド(k8s-ops で developer 作業):

$ sudo tee -a /etc/hosts << 'EOF'
192.168.1.124 k8s-lb
192.168.1.125 k8s-cp-01
192.168.1.126 k8s-cp-02
192.168.1.127 k8s-cp-03
192.168.1.128 k8s-wl-01
192.168.1.129 k8s-wl-02
EOF

第4回の kubeadm join でも各ノードからホスト名解決が必要なので、第2回時点で全 7 件まとめて追加しておくと後の回が楽になります。

containerd インストールと SystemdCgroup 設定

Kubernetes は Pod を動かすためにコンテナランタイムを必要とします。本シリーズでは containerd を使います。Kubernetes は CRI(Container Runtime Interface)という標準インタフェース経由でコンテナランタイムと通信し、containerd はその CRI を実装しています。このセクションでは containerd を導入し、Kubernetes が正しく動くための設定を行います。作業場所は k8s-cp-01 です。

containerd のインストール

AlmaLinux 10 の標準リポジトリ(BaseOS / AppStream / CRB / Extras)には containerd パッケージが含まれていません。本シリーズでは Docker CE 公式リポジトリ(download.docker.com)から containerd.io を導入します。Docker CE の whitelist は第1巻で alma-proxy に登録済みなので proxy 経由で取得できます。

まず Docker CE のリポジトリ定義ファイルを取得します。

実行コマンド:

# curl -fsSL -o /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/rhel/docker-ce.repo

続けて containerd.io パッケージをインストールします。

実行コマンド:

# dnf install -y containerd.io

インストール時に container-selinux が依存パッケージとして導入されます。前のセクションで触れたとおり、これにより SELinux を Enforcing のまま運用できます。インストール後にバージョンを確認します。

実行コマンド:

# containerd --version

実行結果(バージョンは実機検証で確定):

containerd containerd.io v2.2.4 193637f7ee8ae5f5aa5248f49e7baa3e6164966e

container-selinux が導入されたことも確認しておきます。

実行コマンド:

# rpm -q container-selinux

実行結果(パッケージ名とバージョンが表示されれば導入済み):

container-selinux-4:2.240.0-10.el10_1.noarch

containerd デフォルト設定の生成

containerd の設定ファイルは /etc/containerd/config.toml です。インストール直後はこのファイルが最小限の内容、または存在しない状態のことがあります。containerd config default でデフォルト設定の全量を出力し、それをファイルに書き込みます。

実行コマンド:

# containerd config default | tee /etc/containerd/config.toml

tee は標準入力を画面に表示しつつ、同時にファイルへ書き込むコマンドです。これで /etc/containerd/config.toml に containerd のデフォルト設定が全量書き込まれました。次のステップでこの設定の一部を変更します。

SystemdCgroup = true への変更

これが今回 4 つ目の本番ガードレールであり、フル構築回の中でも特に重要な設定です。containerd のデフォルト設定では cgroup ドライバの設定 SystemdCgroupfalse になっています。これを true に変更します。

cgroup(control group)は、Linux がプロセスのリソース(CPU・メモリ)を制限・管理する仕組みです。AlmaLinux 10 は cgroup v2 がデフォルトです。cgroup v2 環境では、systemd 自身が cgroup 階層を管理します。このとき、Kubernetes の kubelet とコンテナランタイムの両方が「同じ cgroup ドライバ」を使う必要があります。

kubelet はデフォルトで systemd cgroup ドライバを期待します。一方、containerd のデフォルト設定(SystemdCgroup = false)は cgroupfs ドライバを使います。両者が食い違うと、Pod 作成時に cgroup の操作が衝突し、kubelet が不安定になります。具体的には、kube-apiserver や etcd の Static Pod が起動しない、あるいは起動してもすぐに落ちるという症状が出ます。kubeadm init がこの食い違いのために途中で失敗することもあります。

したがって、containerd config default で生成した直後の SystemdCgroup = false は必ず true に変更します。sed で一括変更します。

実行コマンド:

# sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

変更されたことを確認します。

実行コマンド:

# grep SystemdCgroup /etc/containerd/config.toml

実行結果:

            SystemdCgroup = true

この設定は [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] セクション内にあります。grep の出力が SystemdCgroup = true であれば変更成功です。false のままだと、後の kubeadm init で時間をかけてエラーに突き当たることになります。ここで確実に true を確認しておきます。

registry config_path の有効化(hosts.toml 方式の前提)

もうひとつ重要な設定があります。containerd v2 系はレジストリ個別設定を /etc/containerd/certs.d/<host:port>/hosts.toml から読み込む「hosts.toml 方式」に統一されましたが、これを有効化するには config.tomlconfig_path をディレクトリパスに変更する必要があります。デフォルトは空文字列(config_path = '')で、空のままだと hosts.toml をいくら書いても読み込まれず、insecure registry 設定(第4回で使用)が効きません。

該当箇所は [plugins.'io.containerd.cri.v1.images'.registry] セクション直下の config_path 行です。sed で一括変更します。なお、containerd 2.2.x ではダブルクォート版 [plugins."io.containerd.cri.v1.images".registry] の場合があり、その際は下記 sed が当たりません。失敗時は vi /etc/containerd/config.toml での手動編集を推奨します。

実行コマンド:

# sed -i "/\[plugins.'io.containerd.cri.v1.images'.registry\]/{n;s|config_path = ''|config_path = '/etc/containerd/certs.d'|}" /etc/containerd/config.toml

変更されたことを確認します。

実行コマンド:

# grep -A1 "cri.v1.images'.registry" /etc/containerd/config.toml | head -2

実行結果:

    [plugins.'io.containerd.cri.v1.images'.registry]
      config_path = '/etc/containerd/certs.d'

config_path = '/etc/containerd/certs.d' になっていれば成功です。本回では /etc/containerd/certs.d/ 配下にはまだ何も置きませんが、第4回で fanclub-api をデプロイする際にプライベートレジストリ(k8s-registry)への insecure 設定をここに置きます。設定をいま入れておくことで、第4回でその場で気づいて慌てる事態を避けられます。

containerd の再起動と自動起動設定

設定ファイルを変更しただけでは containerd に反映されません。再起動が必要です。同時に、ノード再起動後も containerd が自動で立ち上がるよう enable も設定します。

実行コマンド:

# systemctl restart containerd
# systemctl enable containerd

containerd が正常に稼働していることを確認します。

実行コマンド:

# systemctl status containerd

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

● containerd.service - containerd container runtime
     Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; preset: disabled)
     Active: active (running) since Mon 2026-05-19 09:30:00 JST

Active が active (running)、Loaded の行に enabled と表示されていれば、containerd の準備は完了です。SystemdCgroup = true の変更後に再起動を忘れると古い設定のまま稼働し続けるため、設定変更後は必ず systemctl restart containerd を実行します。

kubeadm / kubelet / kubectl のインストール(pkgs.k8s.io・バージョンピン留め)

コンテナランタイムが整ったので、次は Kubernetes 本体のツール群をインストールします。kubeadm(クラスタ構築ツール)・kubelet(ノードエージェント)・kubectl(操作 CLI)の 3 つを pkgs.k8s.io からインストールし、バージョンをピン留めします。作業場所は k8s-cp-01 です。

pkgs.k8s.io リポジトリの設定(v1.35 ピン)

pkgs.k8s.io は Kubernetes コミュニティが運営する公式の RPM / DEB パッケージリポジトリです。2024年3月4日に旧 Google ホストリポジトリ(packages.cloud.google.com)が完全廃止されたため、現在はこの pkgs.k8s.io が唯一の公式 RPM 取得元です。

pkgs.k8s.io はマイナーバージョンごとに専用のリポジトリ URL が用意されています。たとえば v1.35 系は core:/stable:/v1.35、v1.34 系は core:/stable:/v1.34 という具合です。第2巻では K8s v1.35 を使うため、v1.35 専用リポジトリのみを設定します。/etc/yum.repos.d/kubernetes.repo を作成します。

実行コマンド:

# cat > /etc/yum.repos.d/kubernetes.repo << 'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.35/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

各設定項目の意味を整理します。baseurl がパッケージの取得元、gpgcheck=1 はパッケージの GPG 署名検証を有効にする設定、gpgkey は検証に使う公開鍵の URL です。署名検証を有効にすることで、改ざんされたパッケージのインストールを防げます。リポジトリを設定したらメタデータのキャッシュを更新します。

実行コマンド:

# dnf makecache

dnf makecachepkgs.k8s.io からメタデータを取得できれば、whitelist 拡張とプロキシ設定が正しく機能している証拠です。ここでエラーが出る場合は、alma-proxy の whitelist に pkgs.k8s.io が登録されているか、k8s-cp-01 のプロキシ設定が正しいかを確認します。

kubelet / kubeadm / kubectl のインストール

3 つのパッケージをまとめてインストールします。kubernetes.repo に exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni を設定しているため、明示的に --disableexcludes=kubernetes を指定して exclude を一時解除します。第15回・第16回のトラブルシュート演習で crictl を使うため、同時に cri-tools もインストールしておくと後の回が楽になります。

実行コマンド:

# dnf install -y --disableexcludes=kubernetes kubelet kubeadm kubectl cri-tools

インストールされたバージョンを確認します。

実行コマンド(kubeadm のバージョン確認):

# kubeadm version

実行結果:

kubeadm version: &version.Info{Major:"1", Minor:"35", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.35.5", GitCommit:"6636cbce3bbef91ff61d36658757179426f9e1b2", GitTreeState:"clean", BuildDate:"2026-05-12T09:53:04Z", GoVersion:"go1.25.9", Compiler:"gc", Platform:"linux/amd64"}

実行コマンド(kubectl のバージョン確認):

# kubectl version --client

実行結果:

Client Version: v1.35.5
Kustomize Version: v5.7.1

実行コマンド(kubelet のバージョン確認):

# kubelet --version

実行結果:

Kubernetes v1.35.5

3 つとも v1.35.5(2026-05-19 時点)が表示されれば成功です。pkgs.k8s.io の stable/v1.35 リポジトリは v1.35 系の最新パッチバージョンを提供するため、実際にインストールされる X(パッチ番号)は時間経過とともに進む可能性があります。本記事の動作確認バージョンは v1.35.5 で確定しています。

バージョンピン留め(dnf versionlock)

これが今回 5 つ目の本番ガードレールです。インストールしたばかりの kubeadm / kubelet / kubectl のバージョンを「ピン留め」します。ピン留めとは、これらのパッケージが dnf upgrade で意図せず更新されないよう固定することです。

なぜピン留めが必要かを説明します。Kubernetes には「バージョンスキューポリシー」というルールがあり、kubelet と kube-apiserver のバージョン差は一定範囲内に収める必要があります。OS の定期メンテナンスで dnf upgrade を実行したとき、もし kubelet だけが新しいマイナーバージョンに上がってしまうと、API Server とのバージョン差がポリシー違反になり、ノードが正しく動作しなくなります。

さらに、第6回では kubeadm upgrade によるクラスタのバージョンアップを演習として扱います。このとき「意図したバージョンへ意図したタイミングで上げる」ことが重要です。dnf upgrade で勝手に kubeadm が上がってしまうと、第6回の演習の前提が崩れます。本番運用でも、クラスタのバージョンアップは計画的に実施すべき作業であり、OS パッケージ更新に巻き込まれて勝手に上がるのはアンチパターンです。

dnf でバージョンを固定するには versionlock プラグインを使います。まずプラグインをインストールします。

実行コマンド:

# dnf install -y python3-dnf-plugin-versionlock

プラグインのパッケージ名は環境によって python3-dnf-plugin-versionlock または dnf-plugin-versionlock となる場合があります。本記事ではコマンド例として前者を記載しますが、実機検証で確定したパッケージ名に従ってください。プラグインを導入したら、3 つのパッケージをロック対象に追加します。

実行コマンド:

# dnf versionlock add kubelet kubeadm kubectl

ロックが設定されたことを確認します。

実行コマンド:

# dnf versionlock list

実行結果(パッケージ名とバージョンは実機検証で確定):

kubelet-0:1.35.5-150500.1.1.*
kubeadm-0:1.35.5-150500.1.1.*
kubectl-0:1.35.5-150500.1.1.*

3 つのパッケージがリストに表示されれば、以降は dnf upgrade を実行してもこれらは更新されません。第6回でクラスタをバージョンアップする際は、dnf versionlock delete でロックを一時解除してから目的のバージョンへ更新し、再度ロックする流れになります。

kubelet の自動起動設定

kubelet をノード再起動後も自動で立ち上がるよう設定します。

実行コマンド:

# systemctl enable kubelet

ここで systemctl start kubelet はまだ実行しません。kubeadm init を実行する前の時点では、kubelet は起動に必要な設定ファイル(/var/lib/kubelet/config.yaml など)をまだ持っていないためです。この段階で systemctl status kubelet を見ると、kubelet が起動と停止を繰り返している状態が確認できますが、これは正常です。kubeadm init が必要な設定ファイルを配置すると、kubelet は正常に起動するようになります。今は enable だけ済ませておき、起動は kubeadm init に任せます。

これで k8s-cp-01 の準備が完了しました。OS 前提設定・containerd・kubeadm パッケージのすべてが整い、kubeadm init を実行できる状態になりました。次に、init の前提となる k8s-lb の最小設定を行います。

k8s-lb 最小 HAProxy パススルー設定(第2回スコープ確認)

このセクションでは k8s-lb に HAProxy をインストールし、最小限のパススルー設定を入れます。「最小限」という点が重要です。HAProxy の本格的な HA 設計は第3回の専門回で扱います。作業場所は k8s-lb(192.168.1.124)です。k8s-ops から ssh developer@192.168.1.124 で接続します。

第2回と第3回の役割分担

k8s-lb の HAProxy 設定について、第2回と第3回で何をどこまでやるかを明確にします。

k8s-lb の HAProxy 設定
第2回(本回)最小パススルー(:6443 → k8s-cp-01:6443 のみ)
第3回HA 本格設計(CP×3 backend・stats ページ・ヘルスチェック・gateway frontend 追加)

この分担にした理由を説明します。kubeadm init では controlPlaneEndpoint: k8s-lb:6443 を指定するため、第2回時点で「k8s-lb:6443 が k8s-cp-01 に転送される」状態が必要です。これがないと init が成功しません。一方、HAProxy の HA 設計(複数 backend の負荷分散・ヘルスチェック・stats ページ)は第3回が専門回として扱います。そこで第2回では「init が通る最小限」だけを入れ、HA 設計は第3回に集中させます。第2回の HAProxy 設定には CP-02/03 の backend や stats ページは含めません。

k8s-lb の前提設定(proxy・dnf update)

k8s-lb の初期状態も AlmaLinux 10.0 Minimal Install で、k8s-cp-01 と同じく proxy 設定と OS 更新が必要です。k8s-cp-01 で実施した手順と同じ流れで設定します。

実行コマンド(k8s-lb で root 作業):

# cat > /etc/profile.d/proxy.sh << 'EOF'
export http_proxy=http://192.168.1.121:3128
export https_proxy=http://192.168.1.121:3128
export no_proxy=localhost,127.0.0.1,192.168.1.0/24,10.0.10.0/24,10.96.0.0/12,10.244.0.0/16,.svc,.cluster.local,k8s-lb,k8s-ops,k8s-registry,k8s-cp-01,k8s-cp-02,k8s-cp-03,k8s-wl-01,k8s-wl-02,alma-proxy
EOF
# source /etc/profile.d/proxy.sh
# echo 'proxy=http://192.168.1.121:3128' >> /etc/dnf/dnf.conf
# dnf update -y

dnf update でカーネル更新が含まれる場合は reboot で再起動します。k8s-lb は HAProxy のような軽量サーバのみ稼働するため、ここで一度再起動しても影響は限定的です。

HAProxy のインストール

HAProxy を dnf でインストールします。

実行コマンド:

# dnf install -y haproxy

バージョンを確認します。

実行コマンド:

# haproxy -v | head -1

実行結果:

HAProxy version 3.0.5-8e879a5 2024/09/19 - https://haproxy.org/

最小パススルー設定の記述

HAProxy の設定ファイル /etc/haproxy/haproxy.cfg を、最小パススルー設定で上書きします。

実行コマンド:

# cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
  log /dev/log local0
  maxconn 4000
defaults
  log global
  timeout connect 5s
  timeout client  10m
  timeout server  10m
frontend k8s-api
  bind *:6443
  mode tcp
  default_backend k8s-cp-api
backend k8s-cp-api
  mode tcp
  server k8s-cp-01 192.168.1.125:6443 check
EOF

設定内容を整理します。frontend k8s-api*:6443(全インタフェースの 6443 ポート)で待ち受け、backend k8s-cp-api へ転送します。backend には k8s-cp-01 の 6443 だけを登録しています。mode tcp は TCP レイヤでそのまま転送する設定で、これにより HAProxy は API Server の TLS 通信を復号せずパススルーします。server 行末尾の check は backend の死活監視を有効にするオプションです。第3回ではこの backend に k8s-cp-02・k8s-cp-03 を追加し、stats ページや Gateway 用 frontend を加えていきます。

HAProxy の起動と確認

設定ファイルを起動前に検証します。haproxy -c は設定ファイルの構文チェックを行うオプションです。

実行コマンド:

# haproxy -c -f /etc/haproxy/haproxy.cfg
# echo exit:$?

実行結果(HAProxy 3.0 系は -c 単独だと成功時に出力なしのため exit code で判定):

exit:0

続いて SELinux 対応です。本シリーズは SELinux を Enforcing で運用するため、HAProxy が標準外ポート 6443 にバインドするには事前に port label の追加が必要です。これを忘れると HAProxy 起動時に cannot bind socket (Permission denied) でエラーになります。

実行コマンド:

# dnf install -y policycoreutils-python-utils
# semanage port -a -t http_port_t -p tcp 6443
# setsebool -P haproxy_connect_any 1
# semanage port -l | grep http_port_t

実行結果(6443 が http_port_t に登録されたことを確認):

http_port_t                    tcp      6443, 80, 81, 443, 488, 8008, 8009, 8443, 9000
http_port_t                    udp      80, 443

SELinux 対応が済んだら HAProxy を起動します。

実行コマンド:

# systemctl enable --now haproxy
# systemctl is-active haproxy

実行結果:

active

active と表示されれば HAProxy が稼働中です。systemctl status haproxy で詳細ログを確認すれば backend k8s-cp-01 への接続確認も走っていることが分かります(この時点では API Server 未起動のため DOWN 表示で正常)。

firewalld ポート開放(k8s-lb 向け)

k8s-lb でも 6443 ポートを開放します。第2回では 6443 だけ開放します(stats 用 9000・Gateway 用 80/443 は第3回以降)。

実行コマンド:

# firewall-cmd --permanent --add-port=6443/tcp
# firewall-cmd --reload

k8s-cp-01 から k8s-lb:6443 への疎通確認

k8s-cp-01 から k8s-lb の 6443 ポートに接続できるか確認します。AlmaLinux 10 Minimal Install には nc が含まれないため、bash 組み込みの /dev/tcp 疑似デバイスを使います。/dev/tcp/<host>/<port> への書き込みは TCP 接続を試行し、成功すれば exit 0 を返します。このコマンドは k8s-cp-01 で実行します。

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

# timeout 3 bash -c 'echo > /dev/tcp/192.168.1.124/6443' && echo TCP接続OK || echo TCP接続失敗

実行結果:

TCP接続OK

ここで補足があります。この時点では k8s-cp-01 の API Server がまだ起動していません。HAProxy は 6443 で接続を受け付けますが、転送先の k8s-cp-01:6443 で待ち受けているプロセスがないため、HAProxy 側のログには backend DOWN と記録されます。それは正常な状態です。重要なのは「HAProxy 自体が 6443 で待ち受けている」ことで、TCP接続OK が返れば HAProxy への到達は確認できています。kubeadm init 完了後に API Server が起動すると、この経路が完全に通るようになります。

これで kubeadm init の前提がすべて整いました。k8s-cp-01 の OS・containerd・kubeadm パッケージ、k8s-lb のパススルー設定、そして名前解決とプロキシ。次のセクションでいよいよクラスタを起動します。

やってみよう② — kubeadm init 実行(kubeadm-config.yaml v1beta4・–upload-certs)

第2回の中核となる演習です。k8s-cp-01 で kubeadm init を実行し、シングルノードクラスタを起動します。作業場所は k8s-cp-01 です。

kubeadm-config.yaml の作成

kubeadm init はコマンドラインオプションだけでも実行できますが、本シリーズでは設定ファイル(kubeadm-config.yaml)を使う方式を採用します。設定ファイル方式には、構成内容を Git で管理できる・第4回の HA 構成へ拡張しやすい・設定の意図が明示的に残るという利点があります。CKA でも設定ファイルを扱えることが求められます。

設定ファイルを作成します。kubeadm の設定 API バージョンは v1beta4 を使います。ファイルは 2 つのドキュメント(ClusterConfigurationInitConfiguration)を --- で区切った構成です。

実行コマンド(k8s-cp-01 で root 作業):

# cat > /root/kubeadm-config.yaml << 'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.35.5
controlPlaneEndpoint: "k8s-lb:6443"
networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/12"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "192.168.1.125"
  bindPort: 6443
EOF

各フィールドの意味を整理します。

フィールド意味
kubernetesVersionv1.35.5インストールする K8s バージョン(pkgs.k8s.io の stable/v1.35 と整合させる)
controlPlaneEndpointk8s-lb:6443外部から API Server に接続するエンドポイント(LB 経由・HA 化の前提)
podSubnet10.244.0.0/16Pod に割り当てるネットワーク CIDR(Calico が使用)
serviceSubnet10.96.0.0/12Service の ClusterIP 割り当て範囲
advertiseAddress192.168.1.125この Control Plane Node の実 IP(API Server がバインドするアドレス)
bindPort6443API Server が待ち受けるポート

ここで kubernetesVersion について補足します。この値は、前のセクションで実際にインストールした kubeadm のバージョンと一致させる必要があります。pkgs.k8s.io の stable/v1.35 リポジトリは v1.35 系の最新パッチを提供するため、本記事の実機検証時点では v1.35.5 がインストールされました。kubeadm version -o short で確認したバージョンを kubernetesVersion に設定します。両者が食い違うと kubeadm init がコントロールプレーンイメージの取得で不整合を起こすため、必ず一致させます。

コントロールプレーンイメージの事前 pull

kubeadm init は内部でコンテナイメージを pull します。事前に pull しておくと、init コマンド本体の所要時間が短くなり、また「イメージ取得が正しくできるか」を init 前に確認できます。この手順は任意ですが、プロキシ環境では事前確認の意味で実施しておくと安心です。

実行コマンド:

# kubeadm config images pull --config /root/kubeadm-config.yaml

実行結果(registry.k8s.io からイメージを pull):

[config/images] Pulled registry.k8s.io/kube-apiserver:v1.35.5
[config/images] Pulled registry.k8s.io/kube-controller-manager:v1.35.5
[config/images] Pulled registry.k8s.io/kube-scheduler:v1.35.5
[config/images] Pulled registry.k8s.io/kube-proxy:v1.35.5
[config/images] Pulled registry.k8s.io/coredns/coredns:v1.13.1
[config/images] Pulled registry.k8s.io/pause:3.10.1
[config/images] Pulled registry.k8s.io/etcd:3.6.6-0

イメージはすべて registry.k8s.io から取得されます。このドメインは alma-proxy の whitelist に登録済みのため、プロキシ経由で取得できます。ここでイメージ取得エラーが出る場合は、whitelist と containerd のプロキシ設定(コンテナランタイムのプロキシは別途設定が必要なことがある)を確認します。

kubeadm init の実行

クラスタを起動します。--config で設定ファイルを指定し、--upload-certs オプションを付けます。所要時間は環境にもよりますが 3〜5 分程度です。

--upload-certs は、Control Plane の証明書をクラスタ内(kube-system 名前空間の Secret)に暗号化して保存するオプションです。これにより、第4回で k8s-cp-02/03 を追加するときに、それらのノードが証明書を自動取得できます。--upload-certs を付けないと、第4回で追加 Control Plane Node に手動で証明書をコピーする必要が生じます。HA 構成を前提とする本シリーズでは、第2回の init 時点で付けておきます。なお、アップロードされた証明書を解読するための certificate-key は 2 時間で失効します(第4回では必要に応じて再アップロードします)。

実行コマンド:

# kubeadm init --config /root/kubeadm-config.yaml --upload-certs

実行結果(成功時の出力・token / hash はプレースホルダー):

[init] Using Kubernetes version: v1.35.5
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[kubelet-start] Starting the kubelet
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join k8s-lb:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
    --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
    --control-plane --certificate-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join k8s-lb:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \
    --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Your Kubernetes control-plane has initialized successfully! と表示されればクラスタの初期化が成功しています。出力には 2 種類の kubeadm join コマンドが含まれます。--control-plane が付くものは Control Plane Node 追加用(第4回で k8s-cp-02/03 に使用)、付かないものは Workload Node 追加用(第4回で k8s-wl-01/02 に使用)です。これらの join コマンドは第4回で必要になるため、出力をコピーして保存しておきます。トークンの有効期限は 24 時間ですが、第4回で kubeadm token create により再発行できるため、期限が切れても問題ありません。

本記事の xxxxxxxxxxxx 部分は実機ごとに異なる値です。読者の環境では実際の token・hash・certificate-key が表示されます。

kubeadm init で何が起きたか

kubeadm init が内部で行ったことを処理フローとして整理します。

kubeadm init の処理フロー(7 ステップ)
kubeadm init の処理フロー(7 ステップ)

この流れの中で特に重要なのが [4] と [5] です。kubeadm は Control Plane コンポーネントを「Static Pod」として配置します。Static Pod は API Server を介さず、ノード上の kubelet が直接管理する Pod です。

Static Pod の確認

Static Pod の manifest が配置されたディレクトリを確認します。

実行コマンド:

# ls /etc/kubernetes/manifests/

実行結果:

etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

/etc/kubernetes/manifests/ にある 4 つの YAML が Static Pod です。kubelet はこのディレクトリを常時監視しており、YAML ファイルが存在する限り対応する Pod を起動・再起動します。etcd(クラスタ状態の保存先)・kube-apiserver(API のエンドポイント)・kube-controller-manager(各種コントローラ)・kube-scheduler(Pod の配置決定)という 4 つの Control Plane コンポーネントが、この仕組みで稼働します。

このパスは CKA 試験でも頻出です。たとえば「kube-apiserver が起動しない」という障害では、/etc/kubernetes/manifests/kube-apiserver.yaml の設定ミスが原因のことが多く、このファイルを直接編集して修正します。第15回のトラブルシュート演習でこのパスを直接扱います。今の段階では「Control Plane は Static Pod として動いている」「manifest は /etc/kubernetes/manifests/ にある」という 2 点を覚えておいてください。

やってみよう③ — admin.conf を k8s-ops へ転送・alias k=kubectl 導入

kubeadm init でクラスタは起動しましたが、まだ k8s-cp-01 のローカルでしか操作できません。本シリーズの運用方針では、クラスタ操作は作業端末の k8s-ops から行います。このセクションでは、init が生成した認証情報(admin.conf)を k8s-ops に転送し、k8s-ops から kubectl が使える状態を作ります。

admin.conf とは何か

/etc/kubernetes/admin.conf は kubeadm init が生成する kubeconfig ファイルです。クラスタの API Server エンドポイント・CA 証明書・管理者権限のクライアント証明書が含まれています。kubectl はこのファイル(通常 ~/.kube/config に配置)を読んで、どのクラスタにどの権限で接続するかを決めます。admin.conf はクラスタ管理者の全権限を持つ認証情報のため、取り扱いには注意が必要です。

k8s-cp-01 上で admin.conf を一時コピーする

/etc/kubernetes/admin.conf は root 所有のファイルです。k8s-ops の developer ユーザーが scp で直接取得することはできません。そこで、developer が読める場所に一時コピーし、所有者を developer に変更します。一時的とはいえクラスタ管理者の認証情報を扱うため、全ユーザー可読の chmod 644 ではなく、所有者を developer に変更したうえで chmod 600(所有者のみ読み書き可)にします。

実行コマンド(k8s-cp-01 で root 作業):

# cp /etc/kubernetes/admin.conf /tmp/admin.conf
# chown developer:developer /tmp/admin.conf
# chmod 600 /tmp/admin.conf

これで /tmp/admin.conf は developer ユーザーが読めて、かつ他のユーザーからは読めない状態になりました。

k8s-ops へ admin.conf を転送する

k8s-ops に移動して、scp で admin.conf を取得し ~/.kube/config として配置します。この作業は k8s-ops で developer ユーザーとして行います。

実行コマンド(k8s-ops で developer 作業):

$ mkdir -p ~/.kube
$ scp developer@192.168.1.125:/tmp/admin.conf ~/.kube/config
$ chmod 600 ~/.kube/config

実行結果(scp 成功時):

admin.conf                  100% 5648     5.5MB/s   00:00

~/.kube/configchmod 600 で所有者のみ読めるようにします。kubeconfig のパーミッションが緩いと、kubectl が警告を出すことがあります。

一時ファイルの削除

転送が終わったら、k8s-cp-01 上の一時コピーを削除します。認証情報を不要に残さないためです。

実行コマンド(k8s-cp-01 で root 作業):

# rm -f /tmp/admin.conf

kubectl get nodes で接続確認

k8s-ops から kubectl でクラスタに接続できるか確認します。

実行コマンド(k8s-ops で developer 作業):

$ kubectl get nodes

実行結果(CNI 未インストールの段階では NotReady):

NAME        STATUS     ROLES           AGE   VERSION
k8s-cp-01   NotReady   control-plane   2m    v1.35.5

k8s-cp-01 が一覧に表示されました。これは k8s-ops からクラスタへの接続が成功したことを意味します。ただし STATUS は NotReady です。これは異常ではなく、想定どおりの状態です。kubeadm は CNI(Container Network Interface)を含まないため、この時点では Pod ネットワークが確立しておらず、ノードは NotReady になります。次のセクションで Calico CNI を適用すると Ready に変わります。

alias k=kubectl の導入

第1回の次回予告で触れたとおり、第2回で alias k=kubectl を導入します。kubectl は打鍵数が多いコマンドで、クラスタ操作のたびに入力するのは非効率です。k という短縮エイリアスを設定します。

このエイリアスは CKA 試験対策としても重要です。CKA 試験環境にはあらかじめ alias k=kubectl が設定されており、受験者は k get nodesk apply -fk describe pod といった短縮形を使えます。日常的に k を使う習慣をつけておくと、試験本番で違和感なく操作でき、限られた試験時間を節約できます。

実行コマンド(k8s-ops で developer 作業):

$ echo 'alias k=kubectl' >> ~/.bashrc
$ source ~/.bashrc

エイリアスが効くか確認します。

実行コマンド:

$ k get nodes

実行結果:

NAME        STATUS     ROLES           AGE   VERSION
k8s-cp-01   NotReady   control-plane   2m    v1.35.5

k get nodeskubectl get nodes と同じ結果を返せば成功です。以降、本記事のコマンド例は k の短縮形を使います。

kubectl の補完設定

あわせて kubectl の Tab 補完を設定します。これも任意ですが、設定しておくとリソース名やサブコマンドを Tab キーで補完でき、入力が速くなります。

実行コマンド(k8s-ops で developer 作業):

$ kubectl completion bash >> ~/.bashrc
$ echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
$ source ~/.bashrc

1 行目で kubectl の補完スクリプトを ~/.bashrc に追加し、2 行目でその補完をエイリアス k にも適用しています。これで k get no と打って Tab キーを押すと k get nodes に補完されます。CKA 試験環境でも補完は有効なので、普段から使って慣れておくとよいでしょう。

Calico CNI インストールとノード Ready 確認

最後のセクションです。CNI をインストールして、ノードを Ready にします。CNI(Container Network Interface)は Pod 間のネットワークを提供するコンポーネントです。kubeadm は CNI を含まないため、運用者が選んで導入します。本シリーズでは Calico を使います。作業場所は k8s-ops です。

Calico マニフェストのダウンロード

Calico は manifest 方式(calico.yaml を kubectl apply する方式)でインストールします。50 ノード以下の kubeadm 環境ではこの方式が推奨されます。ここで重要なのが「URL から直接 apply せず、いったんダウンロードしてから apply する」という点です。

その理由は Pod CIDR の整合です。calico.yaml には CALICO_IPV4POOL_CIDR という環境変数があり、デフォルト値は 192.168.0.0/16 です。一方、本シリーズの kubeadm の podSubnet は 10.244.0.0/16 です。両者が食い違うと Pod ネットワークの割り当てが意図どおりにならない可能性があります。ダウンロードして設定を確認・必要なら修正してから apply するのが安全です。

実行コマンド(k8s-ops で developer 作業):

$ curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/calico.yaml

Calico v3.32.0 は 2026-05-19 時点の最新安定版です。raw.githubusercontent.com は whitelist 登録済みのためプロキシ経由で取得できます。

CALICO_IPV4POOL_CIDR の確認

ダウンロードした calico.yaml の中で CALICO_IPV4POOL_CIDR がどう設定されているかを確認します。

実行コマンド:

$ grep -n 'CALICO_IPV4POOL_CIDR' calico.yaml

実行結果(多くの場合コメントアウトされている):

              # - name: CALICO_IPV4POOL_CIDR
              #   value: "192.168.0.0/16"

多くのバージョンの calico.yaml では CALICO_IPV4POOL_CIDR がコメントアウトされています。コメントアウトされている場合、Calico は kubeadm が設定した Pod CIDR(10.244.0.0/16)を自動検出します。この場合はコメントアウトのまま apply して問題ありません。

もしコメントが外れていて value: "192.168.0.0/16" が有効になっている場合は、kubeadm の podSubnet に合わせて 10.244.0.0/16 に変更します。

実行コマンド(CALICO_IPV4POOL_CIDR が有効かつ値が異なる場合のみ):

$ sed -i 's#192.168.0.0/16#10.244.0.0/16#' calico.yaml

IP_AUTODETECTION_METHOD を eth0 に固定する(二枚 NIC 環境の必須設定)

本シリーズの検証 VM は eth0(External Switch・192.168.1.0/24)と eth1(Internal Switch・10.0.10.0/24)の二枚 NIC 構成です。Kubernetes ノードの InternalIP は eth0 側(192.168.1.x)に固定しているため、Calico の IPIP トンネル端点も eth0 側で揃える必要があります。Calico はデフォルトで first-found アルゴリズムによって NIC を自動検出しますが、二枚 NIC では eth1(10.0.10.x)を誤検出することがあります。誤検出すると BGP ピアが他ノードに到達できず Calico-node が 0/1 Ready のまま停滞します。これを防ぐため、Calico の DaemonSet に環境変数 IP_AUTODETECTION_METHOD=interface=eth0 を明示します。

calico.yaml の calico-node DaemonSet の env セクションに以下を追加します。CLUSTER_TYPE の直上に挿入するパターンが kubeadm 環境では一般的です。

実行コマンド:

$ sed -i '/name: CLUSTER_TYPE/i\            - name: IP_AUTODETECTION_METHOD\n              value: "interface=eth0"\n            - name: IP6_AUTODETECTION_METHOD\n              value: "none"' calico.yaml

計画的に DaemonSet を直接 apply 後に編集する場合は kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=interface=eth0 IP6_AUTODETECTION_METHOD=none でも同等の効果が得られます。本シリーズの第4回以降で追加する Workload Node も同じ NIC 構成のため、この設定 1 回で全ノードに適用されます。

firewalld の BGP / IPIP / Pod CIDR 開放

本シリーズは firewalld を有効のまま運用するため、Calico の BGP(179/tcp)と IPIP トンネル(IP プロトコル番号 4)、それと Pod CIDR 10.244.0.0/16 およびノード間ネットワーク 192.168.1.0/24 をノード間で疎通可能にする必要があります。これらの開放が抜けると BGP ピアが Socket: No route to host で確立できず、calico-node が 0/1 Ready のまま停滞します。第2回時点ではノードは k8s-cp-01 1 台ですが、第4回以降の HA クラスタ化で複数ノードに広がる前提で、ここで設定しておきます。

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

# firewall-cmd --permanent --add-port=179/tcp
# firewall-cmd --permanent --add-protocol=ipip
# firewall-cmd --permanent --zone=trusted --add-source=10.244.0.0/16
# firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
# firewall-cmd --reload

Calico の経路問題は第2巻全体の前提条件であり、ここで根治しておけば第4回以降の Workload Node 追加でも同じ設定をコピーすれば BGP メッシュが確立します。逆にこの設定が抜けると、第9回以降の MetalLB や cert-manager の admission webhook が「API server から Pod CIDR への到達不能」で context deadline exceeded を出す原因になります。

Calico の apply

calico.yaml をクラスタに適用します。

実行コマンド:

$ k apply -f calico.yaml

実行結果(抜粋):

configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created

多数のリソースが created と表示されます。Calico は DaemonSet(calico-node・全ノードで稼働)と Deployment(calico-kube-controllers)として展開されます。

calico-node Pod の起動確認

Calico の中心コンポーネントである calico-node Pod が起動するまで待ちます。イメージの pull を含めて 1〜3 分かかる場合があります。

実行コマンド:

$ k get pods -n kube-system -l k8s-app=calico-node

実行結果(Running になることを確認):

NAME                READY   STATUS    RESTARTS   AGE
calico-node-xxxxx   1/1     Running   0          Xm

STATUS が Running、READY が 1/1 になれば Calico が稼働しています。STATUSContainerCreatingInit の間は、イメージ pull か初期化が進行中です。少し待ってから再度確認してください。

ノード Ready 確認 — 第2回のゴール

Calico が稼働すると、Pod ネットワークが確立し、ノードが Ready になります。確認します。

実行コマンド:

$ k get nodes

実行結果(Ready になることを確認):

NAME        STATUS   ROLES           AGE   VERSION
k8s-cp-01   Ready    control-plane   3m    v1.35.5

STATUS が Ready に変わりました。これが第2回のゴールです。kubeadm によるシングルノードクラスタの起動が完了し、k8s-ops から kubeadm クラスタを操作できる状態になりました。素の VM から始めて、OS 設定・containerd・kubeadm・CNI までを積み上げ、ここまで到達しました。

kube-system の全 Pod 確認

クラスタの基盤コンポーネントがすべて正常に動いているか、kube-system 名前空間の Pod を一覧で確認します。

実行コマンド:

$ k get pods -n kube-system

実行結果(全 Pod が Running であることを確認):

NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-xxxxxxxxxx-xxxxx   1/1     Running   0          Xm
calico-node-xxxxx                          1/1     Running   0          Xm
coredns-xxxxxxxxxx-xxxxx                   1/1     Running   0          Xm
coredns-xxxxxxxxxx-xxxxx                   1/1     Running   0          Xm
etcd-k8s-cp-01                             1/1     Running   0          Xm
kube-apiserver-k8s-cp-01                   1/1     Running   0          Xm
kube-controller-manager-k8s-cp-01          1/1     Running   0          Xm
kube-proxy-xxxxx                           1/1     Running   0          Xm
kube-scheduler-k8s-cp-01                   1/1     Running   0          Xm

すべての Pod が Running であれば、クラスタは健全な状態です。ここで etcd-k8s-cp-01kube-apiserver-k8s-cp-01kube-controller-manager-k8s-cp-01kube-scheduler-k8s-cp-01 という 4 つの Pod 名に注目してください。末尾に -k8s-cp-01 というサフィックスが付いています。これは Static Pod の特徴です。Static Pod の Pod 名はノード名から自動付与されます。kubeadm init/etc/kubernetes/manifests/ に配置された 4 つの YAML が、この 4 つの Pod として動いているわけです。

一方、coredns-...kube-proxy-...calico-... はランダムなサフィックスです。これらは通常の Pod(Deployment や DaemonSet で管理される Pod)であり、API Server を介して管理されます。「サフィックスがノード名 = Static Pod」「サフィックスがランダム = 通常の Pod」という見分け方を覚えておくと、第15回のトラブルシュート演習で役立ちます。Static Pod の設定変更は /etc/kubernetes/manifests/ のファイルを直接編集して行います。

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

まとめと次回予告

第2回で学んだことをまとめます。

  • alma-proxy の whitelist に pkgs.k8s.ioprojectcalico.docs.tigera.io を追加し、プロキシ環境下での Kubernetes パッケージ取得を可能にした
  • k8s-cp-01 の swap 無効化・カーネルモジュール(overlay / br_netfilter)・sysctl・containerd(SystemdCgroup=true)の設定が、kubeadm init の前提条件である
  • swap は swapoff -a/etc/fstab のコメントアウトをセットで実施しないと、再起動後に復活して kubelet が起動しなくなる
  • containerd の SystemdCgroup は cgroup v2 環境(AlmaLinux 10)では必ず true にする。false のままだと kubelet と cgroup ドライバが食い違いクラスタが不安定になる
  • pkgs.k8s.io は 2024年3月に旧 Google ホストリポジトリが廃止された後の唯一の公式 RPM リポジトリ。v1.35 専用リポジトリを設定し、dnf versionlock でバージョンをピン留めする
  • k8s-lb に最小 HAProxy パススルー設定(6443 → k8s-cp-01:6443)を入れ、controlPlaneEndpoint: k8s-lb:6443 を最初から設定しておくことが HA 構成への必須前提
  • kubeadm init --config kubeadm-config.yaml --upload-certs で Control Plane が起動し、Static Pod(kube-apiserver / etcd 等)が /etc/kubernetes/manifests/ に配置される
  • Calico CNI インストール後に kubectl get nodes で Ready 表示が確認でき、k8s-ops から kubeadm クラスタを操作できるようになった。alias k=kubectl を導入し CKA 試験環境と同じ短縮コマンドを使える

第2回で押さえた本番ガードレールを 5 つの観点で振り返ります。これらは「なぜ必要か」まで説明できることが重要です。

ガードレールなぜ必要か
swap 無効化(fstab 含む)kubelet はメモリ管理の前提として swap 無効を要求。fstab を直さないと再起動で復活し kubelet が起動不能になる
SystemdCgroup = truecgroup v2 環境では kubelet と containerd の cgroup ドライバを揃える必要がある。食い違うと Control Plane Pod が不安定になる
バージョンピン留めdnf upgrade で kubelet が勝手に上がるとバージョンスキュー違反になる。第6回の計画的アップグレード演習も破綻する
controlPlaneEndpointinit 後に変更すると証明書 SAN の再生成が必要。HA 化(第4回)を見据え最初から LB を指す
カーネルモジュールoverlay は containerd、br_netfilter は Pod ネットワークの iptables 連携に必須。未ロードだとネットワークが不安定になる

次回(第3回)では、k8s-lb の HAProxy 設計を HA 本格仕様に作り込みます。第2回で入れた最小パススルー設定を土台に、Control Plane×3 を backend に登録した負荷分散・ヘルスチェック・stats ページの設定を行います。あわせて第4回の HA クラスタ構築(k8s-cp-02/03 の join、k8s-wl-01/02 の join)に向けて、kubeadm-config.yaml の HA 設計を整えます。第3回を終えると、5 ノード HA クラスタを構築する準備が完了します。

現場ヒヤリハット — swap を無効化しても再起動後に kubelet が起動しないパターン

第2回のヒヤリハットは swap の無効化に関するものです。これは kubeadm クラスタ構築で最も多いつまずきの一つです。

状況を再現します。クラスタ構築時に swapoff -a を実行し、swap を無効化したつもりで kubeadm init を完了させました。クラスタは正常に起動し、kubectl get nodes で Ready も確認できました。ここまでは問題ありません。しかし /etc/fstab の swap 行をコメントアウトしていませんでした。

後日、設定変更の確認や OS メンテナンスのために VM を再起動します。すると、再起動時に /etc/fstab の設定に従って swap が復活します。swap が有効な状態では kubelet が起動できないため、ノードが NotReady になるか、そもそも API Server が応答しなくなります。

このとき systemctl status kubeletjournalctl -u kubelet を見ると、kubelet が activating (auto-restart) の状態で起動と失敗を繰り返しているのが分かります。kubelet はデフォルトで swap が有効なノードでの起動を拒否する設定(failSwapOn)になっているためです。ログには swap が有効である旨のエラーが記録されます。

原因は再起動で復活した swap です。free -h で Swap 行の total が 0B でなくなっていれば、これが原因と確定できます。対処は次のとおりです。

# swapoff -a
# sed -i.bak '/swap/s/^/#/' /etc/fstab
# systemctl restart kubelet

swapoff -a で swap を無効化し、/etc/fstab をコメントアウトして再発を防ぎ、systemctl restart kubelet で kubelet を再起動します。これでノードは復旧します。教訓は明快です。swap 無効化は「swapoff -a」と「/etc/fstab のコメントアウト」をセットで実施する、これを鉄則として覚えてください。本記事の H2-4 ではこの 2 手順を必ずセットで実施するよう構成しています。

もう一つ、関連する落とし穴を挙げます。SystemdCgroup = false のまま containerd を起動した状態で kubeadm init を実行すると、Pod の cgroup 操作でエラーが発生し、kube-apiserveretcd の Static Pod が起動しません。kubeadm init[kubelet-check] あたりで止まり、タイムアウトに至ります。原因の特定に時間がかかる厄介な症状です。

このパターンの予防策は、containerd の設定変更後に必ず grep SystemdCgroup /etc/containerd/config.tomltrue を確認し、systemctl restart containerd で再起動することです。設定ファイルを書き換えただけでは反映されません。再起動を忘れると、ファイル上は true、稼働中の containerd は false という食い違いが起き、kubeadm init で詰まります。「設定変更したら再起動して確認」を習慣にしてください。

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

以下の 7 問に○か×で答えてください。答えと解説は下に記載しています。

問1:swapoff -a を実行するだけで、再起動後も swap が無効のままになる

問2:AlmaLinux 10 は cgroup v2 がデフォルトであるため、containerd の SystemdCgroup = true 設定が必須である

問3:pkgs.k8s.io の v1.35 リポジトリの baseurl は https://pkgs.k8s.io/core:/stable:/v1.35/rpm/ である

問4:kubeadm init --upload-certs--upload-certs は etcd のバックアップを自動取得するオプションである

問5:kubectl get nodes で STATUS が NotReady の場合、常にクラスタに問題がある

問6:/etc/kubernetes/manifests/ に配置された YAML ファイルは、kubelet が自動で Pod として起動・管理する

問7:controlPlaneEndpoint を kubeadm init 後に変更するのは、証明書の SAN を作り直す必要があるため容易ではない

— 答えと解説 —

問1:× — swapoff -a は今この瞬間の swap を無効化するだけで、再起動すると /etc/fstab の設定に従い swap が復活します。再起動後も無効を維持するには /etc/fstab の swap 行をコメントアウトする必要があります。この 2 手順はセットです。

問2:○ — AlmaLinux 10 は cgroup v2 がデフォルトです。cgroup v2 環境では kubelet(systemd cgroup ドライバを期待)と containerd の cgroup ドライバを揃える必要があり、containerd 側は SystemdCgroup = true に設定します。false のままだと Control Plane Pod が不安定になります。

問3:○ — v1.35 用リポジトリの baseurl は https://pkgs.k8s.io/core:/stable:/v1.35/rpm/ です。pkgs.k8s.io はマイナーバージョンごとに専用 URL があり、第2巻は v1.35 専用リポジトリを設定します。

問4:× — --upload-certs は Control Plane の証明書をクラスタ内の Secret に暗号化保存し、追加 Control Plane Node が証明書を自動取得できるようにするオプションです。etcd のバックアップとは関係ありません。etcd のバックアップは第5回で etcdctl を使って扱います。

問5:× — Calico CNI をインストールする前は、Pod ネットワークが未確立のためノードは NotReady になります。これは正常な状態です。CNI を適用すると Ready に変わります。NotReady が常に異常を意味するわけではありません。

問6:○ — /etc/kubernetes/manifests/ は Static Pod のディレクトリです。kubelet がこのディレクトリを常時監視し、YAML ファイルが存在する限り対応する Pod を自動で起動・再起動します。kubeadm init は etcd・kube-apiserver・kube-controller-manager・kube-scheduler の 4 つをここに配置します。

問7:○ — controlPlaneEndpoint を init 後に変更するには、API Server の TLS 証明書に含まれる SAN を作り直す必要があります。本番稼働中のクラスタでは大掛かりな作業になるため、HA 化を見据えて init 時に最初から LB のエンドポイントを指定しておくのが定石です。

シリーズ一覧

第1部:クラスタ構築

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

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

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

広告
kubernetes
スポンサーリンク