1. 先搞清楚离线部署的本质:不是没网,是"断"了哪些网
很多朋友一接到"离线部署 Kubernetes"这个需求,第一反应就是:把镜像导出来带进去、把 rpm 包装好拷进去,然后 kubeadm init 一把梭。但我在内网机房摸爬滚打几次之后发现,离线部署真正的难点从来不是"拷贝文件"这一步,而是你在外网环境里习以为常的那些隐性依赖,全部要自己想办法补齐。
先说说我最近一次的真实经历。客户环境是某单位的隔离机房,物理断网,节点总共 5 台,操作系统是 CentOS 7.9,要求用 kubeadm 部署一套 Kubernetes v1.26.0 集群,网络插件用 Calico。听起来很常规是不是?但实际做下来,光准备工作就花了两天,真正踩坑最多的不是 kubeadm 本身,而是 Calico 装上去之后 Pod 网络起不来的那一堆破事。
1.1 离线环境真正缺的是这四样东西
先别急着讨论镜像。在断网机房部署 Kubernetes,你需要逐个确认以下四类资源的可达性:
| 资源类型 | 外网环境默认可用 | 离线环境的替代方案 |
|---|---|---|
| 容器镜像仓库 | Docker Hub / 云厂商镜像仓库 | 自建 Harbor 或 registry,提前同步镜像 |
| 系统软件源 | yum / apt 在线源 | 本地 DVD 源或自行制作离线 rpm 包集合 |
| 域名解析 | 公共 DNS | 内网 DNS 或直接写 /etc/hosts |
| 时间同步 | NTP 公网服务器 | 内网 NTP 服务器或手动设置 |
大多数人只想到第一项,结果进去之后发现:yum install 装不了依赖、coredns 解析不了集群内部域名、kubelet 报证书校验失败——每一件都够你折腾半天的。
1.2 离线环境下的镜像准备策略
镜像这块,我推荐的做法是:在有外网的机器上装好 skopeo 或 docker,先把目标版本 K8s 组件镜像全部 pull 下来,再推到内网仓库。具体怎么选型,后面单独说。
这里要特别提醒一个细节:kubeadm 所需的镜像列表,可以通过kubeadm config images list查看。拿 v1.26.0 举例,默认需要这些镜像:
- registry.k8s.io/kube-apiserver:v1.26.0
- registry.k8s.io/kube-controller-manager:v1.26.0
- registry.k8s.io/kube-scheduler:v1.26.0
- registry.k8s.io/kube-proxy:v1.26.0
- registry.k8s.io/pause:3.9
- registry.k8s.io/etcd:3.5.6
- registry.k8s.io/coredns/coredns:v1.9.3
注意第一点:不同 K8s 版本对应的 pause、etcd、coredns 版本是固定的,你在外网随便 pull 一个 latest 是不行的,必须和 kubeadm 要求的版本一致,否则 preflight 阶段就会报错。
再说第二个细节:很多企业内网的镜像仓库是 HTTP 协议的(没有 TLS 证书),这种情况下你必须在所有节点的/etc/docker/daemon.json或 containerd 的 config.toml 里把这个仓库地址配到 insecure-registries 里,否则拉取镜像会报证书错误。这个问题在离线环境里出现的频率极高,因为内网运维通常不会给镜像仓库配正式证书。
1.3 不只是镜像,还要考虑系统依赖包
这里插一个我吃过亏的地方。Calico 的 calico-node 组件对内核有一定要求,特别是开启 eBPF 数据面的时候,内核版本需要 5.7 以上。但很多内网服务器用的还是老内核(3.10 或者 4.18),导致 calico-node 启动后 Felix 进程报错。如果不打算升级内核,老老实实用 VXLAN 模式或者 IPIP 模式,别一上来就追新。
另外,K8s 集群所有节点都需要安装一些基础软件包,比如ipset、iptables、conntrack-tools、socat。这几个包在离线环境下经常被忽略。你可以在外网机器上先执行yum install --downloadonly --downloaddir=/tmp/k8s-deps ipset iptables conntrack-tools socat,把 rpm 包都带进去,再用rpm -Uvh *.rpm --nodeps批量装上。提前装好这些,能避免后面 kubeadm init 时 preflight 报一堆 FAIL。
2. 离线环境的三板斧:镜像仓库、安装源、容器运行时
这一节我把离线部署的前期准备拆成三块详细讲,每一块都有明确的选型和操作步骤。
2.1 自建镜像仓库:registry 够用,harbor 更稳
如果集群规模不大、就几台机器,用官方 registry:2 就够;如果团队有几十号人、要多人共用、还需要镜像同步策略,那直接上 Harbor。
我推荐直接用 Harbor。原因有几个:
- Harbor 自带 Web UI,镜像列表、项目权限管理一目了然;
- 支持按项目做复制规则,下次有别的离线环境可以直接从这台 Harbor 同步镜像;
- 支持 Helm Chart 存储,后面要离线部署中间件会省很多事。
Harbor 本身也是容器化的,在线环境搭好之后,把 Harbor 的镜像和数据库一并做成离线包带进内网即可。这里有个小技巧:如果你只有一台机器能进内网,那这台机器上最好同时跑 Harbor 和 Docker Registry 的反向代理,形成唯一镜像入口,所有节点都指向它,方便管理。
配置方面,Harbor 默认监听 80 端口,内网环境下可以直接用 HTTP。所有节点的容器运行时配置 insecure registry 即可。
2.2 镜像导出导入:skopeo 比 docker save/load 更推荐
很多人惯性使用docker save把镜像打成 tar 包,进去再docker load。但有几个弊端:
- docker save 会把镜像的所有层打包,文件很大;
- 如果镜像本来就在内网仓库里了,再 save/load 属于重复劳动;
- 对 containerd 环境来说,docker save 的 tar 包还得用 ctr 导入,格式兼容不够直接。
我更推荐用skopeo copy。它的优势在于:可以直接在镜像仓库之间拷贝,不需要本地 docker daemon;支持按需转换格式(docker-archive、oci-archive、docker-daemon);拷贝过程更可控,方便做批量脚本。
离线场景的典型操作是这样的:外网机器上,先把需要镜像全部 pull 下来推到临时目录,然后通过skopeo copy docker://registry.k8s.io/pause:3.9 docker://harbor.internal/k8s/pause:3.9这种方式直接推到内网仓库。全程不需要本地 docker,速度也更快。
2.3 本地 yum 源与 rpm 依赖包
K8s 组件本身的安装包(kubeadm、kubelet、kubectl)可以从阿里云等镜像站下载 rpm 包,版本固定后拷贝进内网。但这里需要连同依赖一起处理,否则安装时仍然会因为缺依赖而失败。
我最常用的办法是:在外网干净的 CentOS 7.9 机器上执行:
yum install --downloadonly --downloaddir=/tmp/k8s-rpms kubeadm-1.26.0 kubelet-1.26.0 kubectl-1.26.0然后把/tmp/k8s-rpms整个目录拷贝进内网,cd进去后执行:
rpm -Uvh *.rpm --nodeps这里提醒一下:加--nodeps是把双刃剑——它能跳过依赖检查,但也可能掩盖缺失的依赖库。如果你的环境里缺 libc.so 之类的系统基础库,kubelet 可能装上之后起不来。所以更稳妥的做法是用yum localinstall *.rpm -y,让它自动从本地源找依赖;如果本地源没有,再考虑--nodeps,但装完了一定要验证kubelet --version和kubectl version能正常输出版本。
2.4 容器运行时:containerd 是默认选择
Kubernetes 1.24 之后彻底移除 dockershim,v1.26.0 默认使用 containerd。所以离线部署这套版本,我建议直接上 containerd,不要再用 docker 去兼容。
containerd 的离线安装也很简单:下载containerd-1.7.x-linux-amd64.tar.gz和runc的二进制,解压到/usr/local/bin,生成 systemd service 文件即可。配置文件/etc/containerd/config.toml里面需要设置:
sandbox_image = "harbor.internal/k8s/pause:3.9",否则 kubelet 创建 Pod 时会去拉外网 pause 镜像导致失败;[plugins."io.containerd.grpc.v1.cri".registry.mirrors]配置内网 Harbor 地址,最好把localhost也加进去。
这一步没配置好,你会遇到经典的Failed to pull image "pause:3.9": failed to resolve reference报错,而这一般要等 kubelet 创建 Pod 时才暴露出来,属于隐藏很深的坑。
3. kubeadm init 的 preflight:看着是警告,实则是离线部署的照妖镜
准备工作完成后,进入实际部署阶段。kubeadm init 执行后会输出一段日志,开头类似:
[init] using kubernetes version: v1.26.0 [preflight] running pre-flight checks如果你在这个阶段看到连续的[WARNING]甚至[ERROR],先别急着跳过,逐个看过去,每一个都可能让你后面开机失败。
3.1 preflight 到底检查了什么
preflight 检查是一组系统环境与资源可用性验证,内容包括:
- 内核版本与模块:overlay、br_netfilter 是否加载;
- swap:kubelet 要求关闭 swap,否则会 FAIL;
- 端口占用:6443、10250 等是否被占用;
- 容器运行时:能否通过 CRI 访问 containerd;
- 镜像拉取:会尝试拉一个 pause 镜像验证 CRI 配置正确;
- 文件系统:/var/lib/kubelet 等目录是否存在冲突。
离线环境下,preflight 最容易翻车的是"容器运行时"检查那一项。如果你的 containerd config.toml 还没改好,crictl info能正常执行,但 kubeadm 仍然会报[ERROR CRI]: container runtime is not running。这个检查项的根本逻辑是:kubeadm 通过 CRI 插件去和 containerd 通信,如果 sandbox_image 配的是外网地址,在断网环境里它拉取失败,就直接判定运行时不可用。
3.2 离线环境下最常碰到的三个 preflight 报错
我自己统计了一下,给内网机器做 K8s 离线部署,preflight 阶段最常出现的是下面三个:
[ERROR Swap]: running with swap on is not supported——内网服务器很多 Swappiness 没关,或者/etc/fstab里 swap 分区没注释掉。处理方式是swapoff -a并且注释/etc/fstab,然后重启确认。[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1——这是经典的网络转发问题。两个 sysctl 要配好:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF modprobe overlay modprobe br_netfilter sysctl --system[ERROR ImagePull]: failed to pull image ...——这个前面说过,就是 containerd 的 sandbox_image 没改,或者镜像仓库地址走不通。到这里,前面准备的 Harbor 和 pause 镜像就派上用场了。
3.3 init 成功后先别急着装 Calico
kubeadm init 成功后会生成一串提示,包括 kubeconfig 路径、join 命令等。但这个时候集群是"裸奔"的,没有任何 CNI 插件,Node 状态会是 NotReady,这才是正常的。
我习惯在装 Calico 之前先做三件事:
- 确认 etcd 是否健康:
kubectl get --raw='/healthz/etcd'返回 ok; - 确认 apiserver 是否正常:
kubectl get --raw='/healthz'返回 ok; - 在 master 节点
crictl ps -a看看 pause 容器有没有正常起来。
这三步能帮你把"K8s 自身问题"和"网络插件问题"切开。很多朋友一看到 NotReady 就说是 Calico 的问题,结果查了半天发现是 containerd 没起来或者镜像配置不对,这就是没先做基础验证。
4. Calico 部署后 Pod 网络不通:我的完整排查过程
这里进入全篇的重头戏。Calico 装上去之后 Pod 网络不通,是我在离线部署中遇到频率最高、耗时最长的一类问题,每个项目几乎都能碰到一两个新花样。我按自己的排查经验,把这套过程完整写下来,希望能帮大家少走一些弯路。
4.1 先给 Problemon 分层:是 CNI 层、路由层还是策略层
网络不通的时候,第一件事不是去翻 Calico 日志,而是先搞清楚问题是出在哪个层级。我的判断顺序是这样的:
- 先看 Pod 能不能拿到 IP:在 Pod 内
ip addr; - 再看同一节点上两个 Pod 能不能互通;
- 接着看跨节点 Pod 能不能互通;
- 最外层是 Service 和 ingress 能不能访问。
不同的失败位置,指向的根因完全不同:
| 现象 | 大概率原因 |
|---|---|
| Pod 内没有 eth0 网卡 | CNI 插件没被调用,calico-node 异常 |
| 同节点 Pod 不通 | veth 或路由表错乱 |
| 跨节点 Pod 不通(IPIP 模式) | BGP 邻居没建立、IPIP 封装被防火墙丢弃 |
| Pod 能通但 Service 不通 | kube-proxy 问题、IPVS 规则异常 |
4.2 calico-node CrashLoopBackOff:一查一个准的原因
我先说一个通用经验:kubectl get pods -n kube-system -o wide看 calico-node 的状态,如果多个节点都在 CrashLoopBackOff,那大概率不是单点问题,而是全局性的配置错误。
用kubectl logs -n kube-system calico-node-xxxxx看日志,最常见的报错是:
Incorrect IP address or unknown host name: 'localhost'这个报错出现在初始化时。原因基本只有两个:一是你拉取的 Calico 版本与 Kubernetes 集群版本不匹配,它去找 API Server 的地址时出错;二是 Calico 的安装 YAML 里CALICO_K8S_NODE_REF或者etcd_endpoints配置不对。
还有另一个高频报错:
Failed to create veth: operation not permitted这个在离线环境出现的概率特别高,原因通常是节点内核没开启需要的模块,或者容器运行时权限受限。最直接的解决办法是检查每个节点内核版本,确认支持 Calico 需要的特性,然后重启 containerd 再循环删除所有 Pod,让 calico-node 重新创建 Pod。如果还是不行,看是不是 SELinux 拦了/var/lib/calico目录的写入。
4.3 BGP 邻居起不来:网段冲突和 IPIP 模式
如果你的 Calico 用的是 BGP 模式(IPIP 或 VXLAN 都依赖 BGP),那calico-node进程里的 bird 组件负责建立 BGP 邻居。排查的命令是:
kubectl exec -n kube-system calico-node-xxxxx -- calicoctl node status常见输出是BGP peer connections state = idle或者active。我的经验是:BGP neighbor 起不来的原因,十有八九是节点网段冲突。
举个具体的例子:A 节点的内网 IP 是 192.168.1.10,B 节点是 192.168.2.10,而 Calico 默认的 Pod 网段正好是 192.168.0.0/16。这就和节点所在的物理网段撞车了。当 BGP 协议去学习路由时,发现目标网段和自身网段重叠,直接拒绝学进来,Pod 到 Pod 就死活不通。
针对这个问题的解决方案是在 Calico 的安装 YAML 里显式修改CALICO_IPV4POOL_CIDR,把它改成一个和物理网络完全隔离的网段,比如 10.244.0.0/16 或 172.24.0.0/16。
另外提醒一下 IPIP 模式的防火墙问题。IPIP 封装的流量走的是 IP 协议号 4,很多内网防火墙对非 TCP/UDP/ICMP 的流量是直接丢弃的。如果你发现同节点 Pod 通、跨节点 Pod 不通,先用tcpdump -i eth0 ip proto 4看看有没有封装包在传输。如果被丢弃,要么在节点上放行协议号 4,要么直接切换 VXLAN 模式(VXLAN 走 UDP 4789,一般防火墙策略宽松一些)。离线环境里,我遇到最多的就是这个坑,因为好多内网安全策略默认只放行固定端口的 TCP/UDP。
4.4 网络策略过于严格导致的"假故障"
最后一个高频问题:Calico 默认安装后,会自带一个default-deny的全局网络策略吗?其实不会。Calico 默认是允许所有流量的。但如果你自己配了 NetworkPolicy 或者装了一些安全组件,那就另说了。
我记得有一次,节点和 Calico 一切正常,但业务 Pod 之间就是不通。排查到最后发现,是另一个团队部署的"默认拒绝所有入站流量"的 NetworkPolicy 把业务端口全封了。这种"假故障"最难查,因为所有底层指标都是正常的,只有具体应用访问失败。
遇到这种情况,推荐按这个顺序查:
# 查看策略 kubectl get networkpolicies -A # 查看某个 Pod 被哪些策略命中 kubectl describe pod <pod-name> -n <namespace>如果确实策略问题,可以先临时加一条Allow All的策略验证,确认通了之后再收敛到最小化规则。这里有个经验:在离线环境里,团队内部沟通链路比开网络策略文档还重要,很多策略加得隐蔽,到排查的时候才发现是"自己人"拦了路。
5. 离线部署的实操经验清单
前面讲了完整流程和 Calico 排查主链路,这一部分我把这些年离线部署 Kubernetes 的经验收敛成一份清单,基本都是文档里不会写、但实战中大概率遇到的点。
5.1 版本匹配是离线部署的第一原则
K8s 集群组件版本、Calico 版本、containerd 版本、内核版本,这四个必须提前核对清楚。我建议用下面的对照思路:
- Kubernetes v1.26.0 对应的 kubeadm/kubelet/kubectl 必须同版本,不能混;
- Calico 3.25+ 支持 K8s 1.26,再老的版本就不建议了;
- containerd 1.7.x 对 K8s 1.24+ 支持良好;
- 内核建议 5.4 以上,否则 Calico 的 eBPF 模式、部分网络策略功能受限。
有一个省心的小技巧:在离线环境里,直接用 kubeadm 默认镜像版本和 Calico 官方推荐的版本组合,别追求最新。你是在内网,没有升级的条件,稳定比新特性更重要。
5.2 时间同步是隐形的定时炸弹
内网机器一旦和外部 NTP 断联,时间会慢慢漂移。Kubernetes 的证书校验对时间敏感,kubelet 和 apiserver 之间如果时间差超过一定阈值,证书直接校验失败,报错通常不直观,比如:
x509: certificate has expired or is not yet valid这时候你查证书有效期会发现证书没问题,其实是本机时间不对。
解决方法是搭一个内网 NTP 服务,所有节点指向它。若无条件,至少在部署前date -s手动校准一次,并且把timedatectl set-ntp true打开。别小看这个环节,我见过一个集群跑了一周之后突然所有节点 NotReady,最终原因就是时间漂移。
5.3 把"后悔药"准备好再动手
离线环境不像在线环境,出问题还能随时从网上拉新版本下来重装。所以我养成一个习惯:在进内网之前,把整个部署脚本、镜像清单、YAML 文件、rpm 包全部放到一个目录并打成一个 tar 包,命名为k8s-offline-bundle.tar.gz,带进去之后先在每台节点上解压到统一的/opt/k8s-bundle路径。
同时,把kubeadm config migrate之后的 kubeadm.yaml、kubectl -n kube-system get cm -o yaml这些关键配置都导出备份。真的遇到配置改坏了,至少能回滚到上一个可用状态,而不用从头再来。
而在 Calico 的排障过程中,我也养成了一个习惯:每做一次改动,就执行一次kubectl apply -f calico.yaml --dry-run=client -o yaml检查语法;每重启一次组件,就同时抓取calico-node的日志和kubectl get pods -o wide的结果,放到同一个时间戳目录下。这样即使过了一整天,也能靠时间线还原当时的系统状态,不会陷入"到底改了什么才变好的"这种死循环。
最后补充一条:离线部署别追求"一次成功"
最后再分享一点我的个人体会。离线环境部署 Kubernetes,除非你是纯空闲时间充足、可以慢慢磨的测试环境,否则我不建议抱着"一次 init 成功、Calico 一条命令搞定"的期待去操作。更实际的做法是:在进内网之前,用虚拟机完整跑一遍流程——从镜像同步、YAML 调整到 Calico 安装,把可能要踩的坑先暴露一遍。
我每次帮客户做离线部署,都会先在本地用 VirtualBox 或 VMware 起三台同一 CentOS 版本的虚拟机,完整跑一遍,确认所有镜像和配置都没问题,再进真实内网操作。这个方法帮我拦下了不下十次"到时候才发现少了某个 rpm 包"的尴尬。
如果你正在准备做类似的离线部署项目,我的建议只有一句话:把准备工作的时间预估得比正式部署长三倍,把 Calico 的排障步骤提前写到自己的笔记里备用。真到现场的时候,你会发现省下来的时间远比想象中多。