- Published on
k0s閉域網インストールとk0sctl自動化 — イメージバンドル作成から複数ノードアップグレードまで
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — バイナリ一つとバンドル一つで終わらせるには
- 搬入対象の算出 — k0sが要求するファイル
- イメージバンドル作成 — list-imagesとbundle-artifacts
- 手動インストール — 単一ノードと、コントローラー・プラス・ワーカー
- k0sctl自動化 — SSHでバンドルとバイナリを押し込む
- アップグレード経路とk0sctlの限界
- ここで詰まります — k0s閉域網の失敗モードとk3sとの比較
- おわりに — 自動化ツールがSSHを使うという事実がすべてです
- 参考資料
はじめに — バイナリ一つとバンドル一つで終わらせるには
k0sの設計思想は単純です。単一バイナリ一つがコントロールプレーンでありワーカーであり、インストールスクリプトもパッケージリポジトリも必要ありません。閉域網の観点からこれは大きな利点です。搬入審議に上げるファイルが実質的に二つに減るからです。k0sバイナリ一つとイメージバンドル一つです。
ただしこの単純さには代償があります。k3sはリリースに上がったエアギャップアーカイブをそのままコピーすれば終わりですが、k0sは「何をバンドルに入れるか」を利用者が明示的に決める構造に近いです。だから自前のワークロードイメージを同じバンドルに合わせるのはむしろ簡単ですが、代わりにバンドル作成段階でインターネットにつながる機器にk0sバイナリを先に用意しておかなければなりません。
検証基準は以下のとおりです。
| 項目 | 値 | 確認時点 | 確認した出典 |
|---|---|---|---|
| k0s最新安定版 | v1.36.3+k0s.0 (2026-07-27) | 2026-07-31 | k0sリリース |
| k0sctl最新版 | v0.32.2 (2026-07-28) | 2026-07-31 | k0sctlリリース |
| バンドル自動インポート | データディレクトリ下位のimages | 2026-07-31 | k0s Airgap Install |
| 既定のデータディレクトリ | /var/lib/k0s | 2026-07-31 | k0s Airgap Install |
k0sctl v0.32.0で、ホスト接続・リモート実行レイヤーがrig v0.xからrig v2.0.0へ丸ごと入れ替わりました。リリースノートは接続・ファイル転送・OS検知領域の回帰に注意するよう明示しているので、閉域網に導入する際はステージングでファイルアップロード経路を必ず先に検証してください。
搬入対象の算出 — k0sが要求するファイル
k0sのリリースページには閉域網に必要な資産がすでに上がっています。v1.36.3+k0s.0基準で確認した主要資産は次のとおりです。
| 資産 | サイズ(確認時点) | 用途 |
|---|---|---|
| k0s-v1.36.3+k0s.0-amd64 | 約250 MB | k0s単一バイナリ |
| k0s-airgap-bundle-v1.36.3+k0s.0-linux-amd64.tar | 約390 MB | 基本システムイメージバンドル(すぐ使用可能) |
| k0s-airgap-bundle-v1.36.3+k0s.0-linux-arm64.tar | 約357 MB | arm64ノード用バンドル |
| airgap-imagesテキストファイル (amd64/arm/arm64) | 数KB | バンドルに入ったイメージ一覧 |
| SHA256チェックサムファイル | 数KB | 整合性検証 |
リリースが提供するバンドルをそのまま使えば、バンドル作成段階を飛ばせます。自前のイメージを混ぜる必要のない最初のインストールなら、この経路がもっとも速いです。
#!/usr/bin/env bash
# collect-k0s.sh — インターネットにつながるステージング機器
set -euo pipefail
K0S_VERSION="v1.36.3+k0s.0"
ARCH="amd64"
URLVER="${K0S_VERSION/+/%2B}"
BASE="https://github.com/k0sproject/k0s/releases/download/${URLVER}"
OUT="./k0s-airgap-${K0S_VERSION}"
mkdir -p "${OUT}" && cd "${OUT}"
curl -fL -o k0s "${BASE}/k0s-${K0S_VERSION}-${ARCH}"
chmod +x k0s
curl -fL -o "k0s-airgap-bundle-${K0S_VERSION}-linux-${ARCH}.tar" \
"${BASE}/k0s-airgap-bundle-${K0S_VERSION}-linux-${ARCH}.tar"
# 搬入マニフェストは自分で作ります — 内側でメディアの破損を判別する唯一の根拠です
sha256sum k0s "k0s-airgap-bundle-${K0S_VERSION}-linux-${ARCH}.tar" > MANIFEST.sha256
echo "${K0S_VERSION}" > VERSION
ls -la
イメージバンドル作成 — list-imagesとbundle-artifacts
リリースバンドルを使わず、直接作らなければならない場合があります。自前のワークロードイメージを一緒に入れたい、基本CNIの代わりに別のものを使う、社内ポリシー上すべてのイメージを社内レジストリ経路にタグ付けし直して搬入しなければならない、といった場合です。
k0sは必要なイメージ一覧を自ら抽出してくれます。
# インターネットにつながる機器でk0sバイナリを使って実行
./k0s airgap list-images --all > airgap-images.txt
wc -l airgap-images.txt
cat airgap-images.txt
一覧が出たら、バンドルにまとめます。公式文書が提示する方法は三つで、それぞれ前提条件が違います。
# 方法A — k0s内蔵ツール(Dockerも動作中のk0sも不要)
./k0s airgap bundle-artifacts -v -o image-bundle.tar < airgap-images.txt
# 方法B — すでに動作中のワーカーノードのcontainerdストレージから書き出す
k0s ctr images export image-bundle.tar $(k0s airgap list-images | xargs)
# 方法C — Dockerがある機器で
k0s airgap list-images --all > airgap-images.txt
xargs -I{} docker pull {} < airgap-images.txt
docker image save -o image-bundle.tar $(xargs < airgap-images.txt)
三つの方法の実務上の違いはこうです。
| 方法 | 前提条件 | 閉域網搬入の観点からの評価 |
|---|---|---|
| bundle-artifacts | k0sバイナリとインターネット | もっともすっきりしている。Dockerデーモン依存がなく審議用ステージング機器に適する |
| ctr images export | すでに動作中のk0sワーカーノード | すでに検証済みのイメージをそのまま取れる利点。ステージングクラスターが必要 |
| docker save | Dockerデーモン | 馴染みはあるがDockerのインストールが必要。マルチアーキテクチャの処理がもっとも面倒 |
方法Aを基本とし、ステージングクラスターがすでにあれば方法Bで「実際に動いたイメージ」を取るほうが安全です。
公式文書はk0sが緩やかなプラットフォームマッチングを使うため、マルチアーキテクチャバンドルが異なるプラットフォームで動作すると明示しています。amd64ノードとarm64エッジノードが混在する閉域網でバンドルを一つで管理できるということなので、搬入単位を減らすのに活用できる余地があります。
自前のワークロードイメージを同じバンドルに合わせる
k0s文書は「k0sが既定で使わないコンテナイメージを含めるようバンドルを簡単にカスタマイズできる」とし、バンドルにまとめる前にイメージ一覧を確認・編集するよう案内しています。実際にはリストファイルに行を追加するだけでよいのです。
# 1) k0sシステムイメージ一覧を抽出し
./k0s airgap list-images --all > airgap-images.txt
# 2) 社内ワークロードイメージを同じ一覧に追記します
cat >> airgap-images.txt <<'EOF'
registry.internal.example:5000/apps/api:1.4.2
registry.internal.example:5000/apps/worker:1.4.2
registry.internal.example:5000/infra/postgres:16.4
registry.internal.example:5000/infra/prometheus:v3.1.0
EOF
# 3) 重複除去後一度にまとめる
sort -u airgap-images.txt -o airgap-images.txt
./k0s airgap bundle-artifacts -v -o image-bundle.tar < airgap-images.txt
# 4) マニフェスト更新
sha256sum image-bundle.tar >> MANIFEST.sha256
このリストファイルをGitに上げておくことを強くお勧めします。閉域網搬入は反復作業であり、「今回の搬入に何が入ったか」を後で再構成できなければなりません。リストがコードとして管理されれば、次の搬入時にdiffだけ見ればよくなります。
手動インストール — 単一ノードと、コントローラー・プラス・ワーカー
搬入が終わっていれば、インストール自体は短いです。バンドルをデータディレクトリ下位のimagesフォルダに置いてk0sを起動すると、k0sがそのフォルダを監視して自動的にインポートします。
#!/usr/bin/env bash
# install-k0s-single.sh — 閉域網単一ノード
set -euo pipefail
STAGE=/opt/staging/k0s-airgap-v1.36.3+k0s.0
# 0) まず整合性検証
cd "${STAGE}" && sha256sum -c MANIFEST.sha256
# 1) バイナリ配置
sudo install -m 0755 "${STAGE}/k0s" /usr/local/bin/k0s
k0s version
# 2) イメージバンドル配置 — 起動前に入れる必要があります
sudo mkdir -p /var/lib/k0s/images
sudo cp "${STAGE}"/k0s-airgap-bundle-*.tar /var/lib/k0s/images/image-bundle.tar
# 3) 単一ノードインストール
sudo k0s install controller --single
sudo k0s start
# 4) 状態確認(バンドルインポートに時間がかかるのですぐReadyにならないことがあります)
sudo k0s status
sudo k0s kubectl get nodes -o wide
sudo k0s kubectl -n kube-system get pods
コントローラーとワーカーを分ける配置ならこうなります。イメージバンドルはワーカーノードに必要です。 実際にイメージをpullしてコンテナを起動する側がワーカーだからです。コントローラーがワーカーを兼ねる場合は、コントローラーにもバンドルが必要です。
# コントローラーノード
sudo install -m 0755 /opt/staging/k0s /usr/local/bin/k0s
sudo k0s install controller --enable-worker --no-taints
sudo k0s start
sudo k0s status
# ワーカー参加用トークン発行(コントローラーで)
sudo k0s token create --role=worker > /opt/staging/worker.token
# ワーカーノード
sudo install -m 0755 /opt/staging/k0s /usr/local/bin/k0s
sudo mkdir -p /var/lib/k0s/images
sudo cp /opt/staging/image-bundle.tar /var/lib/k0s/images/image-bundle.tar
sudo k0s install worker --token-file /opt/staging/worker.token
sudo k0s start
sudo k0s status
外部pull試行を完全に阻止する
バンドルがきちんと入ったか確認するもっとも確実な方法は、外部からイメージを取得する経路自体を遮断してみることです。k0sはクラスター設定でこれをサポートします。
# k0s.yaml
apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
spec:
images:
default_pull_policy: Never
公式文書はこの設定が「PodのimagePullPolicyをNeverにし、インターネットからイメージを取得しないことを保証する」と説明しています。閉域網ではこの値を最初からオンにしておくほうがよいです。そうすればバンドルに漏れたイメージがあるとき、数分後のタイムアウトではなく即座に失敗するので、搬入リストの漏れをその場で見つけられます。
ただしこの値をオンにすると、社内レジストリから取得するイメージも影響を受ける構成になり得るので、アプリケーションPodまで一括適用されるかは、このクラスター設定を適用した状態で実際のデプロイで確認しなければなりません。システムコンポーネントだけに適用される範囲なのかワークロード全体なのかは、今回の確認範囲では文書の本文だけで断定できなかったので、ステージングで直接検証してください。
k0sctl自動化 — SSHでバンドルとバイナリを押し込む
ノードが三台を超えると、手動手順はすぐに崩れます。誰かが一台のノードでバンドルコピーを忘れ、そのノードだけおかしな動作をします。k0sctlはこの反復をYAML一枚で代替します。
核心はk0sctlが閉域網内側のジャンプホストで実行されるという点です。k0sctlはインターネットではなくSSHで対象ノードに接続するので、ジャンプホストにk0sバイナリとイメージバンドルさえあれば、残りはk0sctlが各ノードへ転送します。
# 閉域網ジャンプホストにk0sctlバイナリを配置(これも搬入対象です)
sudo install -m 0755 /opt/staging/k0sctl /usr/local/bin/k0sctl
k0sctl version
# 初期設定ファイル生成
k0sctl init > k0sctl.yaml
# k0sctl.yaml — 閉域網3ノード構成
apiVersion: k0sctl.k0sproject.io/v1beta1
kind: Cluster
metadata:
name: onprem-airgap
spec:
k0s:
version: v1.36.3+k0s.0
config:
apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
spec:
images:
default_pull_policy: Never
hosts:
- role: controller
uploadBinary: true
k0sBinaryPath: /opt/staging/k0s
ssh:
address: 10.10.20.11
user: k0sadmin
keyPath: /home/k0sadmin/.ssh/id_ed25519
- role: worker
uploadBinary: true
k0sBinaryPath: /opt/staging/k0s
ssh:
address: 10.10.20.21
user: k0sadmin
keyPath: /home/k0sadmin/.ssh/id_ed25519
files:
- src: /opt/staging/image-bundle.tar
dstDir: /var/lib/k0s/images
perm: 0755
- role: worker
uploadBinary: true
k0sBinaryPath: /opt/staging/k0s
ssh:
address: 10.10.20.22
user: k0sadmin
keyPath: /home/k0sadmin/.ssh/id_ed25519
files:
- src: /opt/staging/image-bundle.tar
dstDir: /var/lib/k0s/images
perm: 0755
ここで閉域網にとって決定的なフィールドが二つあります。
uploadBinary: true— k0sctlが各ノードでk0sをダウンロードする代わりに、実行ホストのバイナリをアップロードします。この値がなければノードがリリースサーバーへの接続を試み、閉域網ではその場で失敗します。公式エアギャップ文書の例もこのフィールドを使います。files— 任意のファイルをノードの指定経路へ転送します。エアギャップ文書の例がそのままイメージバンドルをこの方式でワーカーに乗せます。
k0sBinaryPathでアップロードするローカルバイナリの経路を明示する方式は実務で広く使われていますが、今回の確認時点で公式エアギャップ文書の本文でこのフィールド名自体を直接確認できませんでした。uploadBinaryだけでも動作するので、フィールド名が不確かなら先にuploadBinary: trueだけを置き、k0sctl apply --debugでどのバイナリがどこからアップロードされているか確認したうえで追加してください。同じ理由で、エアギャップ文書例のkind値がレンダリングによって違って見える場合があるため、ここではk0sctlインストール文書基準のkind: Clusterを使いました。
適用と接続は次のとおりです。
# 構成適用 — バイナリアップロード、バンドル転送、クラスター構成まで一度に
k0sctl apply --config k0sctl.yaml
# kubeconfig回収
k0sctl kubeconfig --config k0sctl.yaml > kubeconfig
kubectl --kubeconfig kubeconfig get nodes -o wide
kubectl --kubeconfig kubeconfig -n kube-system get pods
SSHアクセス要件は閉域網でむしろ厄介になり得ます。ジャンプホストから各ノードの22番ポートが開いていなければならず、鍵認証ができなければならず、対象ユーザーがパスワードなしでsudoを使えなければなりません。この三つのうち一つでも社内セキュリティポリシーと衝突すれば、k0sctl経路は諦めて手動インストールにするべきです。導入前に先に確認すべき項目です。
アップグレード経路とk0sctlの限界
k0sctlのもっとも大きな実務価値はインストールではなくアップグレードです。手順がインストールとまったく同じです。
# 1) 新バージョンのバイナリとバンドルをジャンプホストに搬入
sha256sum -c /opt/staging/k0s-v1.36.3/MANIFEST.sha256
# 2) k0sctl.yaml のバージョン文字列とファイル経路だけ更新
# spec.k0s.version: v1.36.3+k0s.0
# hosts[].files[].src: /opt/staging/k0s-v1.36.3/image-bundle.tar
# 3) 同じコマンドで適用 — k0sctlがノードを順番に処理します
k0sctl apply --config k0sctl.yaml
# 4) 検証
kubectl --kubeconfig kubeconfig get nodes -o wide
kubectl --kubeconfig kubeconfig get pods -A --field-selector=status.phase!=Running
新しいイメージバンドルを先に上げなければならない点が閉域網特有の順序です。files項目が前にあるのでk0sctlがバンドルを転送してからk0sを入れ替えますが、バンドルのファイル名が以前のものと同じなら上書きになり、違えば二つ積み上がります。ディスクが厳しいエッジノードでは、以前のバンドルの整理を別段階として入れる必要があります。
限界も明確です。公式文書はk0sctlがノードを追加できるが、既存ノードを削除できないと明言しています。閉域網でハードウェアを交換したりノードを減らしたりするとき、k0sctl設定から消してもクラスターから抜けないので、別途手順が必要です。
# ノード削除は手動です
kubectl --kubeconfig kubeconfig drain worker-03 --ignore-daemonsets --delete-emptydir-data
kubectl --kubeconfig kubeconfig delete node worker-03
# 該当ノードでk0s整理
sudo k0s stop
sudo k0s reset
ここで詰まります — k0s閉域網の失敗モードとk3sとの比較
| 症状 | 原因 | 確認方法 |
|---|---|---|
| k0sは起動したがすべてのPodがImagePullBackOff | バンドルを起動後に入れたか経路が間違っている | ls /var/lib/k0s/images とk0s ctr images lsを対照 |
| ワーカーだけPodが起動しない | バンドルをコントローラーだけに配置 | 各ワーカーでimagesフォルダを確認 |
| k0sctl applyがダウンロードで止まる | uploadBinaryが漏れている | k0sctl apply --debugで転送ログを確認 |
| k0sctl applyがSSH段階で失敗 | 鍵認証またはパスワードなしsudoが不可 | sshで直接接続後sudo -n trueを試行 |
| ノードを外したのにクラスターに表示され続ける | k0sctlはノード削除をしない | kubectl delete nodeとk0s resetを手動実行 |
| インポートに時間がかかりReadyが遅い | バンドルサイズが数百MB — 正常動作 | journalctl -u k0scontroller またはk0sworkerでインポートログ確認 |
| arm64ノードだけ失敗 | アーキテクチャ別バンドルを誤って配布 | バンドルファイル名とuname -mを対照 |
インポート所要時間について一言添えます。公式文書はk0sがimagesフォルダを監視して自動インポートするとだけ述べ、所要時間には言及していません。実際には数百MBのバンドルを展開してcontainerdストレージに入れる作業なので、ディスクが遅いエッジハードウェアでは数分かかることがあります。起動直後ノードがNotReadyだからといってすぐに失敗と判断せず、サービスログでインポートの進行状況を先に確認してください。
sudo journalctl -u k0scontroller -f # コントローラー
sudo journalctl -u k0sworker -f # ワーカー
sudo k0s ctr images ls | wc -l # インポートされたイメージ数が増えているか
k3sの流れとの正直な比較
| 軸 | k3s | k0s |
|---|---|---|
| イメージ搬入 | リリースアーカイブをイメージフォルダにコピー | リリースバンドルのコピーまたはlist-imagesで直接作成 |
| 自前イメージの統合 | 別のtarを追加で配置 | リストファイルに行を追加するだけで一つのバンドルに統合 — よりすっきり |
| インストール入口 | install.shと環境変数 | k0s installサブコマンド — スクリプト搬入が不要 |
| 複数ノード自動化 | 別のツールが必要(Ansibleなど) | k0sctlが公式ツールとして存在 — よりすっきり |
| 外部pull遮断 | disable-default-registry-endpoint(ミラー設定対象のみ) | default_pull_policy Never — 意図がより直感的 |
| プライベートレジストリミラー | registries.yamlでrewriteまでサポート | containerd設定を直接扱う必要 — 手間が増える |
| ノード削除 | 手動 | 手動(k0sctlが未サポート) |
| コミュニティ資料 | 圧倒的に多い | 相対的に少なく問題解決が遅い — 閉域網で体感が大きい |
まとめると、バンドル作成と複数ノード自動化はk0sが優れており、プライベートレジストリミラーリングと問題解決資料はk3sが優れています。 社内レジストリをすでに運用中でイメージ経路の書き換えが必要な環境なら、k3sのregistries.yamlが与える利点が大きいです。逆にレジストリなしでバンドルだけで複数ノードを繰り返し構築・アップグレードしなければならない環境なら、k0sctl一枚が与える利点が大きいです。
おわりに — 自動化ツールがSSHを使うという事実がすべてです
閉域網でk0sctlが有効な理由は機能が華やかだからではなく、インターネットではなくSSHを使うからです。搬入審議を通過したファイルがジャンプホストに置かれた瞬間から、クラスター構築が純粋な内部作業になります。この性質を持つツールかどうかが、閉域網ツール選定の第一の基準です。
そしてリストファイルをGitに入れてください。バンドルに何が入ったか記録していない搬入は、必ず次の搬入時に同じ調査を最初からやり直させます。