调试服务

Kubernetes 新安装中经常出现的一个问题是服务无法正常工作。您已通过 Deployment(或其他工作负载控制器)运行 Pod 并创建了一个服务,但在尝试访问它时没有收到任何响应。本文档将帮助您找出问题所在。

在 Pod 中运行命令

对于许多步骤,您需要查看集群中运行的 Pod 所见的内容。最简单的方法是运行一个交互式的 busybox Pod

kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh

如果您已经有一个正在运行的 Pod 且您更喜欢使用它,则可以使用以下命令在其中运行命令

kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>

设置

为了便于此演练,让我们运行一些 Pod。由于您可能正在调试自己的服务,因此您可以替换自己的详细信息,或者您可以按照步骤操作并获得第二个数据点。

kubectl create deployment hostnames --image=registry.k8s.io/serve_hostname
deployment.apps/hostnames created

kubectl 命令将打印创建或修改的资源的类型和名称,这些名称可用于后续命令。

让我们将 Deployment 的副本数量扩展到 3 个。

kubectl scale deployment hostnames --replicas=3
deployment.apps/hostnames scaled

请注意,这与您使用以下 YAML 启动 Deployment 相同

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: registry.k8s.io/serve_hostname

标签 "app" 由 kubectl create deployment 自动设置为 Deployment 的名称。

您可以确认您的 Pod 是否正在运行

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          2m
hostnames-632524106-ly40y   1/1       Running   0          2m
hostnames-632524106-tlaok   1/1       Running   0          2m

您还可以确认您的 Pod 是否正在提供服务。您可以获取 Pod IP 地址列表并直接测试它们。

kubectl get pods -l app=hostnames \
    -o go-template='{{range .items}}{{.status.podIP}}{{"\n"}}{{end}}'
10.244.0.5
10.244.0.6
10.244.0.7

用于此演练的示例容器通过端口 9376 上的 HTTP 提供自己的主机名,但如果您正在调试自己的应用程序,则需要使用 Pod 监听的端口号。

从 Pod 内部

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

这应该产生类似以下内容

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

如果您此时没有收到预期的响应,则您的 Pod 可能不正常或可能没有监听您认为它们正在监听的端口。您可能会发现 kubectl logs 有助于查看正在发生的事情,或者您可能需要 kubectl exec 直接进入您的 Pod 并从那里进行调试。

假设到目前为止一切顺利,您可以开始调查为什么您的服务无法正常工作。

服务是否存在?

细心的读者会注意到您实际上还没有创建服务 - 这是故意的。这有时会被人遗忘,并且是第一个要检查的事情。

如果您尝试访问一个不存在的服务会发生什么?如果您有另一个通过名称使用此服务的 Pod,您将获得类似以下内容

wget -O- hostnames
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'

首先要检查的是服务是否存在

kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found

让我们创建服务。如前所述,这是为了演练 - 您可以在此处使用您自己的服务的详细信息。

kubectl expose deployment hostnames --port=80 --target-port=9376
service/hostnames exposed

并读回它

kubectl get svc hostnames
NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
hostnames   ClusterIP   10.0.1.175   <none>        80/TCP    5s

现在您知道服务存在。

如前所述,这与您使用 YAML 启动服务相同

apiVersion: v1
kind: Service
metadata:
  labels:
    app: hostnames
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

为了突出显示所有配置范围,您在此处创建的服务使用的端口号与 Pod 不同。对于许多真实世界服务,这些值可能相同。

是否有任何影响目标 Pod 的网络策略入站规则?

如果您部署了任何可能影响传入 hostnames-* Pod 流量的网络策略入站规则,则需要查看这些规则。

有关更多详细信息,请参阅 网络策略

服务是否可以通过 DNS 名称工作?

客户使用服务的常见方法之一是通过 DNS 名称。

从同一个命名空间中的 Pod

nslookup hostnames
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果失败,则可能是您的 Pod 和服务在不同的命名空间中,请尝试使用命名空间限定名称(同样,从 Pod 内部)

nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

如果这有效,则需要调整您的应用程序以使用跨命名空间名称,或在同一个命名空间中运行您的应用程序和服务。如果仍然失败,请尝试使用完全限定名称

nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local

请注意这里的后缀:"default.svc.cluster.local"。 "default" 是您正在操作的命名空间。 "svc" 表示这是一个服务。 "cluster.local" 是您的集群域,它在您自己的集群中可能不同。

您也可以从集群中的节点尝试此操作

nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server:         10.0.0.10
Address:        10.0.0.10#53

Name:   hostnames.default.svc.cluster.local
Address: 10.0.1.175

如果您能够进行完全限定名称查找但不能进行相对名称查找,则需要检查 Pod 中的 /etc/resolv.conf 文件是否正确。从 Pod 内部

cat /etc/resolv.conf

您应该看到类似以下内容

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

nameserver 行必须指示您的集群的 DNS 服务。这会通过 --cluster-dns 标志传递到 kubelet 中。

search 行必须包含一个适当的后缀,以便您找到服务名称。在本例中,它正在查找本地命名空间中的服务 ("default.svc.cluster.local")、所有命名空间中的服务 ("svc.cluster.local") 以及最后是集群中的名称 ("cluster.local")。根据您自己的安装,您之后可能还有其他记录(最多 6 个)。集群后缀通过 --cluster-domain 标志传递到 kubelet 中。在本文档中,集群后缀假定为 "cluster.local"。您自己的集群可能配置不同,在这种情况下,您应该在所有前面的命令中进行更改。

options 行必须设置 ndots 的值足够高,以便您的 DNS 客户端库考虑搜索路径。Kubernetes 默认将此值设置为 5,这对于涵盖其生成的所有 DNS 名称来说已经足够高。

任何服务是否可以通过 DNS 名称工作?

如果上述操作仍然失败,则您的服务无法进行 DNS 查找。您可以退一步,查看还有什么问题。Kubernetes 主服务始终应该有效。从 Pod 内部

nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

如果失败,请参阅本文档的 kube-proxy 部分,或者甚至回到本文档的顶部重新开始,但不要调试您自己的服务,而是调试 DNS 服务。

服务是否可以通过 IP 地址工作?

假设您已确认 DNS 工作,接下来要测试的是您的服务是否可以通过其 IP 地址工作。从集群中的 Pod,访问服务的 IP(从上面的 kubectl get)。

for i in $(seq 1 3); do 
    wget -qO- 10.0.1.175:80
done

这应该产生类似以下内容

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

如果您的服务正在工作,您应该获得正确的响应。如果没有,则可能有多种问题。继续阅读。

服务是否正确定义?

这听起来可能很愚蠢,但您确实应该反复检查您的服务是否正确,并且与您的 Pod 的端口相匹配。读回您的服务并验证它

kubectl get service hostnames -o json
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "hostnames",
        "namespace": "default",
        "uid": "428c8b6c-24bc-11e5-936d-42010af0a9bc",
        "resourceVersion": "347189",
        "creationTimestamp": "2015-07-07T15:24:29Z",
        "labels": {
            "app": "hostnames"
        }
    },
    "spec": {
        "ports": [
            {
                "name": "default",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 0
            }
        ],
        "selector": {
            "app": "hostnames"
        },
        "clusterIP": "10.0.1.175",
        "type": "ClusterIP",
        "sessionAffinity": "None"
    },
    "status": {
        "loadBalancer": {}
    }
}
  • 您要尝试访问的服务端口是否列在 spec.ports[] 中?
  • targetPort 是否与您的 Pod 相匹配(某些 Pod 使用的服务端口与服务端口不同)?
  • 如果您想使用数字端口,它是一个数字 (9376) 还是一个字符串 "9376"?
  • 如果您想使用命名端口,您的 Pod 是否公开了一个具有相同名称的端口?
  • 端口的 protocol 是否与您的 Pod 相匹配?

服务是否有任何端点?

如果您已经走到这里,您已经确认您的服务已正确定义,并且可以通过 DNS 解析。现在让我们检查一下您运行的 Pod 是否确实被服务选中。

之前您看到 Pod 正在运行。您可以重新检查一下

kubectl get pods -l app=hostnames
NAME                        READY     STATUS    RESTARTS   AGE
hostnames-632524106-bbpiw   1/1       Running   0          1h
hostnames-632524106-ly40y   1/1       Running   0          1h
hostnames-632524106-tlaok   1/1       Running   0          1h

-l app=hostnames 参数是服务上配置的标签选择器。

"AGE" 列表示这些 Pod 大约一小时前启动,这意味着它们运行良好且没有崩溃。

"RESTARTS" 列表示这些 Pod 没有频繁崩溃或重启。频繁重启会导致间歇性连接问题。如果重启次数很高,请阅读有关如何 调试 Pod 的更多信息。

Kubernetes 系统内部有一个控制循环,它会评估每个服务的标签选择器并将结果保存到相应的端点对象中。

kubectl get endpoints hostnames

NAME        ENDPOINTS
hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376

这确认端点控制器已找到您的服务的正确 Pod。如果 ENDPOINTS 列为 <none>,则应该检查您的服务的 spec.selector 字段是否确实为 Pod 的 metadata.labels 值选择。一个常见的错误是输入错误或其他错误,例如服务选择 app=hostnames,但 Deployment 指定 run=hostnames,如 1.18 之前的版本,其中 kubectl run 命令也可以用于创建 Deployment。

Pod 是否正常工作?

此时,您知道您的服务正在运行并且已选择您的 Pod。在本文档的开头,您验证了 Pod 本身。让我们再次检查一下 Pod 是否确实正常工作 - 您可以绕过服务机制,直接进入 Pod,如上面的端点所示。

从 Pod 内部

for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
    wget -qO- $ep
done

这应该产生类似以下内容

hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok

您希望端点列表中的每个 Pod 都返回自己的主机名。如果不是这种情况(或者您自己的 Pod 的正确行为是什么),您应该调查一下发生了什么。

kube-proxy 是否正常工作?

如果您已经走到这里,则您的服务正在运行,有端点,并且您的 Pod 确实正在提供服务。此时,整个服务代理机制都值得怀疑。让我们确认一下,一步一步来。

服务的默认实现(也是大多数集群使用的实现)是 kube-proxy。这是一个在每个节点上运行的程序,它配置一组小型机制来提供服务抽象。如果您的集群没有使用 kube-proxy,则以下部分将不适用,您将不得不调查您正在使用的服务实现。

kube-proxy 是否正在运行?

确认 kube-proxy 是否正在您的节点上运行。直接在节点上运行,您应该获得类似以下内容

ps auxw | grep kube-proxy
root  4194  0.4  0.1 101864 17696 ?    Sl Jul04  25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2

接下来,确认它没有出现明显故障,例如联系主节点。为此,您需要查看日志。访问日志的方式取决于您的节点操作系统。在某些操作系统上,它是文件,例如 /var/log/kube-proxy.log,而在其他操作系统上,则使用 journalctl 访问日志。您应该看到类似以下内容

I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP

如果您看到有关无法联系主节点的错误消息,则应仔细检查您的 Node 配置和安装步骤。

kube-proxy 无法正常运行的可能原因之一是找不到所需的 conntrack 二进制文件。这可能在某些 Linux 系统上发生,具体取决于您安装集群的方式,例如,您是从头开始安装 Kubernetes。如果是这种情况,您需要手动安装 conntrack 包(例如,在 Ubuntu 上使用 sudo apt install conntrack),然后重试。

Kube-proxy 可以运行在几种模式之一中。在上面列出的日志中,Using iptables Proxier 行表明 kube-proxy 正在“iptables”模式下运行。最常见的其他模式是“ipvs”。

Iptables 模式

在“iptables”模式下,您应该在 Node 上看到类似以下内容

iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR

对于每个服务的每个端口,在 KUBE-SERVICES 中应该有一个规则,并且在 KUBE-SVC-<hash> 链中有一个规则。对于每个 Pod 端点,在该 KUBE-SVC-<hash> 中应该有少量规则,并且在 KUBE-SEP-<hash> 链中应该有少量规则。确切的规则将根据您的确切配置(包括节点端口和负载均衡器)而有所不同。

IPVS 模式

在“ipvs”模式下,您应该在 Node 上看到类似以下内容

ipvsadm -ln
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
...
TCP  10.0.1.175:80 rr
  -> 10.244.0.5:9376               Masq    1      0          0
  -> 10.244.0.6:9376               Masq    1      0          0
  -> 10.244.0.7:9376               Masq    1      0          0
...

对于每个服务的每个端口,加上任何 NodePorts、外部 IP 和负载均衡器 IP,kube-proxy 将创建一个虚拟服务器。对于每个 Pod 端点,它将创建相应的真实服务器。在此示例中,服务主机名 (10.0.1.175:80) 有 3 个端点 (10.244.0.5:937610.244.0.6:937610.244.0.7:9376)。

Kube-proxy 是否正在代理?

假设您确实看到了上述情况之一,请尝试再次从其中一个 Node 的 IP 访问您的服务

curl 10.0.1.175:80
hostnames-632524106-bbpiw

如果仍然失败,请查看 kube-proxy 日志中是否有类似以下内容

Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]

如果看不到这些,请尝试使用 -v 标志设置为 4 重新启动 kube-proxy,然后再次查看日志。

边缘情况:Pod 无法通过服务 IP 访问自身

这听起来可能不太可能,但确实会发生,并且应该可以正常工作。

当网络未针对“回环”流量正确配置时,可能会发生这种情况,通常是在 kube-proxyiptables 模式运行且 Pod 通过桥接网络连接时。Kubelet 公开了 hairpin-mode 标志,允许服务的端点在尝试访问自己的服务 VIP 时将负载均衡回自身。hairpin-mode 标志必须设置为 hairpin-vethpromiscuous-bridge

解决此问题的常见步骤如下

  • 确认 hairpin-mode 设置为 hairpin-vethpromiscuous-bridge。您应该看到类似以下内容。在以下示例中,hairpin-mode 设置为 promiscuous-bridge
ps auxw | grep kubelet
root      3392  1.1  0.8 186804 65208 ?        Sl   00:51  11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0
  • 确认有效的 hairpin-mode。为此,您需要查看 kubelet 日志。访问日志取决于您的 Node 操作系统。在某些操作系统上,它是一个文件,例如 /var/log/kubelet.log,而其他操作系统则使用 journalctl 来访问日志。请注意,由于兼容性原因,有效回环模式可能与 --hairpin-mode 标志不匹配。检查 kubelet.log 中是否有包含关键字 hairpin 的任何日志行。应该有日志行指示有效的回环模式,例如以下内容。
I0629 00:51:43.648698    3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
  • 如果有效的回环模式是 hairpin-veth,请确保 Kubelet 具有在节点上的 /sys 中操作的权限。如果一切正常,您应该看到类似以下内容
for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
  • 如果有效的回环模式是 promiscuous-bridge,请确保 Kubelet 具有在节点上操作 Linux 桥接的权限。如果 cbr0 桥接被使用并配置正确,您应该看到
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1
  • 如果上述方法均无效,请寻求帮助。

寻求帮助

如果您走到这一步,那么肯定发生了非常奇怪的事情。您的服务正在运行,具有端点,并且您的 Pod 实际上正在提供服务。您的 DNS 正常工作,kube-proxy 似乎没有出现故障。但是您的服务却无法正常工作。请告诉我们发生了什么,以便我们能够帮助调查!

Slack论坛GitHub 上联系我们。

下一步

访问 故障排除概述文档 以获取更多信息。

上次修改时间:2022 年 12 月 5 日,太平洋标准时间下午 10:36:删除对 kube-proxy 用户空间模式的引用 (37ee1e335c)