开发测试环境-Kubernetes离线部署指南

注意

仅适用于特殊场景,例如客户无k8s运维,客户不付费购买k8s集群,本地开发测试。完整适配离线环境。

Kubernetes 离线部署指南(单 Master + 单 Worker,Master 可承载业务)

适用版本:Kubernetes v1.36.4 / containerd 2.3.3 / Calico v3.32.2 / OpenEBS 4.6.0 部署形态:1 个控制面节点(可调度业务)+ 1 个工作节点,无高可用 交付方式:完全离线 。所有文件与镜像在一台可联网的下载机上人工下载 → 打成单个离线总包 → 经移动介质人工摆渡 到两台节点解压使用(下载机与节点之间无网络互通,不使用 scp) 文档遵循「说明 → 准备环境 → 配置文件 → 部署 → 访问验证」结构


0. 说明

0.1 目标与范围

项目 内容
集群形态 单 Master(去掉 NoSchedule 污点,可承载业务)+ 单 Worker
高可用 。etcd 单实例、控制面单副本,Master 故障即集群不可写入
网络插件 Calico v3.32.2(单文件 calico.yaml,iptables 模式 kube-proxy)
存储插件 OpenEBS 4.6.0 Local PV Hostpath ,并配置为默认 StorageClass
容器运行时 containerd(随 nerdctl-full 2.3.5 交付,含 runc / CNI plugins)
离线范围 操作系统层关闭 swap 后,全程不再访问公网
批量下载 禁止脚本批量下载 。所有 Release 产物逐个人工点击下载;deb 依赖通过 apt-get --download-only 一次性收集

0.2 架构

bash 复制代码
                        ┌──────────────────────────────────────────┐
                        │            master1  172.16.0.19          │
                        │  ┌────────────┐  ┌──────┐  ┌──────────┐  │
   kubectl / helm  ───▶ │  │ kube-api   │  │ etcd │  │ scheduler│  │
                        │  │ controller │  │3.6.8 │  │ c-mgr    │  │
                        │  └────────────┘  └──────┘  └──────────┘  │
                        │  kube-proxy(iptables) + calico-node       │
                        │  openebs-localpv-provisioner              │
                        │  ✔ 已去污点:可调度业务 Pod                │
                        │  /data/containerd  /data/kubelet  /data/etcd
                        │  /data/openebs/local(LocalPV 卷目录)     │
                        └───────────────────┬──────────────────────┘
                                            │ 6443 / 179 / IPIP(协议4)
                        ┌───────────────────┴──────────────────────┐
                        │            work1  172.16.0.20            │
                        │  kubelet + kube-proxy + calico-node       │
                        │  ✔ 可调度业务 Pod                         │
                        │  /data/containerd  /data/kubelet          │
                        │  /data/openebs/local(LocalPV 卷目录)     │
                        └──────────────────────────────────────────┘

数据流 :PVC → StorageClass openebs-hostpath(default)→ openebs.io/local Provisioner → 在被调度节点上创建 /data/openebs/local/<pv名> 目录 → 以 hostPath 方式挂进 Pod。

0.3 版本兼容性矩阵(全部经官方清单核对)

组件 锁定版本 来源 / 兼容性依据
Kubernetes v1.36.4 1.36 线稳定版(dl.k8s.io/release/stable-1.36.txt
Calico v3.32.2 官方明确声明 v3.32 测试 K8s 1.34 / 1.35 / 1.36,1.36 在清单内
containerd 2.3.3 随 nerdctl-full v2.3.5 交付;nerdctl 声明兼容 containerd 2.0--2.3
runc 1.5.1 随 nerdctl-full v2.3.5 交付
CNI plugins v1.9.1 随 nerdctl-full v2.3.5 交付
nerdctl v2.3.5 与 containerd 2.3.3 配套
kubeadm / kubelet / kubectl 1.36.4 三件套与 K8s 主版本严格一致
etcd 3.6.8-0 K8s 1.36.4 内置默认值(DefaultEtcdVersion = "3.6.8-0")。注意镜像 tag 带 -0 后缀,见 §1.6 说明
CoreDNS v1.14.2 K8s 1.36.4 内置默认值(CoreDNSVersion = v1.14.2
pause 3.10.2 K8s 1.36.4 内置默认值(PauseVersion = 3.10.2
OpenEBS 4.6.0 Local PV Hostpath 组件版本 4.6.0(随 chart 交付)
Helm v3.21.4 3.x 线最新稳定版,chart 兼容性优先于 Helm 4
metrics-server v0.9.0 与 K8s 1.36 兼容
操作系统 Ubuntu 24.04 LTS(内核 6.8) Calico 要求内核 ≥ 5.10;Ubuntu 20.04+ 在官方推荐列表

重要提醒(版本选型的取舍) :K8s 最新稳定版是 v1.37.0,但 Calico v3.32 官方未测试 1.37 。为保证「全部落在兼容清单内」,本方案选 v1.36.4。若你后续必须上 1.37,需等 Calico 发布对应测试版本,否则属未验证组合。

0.4 变量约定(部署前只需改这一张表)

变量 出现位置
<MASTER_IP> 172.16.0.19 kubeadm.yaml、hosts、certSANs、命令
<WORK_IP> 172.16.0.20 hosts、join 命令
<MASTER_HOST> master1 主机名、kubeadm.yaml、join 命令
<WORK_HOST> work1 主机名、hosts
<主网卡名> 例如 ens33 / eth0 Calico IP_AUTODETECTION_METHOD
<数据盘挂载点> /data 全部数据目录的根
Pod CIDR 192.168.0.0/16 kubeadm + Calico(保持两者默认,无需改)
Service CIDR 10.96.0.0/12 kubeadm 默认

若你的实际 IP 与本文档不同:全局替换 172.16.0.19 / 172.16.0.20 即可,其余不依赖具体地址。

0.5 端口与协议要求(节点间必须放通)

端口 / 协议 方向 用途
TCP 6443 work1 → master1 kube-apiserver
TCP 2379--2380 节点间 etcd(单实例,本机通信为主)
TCP 10250 双向 kubelet API
TCP 10257 / 10259 本机 controller-manager / scheduler
TCP 179 双向 Calico BGP
IP 协议号 4(IPIP) 双向 Calico 默认封装(vxlan 为 Never)
UDP 4789 双向 仅当你把 Calico 改成 VXLAN 时才需要
TCP 30000--32767 入站 NodePort 服务

0.6 明确不做的事

  • 不做控制面高可用(无 3 master、无 VIP、无 haproxy/keepalived)。
  • 不部署 Ingress 控制器、Dashboard、本地镜像仓库(如后续需要,见 §7.5)。
  • 不改 kubeadm 源码延长证书(证书续期走 §7.1 的年度运维流程)。
  • 不写批量下载脚本;镜像以 tar 形式逐个导入,不做重打标(保留 registry.k8s.io/... 原始 tag,kubeadm 可直接识别)。

0.7 操作位置速查表(每一步在哪台机器执行

本文档涉及三类机器,执行前先看这张表:

机器 角色 能否联网 说明
下载机(跳板机) 下载文件、拉镜像、打离线包 ✅ 能上公网 与两台节点无网络互通
master1 控制面 + 业务 ❌ 离线 kubeadm init 在这里执行,也是 kubectl 操作机
work1 工作节点 ❌ 离线 只做运行时安装与 kubeadm join
章节 执行位置 说明
§1.2 系统准备 master1 + work1 都执行 主机名、swap、内核模块、/data 目录
§1.3 下载机准备 下载机
§1.4 离线文件下载 下载机 逐个手工下载
§1.5 deb 依赖收集 下载机 apt-get install --download-only
§1.6 镜像拉取与打包 下载机 含临时 containerd 与加速配置(只配在这台)
§1.7 打包与摆渡 下载机 → master1 + work1 打总包在下载机,解压在两台节点
§2.1 安装并配置 containerd master1 + work1 都执行
§2.2 kubeadm 初始化配置 master1
§2.3 Calico 清单调整 master1
§2.4 OpenEBS values master1
§2.5 metrics-server 清单调整 master1
§3.1 容器运行时复核 master1 + work1 都执行
§3.2 安装 kubeadm/kubelet/kubectl master1 + work1 都执行
§3.3 导入镜像 master1 + work1 都执行
§3.4 初始化控制面 master1
§3.5 配置 kubectl 授权 master1
§3.6 部署 Calico master1
§3.7 加入工作节点 work1
§3.8 允许 Master 承载业务 master1
§3.9 部署 OpenEBS master1
§3.10 部署 metrics-server master1
§4.1--4.5 访问验证 master1(kubectl 操作机) 4.3/4.4 需要到 Pod 实际落点节点看目录
§7.1 证书续期 master1
§7.2 集群重置与卸载 master1 + work1 都执行 先 master1 后 work1
§7.3 故障排查 master1(查看类)/ 对应节点(修复类)

为什么 2.2--2.5 全在 master1 :这四个文件最终都由 master1 消费------kubeadm initkubeadm.yamlkubectl apply / helm install 在 master1 上执行。虽然两台节点的 /data/offline/files/ 内容完全相同,但只在 master1 上改这一份即可,改完不需要同步到 work1。


1. 准备环境

1.1 主机与资源规划

角色 主机名 IP 最低配置 磁盘 说明
控制面 + 业务 master1 172.16.0.19 4 vCPU / 8 GiB 系统盘 60 G + 数据盘 ≥ 100 G 挂 /data 去污点后承载业务
工作节点 work1 172.16.0.20 4 vCPU / 8 GiB 系统盘 60 G + 数据盘 ≥ 100 G 挂 /data 承载业务

/data 必须是独立可扩容的磁盘 (物理机无 RAID 时先做 LVM;有 RAID 可直接用 RAID 卷)。所有容器、镜像、etcd、kubelet、LocalPV 卷都落在 /data,系统盘保持干净。

1.2 系统准备(master1 与 work1 全部执行

建议全程 root 执行;若用普通用户,命令前加 sudo

(1) 配置主机名与解析
bash 复制代码
# master1 上执行
hostnamectl set-hostname master1

# work1 上执行
hostnamectl set-hostname work1

两个节点都写入 hosts:

bash 复制代码
cat >> /etc/hosts <<'EOF'
172.16.0.19 master1
172.16.0.20 work1
EOF
(2) 关闭 swap(K8s 强制要求)
bash 复制代码
swapoff -a
sed -i 's/.*swap.*/#&/' /etc/fstab
# 验证:free -h 中 swap 必须为 0
free -h
(3) 加载内核模块与开启转发
bash 复制代码
cat <<'EOF' | tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

modprobe br_netfilter

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

sysctl --system
(4) 关闭防火墙 / 时间同步
bash 复制代码
# Ubuntu 24.04 默认 ufw 关闭,确认即可
systemctl disable --now ufw 2>/dev/null || true

# 时区
timedatectl set-timezone Asia/Shanghai

# 离线环境若无 chrony,手动设置一次时间(两台机差值必须 < 1 分钟)
date -s "2026-09-07 16:00:00"
(5) 准备专用数据目录
bash 复制代码
# /data 已由独立磁盘挂载;若无独立磁盘,用系统盘目录替代(不推荐生产)
mkdir -p /data/containerd /data/kubelet /data/etcd /data/openebs/local /data/offline

# kubelet 目录软链接:必须在 kubelet 启动前完成
if [ -e /var/lib/kubelet ] && [ ! -L /var/lib/kubelet ]; then rm -rf /var/lib/kubelet; fi
ln -s /data/kubelet /var/lib/kubelet
ls -ld /var/lib/kubelet    # 应显示 -> /data/kubelet

为什么用软链接而不是 rootDir 配置 :kubeadm 内部有硬编码的 /var/lib/kubelet 路径(如 kubelet 配置文件落盘位置)。用软链接可 100% 复现,不触发这些边界问题。

1.3 下载机(跳板机)准备

需要一台能访问公网、且同为 Ubuntu 24.04 的机器(物理机 / 虚拟机 / 你本地的 WSL 均可),用于下载文件与镜像。

关键前提:下载机与两台部署节点之间没有网络互通 (这正是「离线部署」的含义)。因此 不能用 scp / rsync 直接传节点 ,必须把所有资源在下载机上打成一个总包,再经移动介质人工摆渡到节点解压使用。完整流程见 §1.7。

bash 复制代码
# 下载机上执行
mkdir -p /data/offline/{files,deb,images}
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gnupg

1.4 离线文件清单(下载机执行,逐个人工下载,不写脚本)

在跳板机上逐条执行下方命令 (或按地址用浏览器逐个点击下载),全部落到 /data/offline/files/

# 文件名 版本 用途 下载地址
1 nerdctl-full-2.3.5-linux-amd64.tar.gz 2.3.5 containerd + runc + CNI plugins + nerdctl github.com/containerd/...
2 helm-v3.21.4-linux-amd64.tar.gz 3.21.4 安装 OpenEBS chart get.helm.sh/helm-v3.21....
3 calico.yaml v3.32.2 CNI 网络插件清单 raw.githubusercontent.com/projectcali...
4 openebs-4.6.0.tgz 4.6.0 OpenEBS Helm chart(子 chart 已打包在内) openebs.github.io/openebs/ope...
5 metrics-server-components.yaml(上游原始名 components.yaml v0.9.0 metrics-server 清单 github.com/kubernetes-...
6 kubernetes-apt-key.gpg(上游原始名 Release.key v1.36 K8s apt 源签名密钥(仅跳板机配源用,不下发) mirrors.aliyun.com/kubernetes-...
7 calicoctl-linux-amd64(可选) v3.32.2 Calico 排障命令行 github.com/projectcali...
逐个文件的下载命令

逐条复制执行 ,不要写成 for 循环批量下载。所有命令都用 -o 直接落成本文档后续使用的文件名,因此下载后无需重命名。 先进入目标目录:

bash 复制代码
mkdir -p /data/offline/files && cd /data/offline/files

① nerdctl-full(containerd + runc + CNI plugins + nerdctl)

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o nerdctl-full-2.3.5-linux-amd64.tar.gz \
  https://github.com/containerd/nerdctl/releases/download/v2.3.5/nerdctl-full-2.3.5-linux-amd64.tar.gz

② Helm 3.21.4

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o helm-v3.21.4-linux-amd64.tar.gz \
  https://get.helm.sh/helm-v3.21.4-linux-amd64.tar.gz

③ Calico v3.32.2 清单

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o calico.yaml \
  https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/calico.yaml

④ OpenEBS 4.6.0 Helm chart

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o openebs-4.6.0.tgz \
  https://openebs.github.io/openebs/openebs-4.6.0.tgz

⑤ metrics-server v0.9.0 清单

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o metrics-server-components.yaml \
  https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml

⑥ Kubernetes apt 源签名密钥(v1.36)

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o kubernetes-apt-key.gpg \
  https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/deb/Release.key

⑦ calicoctl(可选,排障用)

bash 复制代码
curl -fL --retry 3 --retry-delay 2 -o calicoctl-linux-amd64 \
  https://github.com/projectcalico/calico/releases/download/v3.32.2/calicoctl-linux-amd64
统一文件名(必做,否则 1.5 会报 No such file or directory)

上面的命令用 -o 直接写成了最终文件名。但如果你是用浏览器点击curl -O 下载的,落盘的是上游原始名:

  • 第 ⑤ 项会是 components.yaml(不是 metrics-server-components.yaml
  • 第 ⑥ 项会是 Release.key(不是 kubernetes-apt-key.gpg

执行下面两条做归一化(已存在的文件不会被误改,可放心重复执行):

bash 复制代码
cd /data/offline/files

[ -f components.yaml ] && mv -f components.yaml metrics-server-components.yaml
[ -f Release.key ]     && mv -f Release.key     kubernetes-apt-key.gpg

ls -1
下载结果校验
bash 复制代码
cd /data/offline/files
ls -lh

# 确认 6 个必需文件 + 1 个可选文件都在
ls -1

# 逐个记录校验值,与发布页公布的 SHA256 核对后填入附录 §7.4 的验收表
sha256sum *

预期 ls -1 输出:

sql 复制代码
calico.yaml
calicoctl-linux-amd64
helm-v3.21.4-linux-amd64.tar.gz
kubernetes-apt-key.gpg
metrics-server-components.yaml
nerdctl-full-2.3.5-linux-amd64.tar.gz
openebs-4.6.0.tgz

若某条 curl 返回 404:说明上游换了发布路径,去对应 Releases 页面人工点一下确认新 URL,改完再执行------不要为了省事写脚本批量重试。

1.5 deb 依赖收集(下载机执行

前置检查:本步依赖 1.4 下载的第 ⑥ 项。先确认文件存在:

bash 复制代码
ls -l /data/offline/files/kubernetes-apt-key.gpg

若报 No such file or directory,说明 1.4 下载时文件保留了原始名 Release.key,回到 1.4 执行「统一文件名」那两条 mv,或直接执行:

bash 复制代码
cd /data/offline/files && [ -f Release.key ] && mv -f Release.key kubernetes-apt-key.gpg && ls -l kubernetes-apt-key.gpg

跳板机上配置阿里云 K8s 源(这一步只影响跳板机,不会下载到目标节点):

bash 复制代码
mkdir -p /etc/apt/keyrings

# 阿里云的 Release.key 是 ASCII-armored 格式(首行 -----BEGIN PGP PUBLIC KEY BLOCK-----)
# apt 的 signed-by 需要 GPG 二进制 keyring,必须先 dearmor,不能直接 cp/install
gpg --batch --yes --dearmor -o /etc/apt/keyrings/kubernetes.gpg /data/offline/files/kubernetes-apt-key.gpg
chmod 644 /etc/apt/keyrings/kubernetes.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/deb/ /" \
  | tee /etc/apt/sources.list.d/kubernetes.list

apt-get update

验证密钥与源都正确(apt-get update 输出中不能 出现 NO_PUBKEYThe following signatures couldn't be verified):

bash 复制代码
apt-get update 2>&1 | grep -iE "NO_PUBKEY|not signed|couldn't be verified" && echo "签名校验失败,检查密钥" || echo "签名校验通过"
apt-cache policy kubeadm | head -5

一次性把主包与其全部依赖抓到 apt 缓存(不安装):

bash 复制代码
apt-get clean
apt-get install --download-only -y kubelet=1.36.4-* kubeadm=1.36.4-* kubectl=1.36.4-*
cp /var/cache/apt/archives/*.deb /data/offline/deb/
ls -lh /data/offline/deb/
预期结果(4 个包即为完整集合
复制代码
kubeadm_1.36.4-1.1_amd64.deb
kubectl_1.36.4-1.1_amd64.deb
kubelet_1.36.4-1.1_amd64.deb
kubernetes-cni_1.9.1-1.1_amd64.deb

依据是阿里云 v1.36 源 Packages 索引里各包的 Depends 字段:

Depends 说明
kubelet 1.36.4-1.1 iptables (>= 1.4.21)kubernetes-cni (>= 1.2.0)mountutil-linuxlibc6 除 kubernetes-cni 外,其余都是 Ubuntu 24.04 的基础包
kubeadm 1.36.4-1.1 自 1.36.1 起 kubeadm 不再声明任何依赖(1.36.0 曾依赖 cri-tools)
kubectl 1.36.4-1.1
kubernetes-cni 1.9.1-1.1

为什么只有 4 个包、看不到 iptables / util-linux / libc6 这是正常的apt-get --download-only 只下载「当前系统尚未安装」的包。上面那几个都是 Ubuntu 24.04 装机就有的基础包,apt 判定依赖已满足,于是跳过。目标节点同样是 Ubuntu 24.04,也自带这些包,dpkg -i 不会缺依赖。
两个版本事实(重要)

  1. conntrack 不在依赖里 :kubeadm 1.36 已移除 conntrack / socat / ebtables 等 PATH 预检,不需要额外安装。
  2. crictl 不会随包安装 :1.36.1 起 kubeadm 不再依赖 cri-tools,所以没有 crictl。见下一步按需补装。
按需补装 cri-tools(建议做

本文档 §3.1 / §3.3 用 crictl 校验镜像,K8s 包不再自带,需显式下载:

bash 复制代码
apt-get install --download-only -y cri-tools
cp /var/cache/apt/archives/cri-tools_*.deb /data/offline/deb/
ls -lh /data/offline/deb/

补完后 /data/offline/deb/ 应为 5 个包(多出 cri-tools_1.36.0-1.1_amd64.deb)。 若你确定不需要 crictl,可以跳过本步,同时把 §3.1 / §3.3 里的 crictl 命令换成等价的 ctr -n k8s.io images ls

apt-get install --download-only 提示某包找不到,先在跳板机执行 apt-cache policy kubeadm 确认可用版本,再把版本号替换成实际值。

1.6 镜像清单与打包(下载机执行

在**下载机(跳板机)**上逐个拉取并打包。共 14 个镜像(全部已逐个向仓库校验过真实存在):

# 完整镜像引用(可直接复制) 类别
1 registry.k8s.io/kube-apiserver:v1.36.4 K8s 核心
2 registry.k8s.io/kube-controller-manager:v1.36.4 K8s 核心
3 registry.k8s.io/kube-scheduler:v1.36.4 K8s 核心
4 registry.k8s.io/kube-proxy:v1.36.4 K8s 核心
5 registry.k8s.io/etcd:3.6.8-0 K8s 核心
6 registry.k8s.io/coredns/coredns:v1.14.2 K8s 核心
7 registry.k8s.io/pause:3.10.2 K8s 核心
8 quay.io/calico/cni:v3.32.2 Calico
9 quay.io/calico/node:v3.32.2 Calico
10 quay.io/calico/kube-controllers:v3.32.2 Calico
11 docker.io/openebs/provisioner-localpv:4.6.0 OpenEBS
12 docker.io/openebs/linux-utils:4.6.0 OpenEBS(helper pod)
13 registry.k8s.io/metrics-server/metrics-server:v0.9.0 监控
14 docker.io/openebs/alpine-bash:4.6.0 存储验证测试用

同步生成了一份 images.txt(同目录),内容是上面 14 行 仓库:标签,可直接用于逐条核对。

两个极易写错的 tag(已实测确认,写错就是 not found):

镜像 正确 tag 错误写法 原因
etcd registry.k8s.io/etcd:3.6.8-0 registry.k8s.io/etcd:3.6.8 kubeadm 常量 DefaultEtcdVersion = "3.6.8-0"-0 是 k8s 打包后缀,不是笔误 ;仓库里只有 3.6.8-0,没有 3.6.8(实测 404)
CoreDNS registry.k8s.io/coredns/coredns:v1.14.2 registry.k8s.io/coredns:v1.14.2 路径是 coredns/coredns 两段,少一段就是 404

权威交叉核对(装完 kubeadm 后可在 master 上跑,输出应与上表 K8s 核心 7 行完全一致):

bash 复制代码
kubeadm config images list --kubernetes-version v1.36.4
① 跳板机安装临时 containerd

用已下载的 nerdctl-full 即可,只在跳板机上用于拉镜像,装完不用管:

bash 复制代码
cd /data/offline/files
tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz
mkdir -p /etc/containerd && containerd config default > /etc/containerd/config.toml
systemctl daemon-reload && systemctl enable --now containerd
② 配置镜像下载加速(强烈建议,正式拉取前先做

registry.k8s.io / docker.io / quay.io 在国内直连经常超时或极慢。先在跳板机参考1ms.run配置加速,再拉取,可以显著提速。

关键点:加速不会改变镜像名和 tag。 hosts.toml 是 containerd 的「镜像仓库镜像」机制------你仍然执行 nerdctl pull registry.k8s.io/pause:3.10.2,名字不变,只是底层改从加速节点取数据。因此打出来的 tar 导入节点后,tag 仍是 registry.k8s.io/...,kubeadm 可直接命中(与 §2.1 的离线策略完全兼容)。

只配在跳板机。 目标节点是离线环境,不需要也不应该配(见 §2.1 的说明)。

第一步:打开 containerd 的 hosts 目录开关

bash 复制代码
sed -i 's#^\( *\)config_path = .*#\1config_path = "/etc/containerd/certs.d"#' /etc/containerd/config.toml

# 确认已生效(没有输出说明 sed 没匹配上,需手工在 [plugins.'"'"'io.containerd.cri.v1.images'"'"'.registry] 段下补一行 config_path = "/etc/containerd/certs.d")
grep -n 'config_path' /etc/containerd/config.toml

期望输出:

ini 复制代码
    config_path = "/etc/containerd/certs.d"

第二步:为三个仓库各写一份 hosts.toml

bash 复制代码
mkdir -p /etc/containerd/certs.d/{docker.io,registry.k8s.io,quay.io}

docker.io 加速:

bash 复制代码
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://registry-1.docker.io"

[host."https://a02b1ed54168c76dec3b92d1784c6036.d.1ms.run"]
  capabilities = ["pull", "resolve"]
EOF

registry.k8s.io 加速:

bash 复制代码
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml <<'EOF'
server = "https://registry.k8s.io"

[host."https://a63f22b4f59f5e2417a9d8ef45808aa6.d.1ms.run"]
  capabilities = ["pull", "resolve"]
EOF

quay.io 加速(可直连,但走加速更稳):

bash 复制代码
cat > /etc/containerd/certs.d/quay.io/hosts.toml <<'EOF'
server = "https://quay.io"

[host."https://d567a6b9cc528c635b6f6d680fdbdec6.d.1ms.run"]
  capabilities = ["pull", "resolve"]
EOF

第三步:重启 containerd 并验证加速生效

bash 复制代码
systemctl restart containerd
systemctl is-active containerd

# 用一个小镜像试拉(registry.k8s.io/pause 只有几百 KB)
time nerdctl pull registry.k8s.io/pause:3.10.2
nerdctl images | grep pause

期望:nerdctl images 里显示的依然是 registry.k8s.io/pause:3.10.2,说明加速生效且未改名。

若加速地址失效或很慢 :把对应 hosts.toml 里的 [host."..."] 段删掉,只保留 server = "https://xxx" 直连即可,改完 systemctl restart containerd。 若 nerdctl 没读到你配置的 hosts 目录,可在 pull 时显式指定:nerdctl --hosts-dir /etc/containerd/certs.d pull <镜像>

③ 逐个拉取(14 条,一条一条执行)
bash 复制代码
nerdctl pull registry.k8s.io/kube-apiserver:v1.36.4
nerdctl pull registry.k8s.io/kube-controller-manager:v1.36.4
nerdctl pull registry.k8s.io/kube-scheduler:v1.36.4
nerdctl pull registry.k8s.io/kube-proxy:v1.36.4
nerdctl pull registry.k8s.io/etcd:3.6.8-0
nerdctl pull registry.k8s.io/coredns/coredns:v1.14.2
nerdctl pull registry.k8s.io/pause:3.10.2
nerdctl pull quay.io/calico/cni:v3.32.2
nerdctl pull quay.io/calico/node:v3.32.2
nerdctl pull quay.io/calico/kube-controllers:v3.32.2
nerdctl pull docker.io/openebs/provisioner-localpv:4.6.0
nerdctl pull docker.io/openebs/linux-utils:4.6.0
nerdctl pull registry.k8s.io/metrics-server/metrics-server:v0.9.0
nerdctl pull docker.io/openebs/alpine-bash:4.6.0

拉取结果校验:

bash 复制代码
# 应看到 14 个镜像(含表头共 15 行)
nerdctl images

# 重点确认最容易写错的两个 tag 已存在
nerdctl images | grep -E 'etcd|coredns'

预期包含 registry.k8s.io/etcd 3.6.8-0registry.k8s.io/coredns/coredns v1.14.2。若某条 pull 报 not found,先核对该镜像的 tag 是否带 -0 后缀路径是否少了一段(见上表),不要直接改版本号跳到别的版本------会脱离兼容性清单。

参考输出(14 行,PLATFORM 均为 linux/amd64,REPOSITORY 均为原始名、无加速域名):

bash 复制代码
REPOSITORY                                       TAG        PLATFORM       SIZE
registry.k8s.io/kube-apiserver                   v1.36.4    linux/amd64    99.1MB
registry.k8s.io/kube-controller-manager          v1.36.4    linux/amd64    88.29MB
registry.k8s.io/kube-scheduler                   v1.36.4    linux/amd64    63.08MB
registry.k8s.io/kube-proxy                       v1.36.4    linux/amd64    84.22MB
registry.k8s.io/etcd                             3.6.8-0    linux/amd64    65.88MB
registry.k8s.io/coredns/coredns                  v1.14.2    linux/amd64    79.53MB
registry.k8s.io/pause                            3.10.2     linux/amd64    741.4kB
quay.io/calico/cni                               v3.32.2    linux/amd64    160.6MB
quay.io/calico/node                              v3.32.2    linux/amd64    379.5MB
quay.io/calico/kube-controllers                  v3.32.2    linux/amd64    151.8MB
openebs/provisioner-localpv                      4.6.0      linux/amd64    122MB
openebs/linux-utils                              4.6.0      linux/amd64    70.27MB
registry.k8s.io/metrics-server/metrics-server    v0.9.0     linux/amd64    87.63MB
openebs/alpine-bash                              4.6.0      linux/amd64    11.51MB

openebs/... 三个镜像没显示 docker.io/ 前缀是正常的docker.io 是 nerdctl/containerd 的默认仓库,显示时被省略,镜像全名仍是 docker.io/openebs/...。后续 nerdctl save 和节点上的 ctr -n k8s.io images import 都会用全名,与 §1.6 ④ 的打包清单保持一致。

④ 打包

打成一个 tar,便于一次传输。打包前再确认一次名字没被改(必须全部是原始 tag,不能带加速域名):

bash 复制代码
nerdctl images

若发现镜像名变成了 xxx.d.1ms.run/...,说明配置有误,删掉对应 hosts.toml 重来------带加速域名的 tag 导入节点后 kubeadm 认不出来。

bash 复制代码
cd /data/offline/images
nerdctl save -o k8s-offline-images.tar \
  registry.k8s.io/kube-apiserver:v1.36.4 \
  registry.k8s.io/kube-controller-manager:v1.36.4 \
  registry.k8s.io/kube-scheduler:v1.36.4 \
  registry.k8s.io/kube-proxy:v1.36.4 \
  registry.k8s.io/etcd:3.6.8-0 \
  registry.k8s.io/coredns/coredns:v1.14.2 \
  registry.k8s.io/pause:3.10.2 \
  quay.io/calico/cni:v3.32.2 \
  quay.io/calico/node:v3.32.2 \
  quay.io/calico/kube-controllers:v3.32.2 \
  docker.io/openebs/provisioner-localpv:4.6.0 \
  docker.io/openebs/linux-utils:4.6.0 \
  registry.k8s.io/metrics-server/metrics-server:v0.9.0 \
  docker.io/openebs/alpine-bash:4.6.0

ls -lh k8s-offline-images.tar

打包结果校验(大小和完整性都要看):

bash 复制代码
# 1) 大小:实测约 471 MB(不同镜像版本会有出入,落在 400--600 MB 区间即正常)
ls -lh k8s-offline-images.tar

# 2) 内容:解出 manifest 确认 14 个 RepoTags 全在,且都是原始名
mkdir -p /tmp/verify-tar
tar xf k8s-offline-images.tar -C /tmp/verify-tar
python3 - <<'PY'
import json,os
p='/tmp/verify-tar/manifest.json'
if os.path.exists(p):
    tags=[t for m in json.load(open(p)) for t in m.get('RepoTags',[])]
else:
    idx=json.load(open('/tmp/verify-tar/index.json'))
    tags=[m['annotations']['io.containerd.image.name'] for m in idx['manifests']]
print(len(tags)); [print(t) for t in sorted(tags)]
PY

预期输出 14 行 ,且包含 registry.k8s.io/etcd:3.6.8-0docker.io/openebs/provisioner-localpv:4.6.0注意这里会带 docker.io/ 全名)。

关于大小的说明nerdctl imagesSIZE 列(解包后大小)相加约 1.43 GBBLOB SIZE 列(压缩层)相加约 0.51 GB 。tar 实测 471 MB ,比 BLOB SIZE 之和还小一点,是因为 nerdctl save 对多个镜像之间共享的层做了去重(Calico 三个镜像、K8s 四个组件镜像、OpenEBS 三个镜像各自共用基础层)。所以:

  • 看到 400--600 MB → 正常;
  • 看到 明显小于 300 MB → 很可能漏了镜像(例如 etcd 或 Calico 没打包进去),回去核对 nerdctl images
  • 看到 1.4 GB 左右 → 说明 save 时没有去重(一般不会出现),不影响使用,只是体积大。
    标签策略 :不做重打标,保持 registry.k8s.io/... 等原始 tag。这样 kubeadm 的 imageRepository: registry.k8s.io 无需修改即可直接命中本地镜像。加速只是「取数通道」,不会进入 tag。

1.7 打包与人工摆渡到离线节点(下载机 → master1 + work1

部署节点无互联网、且与下载机不互通 ,所以不能 scp。流程是:下载机打成一个总包 → 移动介质人工搬运 → 每台节点解压使用

① 在下载机上打成单个总包
bash 复制代码
cd /data
tar cf /data/k8s-offline-bundle.tar offline
ls -lh /data/k8s-offline-bundle.tar
sha256sum /data/k8s-offline-bundle.tar | tee /data/k8s-offline-bundle.tar.sha256

预期体积约 0.9--1.1 GB(镜像 tar 471 MB + nerdctl-full 287 MB + calicoctl 96 MB + helm 18 MB + deb 与各清单)。

不加 -z 压缩的原因 :包内主体(容器镜像层、deb、tar.gz)本身已是压缩格式,再 gzip 只能省下 1--3%,却要多花几分钟 CPU。若你的移动介质空间确实紧张,把 tar cf 换成 tar czf 即可,文件名相应改成 .tar.gz

只搬 /data/k8s-offline-bundle.tar 这一个文件。 不要把下载机的 /etc/containerd/(含 §1.6 ② 配的加速 hosts.toml)一起带上------节点是离线环境,没有加速可用,那份配置反而会让 containerd 去找不存在的 mirror。节点的 containerd 配置按 §2.1 单独生成。

(可选)若介质对单文件大小有限制,先分片再搬:

bash 复制代码
# 下载机分片,每片 900 MB
split -b 900M -d --additional-suffix=.part /data/k8s-offline-bundle.tar /data/bundle.part

# 节点上合并(见 ③,在解压前执行)
cat /mnt/usb/bundle.part* > /data/k8s-offline-bundle.tar
② 人工搬运到每台节点

任选一种,两台节点各搬一次:

方式 操作
移动硬盘 / U 盘 下载机拷入介质 → 拔下 → 插到节点 → 节点挂载
内网摆渡机 / 文件服务器 介质或摆渡机上传到内网 HTTP/FTP,节点用 curl -O内网
运维终端中转 sz/sftp 拉到运维 PC,再从运维 PC 传到节点控制台

无论哪种方式,都只涉及 k8s-offline-bundle.tar(+ 同名 .sha256、可选分片)这一个文件,不涉及任何外网连接。

③ 在每台节点上接收、校验、解压

前置:/data 必须已挂载在数据盘上(见 §1.2,≥100 G)。不要解压到系统盘。

bash 复制代码
# 1) 挂载介质(设备名用 lsblk 确认,不要照抄 /dev/sdb1)
lsblk
mkdir -p /mnt/usb
mount /dev/sdb1 /mnt/usb

# 2) 先拷到节点本地,再解压(不要直接在 U 盘上解压,防止中途断开)
cp /mnt/usb/k8s-offline-bundle.tar      /data/
cp /mnt/usb/k8s-offline-bundle.tar.sha256 /data/ 2>/dev/null || true
cd /data

# 3) 校验完整性(有 .sha256 文件时执行;输出 OK 才继续)
sha256sum -c k8s-offline-bundle.tar.sha256

# 4) 解压
tar xf /data/k8s-offline-bundle.tar -C /data

# 5) 卸载介质
sync && umount /mnt/usb
④ 解压结果校验(两节点都要看到相同结果)
bash 复制代码
find /data/offline -maxdepth 2 | sort
du -sh /data/offline
ls -lh /data/offline/files /data/offline/deb /data/offline/images

节点上的目录结构应为:

bash 复制代码
/data/offline/
├── files/    # nerdctl-full、helm、calico.yaml、openebs-4.6.0.tgz、metrics-server-components.yaml
├── deb/      # kubelet / kubeadm / kubectl 及全部依赖
└── images/   # k8s-offline-images.tar

对照 §1.4 的文件清单逐项核对 files/(应为 6--7 个文件)、deb/ 应为 4--5 个 deb、images/ 下应有 471 MB 左右的 k8s-offline-images.tar

校验无误后可以删掉 /data/k8s-offline-bundle.tar 释放约 1 GB 空间;建议先保留到集群初始化成功之后再删。


2. 配置文件

2.1 安装并配置 containerd(两节点相同)

本节必须整节做完再往下走。 containerd config default 需要 containerd 二进制,而二进制来自离线包里的 nerdctl-full------如果跳过 ① 直接执行 ②,就会报 Command 'containerd' not found。所以「安装」和「配置」放在同一节,不要留到 §3 再装

① 安装 containerd / runc / CNI plugins / nerdctl
bash 复制代码
cd /data/offline/files
tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz

# nerdctl-full 把 CNI 插件放在 /usr/local/libexec/cni,containerd 默认从 /opt/cni/bin 找,必须拷贝过去
mkdir -p /opt/cni/bin
find /usr/local/libexec/cni -maxdepth 1 -type f -perm -u+x -exec cp -v {} /opt/cni/bin/ \;
ls /opt/cni/bin

# systemd 单元随包提供
systemctl daemon-reload

确认二进制就位(三条都要有输出,缺一条说明解压失败):

bash 复制代码
which containerd nerdctl runc
containerd --version
runc --version

预期:containerd github.com/containerd/containerd v2.3.3runc version 1.5.1,且 ls /opt/cni/bin 能看到 bridgehost-localloopback 等插件。

containerd --version 仍报 command not found:确认 /usr/local/bin 在 PATH 里(echo $PATH),Ubuntu 24.04 默认包含;如不在,执行 export PATH=/usr/local/bin:$PATH 或重新登录一次。

② 生成并修改配置
bash 复制代码
mkdir -p /etc/containerd /data/containerd
containerd config default > /etc/containerd/config.toml

# 1) 数据目录改到 /data
sed -i 's#^root = .*#root = "/data/containerd"#' /etc/containerd/config.toml

# 2) pause 镜像对齐 K8s 1.36.4 内置版本
sed -i 's#^  sandbox_image = .*#  sandbox_image = "registry.k8s.io/pause:3.10.2"#' /etc/containerd/config.toml

# 3) cgroup driver 对齐 systemd
sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml
③ 启动 containerd
bash 复制代码
systemctl enable --now containerd
systemctl status containerd --no-pager

期望 Active: active (running)。若失败,用 journalctl -u containerd -n 50 --no-pager 看日志,最常见是 /etc/containerd/config.toml 语法错误或 /data/containerd 所在磁盘不可写。

④ 校验

校验三个配置点(必须逐项确认,sed 没匹配上不会报错):

bash 复制代码
grep -E '^root = |sandbox_image = |SystemdCgroup = ' /etc/containerd/config.toml

# 运行时连通性
ctr version

期望输出:

ini 复制代码
root = "/data/containerd"
  sandbox_image = "registry.k8s.io/pause:3.10.2"
            SystemdCgroup = true

若第 3 项没有输出,说明 containerd 2.3 的默认配置里没有该键,需手工在 [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options] 段下补一行 SystemdCgroup = true

离线环境不要配置镜像加速 :所有镜像已从本地 tar 导入,[plugins.'io.containerd.cri.v1.images'.registry] 下的 config_path 保持为空,避免 containerd 去找不存在的 mirror。 (加速只在跳板机上配,用于拉取阶段,见 §1.6 ②;节点侧一律不配。)

2.2 kubeadm 初始化配置(master1 执行

执行位置:master1。 本文件只被 kubeadm init 消费,而 init 只在 master1 跑,所以只在 master1 准备这一份即可,不需要同步到 work1

同目录已提供 kubeadm.yaml(内容如下,直接放到 master1 的 /data/offline/files/):

yaml 复制代码
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 172.16.0.19
  bindPort: 6443
nodeRegistration:
  name: master1
  criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.4
clusterName: k8s-offline
imageRepository: registry.k8s.io
certificatesDir: /etc/kubernetes/pki
apiServer:
  certSANs:
    - 172.16.0.19
    - 172.16.0.20
    - master1
    - work1
    - 127.0.0.1
    - kubernetes
    - kubernetes.default
  timeoutForControlPlane: 4m0s
controllerManager: {}
scheduler: {}
networking:
  serviceSubnet: 10.96.0.0/12
  podSubnet: 192.168.0.0/16
  dnsDomain: cluster.local
etcd:
  local:
    dataDir: /data/etcd
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: iptables

关键字段说明:

字段 原因
kubernetesVersion v1.36.4 锁死版本,避免 kubeadm 去解析 latest
imageRepository registry.k8s.io 与导入的镜像 tag 完全一致,离线可命中
podSubnet 192.168.0.0/16 与 calico.yaml 的默认值一致,两边都不用改
etcd.local.dataDir /data/etcd etcd 数据落专用数据盘
cgroupDriver systemd 与 containerd SystemdCgroup = true 对齐,否则 kubelet 起不来
mode iptables kube-proxy 使用 iptables,无需额外加载 ip_vs 模块

2.3 Calico 清单调整(master1 执行

执行位置:master1。 kubectl apply -f calico.yaml 在 §3.6 于 master1 执行,清单也只在 master1 上改这一份,不需要同步到 work1
版本事实纠正 :Calico v3.32.2 的 calico.yaml 里实际默认是 CALICO_IPV4POOL_VXLAN = "Never" + CALICO_IPV4POOL_IPIP = "Always"默认封装是 IPIP 而不是 VXLANCALICO_IPV4POOL_CIDR 默认 192.168.0.0/16(已注释)。因此:

  • 选项 A(默认,推荐):不改任何封装参数,直接用 IPIP。两节点同二层或三层可达即可,改动最少。
  • 选项 B:若你所在网络不允许 IPIP(协议号 4 被拦),改成 VXLAN,见下方。

网卡自动发现 (多网卡环境必做;只有一块物理网卡时可跳过):

版本事实纠正(重要) :Calico v3.32.2 的 calico.yaml既没有 IP_AUTODETECTION_METHOD,也没有 first-found 这两个字符串 (已对官方清单全文核对)。所以「把注释掉的这两行打开」这种写法是完全无效的 sed,执行后毫无变化 ------正确做法是新增一段 env。

清单里与网卡相关的只有这一段(全文唯一):

yaml 复制代码
            # Auto-detect the BGP IP address.
            - name: IP
              value: "autodetect"

IP: autodetect 只表示「自动探测」,要指定用哪块网卡,必须另外加 IP_AUTODETECTION_METHOD

先确认网卡名:

bash 复制代码
ip -o -4 addr show scope global | awk '{print $2, $4}'
# 示例输出:eth0 172.16.0.21/24

IP: autodetect 之后插入 IP_AUTODETECTION_METHOD(把 eth0 换成你上面看到的网卡名):

bash 复制代码
cd /data/offline/files

# 唯一匹配项,可安全替换(value: "autodetect" 全文只出现一次)
sed -i 's|              value: "autodetect"|              value: "autodetect"\n            - name: IP_AUTODETECTION_METHOD\n              value: "interface=eth0"|' calico.yaml

grep -n -A3 'IP_AUTODETECTION_METHOD' calico.yaml

期望输出:

makefile 复制代码
7603:            - name: IP_AUTODETECTION_METHOD
7604-              value: "interface=eth0"

grep 没有输出,说明 sed 没匹配上(缩进或引号被改动过),改用手工方式:打开 calico.yaml,找到 - name: IP / value: "autodetect" 这两行,在它们下面手工补两行(缩进与上下行对齐):

yaml 复制代码
            - name: IP_AUTODETECTION_METHOD
              value: "interface=eth0"

另一种写法(推荐用于多网卡且网关固定时,比写死网卡名更稳):value: "can-reach=172.16.0.21"(填 master1 的实际 IP)。

只有一块网卡(如本例的 eth0)时其实可以不改autodetect 会自动选中唯一的可上网网卡。改了也没坏处,且能防止后续加网卡后选错。

(选项 B,按需)切换为 VXLAN :不要用 sed 全局替换(value: "Always" 在文件中出现多次), 手工打开 calico.yaml,在 calico-node(DaemonSet)的 env 段把:

yaml 复制代码
            - name: CALICO_IPV4POOL_IPIP
              value: "Always"
            - name: CALICO_IPV4POOL_VXLAN
              value: "Never"

改为:

yaml 复制代码
            - name: CALICO_IPV4POOL_IPIP
              value: "Never"
            - name: CALICO_IPV4POOL_VXLAN
              value: "Always"

同时放通 UDP 4789。

2.4 OpenEBS values(master1 执行

执行位置:master1。 helm install 在 §3.9 于 master1 执行,values 文件只放 master1 即可,不需要同步到 work1 。 注意:OpenEBS 的 hostpath 数据目录 /data/openebs/local 必须在两台节点上都存在 ------这一步已在 §1.2 (5) 做过(mkdir -p /data/containerd /data/kubelet /data/etcd /data/openebs/local /data/offline),这里无需重复;若你跳过了 §1.2,部署 OpenEBS 前补上即可。

同目录已提供 openebs-values.yaml

yaml 复制代码
# 只启用 Local PV Hostpath,其余引擎全部关闭(离线包最小化)
engines:
  local:
    hostpath:
      enabled: true
    lvm:
      enabled: false
    zfs:
      enabled: false
    rawfile:
      enabled: false
  replicated:
    mayastor:
      enabled: false

# 关闭可观测组件(会额外拉 loki / alloy / nats / etcd 等一堆镜像)
loki:
  enabled: false
alloy:
  enabled: false

# 全新安装不需要 pre-upgrade hook(它会拉 openebs/kubectl:1.25.15)
preUpgradeHook:
  enabled: false

global:
  imagePullPolicy: IfNotPresent

localpv-provisioner:
  analytics:
    enabled: false
  localpv:
    basePath: /data/openebs/local
  hostpathClass:
    enabled: true
    name: openebs-hostpath
    isDefaultClass: true        # ★ 4.6.0 起默认是 false,必须显式打开
    reclaimPolicy: Delete

关键字段说明:

字段 说明
engines.local.hostpath.enabled true 唯一启用的存储引擎
engines.local.lvm/zfs.enabled false 关闭,省掉 lvm-driver / zfs-driver 及 node DaemonSet
engines.replicated.mayastor.enabled false 关闭复制存储,省掉 io-engine / etcd / nats 等十几个镜像
loki.enabled / alloy.enabled false 关闭可观测栈
preUpgradeHook.enabled false 全新安装不需要,避免额外镜像
localpv-provisioner.localpv.basePath /data/openebs/local LocalPV 卷根目录,落在专用数据盘
hostpathClass.isDefaultClass true 必须显式设置,否则不会成为默认 SC
analytics.enabled false 离线环境不通外网

生成的 StorageClass 固定为:provisioner: openebs.io/localvolumeBindingMode: WaitForFirstConsumerreclaimPolicy: Delete

2.5 metrics-server 清单调整(master1 执行

执行位置:master1。 kubectl apply 在 §3.10 于 master1 执行,清单只改 master1 这一份,不需要同步到 work1

bash 复制代码
cd /data/offline/files
# kubeadm 签发的 kubelet 证书是自签的,必须加 --kubelet-insecure-tls
sed -i 's#        - --metric-resolution=15s#        - --metric-resolution=15s\n        - --kubelet-insecure-tls#' metrics-server-components.yaml
grep -n -A5 'metric-resolution' metrics-server-components.yaml

3. 部署

步骤 3.1 -- 3.3 在 master1 与 work1 都要执行;3.4 起只在 master1 执行(3.7 在 work1)。

3.1 容器运行时复核(两节点)

containerd 的安装与配置已在 §2.1 完成 ,本节只做复核。若你跳过了 §2.1,请先回到 §2.1 执行------否则后续步骤会全部失败(containerd 命令不存在、kubelet 连不上 CRI)。

bash 复制代码
systemctl is-active containerd          # 期望 active
ctr version                             # 期望同时打印 Client 与 Server 版本
ls /opt/cni/bin                         # 期望有 bridge / host-local / loopback 等插件

# crictl 指向 containerd(需已装 cri-tools,见 3.2;未装会自动跳过)
command -v crictl >/dev/null \
  && crictl config runtime-endpoint unix:///run/containerd/containerd.sock \
  && crictl version

crictl 不会 随 kubelet/kubeadm 装上(1.36.1 起 kubeadm 不再依赖 cri-tools)。只有按 1.5 末尾「按需补装 cri-tools」下载了 cri-tools_*.deb,3.2 之后才会有 crictl。 若报 command not found:先执行 3.2,仍没有就说明没下载 cri-tools,把 crictl 命令换成 ctr -n k8s.io images ls / ctr -n k8s.io images import 即可,不影响部署。

顺序要点kubernetes-cni_1.9.1-1.1 也会往 /opt/cni/bin 写插件,但它打包的正是 CNI plugins v1.9.1 ,与 nerdctl-full 自带的版本一致,所以两者不会互相退化。若你先装了 deb(3.2)再补做 §2.1,需重跑 §2.1 ① 的 find ... -exec cp 那一行,确保 nerdctl-full 的 CNI 生效。

3.2 安装 kubeadm / kubelet / kubectl(两节点)

bash 复制代码
cd /data/offline/deb
dpkg -i *.deb

# 若报依赖缺失,说明跳板机漏抓了包,回到跳板机补下载对应 deb 后重跑本步
apt-mark hold kubelet kubeadm kubectl
kubeadm version && kubelet --version && kubectl version --client

配置命令补全(可选):

bash 复制代码
mkdir -p /etc/bash_completion.d/
kubectl completion bash > /etc/bash_completion.d/kubectl
kubeadm completion bash > /etc/bash_completion.d/kubeadm
nerdctl completion bash > /etc/bash_completion.d/nerdctl
source /etc/bash_completion.d/kubectl

3.3 导入镜像(两节点)

bash 复制代码
cd /data/offline/images
ctr -n k8s.io images import k8s-offline-images.tar

# 验证(必须能看到 14 个镜像)
crictl images                 # 装了 cri-tools 时用这个
ctr -n k8s.io images ls       # 没装 crictl 时用这个

不要加 --all-platforms(旧版文档此处写错,会导致整包导入失败) 。本离线包的 14 个镜像都是 linux/amd64 单平台,nerdctl save 产出的 tar 只含 amd64 的 manifest 与层。一旦带上 --all-platformsctr 会按「多架构镜像索引」去枚举索引里引用的所有平台并解析其他平台的 manifest,而这些平台内容并不在 tar 中,于是报 content digest sha256:... not found 后整体失败(一个镜像都导不进去)。单平台场景用裸 ctr -n k8s.io images import 即可。

备选命令(最稳妥,是 nerdctl save 的原生逆操作):nerdctl -n k8s.io load -i k8s-offline-images.tar

注意ctr 必须带 -n k8s.io,否则镜像进了 default 命名空间,kubelet/CRI 看不到,kubeadm init 会尝试联网拉取而失败。

3.4 初始化控制面(master1)

bash 复制代码
cp /data/offline/files/kubeadm.yaml /root/kubeadm.yaml
kubeadm init --config /root/kubeadm.yaml --upload-certs

成功标志(输出中出现):

sql 复制代码
Your Kubernetes control-plane has initialized successfully!
...
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 172.16.0.19:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

把这条 join 命令完整复制保存 ,后面 3.7 要用(token 有效期 24 小时,过期用 kubeadm token create --print-join-command 重新生成)。

IP 一致性提醒(必看) :join 命令里的 IP(如 172.16.0.21)是你真实 master1 的 IP ------文档模板用 172.16.0.19 作占位,你部署时已改成实际 IP,这是正常的。务必确认 kubeadm.yamlcertSANs 也包含了这个真实 IP ,否则 work1 在 §3.7 执行 join 时会报 x509: certificate is valid for ... not 172.16.0.21 而失败。快速核对(master1 执行):

bash 复制代码
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 "Subject Alternative Name"

输出里应能看到 IP Address:172.16.0.21(即你的真实 master IP)。看不到就说明 certSANs 还是占位 IP,需改 kubeadm.yaml 后重新 init 或单独重签 apiserver 证书。

§3.7 的 join 命令请使用本步实际打印的那条(真实 IP),不要抄本文档代码块里的 172.16.0.19 占位。

3.5 配置 kubectl 授权(master1)

root 用户:

bash 复制代码
echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /root/.bashrc
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes

3.6 部署 Calico(master1)

bash 复制代码
kubectl apply -f /data/offline/files/calico.yaml
kubectl -n kube-system get pod -l k8s-app=calico-node -w

等待全部 Running(约 1--2 分钟,镜像已本地导入不会卡在拉取)。

3.7 加入工作节点(work1)

bash 复制代码
kubeadm join 172.16.0.19:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash>

在 master1 上确认:

bash 复制代码
kubectl get nodes -o wide
# 期望:STATUS 全为 Ready(Calico 起来后约 30 秒)

3.8 允许 Master 承载业务(master1)

bash 复制代码
kubectl taint nodes master1 node-role.kubernetes.io/control-plane:NoSchedule-

# 验证:Taints 应显示 <none>
kubectl describe node master1 | grep -i taints
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

该操作只去掉 NoSchedule 污点,保留 node-role.kubernetes.io/control-plane 标签,角色仍然清晰。

备选做法(二选一,不要都做):在 kubeadm.yamlnodeRegistration 下加 taints: [],初始化时就不打污点。已执行上面的命令就不要再加,否则 kubectl taint ...- 会报 not found。

3.9 部署 OpenEBS(master1)

bash 复制代码
cd /data/offline/files
tar xzf helm-v3.21.4-linux-amd64.tar.gz
install -m 0755 linux-amd64/helm /usr/local/bin/helm
helm version

kubectl create namespace openebs
helm install openebs ./openebs-4.6.0.tgz \
  -n openebs \
  -f openebs-values.yaml

检查:

bash 复制代码
kubectl -n openebs get pod
# 只应出现 openebs-localpv-provisioner-*(含可能的 -node DaemonSet)与 openebs-crds 相关 Job
kubectl get storageclass

期望:

sql 复制代码
NAME                        PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWEXPANSION   AGE
openebs-hostpath (default)  openebs.io/local  Delete          WaitForFirstConsumer   false            30s

(default) 没出现(说明 isDefaultClass 没生效),手工补:

bash 复制代码
kubectl patch storageclass openebs-hostpath \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

3.10 部署 metrics-server(master1)

bash 复制代码
kubectl apply -f /data/offline/files/metrics-server-components.yaml
kubectl -n kube-system get pod -l k8s-app=metrics-server

4. 访问验证

执行位置:全部在 master1 上用 kubectl 执行 (只有 master1 配了 /root/.kube/config)。 例外:§4.3 与 §4.4 需要另外登录到 Pod 实际落点的那台节点去看本地目录,文档里会明确标出。

4.1 集群状态(master1

bash 复制代码
kubectl get nodes -o wide
kubectl get pod -A -o wide
kubectl cluster-info

期望:2 个节点 Ready;所有 Pod Running

4.2 网络与 DNS(master1

bash 复制代码
# 集群内 DNS 解析
kubectl run net-test -it --rm --image=docker.io/openebs/alpine-bash:4.6.0 -- \
  sh -c 'getent hosts kubernetes.default && echo DNS_OK'

期望输出 DNS_OK

跨节点 Pod 连通性(分别在两个节点上起 Pod,互 ping Pod IP):

bash 复制代码
kubectl run net-a --image=docker.io/openebs/alpine-bash:4.6.0 -- sleep 3600
kubectl run net-b --image=docker.io/openebs/alpine-bash:4.6.0 -- sleep 3600
kubectl get pod net-a net-b -o wide          # 记下两个 Pod IP 与所在节点
kubectl exec net-a -- sh -c 'ping -c 3 <net-b 的 Pod IP>'
kubectl delete pod net-a net-b

4.3 存储(重点)(master1 执行 kubectl;落盘检查在 Pod 所在节点

前半段 kubectl apply / get pvcmaster1 ;最后一小节「验证落盘路径」需要先 kubectl get pod -o wide 看 Pod 落在哪台,再登录到那台节点 ls /data/openebs/local/

使用同目录的 test-pvc.yaml(拷到 master1 上):

bash 复制代码
kubectl apply -f test-pvc.yaml
kubectl get pvc -n storage-test -w

期望:Pod 调度成功后,PVC 由 Pending 变为 BoundWaitForFirstConsumer:只有 Pod 调度了才会创建 PV,这是正常现象,不是卡住)。

bash 复制代码
kubectl get pvc -n storage-test
kubectl get pv
kubectl exec -n storage-test deploy/test-localpv -- sh -c 'cat /data/hello.txt'

期望输出 hello openebs

验证默认 StorageClass 生效 (不写 storageClassName 也能绑定):

bash 复制代码
kubectl -n storage-test apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-default-sc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
EOF
kubectl -n storage-test get pvc pvc-default-sc
# 期望:STORAGECLASS 列 = openebs-hostpath(这已证明它是默认 SC,未显式指定也能认到)

注意openebs-hostpathvolumeBindingModeWaitForFirstConsumer------没有 Pod 真正使用这个 PVC 时,它会一直 Pending ,这是正常行为,不是失败。上面的 STORAGECLASS 列能正确认到 openebs-hostpath 就说明默认 SC 已生效。 若想进一步确认它能真正 Bound,再起一个消费 Pod 即可(会自动触发 Provisioner 在 Pod 所在节点建卷):

bash 复制代码
kubectl -n storage-test apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: use-pvc-default-sc
spec:
  containers:
  - name: writer
    image: docker.io/openebs/alpine-bash:4.6.0
    command: ["sh","-c","echo hello > /data/sc.txt; sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: pvc-default-sc
EOF
kubectl -n storage-test get pvc pvc-default-sc   # 现在应变为 Bound

验证落盘路径(在 Pod 实际所在节点上执行):

bash 复制代码
kubectl -n storage-test get pod -o wide     # 看 Pod 落在哪个节点
# 到对应节点执行
ls -l /data/openebs/local/                  # 应出现 pvc-xxxx 目录
ls -l /data/openebs/local/pvc-*/hello.txt

4.4 调度(Master 可承载业务)(master1

bash 复制代码
kubectl run on-master-test --image=docker.io/openebs/alpine-bash:4.6.0 -- sleep 300
kubectl get pod on-master-test -o wide      # NODE 列可能落在 master1 或 work1
kubectl delete pod on-master-test

# 强制调度到 master1 验证
kubectl run on-master-only --image=docker.io/openebs/alpine-bash:4.6.0 \
  --overrides='{"spec":{"nodeName":"master1"}}' -- sleep 300
kubectl get pod on-master-only -o wide      # NODE 必须是 master1
kubectl delete pod on-master-only

4.5 监控(master1

bash 复制代码
kubectl top nodes
kubectl top pod -A

kubectl topMetrics API not available,等 30 秒重试;仍失败见 §7.3。


5. 已知限制(务必知悉)

限制 说明 影响
无控制面高可用 单 master、单 etcd master1 宕机 → 集群 API 不可写,已运行的 Pod 继续跑但无法调度/重启
LocalPV 不跨节点 卷绑定在特定节点目录 Pod 漂移到另一节点后读不到原数据(新节点会是空目录)
LocalPV 无副本 底层就是本地目录 节点磁盘损坏 = 数据丢失。仅适用于可重建数据,或由应用自身做复制(MySQL 主从、ES 副本等)
无容量硬限制 hostpath SC 默认不启用 quota PVC 声明的 10Gi 不会被强制限制,实际以磁盘剩余空间为准(需要可启用 XFS/ext4 quota)
证书 1 年 admin.conf 等客户端证书 需按 §7.1 年度续期
无 Ingress 未部署 Ingress 控制器 外部访问只能用 NodePort / hostNetwork

6. 关键决策记录

决策点 结论 理由
节点规模 2 台(master1 可调度 + work1) 用户指定;去污点实现
K8s 版本 v1.36.4 Calico v3.32 官方测试清单含 1.36,不含 1.37
CNI Calico v3.32.2 单文件清单 离线只需 3 个镜像,最少改动
封装 保持官方默认 IPIP=Always 3.32 默认不是 VXLAN;同二层/三层均可,改动最少
运行时 nerdctl-full 2.3.5(containerd 2.3.3) 一个 tar 包解决 containerd+runc+CNI,无需逐个下 deb
数据目录 /data 下集中,kubelet 用软链接 规避 kubeadm 硬编码路径问题
镜像交付 tar 导入,保留原始 tag 零额外组件,kubeadm 直接命中
deb 交付 apt-get --download-only 收集 杜绝漏依赖
存储 OpenEBS 4.6.0 仅 Hostpath 镜像最少,2 节点场景够用
默认 SC openebs-hostpath + isDefaultClass: true 4.6.0 默认 false,必须显式打开
helm v3.21.4 + 本地 chart tgz 3.x 线稳定,chart 兼容性优先

7. 运维附录

7.1 证书有效期与续期(master1 执行

kubeadm 生成的证书分两类:

证书 有效期
CA 根证书(/etc/kubernetes/pki/ca.crt 等) 10 年
apiserver / admin.conf / kubelet 客户端等 1 年

查看到期时间:

bash 复制代码
kubeadm certs check-expiration

续期(在 master1 执行,每年做一次,建议加日历提醒):

bash 复制代码
# 1) 续期全部证书
kubeadm certs renew all

# 2) 重启控制面(静态 Pod 会自动重建)
kubectl -n kube-system delete pod kube-apiserver-master1 kube-controller-manager-master1 kube-scheduler-master1

# 3) 更新 admin.conf
cp /etc/kubernetes/admin.conf /root/.kube/config 2>/dev/null || true
export KUBECONFIG=/etc/kubernetes/admin.conf

# 4) 确认
kubeadm certs check-expiration
kubectl get nodes

kubelet 的客户端证书默认自动轮转,无需手工处理。

7.2 集群重置与卸载(master1 + work1,按顺序

执行顺序:① master1 上把 work1 摘出集群 → ② 登录 work1 清理 → ③ 回到 master1 清理。下面每小节都已标注执行位置。

① work1 退出集群 (在 master1 执行):

bash 复制代码
kubectl drain work1 --delete-emptydir-data --force --ignore-daemonsets
kubectl delete node work1

② work1 本地清理 (在 work1 执行):

bash 复制代码
kubeadm reset -f
systemctl stop kubelet containerd
rm -rf /etc/kubernetes /var/lib/kubelet /etc/cni/net.d /opt/cni/bin
rm -rf /data/containerd /data/kubelet /data/etcd /data/openebs/local/*
iptables -F && iptables -t nat -F && iptables -t mangle -F
ip link delete cali0 2>/dev/null; ip link delete tunl0 2>/dev/null; ip link delete vxlan.calico 2>/dev/null

③ master1 完整卸载 (在 master1 执行):

bash 复制代码
kubeadm reset -f
systemctl stop kubelet containerd
rm -rf /etc/kubernetes /etc/cni/net.d /opt/cni/bin
rm -rf /data/containerd /data/kubelet /data/etcd /data/openebs/local/*
iptables -F && iptables -t nat -F && iptables -t mangle -F
ip link delete cali0 2>/dev/null; ip link delete tunl0 2>/dev/null
# 若想彻底移除软链接
rm -f /var/lib/kubelet
# 可选:卸载软件包
apt-mark unhold kubelet kubeadm kubectl
apt-get remove --purge -y kubelet kubeadm kubectl kubernetes-cni cri-tools conntrack

重跑部署前,从 §1.2 重新开始(尤其要重建 /var/lib/kubelet 软链接)。

7.3 故障排查速查表(查看类在 master1;修复类在对应节点

现象 可能原因 处理
kubeadm init 卡在 pulling images 镜像没进 k8s.io 命名空间 / 导入时误加 --all-platforms 导致整包失败 ctr -n k8s.io images ls 确认;用 ctr -n k8s.io images import k8s-offline-images.tar 重新导入(不加 --all-platforms
crictl images 为空但 ctr images ls 导入到了 default 命名空间 -n k8s.io 重新导入
kubelet 起不来,日志报 cgroup driver 不一致 containerd SystemdCgroup 与 kubelet cgroupDriver 不一致 两边都设成 systemd,重启 containerd 与 kubelet
节点 NotReady,calico-node CrashLoopBackOff CNI 二进制缺失或网卡选错 检查 /opt/cni/bin;设置 IP_AUTODETECTION_METHOD
Calico 起来了但跨节点 Pod 不通 IPIP 协议号 4 被防火墙拦截 放通协议 4,或改 VXLAN(放通 UDP 4789)
Pod 一直 Pending,事件显示 no taint 之类 master 污点未去掉 kubectl taint nodes master1 node-role.kubernetes.io/control-plane:NoSchedule-
PVC 一直 Pending WaitForFirstConsumer正常行为,Pod 还没调度 先创建使用该 PVC 的 Pod,PVC 才会 Bound
PVC 报 no volume plugin matched / provisioner 不存在 OpenEBS 未安装或 SC 名写错 kubectl get sc 确认 openebs-hostpath 存在
PVC 报 waiting for a volume to be created 且 provisioner 日志报 mkdir 失败 /data/openebs/local 在目标节点不存在或无权限 两节点都 mkdir -p /data/openebs/local
Pod 调度到另一节点后数据没了 LocalPV 特性(数据在本地目录) 给工作负载加 nodeAffinity 固定节点,或改用复制存储
helm install 报找不到 chart 依赖 直接用了目录而非 tgz,或子 chart 缺失 ./openebs-4.6.0.tgz 安装(子 chart 已打包在内),不要--dependency-update(会联网)
OpenEBS Pod 起不来,报镜像拉取失败 镜像未导入或 tag 不匹配 `crictl images
kubectl top 报 Metrics API 不可用 metrics-server 未就绪 kubectl -n kube-system logs -l k8s-app=metrics-server,确认已加 --kubelet-insecure-tls
dpkg -i 报依赖不满足 跳板机漏抓 deb 回跳板机补 apt-get install --download-only 对应包后重传
install: cannot stat '/data/offline/files/kubernetes-apt-key.gpg' 1.4 下载时保留了原始名 Release.key,未做统一命名 [ -f Release.key ] && mv -f Release.key kubernetes-apt-key.gpg
apt-get updateNO_PUBKEY / 签名无法验证 密钥是 ASCII-armored 格式,直接用 cp/install 当 .gpg 用了 gpg --dearmor 转成二进制 keyring 再写 /etc/apt/keyrings/kubernetes.gpg
apt-cache policy kubeadm 看不到 1.36.4 sources.list 指向了别的版本目录或没生效 确认 kubernetes.list 里路径是 .../core/stable/v1.36/deb/,再 apt-get update
只下载到 4 个 deb,怀疑缺依赖 正常 。kubelet 仅依赖 iptables / kubernetes-cni / mount / util-linux / libc6,后几个是 Ubuntu 基础包,apt 判定已安装故跳过 无需处理;确认识别方法见 1.5
crictl: command not found 1.36.1 起 kubeadm 不再依赖 cri-tools 按 1.5 补下载 cri-tools deb,或改用 ctr -n k8s.io images ls
Command 'containerd' not found 跳过了 §2.1 ① 的安装步骤,直接执行了 containerd config default 回到 §2.1 ① 先 tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz,再 which containerd 确认
containerd --version 有输出但 systemctl start containerd 失败 配置语法错误或数据目录不可写 journalctl -u containerd -n 50 --no-pager;确认 /data/containerd 父目录存在且可写
ctr version 只有 Client 没有 Server containerd 没启动或 socket 路径不对 systemctl status containerd;确认存在 /run/containerd/containerd.sock
calico.yaml 的 sed 执行完 grep 无输出 v3.32.2 清单里没有 IP_AUTODETECTION_METHOD / first-found,那两条 sed 是空操作 按 §2.3 用「在 value: "autodetect" 后插入新 env」的写法,或手工补两行
Calico Pod 起来了但节点间 BGP 不通 自动探测选错了网卡(多网卡环境) kubectl -n kube-system set env ds/calico-node IP_AUTODETECTION_METHOD=interface=eth0,或改回 can-reach=<网关IP>
nerdctl pullnot found / manifest unknownetcd 最常见 tag 写错:etcd 少了 -0 后缀,或 coredns 少了一段路径 改用 registry.k8s.io/etcd:3.6.8-0registry.k8s.io/coredns/coredns:v1.14.2;装完 kubeadm 后用 kubeadm config images list --kubernetes-version v1.36.4 打印权威清单逐条比对
想确认某个镜像 tag 到底存不存在 仓库不提供匿名 tags 列表 nerdctl pull <镜像> 实测,或加 --debug 看 manifest 请求返回码;不要凭版本号推测

7.4 离线文件验收表(下载机上填写

# 文件名 发布页 SHA256 本地 sha256sum 核对人
1 nerdctl-full-2.3.5-linux-amd64.tar.gz
2 helm-v3.21.4-linux-amd64.tar.gz
3 calico.yaml
4 openebs-4.6.0.tgz
5 metrics-server-components.yaml
6 k8s-offline-images.tar (镜像打包产物,无需外部校验)
7 k8s-offline-bundle.tar (总包产物,节点解压前用来自校验)

7.5 后续可选扩展(本次未做)

需求 建议方案 前置条件
外部访问集群内服务 ingress-nginx 或 Higress 需离线导入额外镜像,用 NodePort 暴露
业务镜像离线分发 本地 registry(registry:2 需额外镜像与持久化卷
数据可靠性 改用 OpenEBS Replicated PV Mayastor 需 NVMe、2MiB 大页、nvme_tcp 模块,2 节点意义有限
真正的高可用 扩到 3 master + VIP(haproxy + keepalived) 与本文档「无高可用」前提冲突,需重做控制面

附:同目录配套文件

文件 用途
kubeadm.yaml kubeadm 初始化配置,复制到 master1 的 /root//data/offline/files/
openebs-values.yaml OpenEBS Helm values,仅启用 Hostpath 并设为默认 SC
test-pvc.yaml 存储验证:PVC + Deployment,验证默认 SC 与落盘路径
images.txt 14 个镜像清单,供跳板机逐条 pull 时核对

附:完整配套文件(已内联)

文中 §2.2 已给出 kubeadm.yaml 全文、§2.4 已给出 openebs-values.yaml 全文;此处补充另外两份未内联的配套文件,便于一次性复制。

test-pvc.yaml(存储验证用)

yaml 复制代码
# 存储验证:验证默认 StorageClass 生效 + 卷落在 /data/openebs/local
# 注意:PVC 不写 storageClassName,用于验证 openebs-hostpath 已是默认 SC
apiVersion: v1
kind: Namespace
metadata:
  name: storage-test
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-localpv-pvc
  namespace: storage-test
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-localpv
  namespace: storage-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-localpv
  template:
    metadata:
      labels:
        app: test-localpv
    spec:
      containers:
        - name: writer
          image: docker.io/openebs/alpine-bash:4.6.0
          imagePullPolicy: IfNotPresent
          command: ["sh", "-c"]
          args:
            - |
              echo "hello openebs" > /data/hello.txt
              echo "pod: $(hostname)" >> /data/hello.txt
              sleep 3600
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: test-localpv-pvc

images.txt(14 个离线镜像清单,跳板机逐条 pull 核对用)

text 复制代码
# Kubernetes v1.36.4 离线部署镜像清单(共 14 个)
# 用途:在跳板机上逐条 nerdctl pull,核对用
# 注意:不做重打标,保持原始 tag,kubeadm imageRepository=registry.k8s.io 可直接命中

# --- Kubernetes 核心(registry.k8s.io)---
registry.k8s.io/kube-apiserver:v1.36.4
registry.k8s.io/kube-controller-manager:v1.36.4
registry.k8s.io/kube-scheduler:v1.36.4
registry.k8s.io/kube-proxy:v1.36.4
registry.k8s.io/etcd:3.6.8-0
registry.k8s.io/coredns/coredns:v1.14.2
registry.k8s.io/pause:3.10.2

# --- Calico v3.32.2(quay.io)---
quay.io/calico/cni:v3.32.2
quay.io/calico/node:v3.32.2
quay.io/calico/kube-controllers:v3.32.2

# --- OpenEBS 4.6.0 Local PV Hostpath(docker.io)---
docker.io/openebs/provisioner-localpv:4.6.0
docker.io/openebs/linux-utils:4.6.0

# --- metrics-server v0.9.0 ---
registry.k8s.io/metrics-server/metrics-server:v0.9.0

# --- 存储/网络验证测试用 ---
docker.io/openebs/alpine-bash:4.6.0
相关推荐
遨翔在知识的海洋里2 小时前
nestjs(1)-模块的相互调用
后端
Profile排查笔记2 小时前
指纹浏览器推荐:用一套验收清单筛选 Profile、代理与自动化能力
前端·人工智能·后端·自动化
jsl_jsl_jsl2 小时前
JUC速记
后端
站大爷IP2 小时前
Python的pip依赖把我折腾惨了,原来requirements.txt和poetry能打出火星撞地球
后端
狗哥哥2 小时前
用“十步学习法”带你学会事件驱动架构
后端
挽安6212 小时前
Spring Boot自动配置原理:从@EnableAutoConfiguration源码一步步看懂
后端
深入云栈2 小时前
Netty 4.2.x 源码深度解析 (十二):NIO传输——NioSocketChannel与NioServerSocketChannel的IO读写实现
后端
王的宝库2 小时前
GO常用标准库包
开发语言·后端·golang
php@king2 小时前
hyperf初步认识和安装
后端