- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 —— 想只靠一个二进制和一个镜像包搞定
- 确定要导入的内容 —— k0s 需要哪些文件
- 制作镜像包 —— list-images 与 bundle-artifacts
- 手动安装 —— 单节点,以及 controller 加 worker
- 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。发行说明明确提到要留意连接、文件传输、操作系统探测这几个方面可能出现的回归问题,所以在离线环境里采用它之前,一定要先在预发布环境里核实文件上传路径是否正常。
确定要导入的内容 —— 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 daemon,适合用作审核用的预发布设备 |
| ctr images export | 一个已经在运行的 k0s 工作节点 | 优点是能拿到已经验证过确实能跑的镜像。需要一个预发布集群 |
| docker save | Docker daemon | 用起来熟悉,但需要装 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 就够了。
手动安装 —— 单节点,以及 controller 加 worker
导入完成之后,安装本身很快。把镜像包放进数据目录下的 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
如果是 controller 和 worker 分开部署的架构,则是这样。镜像包需要放在工作节点上,因为真正拉取镜像、启动容器的是工作节点。如果 controller 同时兼任 worker,controller 上也需要有镜像包。
# controller 节点
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
# 签发 worker 加入令牌(在 controller 上执行)
sudo k0s token create --role=worker > /opt/staging/worker.token
# worker 节点
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
彻底阻断外部拉取尝试
确认镜像包是否真正正确导入,最可靠的方法是把从外部获取镜像的路径直接封死,看看会发生什么。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 |
| 只有 worker 节点没有 Pod 起来 | 镜像包只放在了 controller 上 | 检查每个 worker 的 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 # controller
sudo journalctl -u k0sworker -f # worker
sudo k0s ctr images ls | wc -l # 已导入的镜像数量是否在增加
与 k3s 流程的坦诚对比
| 维度 | k3s | k0s |
|---|---|---|
| 镜像导入 | 把发行版归档复制进镜像文件夹 | 复制发行版镜像包,或用 list-images 直接制作 |
| 并入自有镜像 | 另外放一份 tar | 只需在清单文件里加几行就能合并成一个镜像包 —— 更干净 |
| 安装入口 | install.sh 与环境变量 | k0s install 子命令 —— 不需要导入脚本 |
| 多节点自动化 | 需要额外工具(如 Ansible) | k0sctl 作为官方工具存在 —— 更干净 |
| 阻断外部拉取 | disable-default-registry-endpoint(只作用于配置了镜像的对象) | default_pull_policy Never —— 意图更直观 |
| 私有仓库镜像 | registries.yaml,支持 rewrite | 需要直接操作 containerd 配置 —— 更费手工 |
| 节点移除 | 手动 | 手动(k0sctl 不支持) |
| 社区资料 | 压倒性地多 | 相对较少,排查问题更慢 —— 在离线环境里这一点体感明显 |
总结一下,制作镜像包和多节点自动化这两点上 k0s 更胜一筹,私有仓库镜像和问题排查资料这两点上 k3s 更胜一筹。如果已经在运行内部仓库、需要重写镜像路径,k3s 的 registries.yaml 带来的优势会很明显。反过来,如果是一个没有仓库、只靠镜像包反复搭建和升级多个节点的环境,k0sctl 一份配置文件带来的优势会很明显。
结语 —— 自动化工具用的是 SSH,这就是全部
k0sctl 在离线环境里之所以管用,不是因为功能多花哨,而是因为它用的是 SSH,不是互联网。通过导入审核的文件一旦放到跳板机上,搭建集群就变成了纯粹的内部操作。一个工具是否具备这个特性,是在离线环境里挑选工具的第一条标准。
还有,一定要把清单文件放进 Git。一次没有记录镜像包里到底装了什么的导入,必然会让下一次导入时不得不把同样的排查工作从头再来一遍。