调试服务
对于新安装的 Kubernetes,一个相当常见的问题是 Service 无法正常工作。你通过 Deployment(或其他工作负载控制器)运行了 Pod 并创建了 Service,但是当你尝试访问它时却没有得到响应。本文档希望能帮助你找出问题所在。
在 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。由于你可能正在调试自己的 Service,你可以替换自己的详细信息,或者你可以跟着操作并获得第二个数据点。
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
本教程使用的示例容器通过 HTTP 在端口 9376 上提供其自己的主机名,但如果你正在调试自己的应用程序,你将需要使用你的 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 并从那里进行调试。
假设到目前为止一切都按计划进行,你可以开始调查为什么你的 Service 不工作。
Service 是否存在?
细心的读者会注意到你实际上还没有创建 Service——这是故意的。这一步有时会被遗忘,也是要检查的第一件事。
如果你尝试访问一个不存在的 Service 会发生什么?如果你有另一个 Pod 通过名称使用此 Service,你将得到类似以下内容
wget -O- hostnames
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'
首先要检查的是 Service 是否确实存在
kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found
让我们创建 Service。如前所述,这是为了本教程——你可以在此处使用你自己的 Service 的详细信息。
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
现在你知道 Service 存在了。
如前所述,这与你使用 YAML 启动 Service 的情况相同
apiVersion: v1
kind: Service
metadata:
labels:
app: hostnames
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80
targetPort: 9376
为了突出配置的完整范围,你在此处创建的 Service 使用的端口号与 Pod 的端口号不同。对于许多实际的 Service,这些值可能相同。
是否有任何网络策略入口规则影响目标 Pod?
如果你部署了任何可能影响到 hostnames-*
Pods 的传入流量的网络策略入口规则,则需要对其进行审查。
有关更多详细信息,请参阅网络策略。
Service 是否通过 DNS 名称工作?
客户端使用 Service 的最常见方式之一是通过 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 和 Service 在不同的命名空间中,尝试使用命名空间限定名称(同样,在 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
如果这有效,你需要调整你的应用程序以使用跨命名空间名称,或者在同一个命名空间中运行你的应用程序和 Service。如果仍然失败,请尝试完全限定名称
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”表示这是一个 Service。“cluster.local”是你的集群域,在你的集群中可能不同。
你也可以在集群中的节点上尝试此操作
注意
10.0.0.10 是集群的 DNS Service IP,你的可能不同。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 Service。这通过 --cluster-dns
标志传递给 kubelet
。
search
行必须包含适当的后缀,以便你找到 Service 名称。在这种情况下,它正在查找本地命名空间中的 Service("default.svc.cluster.local"),所有命名空间中的 Service("svc.cluster.local"),最后是集群中的名称("cluster.local")。根据你的安装情况,你可能在此之后有额外的记录(最多总共 6 条)。集群后缀通过 --cluster-domain
标志传递给 kubelet
。在本文档中,集群后缀假定为 "cluster.local"。你自己的集群可能配置不同,在这种情况下,你应该在所有前面的命令中更改它。
options
行必须将 ndots
设置得足够高,以便你的 DNS 客户端库考虑搜索路径。Kubernetes 默认将其设置为 5,这足以覆盖它生成的所有 DNS 名称。
是否有任何 Service 通过 DNS 名称工作?
如果上述仍然失败,则你的 Service 的 DNS 查找不工作。你可以退一步,看看还有什么不工作。Kubernetes master Service 应该始终工作。在 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部分,甚至返回本文档的顶部并重新开始,但不是调试你自己的 Service,而是调试 DNS Service。
Service 是否通过 IP 工作?
假设你已确认 DNS 正常工作,下一步是测试你的 Service 是否通过其 IP 地址工作。从集群中的 Pod 访问 Service 的 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
如果你的 Service 正常工作,你应该会收到正确的响应。如果不是,可能会出现一些问题。继续阅读。
Service 定义是否正确?
听起来可能很傻,但你确实应该仔细检查你的 Service 是否正确,并且与你的 Pod 的端口匹配。回读你的 Service 并验证它
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": {}
}
}
- 你尝试访问的 Service 端口是否列在
spec.ports[]
中? targetPort
是否与你的 Pod 匹配(一些 Pod 使用与 Service 不同的端口)?- 如果你打算使用数字端口,它是数字(9376)还是字符串 "9376"?
- 如果你打算使用命名端口,你的 Pod 是否公开了同名的端口?
- 端口的
protocol
是否与你的 Pod 匹配?
Service 是否有任何 EndpointSlice?
如果你已经走到这一步,你已确认你的 Service 定义正确并通过 DNS 解析。现在让我们检查你的 Pod 是否确实被 Service 选中。
早些时候你看到 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
参数是 Service 上配置的标签选择器。
“AGE”列显示这些 Pod 大约一个小时,这意味着它们运行良好且没有崩溃。
“RESTARTS”列显示这些 Pod 没有频繁崩溃或重启。频繁重启可能导致间歇性连接问题。如果重启次数很高,请阅读更多关于如何调试 Pod 的信息。
在 Kubernetes 系统内部有一个控制循环,它评估每个 Service 的选择器并将结果保存到一个或多个 EndpointSlice 对象中。
kubectl get endpointslices -l k8s.io/service-name=hostnames
NAME ADDRESSTYPE PORTS ENDPOINTS
hostnames-ytpni IPv4 9376 10.244.0.5,10.244.0.6,10.244.0.7
这确认 EndpointSlice 控制器已为你的 Service 找到正确的 Pod。如果 ENDPOINTS
列为 <none>
,你应该检查你的 Service 的 spec.selector
字段是否确实选择了你的 Pod 上的 metadata.labels
值。一个常见的错误是拼写错误或其他错误,例如 Service 选择 app=hostnames
,但 Deployment 指定 run=hostnames
,就像在 1.18 之前的版本中,kubectl run
命令也可以用于创建 Deployment。
Pod 是否工作?
此时,你已经知道你的 Service 存在并且已经选择了你的 Pod。在本教程开始时,你已经验证了 Pod 本身。让我们再次检查 Pod 是否确实工作——你可以绕过 Service 机制,直接访问上面 Endpoints 列出的 Pod。
注意
这些命令使用 Pod 端口 (9376),而不是 Service 端口 (80)。从 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
你期望 Endpoint 列表中的每个 Pod 都返回其自己的主机名。如果这不是发生的情况(或你的 Pod 的正确行为),你应该调查那里发生了什么。
kube-proxy 是否工作?
如果你到了这里,你的 Service 正在运行,有 EndpointSlice,并且你的 Pod 正在实际提供服务。此时,整个 Service 代理机制都值得怀疑。让我们一点一点地确认它。
Service 的默认实现,也是大多数集群中使用的实现,是 kube-proxy。这是一个在每个节点上运行的程序,它配置了一小组机制之一来提供 Service 抽象。如果你的集群不使用 kube-proxy,则以下部分将不适用,你将不得不调查你正在使用的 Service 实现。
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
如果你看到有关无法联系 master 的错误消息,你应该仔细检查你的节点配置和安装步骤。
Kube-proxy 可以以几种模式之一运行。在上面列出的日志中,Using iptables Proxier
行表示 kube-proxy 正在以 "iptables" 模式运行。最常见的另一种模式是 "ipvs"。
Iptables 模式
在 "iptables" 模式下,你会在节点上看到类似以下内容
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
对于每个 Service 的每个端口,在 KUBE-SERVICES
中应该有一条规则,并且有一个 KUBE-SVC-<hash>
链。对于每个 Pod 端点,在该 KUBE-SVC-<hash>
中应该有少量规则,并且有一个 KUBE-SEP-<hash>
链,其中包含少量规则。确切的规则将根据你的确切配置(包括节点端口和负载均衡器)而有所不同。
IPVS 模式
在 "ipvs" 模式下,你会在节点上看到类似以下内容
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
...
对于每个 Service 的每个端口,以及任何 NodePort、外部 IP 和负载均衡器 IP,kube-proxy 将创建一个虚拟服务器。对于每个 Pod 端点,它将创建相应的实际服务器。在此示例中,Service 主机名 (10.0.1.175:80
) 有 3 个端点 (10.244.0.5:9376
、10.244.0.6:9376
、10.244.0.7:9376
)。
kube-proxy 是否正在代理?
假设你确实看到上述情况之一,请尝试再次从你的一个节点通过 IP 访问你的 Service
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 无法通过 Service IP 访问自身
这听起来不太可能,但它确实会发生,并且它应该工作。
当网络未正确配置为 "hairpin" 流量时,可能会发生这种情况,通常发生在 kube-proxy
在 iptables
模式下运行且 Pod 连接到桥接网络时。Kubelet
公开了一个 hairpin-mode
标志,允许 Service 的端点在尝试访问其自己的 Service VIP 时将其负载均衡回自身。hairpin-mode
标志必须设置为 hairpin-veth
或 promiscuous-bridge
。
常见的故障排除步骤如下
- 确认
hairpin-mode
设置为hairpin-veth
或promiscuous-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 日志。访问日志取决于你的节点操作系统。在某些操作系统上,它是一个文件,例如 /var/log/kubelet.log,而其他操作系统使用journalctl
访问日志。请注意,由于兼容性问题,有效的 hairpin 模式可能与--hairpin-mode
标志不匹配。检查 kubelet.log 中是否有任何带有关键字hairpin
的日志行。应该有日志行指示有效的 hairpin 模式,例如以下内容。
I0629 00:51:43.648698 3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
- 如果有效的 Hairpin 模式是
hairpin-veth
,请确保Kubelet
在节点上具有操作/sys
的权限。如果一切正常,你应该会看到类似
for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
- 如果有效的 Hairpin 模式是
promiscuous-bridge
,请确保Kubelet
具有在节点上操作 Linux 网桥的权限。如果使用cbr0
网桥并正确配置,你应该会看到
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1460 Metric:1
- 如果以上方法都不奏效,请寻求帮助。
寻求帮助
如果你走到这一步,那一定发生了非常奇怪的事情。你的 Service 正在运行,有 EndpointSlice,并且你的 Pod 正在实际提供服务。你的 DNS 正常工作,并且 kube-proxy
似乎没有异常行为。但你的 Service 仍然不工作。请告诉我们发生了什么,以便我们能帮助调查!
下一步
访问故障排除概览文档以获取更多信息。