Certified Kubernetes Administrator (CKA) #4 kubeadm でのクラスターインストール: 単一 control plane のブートストラップ

#3 ノードと Pod ネットワーキングモデル では、kubelet と kube-proxy、CRI がノードでどう噛み合うかを整理しました。今度はそのコンポーネントを実際にインストールして、空の Linux マシンを Kubernetes クラスターにする 番です。クラウドのマネージドクラスターは control plane を隠してくれますが、CKA はその control plane を自分で立てる能力を見ます。この記事では kubeadm で単一 control plane クラスターをゼロからブートストラップします。

kubeadm はクラスターを立てる公式のブートストラップツールです。apiserver・etcd・scheduler・controller-manager を static Pod として起動し、証明書を発行し、ワーカーが付くためのトークンを作ってくれます。試験でよく出る作業がまさにこの kubeadm initkubeadm join なので、コマンド一つひとつを正確に手に馴染ませます。

大きな絵: kubeadm がやること #

kubeadm でクラスターを立てる手順は、マシンごとに次に分かれます。

  1. すべてのノード共通の事前準備。swap の無効化、カーネルモジュール、sysctl、コンテナランタイム、kubeadm・kubelet・kubectl のインストール
  2. control plane ノードでのブートストラップkubeadm init で apiserver・etcd・scheduler・controller-manager を static Pod として起動
  3. CNI のインストール。Pod ネットワークプラグインを入れて初めてノードが Ready になり、Pod 同士が通信できます
  4. ワーカーノードのジョイン。各ワーカーで kubeadm join を実行して control plane に合流

control plane もワーカーも 1 段階までは同じように準備します。2 段階から役割が分かれます。一つずつコマンドで追っていきます。

1. 事前準備 (すべてのノード共通) #

control plane でもワーカーでも、次の準備を同じように行います。抜けた段階が一つでもあると kubeadm init や kubelet が起動に失敗するので、順番に確認していきます。

swap の無効化 #

kubelet はデフォルト設定では swap が有効だと起動を拒否します。メモリ圧迫時の動作を予測可能にするためです。まず現在のセッションで切り、再起動後も切れたままになるよう /etc/fstab の swap 行をコメントアウトします。

# 即座に swap を切る
sudo swapoff -a

# 再起動後も維持 (fstab の swap 項目をコメントアウト)
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# 確認 (出力が空であること)
swapon --show
free -h

カーネルモジュール: overlay と br_netfilter #

コンテナランタイムの overlay ファイルシステムと、bridge を通過するトラフィックを iptables に通す br_netfilter が必要です。再起動後も自動ロードされるよう設定ファイルに書き、今のセッションにも即座にロードします。

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# ロード確認
lsmod | grep -E 'overlay|br_netfilter'

sysctl: ブリッジトラフィックと IP フォワーディング #

br_netfilter がロードされた状態で、bridge を通るトラフィックが iptables ルールを経由するようにし、ノードの IP フォワーディングを有効にします。CNI と kube-proxy が正常に動作するための前提です。

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 再起動なしで適用
sudo sysctl --system

# 確認 (3 つとも 1 であること)
sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptables

コンテナランタイム: containerd #

#3 で見たとおり、Kubernetes は CRI を通じてランタイムと通信します。最も広く使われる containerd をインストールし、kubelet と同じ cgroup ドライバー (systemd) を使うよう設定します。この cgroup ドライバーの不一致が、初心者が最もよく遭遇する失敗の原因です。

# containerd のインストール (ディストリのパッケージまたは公式バイナリ)
sudo apt-get update && sudo apt-get install -y containerd

# デフォルト設定の生成
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null

# systemd cgroup ドライバーを使用 (kubelet と一致させる)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 再起動と起動時の自動起動
sudo systemctl restart containerd
sudo systemctl enable containerd

kubeadm、kubelet、kubectl のインストール #

3 つのパッケージを公式リポジトリからインストールします。バージョンを固定 (hold) しておくと、意図しない自動アップグレードでクラスターが揺らぐのを防げます。アップグレードは #6 で意図的に扱います。

# リポジトリ利用のためのパッケージ
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

# Kubernetes apt リポジトリのキーとソースを追加 (バージョンは受験時点に合わせる)
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list

# インストール後にバージョン固定
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# kubelet を起動時に自動起動 (init 前は crashloop 状態が正常)
sudo systemctl enable --now kubelet

ここまでがすべてのノード共通です。ワーカーノードはこの状態で止め、control plane ノードで次の段階に進みます。

2. control plane のブートストラップ: kubeadm init #

control plane ノード 1 台で kubeadm init を実行します。このコマンド一つが証明書を発行し、control plane コンポーネントを static Pod として起動し、etcd を起動し、ワーカーが付くためのトークンを作ります。

sudo kubeadm init \
  --pod-network-cidr=192.168.0.0/16 \
  --apiserver-advertise-address=10.0.0.10

2 つのオプションの意味を押さえます。

  • --pod-network-cidr。Pod に割り当てる IP 帯域です。後でインストールする CNI が期待する帯域と一致 させる必要があります。Calico のデフォルトが 192.168.0.0/16、Flannel のデフォルトが 10.244.0.0/16 です。
  • --apiserver-advertise-address。apiserver が広告するノードの IP です。ノードにインターフェースが複数ある場合は明示する方が安全です。

kubeadm init が成功すると、最後に 2 つ を出力します。どちらもそのままコピーしておく必要があります。

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

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

kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
        --discovery-token-ca-cert-hash sha256:1234...cdef

kubeconfig の設定 #

上記出力の最初のブロックをそのまま実行して初めて、kubectl がクラスターに接続できます。admin.conf を一般ユーザーの ~/.kube/config にコピーする作業です。

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

# 接続確認 (control plane ノードが 1 行見えること)
kubectl get nodes

この時点で kubectl get nodes を実行すると control plane ノードが 1 つ見えますが、状態は NotReady です。まだ CNI を入れていないためであり、正常です。

3. CNI のインストール: ノードを Ready に #

Kubernetes は Pod ネットワークプラグイン (CNI) を自身では提供しません。別途インストールして初めてノードが Ready になり、Pod 同士が通信できます。CNI を入れるまでは CoreDNS Pod も Pending に留まります。

Calico のインストール例 #

# Calico マニフェストの適用
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml

# CNI Pod が Running になる過程を観察
kubectl get pods -n kube-system -w

Flannel のインストール例 #

Flannel を使うなら、kubeadm init--pod-network-cidr=10.244.0.0/16 で実行している必要があります。マニフェストと CIDR がずれると Pod が IP をもらえません。

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

CNI Pod がすべて Running になると、ノード状態が NotReady から Ready に変わります。

# しばらくすると Ready に切り替わる
kubectl get nodes

4. ワーカーノードのジョイン: kubeadm join #

各ワーカーノードで、kubeadm init の出力にあった kubeadm join コマンドを root で 実行します。ワーカーも 1 段階の事前準備がすべて終わっている必要があります。

sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:1234...cdef

コマンドの 3 つの断片がそれぞれ信頼関係を作ります。

  • 10.0.0.10:6443。ワーカーが接続する apiserver のアドレスとポートです。
  • --token。ブートストラップトークンです。ワーカーが自身を証明するのに使います。デフォルトの有効期間が 24 時間 なので失効に注意します。
  • --discovery-token-ca-cert-hash。control plane の CA 証明書のハッシュです。ワーカーが接続した apiserver が本当に自分たちのクラスターかを検証します。

トークンを失くした、または失効したとき #

kubeadm init の出力を取り損ねた、または 24 時間が過ぎたなら、control plane ノードで join コマンドを新たに発行します。この 1 行がトークンと CA ハッシュをすべて含んだ完成版の join コマンドを出力します。

# control plane ノードで: 完成版の join コマンドを再発行
kubeadm token create --print-join-command

トークンだけを別に作ったり、現在生きているトークンを確認したりもできます。

# 新しいトークンだけを生成
kubeadm token create

# 現在有効なトークン一覧
kubeadm token list

5. 検証 #

ジョインが終わったら control plane ノードでクラスター全体の状態を確認します。CKA では作業後にこの検証を必ず通さないと点数を落とします。

# すべてのノードが Ready か
k get nodes

# control plane コンポーネントと CNI、CoreDNS が Running か
k get pods -n kube-system

正常なら control plane とワーカーがすべて Ready で、kube-system ネームスペースの apiserver・etcd・scheduler・controller-manager・kube-proxy・CoreDNS・CNI Pod がすべて Running で見えます。

NAME           STATUS   ROLES           AGE   VERSION
controlplane   Ready    control-plane   8m    v1.31.0
node01         Ready    <none>          3m    v1.31.0

ノードの中で kubelet 自体の状態を確認するには systemctl と journalctl を使います。#1 でセットアップしたコマンドです。

systemctl status kubelet
journalctl -u kubelet -f

よくある落とし穴 #

インストール作業で点数を落とすパターンはほぼ決まっています。

  • CNI を入れずノードが NotReadykubeadm init 直後にノードが NotReady なのは正常ですが、CNI を入れないと永遠に NotReady のままです。CoreDNS も Pending に留まります。インストールを全部終えたのにノードが Ready にならないなら、まず kubectl get pods -n kube-system で CNI Pod を確認します。
  • swap が有効で kubelet が起動しないkubeadm init が事前点検 (preflight) で swap エラーに止まります。swapoff -a を抜かしたか、再起動後に /etc/fstab の処理をしていない場合です。
  • トークンの失効。ブートストラップトークンの有効期間が 24 時間なので、時間が過ぎた後にワーカーを付けようとすると認証に失敗します。kubeadm token create --print-join-command で新たに発行します。
  • cgroup ドライバーの不一致。containerd が cgroupfs、kubelet が systemd のように互いに異なる cgroup ドライバーを使うと、kubelet がコンテナを起動できません。containerd の SystemdCgroup = true 設定を確認します。
  • pod-network-cidr と CNI の不一致kubeadm init に与えた CIDR と CNI マニフェストが期待する帯域がずれると Pod が IP をもらえません。Calico は 192.168.0.0/16、Flannel は 10.244.0.0/16 をデフォルトに合わせます。
  • CRI ソケットの未設定。ランタイムが複数入っている場合は kubeadm init --cri-socket unix:///run/containerd/containerd.sock のように明示する必要があります。

失敗した init を巻き戻すには #

設定を間違えて最初からやり直したいときは、kubeadm reset でノードを初期状態に戻します。

sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d $HOME/.kube/config

試験ポイント #

  • kubeadm init は control plane を static Pod として起動します。apiserver・etcd・scheduler・controller-manager のマニフェストは /etc/kubernetes/manifests/ にあります。
  • kubeadm init 直後の出力の 2 ブロック (kubeconfig 設定と join コマンド) を両方とも確保します。join コマンドを逃したら kubeadm token create --print-join-command で再発行します。
  • ノードが NotReady なら、まず CNI のインストール有無 を疑います。CNI なしではノードが Ready になりません。
  • 事前準備 4 種 (swap off、カーネルモジュール、sysctl、コンテナランタイム) が抜けると init や kubelet が起動に失敗します。
  • --pod-network-cidr はインストールする CNI のデフォルト帯域と一致させます。
  • kubelet の状態は systemctl status kubeletjournalctl -u kubelet で追跡します。

まとめ #

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

  • 事前準備 (すべてのノード)。swap の無効化、カーネルモジュール (overlay・br_netfilter)、sysctl (ip_forward・bridge-nf)、containerd のインストールと systemd cgroup、kubeadm・kubelet・kubectl のインストール
  • control plane のブートストラップkubeadm init --pod-network-cidr=...、出力の kubeconfig 設定 (mkdir -p ~/.kube; cp ...) と join コマンドの確保
  • CNI のインストール。Calico や Flannel のマニフェストを適用して初めてノードが Ready
  • ワーカーのジョインkubeadm join (トークン + discovery hash)、失効時は kubeadm token create --print-join-command で再発行
  • 検証と落とし穴k get nodesk get pods -n kube-system で確認し、NotReady・swap・トークンの失効・cgroup の不一致を点検

次へ: HA クラスター #

単一 control plane クラスターを立てました。ところが control plane ノードが 1 台だけだと、その 1 台が落ちた瞬間にクラスター管理が止まります。

#5 HA クラスター: 複数 control plane、外部 etcd cluster では、control plane を複数台に増やして可用性を確保する構成を扱います。apiserver の前にロードバランサーを置く方法、kubeadm init--control-plane-endpoint を与えて追加の control plane が付くようにする方法、そして etcd を control plane に一緒に置く stacked 構成と別クラスターに分離する external etcd 構成の違いまで整理します。

X