除錯服務
對於新安裝的 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 仍然不工作。請告訴我們發生了什麼,以便我們能幫助調查!
下一步
訪問故障排除概覽文件以獲取更多資訊。