Velero BSL Unavailable: Pod 网络转发异常排查记录
本文档记录一次 DisasterConfig 因目标集群 Velero BackupStorageLocation 不可用而进入 Error 的排查过程。该案例适用于以下场景:
- 管理集群中的同名 BSL 正常。
- 目标集群中的 BSL 为
Unavailable。 - 对象存储地址从节点宿主机可以访问,但从普通 Pod 访问超时。
- Velero pod 运行在异常节点上。
现象
DisasterConfig 状态为 Error,operator 日志中出现类似错误:
BackupStorageLocation <storage-repository>-<source-cluster> is in Unavailable status
对应 DisasterConfig.status.message 类似:
failed to apply storage repository "<storage-repository>" to target cluster "<target-cluster>":
BackupStorageLocation <bsl-name> is in Unavailable status
目标集群 Velero BSL 的错误信息为:
BackupStorageLocation "<bsl-name>" is unavailable:
operation error S3: ListObjectsV2 ...
Get "http://<s3-endpoint>/<bucket>?...": dial tcp <s3-endpoint>: i/o timeout
关键判断
该报错容易被误判为管理集群 BSL 异常。实际需要先区分 BSL 所在集群:
- 管理集群中的 BSL 可能是
Available。 DisasterConfig报错指向的是targetCluster。- operator 会把
StorageRepository应用到目标集群,并等待目标集群 Velero BSL 进入Available。 - 因此,应重点检查目标集群中的同名 BSL。
排查步骤
1. 确认 DisasterConfig 指向的集群
KUBECONFIG=<management-kubeconfig>
DC=<disasterconfig-name>
kubectl get disasterconfig.testudo.softcdata.com "$DC" \
-o jsonpath='sourceCluster={.spec.sourceCluster}{"\n"}targetCluster={.spec.targetCluster}{"\n"}storageRepository={.spec.storageRepository}{"\n"}status={.status.status}{"\n"}message={.status.message}{"\n"}'
重点看:
spec.sourceClusterspec.targetClusterspec.storageRepositorystatus.message
2. 对比管理集群和目标集群 BSL 状态
管理集群:
kubectl get backupstoragelocations.velero.io -A -o wide
目标集群:
kubectl --kubeconfig=<target-kubeconfig> \
get backupstoragelocations.velero.io -A -o wide
如果管理集群同名 BSL 为 Available,但目标集群同名 BSL 为 Unavailable,说明问题发生在目标集群。
3. 查看目标集群 BSL 的详细错误
kubectl --kubeconfig=<target-kubeconfig> \
get backupstoragelocation.velero.io -n velero <bsl-name> \
-o jsonpath='phase={.status.phase}{"\n"}message={.status.message}{"\n"}lastValidation={.status.lastValidationTime}{"\n"}'
如果 message 中出现:
dial tcp <s3-endpoint>: i/o timeout
说明 Velero 从所在运行环境访问对象存储超时。
4. 确认 Velero pod 所在节点
kubectl --kubeconfig=<target-kubeconfig> \
get pod -n velero -o wide
记录 Velero pod 所在节点,例如:
velero-xxxxx Running ... NODE=<affected-node>
5. 从普通 Pod 复现访问对象存储
强制创建普通 Pod 到 Velero 所在节点:
K=<target-kubeconfig>
NODE=<affected-node>
S3_ENDPOINT=<s3-endpoint-host>
S3_PORT=<s3-endpoint-port>
POD=netcheck-${NODE}
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
kubectl --kubeconfig=$K run $POD -n default \
--image=nginx:alpine \
--restart=Never \
--overrides="{\"spec\":{\"nodeName\":\"$NODE\"}}" \
--command -- sh -c "nc -vz -w 5 $S3_ENDPOINT $S3_PORT 2>&1 || true; wget -S -T 8 -O - http://$S3_ENDPOINT:$S3_PORT/ 2>&1 | head -n 20 || true"
kubectl --kubeconfig=$K logs -n default $POD --tail=80
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
异常时通常表现为:
nc: <s3-endpoint> (<s3-endpoint>:<port>): Operation timed out
wget: download timed out
正常时访问对象存储根路径可能返回:
HTTP/1.1 403 Forbidden
403 Forbidden 不代表网络异常,反而说明已经连到对象存储服务,只是访问根路径没有携带 S3 签名。
6. 对比 hostNetwork 访问
如果用户在节点上 ping 或 telnet 正常,但普通 Pod 访问超时,需要继续对比 hostNetwork:
POD=netcheck-${NODE}-host
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
kubectl --kubeconfig=$K run $POD -n default \
--image=nginx:alpine \
--restart=Never \
--overrides="{\"spec\":{\"nodeName\":\"$NODE\",\"hostNetwork\":true,\"dnsPolicy\":\"ClusterFirstWithHostNet\"}}" \
--command -- sh -c "nc -vz -w 5 $S3_ENDPOINT $S3_PORT 2>&1 || true; wget -S -T 8 -O - http://$S3_ENDPOINT:$S3_PORT/ 2>&1 | head -n 20 || true"
kubectl --kubeconfig=$K logs -n default $POD --tail=80
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
判断方式:
- 普通 Pod 超时,
hostNetwork正常:重点查节点上的 Pod 转发、iptables、conntrack、CNI。 - 普通 Pod 和
hostNetwork都超时:重点查节点到对象存储的主机网络、防火墙、路由或对象存储服务。
7. 检查节点 PodCIDR 和 CNI
kubectl --kubeconfig=$K get nodes \
-o custom-columns=NAME:.metadata.name,INTERNAL-IP:.status.addresses[0].address,PODCIDR:.spec.podCIDR,PODCIDRS:.spec.podCIDRs
kubectl --kubeconfig=$K get pods -A -o wide | grep -Ei 'cilium|calico|canal|flannel|rke2|kube-router|multus'
kubectl --kubeconfig=$K get networkpolicy -A
本案例中:
- CNI 为
rke2-canal。 - 没有发现
NetworkPolicy。 - 异常节点上的普通 Pod 访问其他节点 IP 超时。
8. 检查节点 sysctl 差异
可以使用临时 privileged Pod 检查宿主机 sysctl:
NODE=<affected-node>
POD=hostcheck-${NODE}-sysctl
cat <<EOF | kubectl --kubeconfig=$K apply -f -
apiVersion: v1
kind: Pod
metadata:
name: $POD
namespace: default
spec:
nodeName: $NODE
hostNetwork: true
hostPID: true
restartPolicy: Never
containers:
- name: check
image: busybox:1.36
securityContext:
privileged: true
command:
- sh
- -c
- |
chroot /host sh -c '
echo node=$(hostname)
for i in all default eth0 flannel.1; do
sysctl net.ipv4.conf.$i.forwarding net.ipv4.conf.$i.rp_filter 2>/dev/null || true
done
cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null || true
cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null || true
'
volumeMounts:
- name: host
mountPath: /host
readOnly: true
volumes:
- name: host
hostPath:
path: /
type: Directory
EOF
kubectl --kubeconfig=$K wait -n default --for=jsonpath='{.status.phase}'=Succeeded pod/$POD --timeout=60s
kubectl --kubeconfig=$K logs -n default $POD --tail=120
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
本案例中,异常节点与正常节点的关键差异为:
正常节点: net.ipv4.conf.eth0.forwarding = 1
异常节点: net.ipv4.conf.eth0.forwarding = 0
9. 使用抓包确认回包未转发
可以在异常节点启动临时抓包 Pod:
NODE=<affected-node>
CAP=tcpdump-${NODE}
cat <<EOF | kubectl --kubeconfig=$K apply -f -
apiVersion: v1
kind: Pod
metadata:
name: $CAP
namespace: default
spec:
nodeName: $NODE
hostNetwork: true
restartPolicy: Never
containers:
- name: tcpdump
image: nicolaka/netshoot:latest
securityContext:
privileged: true
command:
- sh
- -c
- timeout 20 tcpdump -i any -nn -c 30 "host $S3_ENDPOINT and tcp port $S3_PORT"
EOF
kubectl --kubeconfig=$K wait -n default --for=condition=Ready pod/$CAP --timeout=120s
同时从普通 Pod 发起访问。如果抓包看到:
Pod -> 节点 eth0 -> 对象存储: SYN
对象存储 -> 节点 eth0: SYN/ACK
Pod 持续重传 SYN
说明包已经出节点,对端也有回包,但回包没有被节点正确转发回 Pod。此时重点检查节点 forwarding、conntrack、iptables/CNI 数据面。
根因
异常节点的宿主机接口转发被关闭:
net.ipv4.conf.eth0.forwarding = 0
这导致普通 Pod 经过该节点访问其他节点 IP 或对象存储时,返回包无法正确转发回 Pod。宿主机直接 ping、telnet 正常,但普通 Pod 访问超时。
临时修复
在异常节点上将 eth0.forwarding 打开:
sysctl -w net.ipv4.conf.eth0.forwarding=1
如果不能直接登录节点,也可以通过临时 privileged Pod 执行:
NODE=<affected-node>
POD=fix-${NODE}-eth0-forwarding
cat <<EOF | kubectl --kubeconfig=$K apply -f -
apiVersion: v1
kind: Pod
metadata:
name: $POD
namespace: default
spec:
nodeName: $NODE
hostNetwork: true
hostPID: true
restartPolicy: Never
containers:
- name: fix
image: busybox:1.36
securityContext:
privileged: true
command:
- sh
- -c
- |
chroot /host sh -c '
echo before=$(cat /proc/sys/net/ipv4/conf/eth0/forwarding)
sysctl -w net.ipv4.conf.eth0.forwarding=1
echo after=$(cat /proc/sys/net/ipv4/conf/eth0/forwarding)
'
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath:
path: /
type: Directory
EOF
kubectl --kubeconfig=$K wait -n default --for=jsonpath='{.status.phase}'=Succeeded pod/$POD --timeout=60s
kubectl --kubeconfig=$K logs -n default $POD --tail=50
kubectl --kubeconfig=$K delete pod -n default $POD --ignore-not-found
修复验证
重新从异常节点上的普通 Pod 访问对象存储:
kubectl --kubeconfig=$K run netcheck-after-fix -n default \
--image=nginx:alpine \
--restart=Never \
--overrides="{\"spec\":{\"nodeName\":\"$NODE\"}}" \
--command -- sh -c "nc -vz -w 5 $S3_ENDPOINT $S3_PORT 2>&1 || true; wget -S -T 8 -O - http://$S3_ENDPOINT:$S3_PORT/ 2>&1 | head -n 20 || true"
kubectl --kubeconfig=$K logs -n default netcheck-after-fix --tail=80
kubectl --kubeconfig=$K delete pod -n default netcheck-after-fix --ignore-not-found
期望结果:
<s3-endpoint> (<s3-endpoint>:<port>) open
HTTP/1.1 403 Forbidden
再确认目标集群 BSL:
kubectl --kubeconfig=$K \
get backupstoragelocation.velero.io -n velero <bsl-name> \
-o jsonpath='phase={.status.phase}{"\n"}message={.status.message}{"\n"}lastValidation={.status.lastValidationTime}{"\n"}'
期望:
phase=Available
message=
最后确认管理集群中的 DisasterConfig:
kubectl --kubeconfig=<management-kubeconfig> \
get disasterconfig.testudo.softcdata.com <disasterconfig-name> \
-o jsonpath='status={.status.status}{"\n"}reason={.status.reason}{"\n"}message={.status.message}{"\n"}'
期望:
status=Ready
reason=
message=
持久化建议
临时 sysctl -w 在节点重启或网络服务重载后可能失效。建议在异常节点上持久化:
cat >/etc/sysctl.d/99-k8s-forwarding.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.forwarding = 1
net.ipv4.conf.eth0.forwarding = 1
EOF
sysctl --system
确认:
sysctl net.ipv4.conf.eth0.forwarding
期望:
net.ipv4.conf.eth0.forwarding = 1
本次排查结论
本次故障并非 StorageRepository 凭证错误,也不是管理集群 BSL 异常。根因是目标集群中 Velero 所在节点的普通 Pod 网络转发异常:
- 宿主机网络访问对象存储正常。
hostNetworkPod 访问对象存储正常。- 普通 Pod 访问对象存储超时。
- 抓包显示对象存储已经回包,但异常节点未将回包转发回 Pod。
- 异常节点
net.ipv4.conf.eth0.forwarding=0,正常节点为1。 - 将异常节点
eth0.forwarding改为1后,普通 Pod 访问恢复,目标集群 BSL 恢复为Available,管理集群DisasterConfig恢复为Ready。