KubesprayでKubernetesを構築:Rocky Linux 9・Cilium運用ガイド
EdwardMoon
Kubesprayを使ったKubernetes構築ガイドです。Rocky Linux 9のノードにKubesprayとAnsibleで再現可能なクラスターを構築し、3台のコントロールプレーン、etcdのクォーラム、API VIP、Ciliumネットワークを展開後に検証します。コマンドの実行だけでなく、障害やアップグレードに対応できる構成かを確認します。
旧版で使用したKubernetes 1.31.6は2025年11月にサポートが終了しました。この改訂版は2026年7月時点のKubespray 2.31.0の標準構成であるKubernetes 1.35.4と、同梱のCilium 1.19.3を基準とします。実際の導入前には、選択したKubesprayタグのチェックサムとCiliumのKubernetes互換性マトリクスを併せて確認してください。

KubesprayによるKubernetes構築のバージョンとサポート範囲
| 構成要素 | この記事の基準 | 選定理由と注意点 |
|---|---|---|
| Rocky Linux | 9.xの最新パッチ | 全ノードのマイナーバージョン、カーネル、時刻同期を統一 |
| Kubespray | v2.31.0タグ | リリースタグとrequirementsを固定して再現性を確保 |
| Kubernetes | v1.35.4 | Kubespray 2.31.0の既定値で、サポート期間中のマイナーバージョン |
| Cilium | v1.19.3 | Kubespray 2.31.0の同梱バージョン |
| containerd | Kubesprayの既定値 | 2.31.0リリースで検証された組み合わせを使用 |
| etcd | 3メンバー | 1台の障害でも過半数による合意を維持 |
KubesprayによるKubernetes構築のトポロジー
| ホスト | IPアドレスの例 | 役割 |
|---|---|---|
| ansible01 | 10.20.0.5 | Kubesprayの実行、inventoryとartifactの保管 |
| api.k8s.example.com | 10.20.0.10 | 外部HAProxy/ロードバランサーのVIP:6443 |
| cp01~cp03 | 10.20.0.11~13 | kube_control_plane + etcd |
| wk01~wk03 | 10.20.0.21~23 | kube_node |
| backup01 | 10.20.0.30 | 暗号化したetcdスナップショットとinventoryのバックアップ |
API VIPはHAProxy 1台だけに依存させず、最低2台のプロキシとVRRP、または既存のロードバランサーで構成します。VIPの6443ヘルスチェックは全コントロールプレーンのkube-apiserverを対象とします。ノード、Pod、ServiceのCIDRと、社内ネットワーク、VPN、ストレージネットワークが重複しないよう、先にIPアドレス計画を確定します。
名前解決とポートの確認
getent hosts api.k8s.example.com cp01 cp02 cp03 wk01 wk02 wk03
for host in cp01 cp02 cp03 wk01 wk02 wk03; do
printf '%-6s ' "$host"
timeout 3 bash -c "</dev/tcp/${host}/22" && echo SSH_OK || echo SSH_FAIL
done
timeout 3 bash -c '</dev/tcp/api.k8s.example.com/6443' && echo API_VIP_OK || echo API_VIP_FAIL
KubesprayによるKubernetes構築:Rocky Linux 9ノードの事前確認
Kubesprayがcontainerd、kubelet、カーネルモジュール、sysctlを管理するため、別のインストールスクリプトを先に実行しないでください。まず全ノードのOS、CPU、メモリ、ディスク、cgroup、時刻同期と、既存コンテナーランタイムの残存状況を確認します。
cat /etc/rocky-release
uname -r
timedatectl status
free -h
lsblk -f
findmnt -no FSTYPE,OPTIONS / /var
stat -fc %T /sys/fs/cgroup
swapon --show
rpm -qa | grep -E 'kube(let|adm|ctl)|containerd|docker|cri-o' || true
Kubernetes 1.35はcgroup v2を基本とします。従来のcgroup v1回避オプションに依存せず、Rocky Linux 9のsystemd cgroup v2構成を維持します。SELinuxやfirewalldを無条件に無効化せず、Kubesprayリリース文書の対応方法と組織のネットワークACLを検証してください。
専用管理アカウントとSSHホスト鍵の登録
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/kubespray_ed25519
for host in cp01 cp02 cp03 wk01 wk02 wk03; do
ssh-copy-id -i ~/.ssh/kubespray_ed25519.pub "ansible@${host}"
ssh-keyscan -H "$host" >> ~/.ssh/known_hosts.new
done
sort -u ~/.ssh/known_hosts.new >> ~/.ssh/known_hosts
rm -f ~/.ssh/known_hosts.new
chmod 0600 ~/.ssh/known_hosts
ssh-keyscanの結果は、信頼できるコンソールや資産管理システムのフィンガープリントと照合してから登録します。StrictHostKeyCheckingを無効にすると、中間者攻撃を検出できません。ansibleアカウントには必要なsudo権限だけを付与し、rootでの直接SSHログインは許可しません。
KubesprayによるKubernetes構築:2.31.0の実行環境を固定
Kubesprayの構築ファイルはmainブランチではなくリリースタグで固定します。Python仮想環境とrequirements.txtを使用すると、システムPythonとAnsibleの依存関係を混在させず、同じ制御環境を再現できます。
sudo dnf install -y git python3.12 python3.12-pip
git clone --branch v2.31.0 --depth 1 https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
git verify-tag v2.31.0 || git show --show-signature --no-patch v2.31.0
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install --requirement requirements.txt
python --version
ansible --version
git describe --tags --always
Gitの署名検証は、信頼するメンテナーの鍵を別経路で確認している場合に意味を持ちます。閉域環境では、リリースアーカイブ、Python wheel、コンテナーイメージのチェックサムと由来を接続環境で検証し、承認済みリポジトリへ搬入します。
KubesprayによるKubernetes構築のinventory作成
inventory/prod/inventory.yml
cp -a inventory/sample inventory/prod
cat > inventory/prod/inventory.yml <<'YAML'
all:
hosts:
cp01: {ansible_host: 10.20.0.11, ip: 10.20.0.11, access_ip: 10.20.0.11}
cp02: {ansible_host: 10.20.0.12, ip: 10.20.0.12, access_ip: 10.20.0.12}
cp03: {ansible_host: 10.20.0.13, ip: 10.20.0.13, access_ip: 10.20.0.13}
wk01: {ansible_host: 10.20.0.21, ip: 10.20.0.21, access_ip: 10.20.0.21}
wk02: {ansible_host: 10.20.0.22, ip: 10.20.0.22, access_ip: 10.20.0.22}
wk03: {ansible_host: 10.20.0.23, ip: 10.20.0.23, access_ip: 10.20.0.23}
children:
kube_control_plane:
hosts: {cp01: {}, cp02: {}, cp03: {}}
kube_node:
hosts: {wk01: {}, wk02: {}, wk03: {}}
etcd:
hosts: {cp01: {}, cp02: {}, cp03: {}}
k8s_cluster:
children:
kube_control_plane: {}
kube_node: {}
calico_rr:
hosts: {}
YAML
etcdのメンバー数は奇数にします。コントロールプレーンとワーカーを混在させて定義せず、ansible_hostはSSH接続先、ipとaccess_ipはノード間通信のアドレスとして区別します。NATや複数NICの環境では、通知するアドレスとルーティングを個別に検証します。
次の3ファイルをエディターで開き、該当キーを追加するか既存の値を変更します。各例はファイルに記述するYAMLであり、シェルコマンドではありません。
inventory/prod/group_vars/all/all.ymlの主要設定
ansible_user: ansible
ansible_ssh_private_key_file: ~/.ssh/kubespray_ed25519
loadbalancer_apiserver:
address: 10.20.0.10
port: 6443
kubeconfig_localhost: true
kubectl_localhost: true
Kubernetesとセキュリティの既定値
Kubespray v2.31.0のkube_versionとcilium_versionには、接頭辞vを付けない数値のバージョンを指定します。ダウンロードURLやコンテナータグの接頭辞はKubesprayが付けるため、v1.35.4のように入力してはいけません。サンプルYAMLの該当キーを以下の値に変更し、同じキーを後ろに追加しないでください。
kube_version: 1.35.4
container_manager: containerd
kube_network_plugin: cilium
kube_service_addresses: 10.233.0.0/18
kube_pods_subnet: 10.233.64.0/18
cluster_name: cluster.local
kube_api_anonymous_auth: false
remove_anonymous_access: true
kubernetes_audit: true
supplementary_addresses_in_ssl_keys:
- 10.20.0.10
- api.k8s.example.com
PodとServiceのCIDRは、ルーター、VPN、データセンターのアドレス範囲と重複させてはいけません。使用中のCIDRを後で変更する作業は、新しいクラスターへの移行に匹敵する規模になる場合があります。API VIPとDNS名はkube-apiserver証明書のSANに含めます。
CiliumのオーバーレイとHubbleの設定
cilium_version: 1.19.3
cilium_tunnel_mode: vxlan
cilium_identity_allocation_mode: crd
cilium_ipam_mode: kubernetes
cilium_cni_exclusive: true
cilium_enable_hubble: true
cilium_hubble_install: true
cilium_hubble_tls_generate: true
cilium_enable_hubble_ui: false
kube-proxy replacementは単なる性能オプションではなく、Serviceのデータパスを変更する設定です。この基本ガイドではkube-proxyを維持します。置き換える場合は、cilium_kube_proxy_replacement、APIのグローバルエンドポイント、DSR・SNAT、ホストファイアウォールの動作を別途設計し、負荷試験と障害試験を行います。
KubesprayによるKubernetes構築前の検証
CIDRの重複とinventoryの構造
ip route
ip -4 address show
grep -R --line-number -E 'kube_(service_addresses|pods_subnet)|loadbalancer_apiserver|kube_version|cilium_version' inventory/prod/group_vars
ansible-inventory -i inventory/prod/inventory.yml --graph
ansible-inventory -i inventory/prod/inventory.yml --list > inventory/prod/inventory-expanded.json
SSH・sudo・Pythonの確認
ansible -i inventory/prod/inventory.yml all -m ping
ansible -i inventory/prod/inventory.yml all --become -m command -a 'id'
ansible -i inventory/prod/inventory.yml all --become -m shell -a 'python3 --version; stat -fc %T /sys/fs/cgroup; swapon --show'
pingモジュールの成功だけでは不十分です。becomeが非対話で動作し、Pythonインタープリターとcgroup v2の構成が全ノードで一致していることを確認します。Ansible VaultのパスワードファイルとSSH秘密鍵はリポジトリにコミットしないでください。
playbookの構文と変更一覧を確認
ansible-playbook -i inventory/prod/inventory.yml cluster.yml --syntax-check
git status --short
git diff -- inventory/prod/group_vars inventory/prod/inventory.yml
tar --exclude='credentials' --exclude='artifacts' -czf "inventory-prod-$(date +%F).tgz" inventory/prod
KubesprayによるKubernetes構築の実行
最初から全ノードに任意のタグを指定して実行せず、検証済みinventoryで公式のcluster.ymlを実行します。失敗した場合は、同じコマンドを再実行する前に最初に失敗したタスクとノードの状態を確認します。Kubesprayは冪等性を目指していますが、外部ロードバランサーとネットワークの状態は別途管理が必要です。
mkdir -p logs
ansible-playbook -i inventory/prod/inventory.yml cluster.yml --become 2>&1 | tee "logs/cluster-$(date +%F-%H%M%S).log"
test ${PIPESTATUS[0]} -eq 0
teeを使用すると、シェルが最後に返す終了コードがteeの結果になる場合があります。PIPESTATUS[0]でansible-playbookの結果を確認してください。ログにはホスト名、IPアドレス、タスク結果が含まれるため、アクセス権限と保管期間を決め、外部共有前に機密情報を除去します。
KubesprayによるKubernetes構築結果の検証
kubeconfigとコントロールプレーンのエンドポイント
find inventory/prod/artifacts -maxdepth 2 -type f -ls
install -d -m 0700 "$HOME/.kube"
install -m 0600 inventory/prod/artifacts/admin.conf "$HOME/.kube/config"
kubectl config view --minify
kubectl cluster-info
kubectl get --raw='/readyz?verbose'
artifactのファイル名はKubesprayのタグと設定により異なる場合があるため、まずfindの結果を確認します。kubeconfigにはcluster-adminの認証情報が含まれるので、共有ホームディレクトリやCIのartifactにそのまま配置しないでください。運用担当者はOIDCと最小権限のRBACを使用します。
ノード・etcd・システムPodの状態
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get --raw='/livez?verbose'
ansible -i inventory/prod/inventory.yml etcd --become -m command -a 'systemctl status etcd --no-pager'
kubectl -n kube-system get endpoints kube-dns
kubectl get events -A --sort-by='.lastTimestamp' | tail -n 50
CiliumとHubbleの状態
kubectl -n kube-system rollout status daemonset/cilium --timeout=10m
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
cilium status --wait --wait-duration 10m
cilium connectivity test
kubectl -n kube-system get secret | grep -i hubble
kubectl -n kube-system logs deployment/hubble-relay --tail=100
cilium connectivity testは複数のnamespaceとPodを作成するため、運用方針に沿った検証用namespaceで実行し、終了後に削除します。全ノードのCilium DaemonSet、operator、CoreDNS、Serviceルーティングが正常であることを確認してからワークロードを展開します。
KubesprayによるKubernetes構築:ワークロードとNetworkPolicyの検証
Deployment・Service・DNSのスモークテスト
kubectl create namespace smoke-test
kubectl -n smoke-test create deployment web --image=registry.k8s.io/e2e-test-images/agnhost:2.53 -- /agnhost netexec --http-port=8080
kubectl -n smoke-test expose deployment web --port=80 --target-port=8080
kubectl -n smoke-test rollout status deployment/web --timeout=5m
kubectl -n smoke-test run client --rm -it --restart=Never --image=docker.io/library/busybox:1.36.1 --command -- /bin/sh -c 'nslookup web; wget -qO- http://web/'
既定で拒否してから明示的に許可
cat <<'YAML' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-default-deny
namespace: smoke-test
spec:
podSelector: {matchLabels: {app: web}}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-client
namespace: smoke-test
spec:
podSelector: {matchLabels: {app: web}}
policyTypes: [Ingress]
ingress:
- from:
- podSelector: {matchLabels: {run: client}}
ports:
- {protocol: TCP, port: 8080}
YAML
kubectl -n smoke-test get networkpolicy
ポリシー適用前の成功、既定の拒否による失敗、明示的な許可後の成功をそれぞれ確認します。DNSの送信通信も制限する場合は、kube-dnsサービスの実際のselectorとUDP・TCP 53を確認してください。テスト後はkubectl delete namespace smoke-testでリソースを削除します。
etcdスナップショットと復旧準備
Kubesprayによる構築が完了したら、直ちにetcdスナップショットを自動化します。スナップショットは1メンバーで作成し、クラスター外の暗号化されたストレージへコピーします。inventory、証明書、Kubesprayタグと併せて復旧手順を記録します。
# 既定のhost展開方式のetcdノード1台で実行します。
sudo systemctl cat etcd
sudo grep -E '^ETCD_(LISTEN_CLIENT_URLS|TRUSTED_CA_FILE|CERT_FILE|KEY_FILE)=' /etc/etcd.env
sudo bash -euo pipefail <<'BASH'
backup_dir=/var/backups/etcd
install -d -m 0700 "$backup_dir"
snapshot="$backup_dir/snapshot-$(date +%F-%H%M%S).db"
node_name=$(hostname -s)
ca=/etc/ssl/etcd/ssl/ca.pem
cert="/etc/ssl/etcd/ssl/admin-${node_name}.pem"
key="/etc/ssl/etcd/ssl/admin-${node_name}-key.pem"
for file in "$ca" "$cert" "$key"; do test -r "$file"; done
etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert="$ca" --cert="$cert" --key="$key" snapshot save "$snapshot"
etcdutl snapshot status "$snapshot" --write-out=table
BASH
既定のetcd_deployment_type: hostでは、etcdはsystemdサービスとして動作します。/etc/etcd.envとsystemctl cat etcdで実際のエンドポイントと証明書パスを確認し、上記の値を調整してください。inventoryのホスト名とOSのhostnameが異なる場合は、管理者証明書のファイル名も実在するものに修正します。同じクラスターで復旧を試さず、隔離環境でスナップショットのリストア、APIの起動、オブジェクトの整合性まで定期的に検証します。
証明書・監査・運用の確認
sudo kubeadm certs check-expiration
kubectl auth can-i --list
kubectl auth can-i create clusterrolebindings --as=system:anonymous
kubectl get --raw='/metrics' | grep -E 'apiserver_request_total|apiserver_request_duration_seconds' | head
kubectl get nodes -o json | jq -r '.items[] | [.metadata.name,.status.nodeInfo.kubeletVersion,.status.nodeInfo.containerRuntimeVersion] | @tsv'
- API VIPとDNSを確認し、コントロールプレーンの1台を停止してもkubectlのリクエストが成功し続けることを確認します。
- etcdのメンバー、リーダー、DBサイズ、スナップショットの成否をアラートに連携します。
- ノードのファイルシステム、inode、memory pressure、PID pressure、イメージ用ファイルシステムの容量を監視します。
- kube-apiserverの監査ログを中央ストレージへ転送し、改ざん防止と保管ポリシーを適用します。
- 日常利用のアカウントからcluster-adminのkubeconfigを取り除き、OIDC、RBAC、短時間のセッションを使用します。
- Ciliumのdrop、policy verdict、DNSエラー、Hubble証明書の有効期限を監視します。
KubesprayによるKubernetesのアップグレード
Kubernetesのマイナーバージョンは1段階ずつ更新し、version skewポリシーに従います。まず対象のKubernetesパッチとCiliumバージョンが、移行先Kubesprayタグのチェックサムとサポート一覧に含まれることを確認します。その後、etcdスナップショット、ワークロードのバックアップ、PodDisruptionBudget、余剰リソースを確認します。
git fetch --tags --prune
git tag --sort=-version:refname | head
# 新しいclone/venvへ既存のinventoryをコピーし、差分を確認します。
git diff v2.31.0..'<TARGET_KUBESPRAY_TAG>' -- inventory/sample roles/kubespray_defaults docs
ansible-playbook -i inventory/prod/inventory.yml upgrade-cluster.yml --become --syntax-check
ansible-playbook -i inventory/prod/inventory.yml upgrade-cluster.yml --become 2>&1 | tee "logs/upgrade-$(date +%F-%H%M%S).log"
test ${PIPESTATUS[0]} -eq 0
upgrade-cluster.ymlの実行前に、新リリースのurgent upgrade noteを読みます。特にKubespray 2.31では、cgroup v1、サポートを終えたingress-nginx、アーカイブされたKubernetes Dashboard、etcdの前提バージョン、削除された変数に注意してください。コントロールプレーンとワーカーを一斉に再起動しないでください。
よくあるミスと安全な代替策
| ミス | 影響 | 代替策 |
|---|---|---|
| mainブランチから直接展開 | 依存関係と既定値が随時変化する | リリースタグ、requirements、チェックサムを固定 |
| etcdを2メンバーで構成 | 1台の障害で過半数を失う | 3または5の奇数メンバーで構成 |
| APIエンドポイントが1つだけ | コントロールプレーンの単一障害点になる | 複数のLBとVIPヘルスチェックを使用 |
| CIDRの重複 | Pod・Service・社内ネットワークのルーティングが競合 | 展開前にIPAMとルートを検証 |
| SSHホスト鍵の確認を無効化 | 中間者攻撃を検出できない | フィンガープリント検証後にknown_hostsへ登録 |
| cluster-adminのkubeconfigを共有 | クラスター全体の権限が露出する | OIDC、RBAC、短時間のセッションを使用 |
| スナップショットファイルの作成だけで終了 | 復旧できるか未確認のままになる | 隔離環境で定期的にリストア訓練を実施 |
KubesprayによるKubernetes構築のチェックリスト
- KubesprayタグとPython・Ansibleのrequirementsを固定した。
- Kubernetes・Cilium・containerdの組み合わせに関するサポート文書を確認した。
- 3台のコントロールプレーン・etcdと、複数のAPIロードバランサーを準備した。
- Pod・Service・ノード・VPN・ストレージのCIDRが重複していない。
- SSHホスト鍵、sudoの権限範囲、Vaultと秘密鍵の保管を検証した。
- ノード・API・CoreDNS・Ciliumの接続性とNetworkPolicyを試験した。
- etcdスナップショットを外部に保管し、隔離環境での復旧訓練を完了した。
- 証明書の期限、監査ログ、リソース不足、Ciliumのdropを監視している。
- アップグレード前にurgent note、version skew、PDB、余剰リソースを確認する。
関連資料
- Kubespray v2.31.0公式リリース
- Kubernetes公式リリースとサポート期間
- Kubernetes公式version skewポリシー
- Cilium公式Kubernetes互換性要件
- Ansible playbook・Vault・ローリングデプロイガイド
- Linuxのディスク・inode不足を診断するガイド
- Linuxコンテナーとcgroups v2のガイド
KubesprayによるKubernetes構築のまとめ
KubesprayによるKubernetes構築では、cluster.ymlが一度成功しただけでなく、バージョンの組み合わせ、3ノードの合意、APIエンドポイント、ネットワーク、復旧計画を併せて検証します。リリースタグとinventoryを固定し、匿名アクセスを制限して、Ciliumの接続性、NetworkPolicy、etcdのリストアを実際に試験します。この基準を満たすことで、Rocky Linux 9上の実習を運用可能なKubernetes基盤へ発展させられます。