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 内正在运行的容器中执行命令。这对于检查日志、检查配置以及直接在应用所在位置运行诊断工具非常有用。

在生产环境中要谨慎使用。你是在真实的工作负载容器中运行命令。像 envcatcurlss 这样的无害命令是正常的。在实时 Pod 中安装包、更改文件或运行昂贵的诊断操作可能会掩盖原始问题或引发新问题。

基本语法

kubectl exec 的基本语法是:

kubectl exec <pod-name> -- <command> [args...]
  • <pod-name>:要执行命令的 Pod 名称。
  • --:这个分隔符至关重要。它区分了 kubectl 标志和要在容器内运行的命令。
  • <command>:要在容器内执行的命令(例如 lscatping)。
  • [args...]:命令的任何参数。

交互式 Shell 访问

kubectl exec 最常见的用途之一是获取容器内的交互式 Shell(如 bashsh)。这允许你探索容器的文件系统并运行多个命令。

要获取交互式 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-podsidecar-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 的解决方案:
    1. 获取 Pod 的交互式 Shell:kubectl exec -it <pod-name> -- /bin/bash
    2. 在 Shell 中,检查应用日志(例如 tail -f /var/log/myapp.log)。
    3. 验证应用的内部配置文件。
    4. 使用 pingcurl(如果已安装)检查 Pod 内到其他服务的网络连接。
  • 使用 kubectl port-forward 的解决方案:
    1. 将端口转发到应用的监听端口:kubectl port-forward <pod-name> 8080:<app-port>
    2. 尝试通过 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 的解决方案:
    1. 确定运行数据库的 Pod 及其端口(例如 mysql-pod,端口 3306)。
    2. 将本地端口转发到数据库端口:kubectl port-forward mysql-pod 3306:3306
    3. 配置你的本地数据库客户端,使用适当的数据库凭据连接到 localhost:3306

场景 3:诊断 Pod 内的 DNS 解析问题。

  • 问题: Pod 中的应用无法通过服务名称访问其他服务,表明存在 DNS 问题。
  • 使用 kubectl exec 的解决方案:
    1. 获取 Pod 的交互式 Shell:kubectl exec -it <pod-name> -- /bin/bash
    2. 在 Shell 中,尝试解析已知的服务名称:nslookup <service-name>.<namespace>.svc.cluster.localdig <service-name>.<namespace>.svc.cluster.local
    3. 检查 /etc/resolv.conf 的内容,确保 Pod 内集群的 DNS 配置正确。

如果镜像不包含 nslookupdigcurl,请在同一个命名空间中使用临时调试 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 无法解析,请在归咎于应用之前先测试完全限定的服务名称。

简单的连接阶梯

当请求失败时,从最近到最远进行测试:

  1. 在 Pod 内部,测试本地应用进程:
    kubectl exec <pod> -n <namespace> -- curl -v http://127.0.0.1:<port>/health
    
  2. 从同一命名空间中的另一个 Pod,测试 Service:
    kubectl run curl-test -n <namespace> --rm -it --image=curlimages/curl -- curl -v http://<service>:<port>/health
    
  3. 从你的笔记本电脑,通过端口转发测试:
    kubectl port-forward service/<service> -n <namespace> 8080:<service-port>
    curl -v http://localhost:8080/health
    
  4. 只有这些测试通过后,再向外移动到 Ingress、负载均衡器、DNS 和防火墙规则。

这个顺序可以节省时间,因为每一步都证明了一个层面。如果 Pod 内的 localhost 失败,那么 Ingress 就无关紧要。如果 Pod 本地成功但 Service 访问失败,则应用可能没问题,需要关注 Kubernetes 服务发现。

还有一个习惯很有帮助:在调试时明确指定命名空间。许多令人困惑的会话源于在默认命名空间中运行 kubectl exec,而故障工作负载却在其他地方。要么在会话的上下文中设置命名空间,要么在每个命令中添加 -n <namespace>。多打几个字比测试错误的 Pod 代价要小。

另外,保存证明故障的确切命令。下一个值班人员可以重新运行它,而无需根据记忆重建你的上下文。

最佳实践和技巧

  • 保持 port-forward 运行: kubectl port-forward 在前台运行。你需要保持终端窗口打开。要在后台运行它,可以使用 nohupscreen/tmux 等工具。
  • 调试时使用特定 Pod: 虽然转发到 Service 很方便,但对于定位特定实例的问题,使用 Pod 名称转发到特定 Pod 通常更有效。
  • 安全性: 注意你暴露的端口。除非绝对必要,否则避免转发敏感端口,并确保你的本地机器是安全的。
  • 资源使用: kubectl exec 会消耗资源。请谨慎使用,尤其是在生产集群上。
  • 权限: 确保你的 kubectl 上下文具有在 Pod 中执行命令或转发端口的必要权限。

修复后应记录的内容

良好的调试会留下痕迹。记录失败的 URL、源 Pod、目标服务、命名空间、重现问题的确切命令以及失败所在的层面。“连接问题”太模糊,对下次没有帮助。“标签重命名后 Service 选择器与 Pod 不匹配”是一个可修复的模式。