这篇文章记录一次真实的 Kubernetes CNI 迁移:把现有集群的网络插件从 Flannel 切换到 Calico。
这不是只执行两条命令就结束的事情。CNI 切换后,旧 Pod 可能还挂着旧网络;如果集群里有 Longhorn、PostgreSQL、Redis、Ingress、Prometheus 这些组件,还要按顺序恢复存储、数据库、业务和监控。
这次最后的完成标准是:
所有 Pod 都 Ready
所有 Deployment 都 Available
metrics-server 可以正常返回 kubectl top nodes
new-api 和 Grafana 入口可以访问
Longhorn 业务卷恢复 healthy
最终验证结果:
kubectl wait --for=condition=Ready pod --all -A --timeout=120s
kubectl wait --for=condition=Available deployment --all -A --timeout=120s
kubectl top nodes
输出里 93 个 Pod 全部 Ready,所有 Deployment 都 Available,kubectl top nodes 正常返回。
业务入口也恢复:
https://k8s-ai.jihw.top HTTP/2 200
https://grafana.jihw.top HTTP/2 302 -> /login
当前集群
当前集群是三主节点 kubeadm 集群:
k8s-master1 192.168.3.214
k8s-master2 192.168.3.215
k8s-master3 192.168.3.216
API VIP 192.168.3.217
kubeadm 初始化时使用的 Pod CIDR 是:
10.244.0.0/16
这个网段很重要。Flannel 常见默认 Pod 网段就是 10.244.0.0/16。已有集群迁移时,Calico 的 IPPool 要继续使用这个 CIDR,不要随手改成 192.168.0.0/16 或其他新网段。
当前集群里还有这些关键组件:
Longhorn
CloudNativePG PostgreSQL
Redis HA + redis-master-proxy
ingress-nginx
MetalLB
cert-manager
kube-prometheus-stack
Headlamp
new-api
这也决定了迁移完成后不能只看 kubectl get nodes,还要看所有命名空间的 Pod。
为什么换 Calico
Flannel 的优点是简单,适合快速把 Pod 网络跑起来。它主要解决 Pod 跨节点通信问题。
Calico 更适合后续生产化:
- 支持 Kubernetes NetworkPolicy,可以控制 Pod 之间的访问。
- 支持 VXLAN、IPIP、BGP 等更多网络模式。
- 有更完整的网络排错和可观测能力。
- 后面可以继续探索 eBPF、全局网络策略等能力。
如果只是学习 Kubernetes,Flannel 够用。如果想继续做安全隔离和网络治理,Calico 更值得切换。
这里也顺手纠正一下:fannel 应该写作 flannel。
最重要的结论
全新集群最简单:kubeadm init 后直接安装 Calico,不安装 Flannel。
已有集群也能迁移,但必须安排维护窗口,因为 CNI 切换会影响 Pod 网络。尤其是使用 Longhorn 时,迁移完成后要优先恢复 Longhorn 自身组件,否则 PostgreSQL、Redis 这类依赖 PVC 的业务会继续不可用。
这次真实故障链路是:
new-api 无法访问
|
v
new-api Service 没有 endpoints
|
v
new-api Pod CrashLoopBackOff
|
v
new-api 连接 PostgreSQL / DNS 超时
|
v
PostgreSQL / Redis NotReady
|
v
Longhorn CSI 和卷状态异常
|
v
Longhorn instance-manager 还保留旧 Pod IP
|
v
Longhorn engine / replica 之间 no route to host
所以恢复顺序不能反过来。不要一上来反复重启 new-api。应该先确认 Calico,再恢复 Longhorn,再恢复数据库和 Redis,最后恢复业务和监控。
全新集群怎么做
如果集群还没正式跑业务,建议直接重建。
初始化 Kubernetes 时继续指定 Pod CIDR:
sudo kubeadm init \
--control-plane-endpoint "192.168.3.217:8443" \
--apiserver-advertise-address=192.168.3.214 \
--pod-network-cidr=10.244.0.0/16 \
--upload-certs
不要再安装 Flannel:
# 不再执行这个
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
安装 Tigera Operator:
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml
下载 Calico 自定义资源:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/custom-resources.yaml
把 custom-resources.yaml 里的 IP 池改成当前 Pod CIDR:
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: 10.244.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
应用:
kubectl create -f custom-resources.yaml
等待 Calico 就绪:
kubectl -n calico-system get pods -o wide
kubectl get tigerastatus
kubectl get nodes
已有集群迁移总流程
这次已有集群迁移按这个顺序收口:
1. 备份资源和 etcd
2. 确认 Pod CIDR 和 NetworkPolicy
3. 删除 Flannel
4. 清理每台节点上的 Flannel CNI 残留
5. 安装 Calico
6. 验证 Calico 和 DNS
7. 恢复 Longhorn
8. 恢复 PostgreSQL / Redis
9. 恢复 new-api 和 Ingress
10. 恢复 metrics-server / cert-manager / Prometheus operator 等旧 Pod
11. 全集群 Pod Ready 检查
不要在业务高峰期做。
迁移前备份
先导出资源清单:
mkdir -p ~/k8s-backup/flannel-to-calico
kubectl get all -A -o yaml > ~/k8s-backup/flannel-to-calico/all.yaml
kubectl get cm,secret,ingress,svc,pvc,pv -A -o yaml > ~/k8s-backup/flannel-to-calico/common-resources.yaml
kubectl get nodes -o wide > ~/k8s-backup/flannel-to-calico/nodes.txt
kubectl get pods -A -o wide > ~/k8s-backup/flannel-to-calico/pods.txt
备份 Flannel 资源:
kubectl -n kube-flannel get all -o yaml > ~/k8s-backup/flannel-to-calico/flannel.yaml
kubectl -n kube-flannel get cm -o yaml >> ~/k8s-backup/flannel-to-calico/flannel.yaml
备份 NetworkPolicy:
kubectl get networkpolicy -A -o yaml > ~/k8s-backup/flannel-to-calico/networkpolicy.yaml
这一点很关键。Flannel 本身不实现 Kubernetes NetworkPolicy,Calico 会实现。也就是说,如果集群里以前已经创建过 NetworkPolicy,但因为 Flannel 没有执行,所以没有产生实际限制;换成 Calico 以后,这些策略可能会突然生效。
备份 etcd
kubeadm 默认把 etcd 跑成静态 Pod,宿主机上不一定安装了 etcdctl。如果直接执行:
sudo ETCDCTL_API=3 etcdctl snapshot save /root/etcd-snapshot-before-calico.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
可能会报:
sudo: etcdctl:找不到命令
说明只是宿主机没有 etcdctl,不代表 etcd 有问题。
更推荐直接使用 etcd 容器里自带的 etcdctl。注意不要写 sh -c,有些 etcd 镜像非常精简,容器里没有 sh,会报:
exec: "sh": executable file not found in $PATH
直接执行 etcdctl:
ETCD_CONTAINER_ID=$(sudo crictl ps --name etcd -q | head -n 1)
sudo crictl exec "$ETCD_CONTAINER_ID" etcdctl snapshot save /var/lib/etcd/etcd-snapshot-before-calico.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
sudo cp /var/lib/etcd/etcd-snapshot-before-calico.db /root/etcd-snapshot-before-calico.db
sudo ls -lh /root/etcd-snapshot-before-calico.db
如果提示找不到 etcdctl,再尝试完整路径:
sudo crictl exec "$ETCD_CONTAINER_ID" /usr/local/bin/etcdctl snapshot save /var/lib/etcd/etcd-snapshot-before-calico.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
如果 crictl 也不可用,先确认容器运行时:
sudo crictl ps | grep etcd
sudo ctr -n k8s.io containers list | grep etcd
也可以安装客户端:
sudo apt update
sudo apt install -y etcd-client
迁移前检查
确认 Flannel:
kubectl get ns | grep flannel
kubectl -n kube-flannel get pods -o wide
kubectl -n kube-flannel get ds
确认节点和 Pod:
kubectl get nodes -o wide
kubectl get pods -A -o wide
确认 kubeadm 配置:
kubectl -n kube-system get cm kubeadm-config -o yaml
确认当前 NetworkPolicy:
kubectl get networkpolicy -A
删除 Flannel
如果当初用官方 manifest 安装 Flannel,可以先尝试:
kubectl delete -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
也可以删除当前集群里的 Flannel 资源:
kubectl -n kube-flannel delete ds kube-flannel-ds
kubectl delete ns kube-flannel
确认 Flannel Pod 消失:
kubectl get pods -A | grep -i flannel
清理 Flannel CNI 残留
下面操作要在每台节点执行:
sudo systemctl stop kubelet
sudo rm -f /etc/cni/net.d/*flannel*
sudo rm -f /etc/cni/net.d/10-flannel.conflist
sudo ip link delete flannel.1 2>/dev/null || true
sudo ip link delete cni0 2>/dev/null || true
sudo rm -rf /var/lib/cni/networks/cni0
sudo rm -rf /var/lib/cni/results
sudo systemctl start kubelet
如果节点上还有其他 CNI 配置文件,先看清楚再删:
ls -l /etc/cni/net.d/
安装 Calico
安装 Tigera Operator:
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml
下载自定义资源:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/custom-resources.yaml
修改 custom-resources.yaml:
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: 10.244.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
应用:
kubectl create -f custom-resources.yaml
等待 Calico:
kubectl -n calico-system get pods -o wide
kubectl get tigerastatus
这次看到的 Calico 状态是:
apiserver True
calico True
goldmane True
ippools True
tiers True
whisker True
如果 tigerastatus 都是 Available,说明 Calico 这层基本正常。
先验证新 Pod DNS
切换后先创建临时 Pod 验证 DNS,不要马上判断业务问题都是 Calico 问题。
kubectl run dns-test --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/dns-test --timeout=60s
kubectl exec dns-test -- nslookup kubernetes.default.svc.cluster.local
kubectl exec dns-test -- nslookup newapi-postgres-rw.new-api.svc.cluster.local
kubectl delete pod dns-test --wait=false
这次新建的临时 Pod DNS 是正常的,可以解析:
kubernetes.default.svc.cluster.local
newapi-postgres-rw.new-api.svc.cluster.local
所以后面 new-api 里的 DNS 超时,不是 Calico 整体坏了,而是旧 Pod 和 Longhorn 状态还没恢复。
真实故障:new-api 不能访问
迁移后重启了 new-api:
kubectl -n new-api rollout restart deployment/new-api
但是访问仍然失败。
先看资源:
kubectl -n new-api get pods,svc,endpoints,ingress -o wide
当时看到:
new-api Pod CrashLoopBackOff
new-api Service endpoints 为空
newapi-postgres-rw endpoints 为空
redis-ha endpoints 为空
这说明入口不是第一现场。Ingress 可以转发,但 Service 后面没有 Ready Pod。
看 new-api 日志:
kubectl -n new-api logs deploy/new-api --tail=120 --all-containers=true
关键错误:
failed to connect to user=newapi database=newapi
lookup newapi-postgres-rw.new-api.svc.cluster.local on 10.96.0.10:53: i/o timeout
继续看 PostgreSQL、Redis 和 Longhorn:
kubectl -n new-api get pods -o wide
kubectl -n longhorn-system get pods -o wide
kubectl get csidriver
当时事件里看到:
driver name driver.longhorn.io not found in the list of registered CSI drivers
volume is not ready for workloads
Multi-Attach error
MountVolume.MountDevice failed
这说明真正卡住的是存储层。
恢复 Longhorn
先看 Longhorn 组件:
kubectl -n longhorn-system get pods -o wide
kubectl -n longhorn-system get ds,deploy -o wide
kubectl -n longhorn-system get volumes.longhorn.io -o wide
当时 longhorn-manager、longhorn-csi-plugin 都不健康,业务卷卡在:
detaching
unknown
not ready for workloads
先重启 Longhorn 控制组件:
kubectl -n longhorn-system rollout restart \
daemonset/longhorn-manager \
daemonset/longhorn-csi-plugin \
deployment/longhorn-driver-deployer \
deployment/csi-attacher \
deployment/csi-provisioner \
deployment/csi-resizer \
deployment/csi-snapshotter
等待恢复:
kubectl -n longhorn-system rollout status daemonset/longhorn-manager --timeout=180s
kubectl -n longhorn-system rollout status daemonset/longhorn-csi-plugin --timeout=180s
kubectl -n longhorn-system rollout status deployment/longhorn-driver-deployer --timeout=180s
然后继续看 instance-manager:
kubectl -n longhorn-system get pods -l longhorn.io/component=instance-manager -o wide
这次关键问题是:instance-manager 还保留旧 Pod IP,例如:
10.244.1.169
10.244.2.152
Longhorn engine 和 replica 互相访问时报:
no route to host
read: connection timed out
删除旧 instance-manager Pod,让 Longhorn 重新创建:
kubectl -n longhorn-system delete pod instance-manager-旧Pod名 --wait=false
等新 Pod 拿到 Calico 新 IP:
kubectl -n longhorn-system get pods -l longhorn.io/component=instance-manager -o wide
这次新 IP 类似:
10.244.159.x
10.244.224.x
10.244.135.x
再看卷:
kubectl -n longhorn-system get volumes.longhorn.io -o wide
目标是业务卷变成:
attached healthy
恢复 PostgreSQL 和 Redis
Longhorn 恢复后,再处理依赖 PVC 的业务。不要在 Longhorn 还卡着时反复删 Pod。
查看状态:
kubectl -n new-api get pods,svc,endpoints -o wide
kubectl -n new-api get clusters.postgresql.cnpg.io -o wide
这次在 Longhorn 恢复后,重新创建 PostgreSQL 和 Redis Pod:
kubectl -n new-api delete pod newapi-postgres-1 newapi-postgres-2 newapi-postgres-3 --wait=false
kubectl -n new-api delete pod redis-ha-node-0 redis-ha-node-1 redis-ha-node-2 --wait=false
然后等待:
kubectl -n new-api get pods -o wide
kubectl -n new-api get endpoints
kubectl -n new-api get clusters.postgresql.cnpg.io -o wide
恢复后的目标:
newapi-postgres-1 1/1 Running
newapi-postgres-2 1/1 Running
newapi-postgres-3 1/1 Running
redis-ha-node-0 2/2 Running
redis-ha-node-1 2/2 Running
redis-ha-node-2 2/2 Running
CloudNativePG 最终状态:
Cluster in healthy state
PostgreSQL 和 Redis 的 endpoints 也要恢复:
newapi-postgres-rw 10.244.x.x:5432
redis-ha 10.244.x.x:6379,26379
恢复 new-api
数据库和 Redis 恢复后,再重启代理和业务:
kubectl -n new-api rollout restart deployment/redis-master-proxy deployment/new-api
等待:
kubectl -n new-api rollout status deployment/redis-master-proxy --timeout=180s
kubectl -n new-api rollout status deployment/new-api --timeout=240s
看 endpoints:
kubectl -n new-api get pods,svc,endpoints,ingress -o wide
恢复后的目标:
new-api 3 个 Pod 都是 1/1 Running
new-api endpoints 有 3 个 Pod IP
redis-master-proxy endpoints 有 2 个 Pod IP
newapi-postgres-rw endpoints 有 primary Pod IP
验证入口:
curl -kI --connect-timeout 5 https://k8s-ai.jihw.top
curl -kI --connect-timeout 5 -H 'Host: k8s-ai.jihw.top' https://192.168.3.230
这次恢复后返回:
HTTP/2 200
x-new-api-version: v1.0.0-rc.10
恢复 Ingress
这次 new-api 恢复后,Ingress 还有旧 Pod 不 Ready,所以重启 ingress-nginx:
kubectl -n ingress-nginx rollout restart deployment/ingress-nginx-controller
kubectl -n ingress-nginx rollout status deployment/ingress-nginx-controller --timeout=180s
kubectl -n ingress-nginx get pods,endpoints -o wide
目标:
ingress-nginx-controller 3/3 Running
ingress-nginx-controller endpoints 有 3 个 Pod
恢复监控和证书组件
业务恢复不代表迁移完成。后面检查全集群 Pod 时,又发现这些旧 Pod 还在 CrashLoop:
metrics-server
cert-manager
kube-prometheus-stack-operator
kube-prometheus-stack-kube-state-metrics
它们也都是切 CNI 前创建的旧 Pod,保留旧 Pod IP 后会出现探针超时、连接 API Server 超时等问题。
滚动重启:
kubectl -n kube-system rollout restart deployment/metrics-server
kubectl -n cert-manager rollout restart deployment/cert-manager
kubectl -n monitoring rollout restart deployment/kube-prometheus-stack-operator
kubectl -n monitoring rollout restart deployment/kube-prometheus-stack-kube-state-metrics
等待:
kubectl -n kube-system rollout status deployment/metrics-server --timeout=180s
kubectl -n cert-manager rollout status deployment/cert-manager --timeout=180s
kubectl -n monitoring rollout status deployment/kube-prometheus-stack-operator --timeout=180s
kubectl -n monitoring rollout status deployment/kube-prometheus-stack-kube-state-metrics --timeout=180s
metrics-server 恢复后,验证:
kubectl top nodes
这次返回:
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
k8s-master1 611m 15% 5250Mi 67%
k8s-master2 443m 11% 3831Mi 49%
k8s-master3 447m 11% 4419Mi 56%
清理 Longhorn 其他旧 Pod
为了避免 Longhorn 后续继续被旧 IP 绊住,这次又重启了剩余 Longhorn 组件:
kubectl -n longhorn-system rollout restart daemonset/engine-image-ei-c9fa6d45
kubectl -n longhorn-system rollout restart deployment/longhorn-ui
等待:
kubectl -n longhorn-system rollout status daemonset/engine-image-ei-c9fa6d45 --timeout=180s
kubectl -n longhorn-system rollout status deployment/longhorn-ui --timeout=180s
确认所有 Longhorn Pod 都是新 IP:
kubectl -n longhorn-system get pods -o wide
最终验证
最终不要只看某个业务能不能访问,要用全集群检查收口。
所有 Pod Ready:
kubectl wait --for=condition=Ready pod --all -A --timeout=120s
所有 Deployment Available:
kubectl wait --for=condition=Available deployment --all -A --timeout=120s
检查 Deployment、StatefulSet、DaemonSet:
kubectl get deploy,sts,ds -A
检查 Longhorn 卷:
kubectl -n longhorn-system get volumes.longhorn.io -o wide
目标是业务相关卷都 healthy:
attached healthy
检查 metrics-server:
kubectl top nodes
检查业务入口:
curl -kI --connect-timeout 5 https://k8s-ai.jihw.top
curl -kI --connect-timeout 5 https://grafana.jihw.top
这次最终结果:
ALL_PODS_READY
ALL_DEPLOYMENTS_AVAILABLE
new-api: HTTP/2 200
Grafana: HTTP/2 302 -> /login
到这里才算从 Flannel 正式切到了 Calico。
常见问题
节点一直 NotReady
先看 kubelet:
sudo journalctl -u kubelet -n 100 --no-pager
再看 CNI 配置:
ls -l /etc/cni/net.d/
正常情况下应该能看到 Calico 配置,例如:
10-calico.conflist
Calico Pod 起不来
看 operator 和 Calico 组件:
kubectl -n tigera-operator get pods
kubectl -n tigera-operator logs -l k8s-app=tigera-operator --tail=100
kubectl -n calico-system get pods -o wide
kubectl -n calico-system describe pod Pod名称
kubectl get tigerastatus
新 Pod DNS 正常,旧 Pod DNS 超时
这次就遇到了这种情况。
新创建的 dns-test 可以正常解析 Service,但旧业务 Pod 仍然 DNS 超时。这通常说明不是 CoreDNS 整体坏了,而是旧 Pod、旧 CNI 网络命名空间、Longhorn instance-manager 或依赖组件没有重建干净。
处理方式是按层重启:
CoreDNS
Longhorn
数据库和 Redis
业务 Deployment
监控和证书组件
Service 没有 endpoints
先看后端 Pod 是否 Ready:
kubectl -n 命名空间 get pods,svc,endpoints -o wide
kubectl -n 命名空间 describe pod Pod名称
kubectl -n 命名空间 logs Pod名称 --tail=100
Service 没有 endpoints 通常不是 Service 自己坏了,而是 selector 匹配的 Pod 没有 Ready。
Longhorn volume 卡在 detaching
先看 Longhorn:
kubectl -n longhorn-system get volumes.longhorn.io -o wide
kubectl -n longhorn-system get pods -l longhorn.io/component=instance-manager -o wide
kubectl -n longhorn-system logs -l app=longhorn-manager -c longhorn-manager --tail=200
如果日志里有:
no route to host
read: connection timed out
并且 instance-manager 还在旧 IP,删除旧 instance-manager Pod,让 Longhorn 重建。
回滚到 Flannel
如果 Calico 安装失败,并且短时间内无法修好,可以回滚。
先删除 Calico:
kubectl delete -f custom-resources.yaml
kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml
每台节点清理 Calico CNI 残留:
sudo systemctl stop kubelet
sudo rm -f /etc/cni/net.d/*calico*
sudo ip link delete tunl0 2>/dev/null || true
sudo ip link delete vxlan.calico 2>/dev/null || true
sudo rm -rf /var/lib/cni/networks/k8s-pod-network
sudo rm -rf /var/lib/cni/results
sudo systemctl start kubelet
重新安装 Flannel:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
然后按同样思路重启 CoreDNS、Longhorn、业务和监控组件。
这次经验
如果只是刚搭好的实验集群,我更倾向于重建:
kubeadm reset
重新 kubeadm init
直接安装 Calico
重新加入其他节点
重新部署业务
如果集群里已经有 Longhorn、PostgreSQL、Redis、Ingress、Prometheus、业务服务,就不要把 CNI 迁移理解成“删 Flannel、装 Calico”。真正的工作是迁移后的恢复顺序:
Calico Ready
CoreDNS 正常
Longhorn healthy
数据库和 Redis Ready
业务 endpoints 恢复
Ingress 可访问
metrics-server 和 Prometheus 组件恢复
所有 Pod Ready
这些都验证通过,迁移才算完成。