调试服务
Kubernetes 新安装中经常出现的一个问题是服务无法正常工作。您已经通过部署(或其他工作负载控制器)运行了 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
命令将打印创建或修改的资源的类型和名称,这些名称可以在后续命令中使用。
让我们将部署扩展到 3 个副本。
kubectl scale deployment hostnames --replicas=3
deployment.apps/hostnames scaled
请注意,这与您使用以下 YAML 启动部署时相同
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
自动设置为部署的名称。
您可以确认您的 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 中并从那里进行调试。
假设到目前为止一切顺利,您可以开始调查为什么您的服务无法正常工作。
服务是否存在?
细心的读者会注意到您实际上还没有创建服务 - 这是故意的。这是一个有时会被遗忘的步骤,也是首先要检查的步骤。
如果您尝试访问一个不存在的服务会发生什么?如果您有另一个通过名称使用此服务的 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”是您的集群域,它可能在您自己的集群中有所不同。
您也可以从集群中的节点尝试此操作
注意
10.0.0.10 是集群的 DNS 服务 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 服务。这将使用 --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 系统内部有一个控制循环,它会评估每个服务的 selector 并将结果保存到相应的 Endpoints 对象中。
kubectl get endpoints hostnames
NAME ENDPOINTS
hostnames 10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
这确认了 Endpoints 控制器已找到服务所需的正确 Pod。如果“ENDPOINTS” 列为“<none>”,则应检查服务的“spec.selector” 字段是否实际选择了 Pod 上的“metadata.labels” 值。常见的错误是存在拼写错误或其他错误,例如服务选择“app=hostnames”,但 Deployment 指定“run=hostnames”,就像 1.18 之前的版本一样,其中“kubectl run” 命令也可以用来创建 Deployment。
这些 Pod 是否正常工作?
此时,您知道您的服务已存在并已选择您的 Pod。在本演练的开头,您验证了 Pod 本身。让我们再次检查 Pod 是否实际正常工作 - 您可以绕过服务机制,直接转到 Pod,如上面的 Endpoints 所列。
注意
这些命令使用 Pod 端口(9376),而不是服务端口(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
您希望 Endpoints 列表中的每个 Pod 都返回其自己的主机名。如果情况并非如此(或您的 Pod 的正确行为是什么),您应该调查那里发生了什么。
kube-proxy 是否正常工作?
如果您到达这里,您的服务正在运行,有 Endpoints,并且您的 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
如果您看到有关无法联系主节点的错误消息,您应该仔细检查您的节点配置和安装步骤。
kube-proxy 无法正常运行的可能原因之一是找不到所需的“conntrack” 二进制文件。这可能会发生在某些 Linux 系统上,具体取决于您安装集群的方式,例如,您是从头开始安装 Kubernetes。如果是这种情况,您需要手动安装“conntrack” 包(例如,在 Ubuntu 上使用“sudo apt install conntrack”),然后重试。
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
对于每个服务的每个端口,应该在“KUBE-SERVICES” 中有 1 条规则,并且有一个“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
...
对于每个服务的每个端口,加上任何节点端口、外部 IP 和负载均衡器 IP,kube-proxy 将创建一个虚拟服务器。对于每个 Pod 端点,它将创建相应的真实服务器。在此示例中,服务主机名(“10.0.1.175:80”)有 3 个端点(“10.244.0.5:9376”、“10.244.0.6:9376”、“10.244.0.7:9376”)。
kube-proxy 是否正在代理?
假设您确实看到了上述情况之一,请再次尝试从您的节点之一通过 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 访问自身
这听起来可能不太可能,但确实会发生,而且它应该可以工作。
当网络未针对“hairpin” 流量正确配置时,这可能会发生,通常是在 kube-proxy 在“iptables” 模式下运行且 Pod 通过桥接网络连接时。Kubelet 公开了一个“hairpin-mode” 标志,允许服务的端点在尝试访问自己的服务 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
- 如果以上方法都不起作用,请寻求帮助。
寻求帮助
如果您走到这一步,说明发生了非常奇怪的事情。您的服务正在运行,有 Endpoints,并且您的 Pod 实际上正在提供服务。您的 DNS 正常工作,并且 kube-proxy 似乎没有出现故障行为。但是您的服务仍然无法正常工作。请告诉我们发生了什么,以便我们帮助调查!
下一步
访问 故障排除概述文档 以获取更多信息。