【K8s任務】除錯 Service
參考:https://kubernetes.io/zh/docs/tasks/debug-application-cluster/debug-service/
對於新安裝的 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=k8s.gcr.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: k8s.gcr.io/serve_hostname
"app" 標籤是 kubectl create deployment 根據 Deployment 名稱自動設定的。
確認你的 Pods 是執行狀態:
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:
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80
targetPort: 9376
為了突出配置範圍的完整性,你在此處建立的 Service 使用的埠號與 Pods 不同。 對於許多真實的 Service,這些值可以是相同的。
Service 是否可通過 DNS 名字訪問?
通常客戶端通過 DNS 名稱來匹配到 Service。
從相同名稱空間下的 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 服務 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 名稱。 在本例中,它查詢本地名稱空間(default.svc.cluster.local)中的服務和 所有名稱空間(svc.cluster.local)中的服務,最後在叢集(cluster.local)中查詢 服務的名稱。根據你自己的安裝情況,可能會有額外的記錄(最多 6 條)。 集群后綴是通過 --cluster-domain 標誌傳遞給 kubelet 的。 本文中,我們假定字尾是 “cluster.local”。 你的叢集配置可能不同,這種情況下,你應該在上面的所有命令中更改它。
options 行必須設定足夠高的 ndots,以便 DNS 客戶端庫考慮搜尋路徑。 在預設情況下,Kubernetes 將這個值設定為 5,這個值足夠高,足以覆蓋它生成的所有 DNS 名稱。
是否存在 Service 能通過 DNS 名稱訪問?
如果上面的方式仍然失敗,DNS 查詢不到你需要的 Service ,你可以後退一步, 看看還有什麼其它東西沒有正常工作。 Kubernetes 主 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 有 Endpoints 嗎?
如果你已經走到了這一步,你已經確認你的 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 的選擇算符,並將結果儲存到 Endpoints 物件中。
kubectl get endpoints hostnames
NAME ENDPOINTS
hostnames 10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
這證實 Endpoints 控制器已經為你的 Service 找到了正確的 Pods。 如果 ENDPOINTS 列的值為
Pod 正常工作嗎?
至此,你知道你的 Service 已存在,並且已匹配到你的Pod。在本實驗的開始,你已經檢查了 Pod 本身。 讓我們再次檢查 Pod 是否確實在工作 - 你可以繞過 Service 機制並直接轉到 Pod,如上面的 Endpoint 所示。
說明: 這些命令使用的是 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 正在執行,擁有 Endpoints,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/messages 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:53.999055 5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
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"。先前的 "userspace" 模式已經被這些所代替。
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 的每個埠,應有 1 條 KUBE-SERVICES 規則、一個 KUBE-SVC-
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,External IP 和 LoadBalancer 型別服務 的 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)。
Userspace 模式
在極少數情況下,你可能會用到 "userspace" 模式。在你的節點上執行:
iptables-save | grep hostnames
-A KUBE-PORTALS-CONTAINER -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j REDIRECT --to-ports 48577
-A KUBE-PORTALS-HOST -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j DNAT --to-destination 10.240.115.247:48577
對於 Service (本例中只有一個)的每個埠,應當有 2 條規則: 一條 "KUBE-PORTALS-CONTAINER" 和一條 "KUBE-PORTALS-HOST" 規則。
幾乎沒有人應該再使用 "userspace" 模式,因此你在這裡不會花更多的時間。
kube-proxy 是否在執行?
假設你確實遇到上述情況之一,請重試從節點上通過 IP 訪問你的 Service :
curl 10.0.1.175:80
hostnames-632524106-bbpiw
如果失敗,並且你正在使用使用者空間代理,則可以嘗試直接訪問代理。 如果你使用的是 iptables 代理,請跳過本節。
回顧上面的 iptables-save 輸出,並提取 kube-proxy 為你的 Service 所使用的埠號。 在上面的例子中,埠號是 “48577”。現在試著連線它:
curl localhost:48577
hostnames-632524106-tlaok
如果這步操作仍然失敗,請檢視 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。
診斷此類問題的常見步驟如下:
1.確認 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
2.確認有效的 hairpin-mode。要做到這一點,你必須檢視 kubelet 日誌。 訪問日誌取決於節點的作業系統。在一些作業系統上,它是一個檔案,如 /var/log/kubelet.log, 而其他作業系統則使用 journalctl 訪問日誌。請注意,由於相容性, 有效的 hairpin-mode 可能不匹配 --hairpin-mode 標誌。在 kubelet.log 中檢查是否有帶有關鍵字 hairpin 的日誌行。應該有日誌行指示有效的 hairpin-mode,就像下面這樣。
I0629 00:51:43.648698 3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
3.如果有效的髮夾模式是 hairpin-veth, 要保證 Kubelet 有操作節點上 /sys 的許可權。 如果一切正常,你將會看到如下輸出:
for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
1
1
1
1
4.如果有效的髮卡模式是 promiscuous-bridge, 要保證 Kubelet 有操作節點上 Linux 網橋的許可權。如果 cbr0 橋正在被使用且被正確設定,你將會看到如下輸出:
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1460 Metric:1
5.如果以上步驟都不能解決問題,請尋求幫助。
尋求幫助
如果你走到這一步,那麼就真的是奇怪的事情發生了。你的 Service 正在執行,有 Endpoints 存在, 你的 Pods 也確實在提供服務。你的 DNS 正常,iptables 規則已經安裝,kube-proxy 看起來也正常。 然而 Service 還是沒有正常工作。這種情況下,請告訴我們,以便我們可以幫助調查!
通過 Slack 或者 Forum 或者 GitHub 聯絡我們。
https://kubernetes.io/zh/docs/tasks/debug-application-cluster/troubleshooting/#slack
https://discuss.kubernetes.io/
https://github.com/kubernetes/kubernetes