除錯服務

對於 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 命令將列印已建立或變更的資源類型與名稱,然後可以在後續命令中使用。

讓我們將部署擴展到 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 自動設定為 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 不同的連接埠號碼。對於許多真實世界的服務,這些值可能會相同。

是否有任何網路策略 Ingress 規則影響目標 Pod?

如果您已部署任何可能影響傳入 hostnames-* Pod 流量的網路策略 Ingress 規則,則需要審查這些規則。

請參閱 網路策略 以取得更多詳細資訊。

服務是否透過 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 master 服務應該始終運作。從 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[] 中?
  • 針對您的 Pod,targetPort 是否正確 (有些 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

這確認端點控制器已找到適用於您 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 內部

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 是否正在運作?

如果您到此步驟,您的 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

接下來,確認它沒有發生明顯的錯誤,例如連線到 master。若要執行此操作,您必須查看記錄。存取記錄取決於您的節點作業系統。在某些作業系統上,它是一個檔案,例如 /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 中應該有 1 條規則和一個 KUBE-SVC-<hash> 鏈。對於每個 Pod 端點,在該 KUBE-SVC-<hash> 中應該有少量規則,以及一個 KUBE-SEP-<hash> 鏈,其中包含少量規則。確切的規則會根據您的確切組態而有所不同 (包括 node-ports 和 load-balancers)。

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 的每個埠口,加上任何 NodePorts、外部 IP 和負載平衡器 IP,kube-proxy 都會建立虛擬伺服器。對於每個 Pod 端點,它將建立對應的真實伺服器。在此範例中,服務 hostnames (10.0.1.175:80) 具有 3 個端點 (10.244.0.5:937610.244.0.6:937610.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]

如果您沒有看到這些行,請嘗試使用設定為 4 的 -v 旗標重新啟動 kube-proxy,然後再次查看記錄。

邊緣情況:Pod 無法透過 Service IP 連線到自身

這聽起來可能不太可能,但它確實會發生,而且應該要能運作。

當網路未針對 "hairpin" 流量正確設定時,可能會發生這種情況,通常是在 kube-proxyiptables 模式執行,且 Pod 以橋接網路連線時。如果 Service 的端點嘗試存取自己的 Service VIP,Kubelet 會公開一個 hairpin-mode 旗標,允許這些端點負載平衡回到自身。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 記錄。存取記錄取決於您的節點作業系統。在某些作業系統上,它是一個檔案,例如 /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 bridge 的權限。如果 cbr0 bridge 已使用且組態正確,您應該會看到
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1460  Metric:1
  • 如果以上方法都無效,請尋求協助。

尋求協助

如果您到此步驟,表示發生非常奇怪的事情。您的 Service 正在執行、具有端點 (Endpoints),且您的 Pod 實際上正在提供服務。您的 DNS 運作正常,且 kube-proxy 似乎沒有異常行為。然而,您的 Service 仍然無法運作。請告訴我們發生了什麼事,以便我們協助調查!

Slack論壇GitHub 上聯絡我們。

下一步

請造訪疑難排解總覽文件以取得更多資訊。

上次修改時間:2024 年 8 月 26 日下午 6:44 PST:remove conntrack ref from debug/debug-application/debug-service (bc94badee7)