Kubernetes 连接故障排查:高效使用 exec 和 port-forward
自信地排查 Kubernetes 连接和内部应用问题。本指南提供了使用 `kubectl exec` 在容器内运行命令以及使用 `kubectl port-forward` 从本地机器安全访问服务的实用示例。学习如何诊断网络问题、检查配置,并深入了解集群中应用的行为。
Kubernetes 连接故障排查:高效使用 exec 和 port-forward
当 Kubernetes 服务超时时,在修复任何问题之前,通常需要回答一个简单的问题:连接在哪里中断?kubectl exec 允许你从集群内部、靠近应用的位置进行测试。kubectl port-forward 则让你无需更改 Ingress、LoadBalancer、防火墙规则或 DNS 记录,即可将某个 Pod 或服务带回你的笔记本电脑。
结合使用这两个命令,你可以避免猜测。你可以测试应用是否在监听、集群 DNS 是否工作、Service 是否指向正确的 Pod,以及问题是在 Kubernetes 网络层面还是应用本身。
理解 kubectl exec
kubectl exec 命令允许你在 Pod 内正在运行的容器中执行命令。这对于检查日志、检查配置以及直接在应用所在位置运行诊断工具非常有用。
在生产环境中要谨慎使用。你是在真实的工作负载容器中运行命令。像 env、cat、curl 或 ss 这样的无害命令是正常的。在实时 Pod 中安装包、更改文件或运行昂贵的诊断操作可能会掩盖原始问题或引发新问题。
基本语法
kubectl exec 的基本语法是:
kubectl exec <pod-name> -- <command> [args...]
<pod-name>:要执行命令的 Pod 名称。--:这个分隔符至关重要。它区分了kubectl标志和要在容器内运行的命令。<command>:要在容器内执行的命令(例如ls、cat、ping)。[args...]:命令的任何参数。
交互式 Shell 访问
kubectl exec 最常见的用途之一是获取容器内的交互式 Shell(如 bash 或 sh)。这允许你探索容器的文件系统并运行多个命令。
要获取交互式 Shell:
kubectl exec -it <pod-name> -- /bin/bash
-i(或--stdin):保持 stdin 打开,即使未连接。-t(或--tty):分配一个伪 TTY,这对于交互式 Shell 会话是必需的。
示例: 访问名为 my-app-pod 的 Pod 中的 bash shell:
kubectl exec -it my-app-pod -- /bin/bash
进入后,你可以使用标准的 Linux 命令。要退出 Shell,输入 exit 或按 Ctrl+D。如果 /bin/bash 不存在,请尝试 /bin/sh;许多小型镜像不包含 Bash。
执行单个命令
你也可以在不使用交互式 Shell 的情况下执行单个命令。这对于快速检查或脚本编写很有用。
示例: 检查 my-app-pod 中 /app 目录的文件:
kubectl exec my-app-pod -- ls /app
示例: 查看配置文件 config.yaml 的内容:
kubectl exec my-app-pod -- cat /etc/my-app/config.yaml
指定 Pod 内的容器
如果你的 Pod 有多个容器,你需要使用 -c 标志指定要在哪个容器中执行命令。
kubectl exec <pod-name> -c <container-name> -- <command>
示例: 在 multi-container-pod 的 sidecar-container 中执行 env:
kubectl exec multi-container-pod -c sidecar-container -- env
理解 kubectl port-forward
kubectl port-forward 命令允许你建立从本地机器到 Kubernetes 集群中特定 Pod 或 Service 的安全隧道。这对于调试未对外暴露的应用、访问数据库或测试内部 API 非常宝贵。
它不是生产流量路径。它是通过 Kubernetes API 连接建立的调试隧道。如果 API 服务器连接断开,你的本地隧道也会断开。
基本语法
一般语法是:
kubectl port-forward <pod-name> <local-port>:<remote-port>
<pod-name>:要连接的 Pod 名称。<local-port>:本地机器上监听连接的端口。<remote-port>:Pod 上接收转发流量的端口。
示例: 将本地端口 8080 转发到 my-app-pod 的端口 80:
kubectl port-forward my-app-pod 8080:80
此命令运行后,你可以通过导航到 http://localhost:8080 或使用本地机器上的 curl 等工具来访问你的应用。
转发到 Service
你也可以将流量转发到 Kubernetes Service 而不是特定的 Pod。kubectl 将自动选择一个支持该 Service 的 Pod。
kubectl port-forward service/<service-name> <local-port>:<service-port>
示例: 将本地端口 3000 转发到 my-service Service 的端口 80:
kubectl port-forward service/my-service 3000:80
当转发到 Service 时,请记住 kubectl 只选择一个后端 Pod。如果只有一个副本出现故障,Service 级别的转发可能会错过它。例如,一个具有三个 Pod 的 Deployment 可能有两个健康的 Pod 和一个配置错误的 Pod。转发到 service/my-service 可能会选择一个健康的 Pod,从而使 Service 看起来正常。当你怀疑是特定副本的问题时,请转发到确切的 Pod 名称。
转发到 Deployment 或 StatefulSet
类似地,你可以转发到 Deployment 或 StatefulSet。kubectl 将选择由指定资源管理的其中一个 Pod。
kubectl port-forward deployment/<deployment-name> <local-port>:<container-port>
kubectl port-forward statefulset/<statefulset-name> <local-port>:<container-port>
绑定到特定地址
默认情况下,port-forward 绑定到 localhost。你可以使用 --address 标志指定不同的本地地址。
kubectl port-forward --address 127.0.0.1 <pod-name> <local-port>:<remote-port>
多端口转发
kubectl port-forward 可以同时转发多个端口。
kubectl port-forward my-app-pod 8080:80 9090:90
此命令将本地端口 8080 转发到 Pod 端口 80,并将本地端口 9090 转发到 Pod 端口 90。
常见故障排查场景及解决方案
场景 1:应用无响应,但 Pod 看起来健康。
- 问题: Pod 正在运行,但对其服务的请求失败或超时。应用可能存在内部配置问题或卡住。
- 使用
kubectl exec的解决方案:- 获取 Pod 的交互式 Shell:
kubectl exec -it <pod-name> -- /bin/bash - 在 Shell 中,检查应用日志(例如
tail -f /var/log/myapp.log)。 - 验证应用的内部配置文件。
- 使用
ping或curl(如果已安装)检查 Pod 内到其他服务的网络连接。
- 获取 Pod 的交互式 Shell:
- 使用
kubectl port-forward的解决方案:- 将端口转发到应用的监听端口:
kubectl port-forward <pod-name> 8080:<app-port> - 尝试通过
http://localhost:8080本地访问应用。这有助于确定问题是出在 Kubernetes 服务发现或 Ingress,还是应用本身无响应。
- 将端口转发到应用的监听端口:
如果端口转发有效但正常的服务 URL 失败,请检查 Service 选择器和端点:
kubectl get service <service-name> -n <namespace> -o yaml
kubectl get endpoints <service-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels
一个非常常见的故障是标签不匹配。Pod 是健康的,Service 也存在,但选择器与 Pod 标签不匹配,因此 Service 没有端点。
场景 2:需要调试在 Pod 中运行的数据库。
- 问题: 你需要将本地数据库客户端连接到在 Kubernetes Pod 内运行的数据库,以检查数据或运行查询。
- 使用
kubectl port-forward的解决方案:- 确定运行数据库的 Pod 及其端口(例如
mysql-pod,端口3306)。 - 将本地端口转发到数据库端口:
kubectl port-forward mysql-pod 3306:3306 - 配置你的本地数据库客户端,使用适当的数据库凭据连接到
localhost:3306。
- 确定运行数据库的 Pod 及其端口(例如
场景 3:诊断 Pod 内的 DNS 解析问题。
- 问题: Pod 中的应用无法通过服务名称访问其他服务,表明存在 DNS 问题。
- 使用
kubectl exec的解决方案:- 获取 Pod 的交互式 Shell:
kubectl exec -it <pod-name> -- /bin/bash - 在 Shell 中,尝试解析已知的服务名称:
nslookup <service-name>.<namespace>.svc.cluster.local或dig <service-name>.<namespace>.svc.cluster.local。 - 检查
/etc/resolv.conf的内容,确保 Pod 内集群的 DNS 配置正确。
- 获取 Pod 的交互式 Shell:
如果镜像不包含 nslookup、dig 或 curl,请在同一个命名空间中使用临时调试 Pod:
kubectl run net-debug -n <namespace> --rm -it --image=curlimages/curl -- sh
在那里,测试你的应用使用的相同服务名称。这有助于区分“我的应用镜像没有工具”和“集群 DNS 损坏”。
场景 4:端口转发连接成功,然后立即关闭。
- 问题:
kubectl port-forward打印转发消息,但当你打开浏览器或运行curl时连接关闭。 - 可能的原因: 目标进程未在远程端口上监听,应用仅在容器内绑定到
127.0.0.1,或者 Pod 在隧道打开时重启。 - 检查:
kubectl exec <pod-name> -n <namespace> -- ss -lntp kubectl get pod <pod-name> -n <namespace> -w kubectl logs <pod-name> -n <namespace> --tail=100
如果进程在端口 8080 上监听,但你转发到 80,则隧道本身没问题;是目标错误。如果 Pod 在你测试时重启,请先修复重启原因,再追查网络问题。
场景 5:服务从一个 Pod 工作,但从另一个 Pod 不工作。
- 问题: 后端可以访问 Redis,但另一个命名空间中的 Worker Pod 无法访问。
- 使用
exec检查:kubectl exec <source-pod> -n <source-namespace> -- curl -v http://<service>.<target-namespace>.svc.cluster.local:<port> kubectl exec <source-pod> -n <source-namespace> -- cat /etc/resolv.conf
如果 DNS 解析成功但 TCP 失败,请检查 NetworkPolicy、服务端点以及目标应用是否接受来自该命名空间的流量。如果 DNS 无法解析,请在归咎于应用之前先测试完全限定的服务名称。
简单的连接阶梯
当请求失败时,从最近到最远进行测试:
- 在 Pod 内部,测试本地应用进程:
kubectl exec <pod> -n <namespace> -- curl -v http://127.0.0.1:<port>/health - 从同一命名空间中的另一个 Pod,测试 Service:
kubectl run curl-test -n <namespace> --rm -it --image=curlimages/curl -- curl -v http://<service>:<port>/health - 从你的笔记本电脑,通过端口转发测试:
kubectl port-forward service/<service> -n <namespace> 8080:<service-port> curl -v http://localhost:8080/health - 只有这些测试通过后,再向外移动到 Ingress、负载均衡器、DNS 和防火墙规则。
这个顺序可以节省时间,因为每一步都证明了一个层面。如果 Pod 内的 localhost 失败,那么 Ingress 就无关紧要。如果 Pod 本地成功但 Service 访问失败,则应用可能没问题,需要关注 Kubernetes 服务发现。
还有一个习惯很有帮助:在调试时明确指定命名空间。许多令人困惑的会话源于在默认命名空间中运行 kubectl exec,而故障工作负载却在其他地方。要么在会话的上下文中设置命名空间,要么在每个命令中添加 -n <namespace>。多打几个字比测试错误的 Pod 代价要小。
另外,保存证明故障的确切命令。下一个值班人员可以重新运行它,而无需根据记忆重建你的上下文。
最佳实践和技巧
- 保持
port-forward运行:kubectl port-forward在前台运行。你需要保持终端窗口打开。要在后台运行它,可以使用nohup或screen/tmux等工具。 - 调试时使用特定 Pod: 虽然转发到 Service 很方便,但对于定位特定实例的问题,使用 Pod 名称转发到特定 Pod 通常更有效。
- 安全性: 注意你暴露的端口。除非绝对必要,否则避免转发敏感端口,并确保你的本地机器是安全的。
- 资源使用:
kubectl exec会消耗资源。请谨慎使用,尤其是在生产集群上。 - 权限: 确保你的
kubectl上下文具有在 Pod 中执行命令或转发端口的必要权限。
修复后应记录的内容
良好的调试会留下痕迹。记录失败的 URL、源 Pod、目标服务、命名空间、重现问题的确切命令以及失败所在的层面。“连接问题”太模糊,对下次没有帮助。“标签重命名后 Service 选择器与 Pod 不匹配”是一个可修复的模式。