跳到主要内容

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 所在集群:

  1. 管理集群中的 BSL 可能是 Available
  2. DisasterConfig 报错指向的是 targetCluster
  3. operator 会把 StorageRepository 应用到目标集群,并等待目标集群 Velero BSL 进入 Available
  4. 因此,应重点检查目标集群中的同名 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.sourceCluster
  • spec.targetCluster
  • spec.storageRepository
  • status.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 访问

如果用户在节点上 pingtelnet 正常,但普通 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。宿主机直接 pingtelnet 正常,但普通 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 网络转发异常:

  • 宿主机网络访问对象存储正常。
  • hostNetwork Pod 访问对象存储正常。
  • 普通 Pod 访问对象存储超时。
  • 抓包显示对象存储已经回包,但异常节点未将回包转发回 Pod。
  • 异常节点 net.ipv4.conf.eth0.forwarding=0,正常节点为 1
  • 将异常节点 eth0.forwarding 改为 1 后,普通 Pod 访问恢复,目标集群 BSL 恢复为 Available,管理集群 DisasterConfig 恢复为 Ready