简介:Calico是Kubernetes集群常用的网络插件,其v3.25.0离线安装包面向需要在内网或完全离线环境中部署网络方案的K8s管理员,解决在线拉取镜像频繁失败、插件无法启动的问题。压缩包共3个文件,其中tar文件保存了Felix、BIRD、Typha等完整镜像,yaml文件定义部署所需的工作负载与策略模板,txt文档则提供环境准备和校验指引;整体大小187.24MB,携带和分发都很方便。目前已有449人学习/下载,特别适合网络受限但需要搭建或扩展集群的运维工程师。使用时可先导入tar镜像,再按文档调整Pod网段、IP池等参数,应用yaml即可完成Calico部署;同时能借此熟悉各组件在路由发布和策略执行中的分工,理解BGP模式下的容器跨节点通信原理。文档还提供了常见参数调整与排错提示,便于团队快速落地并保障集群网络策略的稳定执行,整体上是一份可直接使用的离线交付组件。
1. 为什么需要自己打一个calico离线包
做Kubernetes集群部署,尤其是政企、运营商、制造这些内网隔离环境,大家早晚都会遇到一个绕不过去的坎:集群节点没有外网,镜像拉不下来,所有组件都得靠离线包搬运。我在帮客户搭生产集群的时候,几乎每次都会卡在同一个环节——网络插件Calico。
先说下这个需求的普遍程度。Kubernetes集群里最常用的网络方案就是Calico,它基于BGP路由协议转发pod流量,性能和可扩展性在CNI插件里都是第一梯队的。v3.25.0这个版本在2023年发布,对应Kubernetes 1.24到1.27的版本区间,是目前生产环境里使用率很高的一版。问题是,Calico的组件依赖容器镜像运行,而这些镜像默认都托管在Docker Hub和quay.io这类外网仓库,内网环境根本拉不到。更尴尬的是,网上流传的所谓离线包很多只打包了镜像文件,yaml清单、版本配套关系、导入方法全都没有,照搬下来部署大概率踩坑。
所以这篇博文就把我实际做过的v3.25.0离线包完整流程拆开讲清楚:镜像清单怎么定、导入内网仓库用什么姿势、calico.yaml哪些字段必须改、部署后常见报错怎么排查。内容基于我一个客户项目的真实操作记录,是经过生产环境验证的,可以放心抄作业。
需要提前说明一点:不同环境下网络隔离策略、镜像仓库方案各不相同,所以文中的具体步骤我尽量写得通用一些,凡是涉及环境差异的部分会单独标注,你照着改就行。
2. v3.25.0的离线包该装什么:镜像清单与版本配套
2.1 哪些组件镜像缺一不可
Calico v3.25.0部署到Kubernetes集群上,默认需要用到下面这几个镜像,一个都不能少:
- calico/cni: v3.25.0 —— CNI插件本体,负责处理pod网卡创建和网络配置
- calico/node: v3.25.0 —— agent组件,运行在每个节点上,负责路由宣告和策略执行
- calico/kube-controllers: v3.25.0 —— 控制器,处理IPAM分配、策略同步等逻辑
- calico/typha: v3.25.0 —— 扩展组件,节点数量多时用来减轻API Server压力
- calico/pod2daemon-flexvol: v3.25.0 —— flexvolume插件驱动,只有启用CSI或flexvolume存储能力时才需要
如果是小规模集群(节点少于50个),typha可以不开,但镜像最好一并打包进去,免得后面扩容时手忙脚乱。pod2daemon-flexvol这个镜像最容易被漏掉,因为它要等某些pod调度到节点上才被拉取,而等真正需要时内网已经来不及下载了。
还有一个细节:Calico v3.25.0的镜像tag是完整的“v3.25.0”,但个别镜像可能带后缀,比如calico/cni对应的tag是v3.25.0,而有的版本会同时存在v3.25.0-0.dev这样的开发tag。做离线包时务必以官方v3.25.0 release的manifests为准,不要凭感觉抄网上的镜像列表。
2.2 版本配套关系必须先确认
很多新手栽的第一个跟头就是版本配套。Calico v3.25.0和Kubernetes的兼容范围是1.24到1.27,也就是说如果你的K8s版本不在这个区间,要么升级集群,要么换对应版本的Calico。此外还需要确认etcd的版本,Calico v3.25.0使用的是Kubernetes datastore(KDD)模式,不再像v2.x那样依赖独立的etcd集群,这点倒是不用担心。
我建议打包离线包以前,先执行下面这个命令确认当前K8s版本:
kubectl version --short或者看服务端版本:
kubectl get node -o wide版本确认好后,去GitHub上找对应release的yaml清单。v3.25.0版本的官方清单文件名是calico.yaml,里面包含了所有需要创建的CRD、RBAC、DaemonSet和Deployment。但要注意,官方清单默认从docker.io拉镜像,离线环境下必须先把所有镜像导入到内网仓库,然后修改yaml里所有image字段。
2.3 镜像下载与打包的具体操作
在一台能访问公网的机器上,先把镜像拉下来。我习惯用docker来拉,因为后面导入到内网仓库时操作最直观:
docker pull docker.io/calico/cni:v3.25.0 docker pull docker.io/calico/node:v3.25.0 docker pull docker.io/calico/kube-controllers:v3.25.0 docker pull docker.io/calico/typha:v3.25.0 docker pull docker.io/calico/pod2daemon-flexvol:v3.25.0拉完后,统一打tag并推送到内网registry,也可以先保存成tar包再拷贝进内网。两种方式我都试过,如果是跨网段传输,tar包更稳;如果内网有可用镜像仓库,直接打tag推送更省事。这里先演示打tag推送的做法:
docker tag calico/cni:v3.25.0 registry.internal.lan/calico/cni:v3.25.0 docker push registry.internal.lan/calico/cni:v3.25.0其他几个镜像照葫芦画瓢,改对应的镜像名就行。
如果要保存成tar包,命令是:
docker save calico/cni:v3.25.0 calico/node:v3.25.0 calico/kube-controllers:v3.25.0 calico/typha:v3.25.0 calico/pod2daemon-flexvol:v3.25.0 -o calico-images-v3.25.0.tar拷贝到内网机器上导入:
docker load -i calico-images-v3.25.0.tar如果内网节点用的是containerd而不是docker,那导入命令要换成ctr或crictl,这个后面专门讲。
3. 镜像导入内网仓库时最容易踩的三个坑
3.1 坑一:用ctr导入到错误命名空间
很多K8s集群用的是containerd运行时,这时候你可能会用ctr命令导入镜像。但containerd有一个“命名空间”的概念,默认情况下ctr看到的命名空间是default,而kubelet实际使用的命名空间是k8s.io。如果你直接:
ctr -n=k8s.io images import calico-images-v3.25.0.tar这没问题,但你要知道加了-n=k8s.io才是在给kubelet准备的。不少教程没写这个参数,结果镜像导入后显示成功,pod启动时仍然报ImagePullBackOff,就是因为镜像根本没导入到kubelet认识的那个命名空间里。
最终生产中我建议干脆直接把镜像推到内网Harbor仓库,让kubelet从仓库拉取,避免绕这一层。除非你的环境连内网仓库都没有,只能用ctr手动导入。
3.2 坑二:镜像tag被截断或不一致
镜像从外网仓库同步到内网时,很多人喜欢用Harbor的proxy cache功能,或者skopeo同步,结果同步完发现镜像tag变成了一长串带hash的地址,或者多了个@sha256:的digest。Calico的yaml里写的是具体tag,比如calico/cni:v3.25.0,Harbor proxy cache拉下来的镜像在本地仓库里显示的tag可能就是v3.25.0,这没问题。但如果你手动docker tag的时候多打了一个前缀,比如registry.internal.lan/calico/cni:v3.25.0,那yaml里也必须同步改成这个完整地址,不能只改一半。
最好在导入后验证一下:
docker images | grep calico确保镜像REPOSITORY和TAG跟你接下来要改的yaml字段完全一致。
3.3 坑三:忽略了镜像签名或私有仓库认证
有些内网Harbor仓库开启了镜像签名或者匿名拉取限制。Calico默认的manifest里没有写imagePullSecrets,如果你的仓库需要在拉取时认证,所有使用了Calico组件的命名空间都得配置对应的secret。最典型的报错就是在描述pod时看到类似这种信息:
Failed to pull image "registry.internal.lan/calico/node:v3.25.0": rpc error: code = Unknown desc = failed to pull and unpack image: unable to retrieve some image pull secrets (user-1-registrysecret); attempting to pull may succeed...遇到这种错误,处理办法就是在calico.yaml所在的命名空间(一般是kube-system)创建imagePullSecret,然后把secret名称加到DaemonSet或Deployment的template里。如果只是临时测试,也可以把所有节点的containerd配置里加上registry的认证信息,这个看实际情况。但生产环境我还是建议直接在yaml里写secret,可维护性好一些。
4. 部署前必须改的calico.yaml配置项
4.1 镜像地址替换
拿到官方calico.yaml后,第一步就是把所有image字段替换成内网仓库地址。手动改容易漏,这里推荐用sed批量处理:
sed -i 's#docker.io/calico#registry.internal.lan/calico#g' calico.yaml注意有些版本默认镜像地址是calico/cni,前面没有docker.io前缀,那么替换规则要稍微调整:
sed -i 's#calico/cni#registry.internal.lan/calico/cni#g' calico.yaml sed -i 's#calico/node#registry.internal.lan/calico/node#g' calico.yaml sed -i 's#calico/kube-controllers#registry.internal.lan/calico/kube-controllers#g' calico.yaml替换完一定要检查一下:
grep -E "image:" calico.yaml确保没有一个漏网的。
4.2 IP池配置:pod CIDR和IPIP模式
Calico默认的IP池用的是192.168.0.0/16,很多公司的K8s集群为了跟办公网、业务网段区分,通常会自定义Pod网段,比如10.244.0.0/16。如果你不修改,默认网段可能跟内网已有网段冲突,导致路由不通。
改IP池有两种方式:一种是在calico.yaml里直接改IPPool的cidr字段,一种是部署完成后再用calicoctl修改。建议部署前一次性改好,因为IPPool创建后修改CIDR要重建很多pod,麻烦得很。
找到calico.yaml里类似下面这段:
apiVersion: crd.projectcalico.org/v1 kind: IPPool metadata: name: default-ipv4-ippool spec: cidr: 192.168.0.0/16 ipipMode: Always natOutgoing: true把cidr改成你的自定义网段,比如10.244.0.0/16。ipipMode要根据你的网络环境决定:如果所有节点都在同一个二层网络里,推荐用Never(即纯BGP模式),转发效率更高,报文也更小;如果节点跨网段,或者网络设备不支持BGP,就用Always。最稳的做法是先看客户网络环境,否则默认IPIP容易导致MTU问题,这个下面会说到。
4.3 MTU调整:内网环境最容易忽视的问题
Calico默认MTU是1500,很多内网环境的主机网卡MTU就是1500,这没问题。但如果你用的是VXLAN或IPIP封装,实际的报文头会额外增加20字节左右,如果底层网络设备支持巨型帧(MTU设置为9000),那就要把Calico的MTU调成比物理网卡小几十字节的值,否则大包传输会被静默丢弃,表现就是ping小包通、ssh正常,但大文件传输、pod互访大规模流量时异常卡顿。
v3.25.0版本里,MTU在ConfigMap里设置:
kind: ConfigMap apiVersion: v1 metadata: name: calico-config namespace: kube-system data: veth_mtu: "1500"如果主机网卡MTU是1500,那veth_mtu保持1500即可;如果主机网卡是9000,建议改成8980左右。这个参数配错不会让Calico启动失败,但会让整体网络表现非常诡异,排查起来相当费劲。
4.4 开启或关闭BGP对等
Calico默认会为每个节点自动建立BGP mesh连接。如果你的集群节点数量少于100,默认的full mesh够用;节点多了就建议配置Route Reflector。v3.25.0里如果不想让Calico自动起BGP,可以给节点打标签:
kubectl label node <node-name> route-reflector=true然后把calico.yaml里的环境变量CALICO_NETWORKING_BACKEND改成none或vxlan。这个要看具体需求,不展开太多。
5. calico/node is not ready的完整排查链路
热词里反复出现number of node(s) with BGP peering established = 0和calico/node is not ready,这是Calico部署后最常见的两个报错。我把实际排查过程完整写出来。
5.1 先看calico-node这个pod的状态
部署完Calico后,第一时间看pod状态:
kubectl get pods -n kube-system -o wide | grep calico如果看到类似这样的状态,说明有问题:
calico-node-xxxxx 0/1 Running 0 5mpod虽然在Running,但READY是0/1,说明容器里的进程起来了,但尚未就绪。这时必须看日志:
kubectl logs -n kube-system calico-node-xxxxx -c calico-node最常见的日志是:
BGP peering established = 0这就是BGP邻居没建立起来,注意“established = 0”是统计信息,真正要看的是它前面的error信息。
5.2 从日志顺序定位故障点
我实际遇到过导致这个报错的原因,按排查优先级排列:
节点间网络不通或防火墙拦截179端口:BGP默认使用TCP 179端口,内网环境经常有防火墙策略限制节点间通信。先测试:
telnet <目标节点IP> 179如果telnet不通,需要协调网络侧放行该端口。还有一个隐蔽问题:云环境安全组一般只放行常用端口,179端口很容易被漏掉。
BGP mesh模式下节点IP不对:Calico默认使用节点的内网IP作为BGP peer地址,如果你的节点有多个网卡,它可能选到了错误的IP。排查命令:
kubectl get nodes -o wide如果节点的INTERNAL-IP不是预期的内网网段,需要给节点加annotation指定BGP地址。
IPPool配置错误导致Felix收不到路由:如果IPPool里指定的网段和节点实际不在同一网络,BGP邻居可能建立了,但路由无法同步。日志里会出现类似
Failed to apply routes或Could not find container ID的提示。MTU问题导致的连接超时:BGP peering建立后如果MTU不匹配,大包握手会失败,表现为BGP邻居反复up/down。这跟前面说的MTU配置密切相关。
5.3 calico/node is not ready的另类原因
除了BGP问题,还有一种情况比较隐蔽:节点的时间不同步。Calico的BGP会话对时间偏移很敏感,节点间时间差超过几秒就会反复断开。我排查过几次生产环境的诡异问题,最后发现都是ntp没配置。所以在部署Calico之前,先保证所有节点时间一致:
date再配合chronyc或ntpq检查同步状态。这是很多文档不会提到的坑,但确实真实存在。
5.4 初始化检查的快捷方式
v3.25.0的Calico部署完成后,可以用calicoctl做整体体检:
kubectl exec -n kube-system calico-node-xxxxx -- calicoctl node status如果输出里每个节点都有BGP peer状态是Established,那基本就成功了。如果是0个established,按上面几类原因逐一排查。
更简单的方式是直接跑一个测试pod验证:
kubectl run test-pod --image=busybox -- sleep 3600 kubectl exec -it test-pod -- ping <另一个节点的podIP>如果ping不通,再回头看BGP状态。
6. 验证离线包是否真正可用的标准方法
6.1 集群内的连通性验证
部署完Calico,不等于离线包就合格了。我的习惯是分三步验证:
第一步,检查所有calico-node pod都处于Running且READY为1/1:
kubectl get pods -n kube-system -o wide | grep calico第二步,创建一个跨节点测试pod,验证跨主机容器网络:
kubectl create deployment nettest --image=busybox -- sleep 3600 kubectl scale deployment nettest --replicas=2 kubectl get pods -o wide从其中一个pod ping另一个pod的IP,如果通了,说明BGP路由正常工作。这一步能直接检验IPIP或VXLAN封装是否正确、MTU是否匹配。
第三步,验证service网络,创建nginx服务并跨节点访问ClusterIP:
kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --target-port=80 kubectl run testpod --image=busybox -- sleep 3600 kubectl exec -it testpod -- wget -qO- http://<ClusterIP>6.2 离线包内镜像的完整性核对
建议在导入镜像后,用镜像仓库的API或docker images命令核对所有镜像的digest是否跟官方一致。v3.25.0的镜像可以从quay.io或docker.io上查看到对应的digest值,离线包制作完成后最好把digest也记录下来,后续安全审计或复现时用得上。
比如:
docker inspect registry.internal.lan/calico/node:v3.25.0 --format '{{index .RepoDigests 0}}'拿到digest后跟官方仓库对比。这一步很多人不做,但真出了问题(比如镜像被篡改或传输损坏),能有据可查。
6.3 升级场景里的额外注意事项
如果你是在已有Calico旧版本的集群上升级,比如从v3.20升到v3.25.0,那离线包还需要额外检查:旧版本的CRD在v3.25.0里是否有废弃或改名。比如v3.25.0对GlobalNetworkPolicy、NetworkPolicy等CRD做了兼容性调整,跨大版本升级时最好先看官方release notes,不要直接在旧数据上硬上。
最稳的做法是先在测试集群验证一遍,确认没问题再上生产。别急着在生产环境直接升级,除非你有一颗强大的心脏。
7. 一个日常维护的小技巧
最后分享一个我常做的维护动作:Calico部署好以后,建议把calico.yaml这个文件留存好,并且在集群里用Git管理。以后不管是谁要重新部署、扩容或迁移集群,直接拿这份yaml就能复现,不用再到网上去翻官方文档拼拼凑凑。我一直觉得,离线包不只是几个镜像的简单打包,它应该包括一整套可重复执行的部署方案——镜像清单、配置清单、验证清单,缺一不可。按照这套思路做出来的离线包,才是真正能落地的离线包。
本文还有配套的精品资源,点击获取