注意
仅适用于特殊场景,例如客户无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 init读kubeadm.yaml,kubectl 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 下载的第 ⑥ 项。先确认文件存在:
bashls -l /data/offline/files/kubernetes-apt-key.gpg若报
No such file or directory,说明 1.4 下载时文件保留了原始名Release.key,回到 1.4 执行「统一文件名」那两条mv,或直接执行:
bashcd /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_PUBKEY 或 The 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)、mount、util-linux、libc6 |
除 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不会缺依赖。
两个版本事实(重要)
conntrack不在依赖里 :kubeadm 1.36 已移除conntrack/socat/ebtables等 PATH 预检,不需要额外安装。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 行完全一致):
bashkubeadm 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-0 与 registry.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-0、docker.io/openebs/provisioner-localpv:4.6.0(注意这里会带 docker.io/ 全名)。
关于大小的说明 :
nerdctl images里SIZE列(解包后大小)相加约 1.43 GB ,BLOB 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.3、runc version 1.5.1,且 ls /opt/cni/bin 能看到 bridge、host-local、loopback 等插件。
若
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 而不是 VXLAN ,CALICO_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/local、volumeBindingMode: WaitForFirstConsumer、reclaimPolicy: 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-platforms,ctr会按「多架构镜像索引」去枚举索引里引用的所有平台并解析其他平台的 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.yaml的certSANs也包含了这个真实 IP ,否则 work1 在 §3.7 执行 join 时会报x509: certificate is valid for ... not 172.16.0.21而失败。快速核对(master1 执行):
bashopenssl 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.yaml的nodeRegistration下加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 pvc在 master1 ;最后一小节「验证落盘路径」需要先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 变为 Bound(WaitForFirstConsumer:只有 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-hostpath的volumeBindingMode是WaitForFirstConsumer------没有 Pod 真正使用这个 PVC 时,它会一直Pending,这是正常行为,不是失败。上面的STORAGECLASS列能正确认到openebs-hostpath就说明默认 SC 已生效。 若想进一步确认它能真正Bound,再起一个消费 Pod 即可(会自动触发 Provisioner 在 Pod 所在节点建卷):
bashkubectl -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 top 报 Metrics 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 update 报 NO_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 pull 报 not found / manifest unknown(etcd 最常见) |
tag 写错:etcd 少了 -0 后缀,或 coredns 少了一段路径 |
改用 registry.k8s.io/etcd:3.6.8-0、registry.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