全網最詳細的 K8s Service 不能訪問排查流程
對於新安裝的 Kubernetes,經常出現的一個問題是 Service 沒有正常工作。如果您已經運行了 Deployment 並建立了一個 Service,但是當您嘗試訪問它時沒有得到響應,希望這份文件能幫助您找出問題所在。
先來熟悉下Service工作邏輯:
為了完成本次演練的目的,我們先執行幾個 Pod。
$kubectlrunhostnames--image=k8s.gcr.io/serve_hostname\
--labels=app=hostnames\
--port=9376\
--replicas=3
deployment.apps/hostnamescreated
確認您的 Pods 是執行狀態:
$kubectlgetpods-lapp=hostnames
NAMEREADYSTATUSRESTARTSAGE
hostnames-632524106-bbpiw1/1Running02m
hostnames-632524106-ly40y1/1Running02m
hostnames-632524106-tlaok1/1Running02m
問題1:Service 存在嗎?
細心的讀者會注意到我們還沒有真正建立一個 Service - 其實這是我們有意的。這是一個有時會被遺忘的步驟,也是第一件要檢查的事情。
那麼,如果我試圖訪問一個不存在的 Service,會發生什麼呢?假設您有另一個 Pod,想通過名稱使用這個 Service,您將得到如下內容:
u@pod$wget-O-hostnames
Resolvinghostnames(hostnames)...failed:Nameorservicenotknown.
wget:unabletoresolvehostaddress'hostnames'
因此,首先要檢查的是 Service 是否確實存在:
$kubectlgetsvchostnames
Noresourcesfound.
Errorfromserver(NotFound):services"hostnames"notfound
我們已經有一個罪魁禍首了,讓我們來建立 Service。就像前面一樣,這裡的內容僅僅是為了步驟的執行 - 在這裡您可以使用自己的 Service 細節。
$kubectlexposedeploymenthostnames--port=80--target-port=9376
service/hostnamesexposed
再查詢一遍,確定一下:
$kubectlgetsvchostnames
NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
hostnamesClusterIP10.0.1.175<none>80/TCP5s
與前面相同,這與您使用 YAML 啟動的 Service 一樣:
apiVersion:v1
kind:Service
metadata:
name:hostnames
spec:
selector:
app:hostnames
ports:
-name:default
protocol:TCP
port:80
targetPort:9376
現在您可以確認 Service 存在。
問題2:Service 是否通過 DNS 工作?
從相同 Namespace 下的 Pod 中執行:
u@pod$nslookuphostnames
Address1:10.0.0.10kube-dns.kube-system.svc.cluster.local
Name:hostnames
Address1:10.0.1.175hostnames.default.svc.cluster.local
如果失敗,那麼您的 Pod 和 Service 可能位於不同的 Namespace 中,請嘗試使用限定名稱空間的名稱:
u@pod$nslookuphostnames.default
Address1:10.0.0.10kube-dns.kube-system.svc.cluster.local
Name:hostnames.default
Address1:10.0.1.175hostnames.default.svc.cluster.local
如果成功,那麼需要調整您的應用,使用跨名稱空間的名稱去訪問服務,或者,在相同的 Namespace 中執行應用和 Service。如果仍然失敗,請嘗試一個完全限定的名稱:
u@pod$nslookuphostnames.default.svc.cluster.local
Address1:10.0.0.10kube-dns.kube-system.svc.cluster.local
Name:hostnames.default.svc.cluster.local
Address1:10.0.1.175hostnames.default.svc.cluster.local
注意這裡的字尾:”default.svc.cluster.local”。”default” 是我們正在操作的 Namespace。”svc” 表示這是一個 Service。”cluster.local” 是您的叢集域,在您自己的叢集中可能會有所不同。
您也可以在叢集中的 Node 上嘗試此操作:
注意:10.0.0.10 是我的 DNS Service,您的可能不同)
u@node$nslookuphostnames.default.svc.cluster.local10.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
如果您能夠使用完全限定的名稱查詢,但不能使用相對名稱,則需要檢查 /etc/resolv.conf 檔案是否正確。
u@pod$cat/etc/resolv.conf
nameserver10.0.0.10
searchdefault.svc.cluster.localsvc.cluster.localcluster.localexample.com
optionsndots:5
nameserver 行必須指示您的叢集的 DNS Service,它通過 --cluster-dns 標誌傳遞到 kubelet。
search 行必須包含一個適當的字尾,以便查詢 Service 名稱。在本例中,它在本地 Namespace(default.svc.cluster.local)、所有 Namespace 中的 Service(svc.cluster.local)以及叢集(cluster.local)中查詢服務。根據您自己的安裝情況,可能會有額外的記錄(最多 6 條)。集群后綴通過 --cluster-domain 標誌傳遞給 kubelet。本文件中,我們假定它是 “cluster.local”,但是您的可能不同,這種情況下,您應該在上面的所有命令中更改它。
options 行必須設定足夠高的 ndots,以便 DNS 客戶端庫考慮搜尋路徑。在預設情況下,Kubernetes 將這個值設定為 5,這個值足夠高,足以覆蓋它生成的所有 DNS 名稱。
問題3:DNS 是否可以解析預設服務?
如果上面仍然失敗 - DNS 查詢不到您需要的 Service - 我們可以後退一步,看看還有什麼不起作用。Kubernetes 主 Service 應該一直是工作的:
u@pod$nslookupkubernetes.default
Server:10.0.0.10
Address1:10.0.0.10kube-dns.kube-system.svc.cluster.local
Name:kubernetes.default
Address1:10.0.0.1kubernetes.default.svc.cluster.local
如果失敗,您可能需要轉到這個文件的 kube-proxy 部分,或者甚至回到文件的頂部重新開始,但不是除錯您自己的 Service,而是除錯 DNS。
問題4:Service 能夠通過 IP 訪問麼?
假設我們可以確認 DNS 工作正常,那麼接下來要測試的是您的 Service 是否工作正常。從叢集中的一個節點,訪問 Service 的 IP(從上面的 kubectl get 命令獲取)。
u@node$curl10.0.1.175:80
hostnames-0uton
u@node$curl10.0.1.175:80
hostnames-yp2kp
u@node$curl10.0.1.175:80
hostnames-bvc05
如果 Service 是正常的,您應該得到正確的響應。如果沒有,有很多可能出錯的地方,請繼續。
問題5:Service 是對的嗎?
這聽起來可能很愚蠢,但您應該加倍甚至三倍檢查 Service 是否正確,並且與您的 Pod 匹配。檢視 Service 並驗證它:
$kubectlgetservicehostnames-ojson
{
"kind":"Service",
"apiVersion":"v1",
"metadata":{
"name":"hostnames",
"namespace":"default",
"selfLink":"/api/v1/namespaces/default/services/hostnames",
"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 選擇使用與 Service 不同的埠)?如果您想把它變成一個數字埠,那麼它是一個數字(9376)還是字串 “9376”?如果您想把它當作一個指定的埠,那麼您的 Pod 是否公開了一個同名埠?埠的 protocol 和 Pod 的一樣嗎?
問題6:Service 有端點嗎?
如果您已經走到了這一步,我們假設您已經確認 Service 存在,並能通過 DNS 解析。現在,讓我們檢查一下,您執行的 Pod 確實是由 Service 選擇的。
早些時候,我們已經看到 Pod 是執行狀態。我們可以再檢查一下:
$kubectlgetpods-lapp=hostnames
NAMEREADYSTATUSRESTARTSAGE
hostnames-0uton1/1Running01h
hostnames-bvc051/1Running01h
hostnames-yp2kp1/1Running01h
“AGE” 列表明這些 Pod 已經啟動一個小時了,這意味著它們執行良好,而不是崩潰。
-l app=hostnames 引數是一個標籤選擇器 - 就像我們的 Service 一樣。在 Kubernetes 系統中有一個控制迴圈,它評估每個 Service 的選擇器,並將結果儲存到 Endpoints 物件中。
$kubectlgetendpointshostnames
NAMEENDPOINTS
hostnames10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
這證實 endpoints 控制器已經為您的 Service 找到了正確的 Pods。如果 hostnames 行為空,則應檢查 Service 的 spec.selector 欄位,以及您實際想選擇的 Pods 的 metadata.labels 的值。常見的錯誤是輸入錯誤或其他錯誤,例如 Service 想選擇 run=hostnames,但是 Deployment 指定的是 app=hostnames。
問題7:Pod 正常工作嗎?
到了這步,我們知道您的 Service 存在並選擇了 Pods。讓我們檢查一下 Pod 是否真的在工作 - 我們可以繞過 Service 機制,直接進入 Pod。
注意:這些命令使用的是 Pod 埠(9376),而不是 Service 埠(80)。
u@pod$wget-qO-10.244.0.5:9376
hostnames-0uton
pod$wget-qO-10.244.0.6:9376
hostnames-bvc05
u@pod$wget-qO-10.244.0.7:9376
hostnames-yp2kp
我們期望的是 Endpoints 列表中的每個 Pod 返回自己的主機名。如果這沒有發生(或者您自己的 Pod 的正確行為沒有發生),您應該調查發生了什麼。您會發現 kubectl logs 這個時候非常有用,或者使用 kubectl exec 直接進入到您的 Pod,並從那裡檢查服務。
另一件要檢查的事情是,您的 Pod 沒有崩潰或正在重新啟動。頻繁的重新啟動可能會導致斷斷續續的連線問題。
$kubectlgetpods-lapp=hostnames
NAMEREADYSTATUSRESTARTSAGE
hostnames-632524106-bbpiw1/1Running02m
hostnames-632524106-ly40y1/1Running02m
hostnames-632524106-tlaok1/1Running02m
如果重新啟動計數很高,請查閱有關如何除錯 pods 獲取更多資訊。
問題8:kube-proxy 正常工作嗎?
如果您到了這裡,那麼 Service 正在執行,也有 Endpoints,而您的 Pod 實際上也正在服務。在這一點上,整個 Service 代理機制是否正常就是可疑的了。我們來確認一下,一部分一部分來。
確認 kube-proxy 正在您的 Nodes 上執行。您應該得到如下內容:
u@node$psauxw|grepkube-proxy
root41940.40.110186417696?SlJul0425:43/usr/local/bin/kube-proxy--master=https://kubernetes-master--kubeconfig=/var/lib/kube-proxy/kubeconfig--v=2
下一步,確認它並沒有出現明顯的失敗,比如連線主節點失敗。要做到這一點,您必須檢視日誌。訪問日誌取決於您的 Node 作業系統。在某些作業系統是一個檔案,如 /var/log/messages kube-proxy.log,而其他作業系統使用 journalctl 訪問日誌。您應該看到類似的東西:
I102722:14:53.9951345063server.go:200]Runninginresource-onlycontainer"/kube-proxy"
I102722:14:53.9981635063server.go:247]UsingiptablesProxier.
I102722:14:53.9990555063server.go:255]Tearingdownuserspacerules.Errorshereareacceptable.
I102722:14:54.0381405063proxier.go:352]Settingendpointsfor"kube-system/kube-dns:dns-tcp"to[10.244.1.3:53]
I102722:14:54.0381645063proxier.go:352]Settingendpointsfor"kube-system/kube-dns:dns"to[10.244.1.3:53]
I102722:14:54.0382095063proxier.go:352]Settingendpointsfor"default/kubernetes:https"to[10.240.0.2:443]
I102722:14:54.0382385063proxier.go:429]NotsyncingiptablesuntilServicesandEndpointshavebeenreceivedfrommaster
I102722:14:54.0400485063proxier.go:294]Addingnewservice"default/kubernetes:https"at10.0.0.1:443/TCP
I102722:14:54.0401545063proxier.go:294]Addingnewservice"kube-system/kube-dns:dns"at10.0.0.10:53/UDP
I102722:14:54.0402235063proxier.go:294]Addingnewservice"kube-system/kube-dns:dns-tcp"at10.0.0.10:53/TCP
如果您看到有關無法連線主節點的錯誤訊息,則應再次檢查節點配置和安裝步驟。
kube-proxy 無法正確執行的可能原因之一是找不到所需的 conntrack 二進位制檔案。在一些 Linux 系統上,這也是可能發生的,這取決於您如何安裝叢集,例如,您正在從頭開始安裝 Kubernetes。如果是這樣的話,您需要手動安裝 conntrack 包(例如,在 Ubuntu 上使用 sudo apt install conntrack),然後重試。
問題9:kube-proxy 是否在寫 iptables 規則?
kube-proxy 的主要職責之一是寫實現 Services 的 iptables 規則。讓我們檢查一下這些規則是否已經被寫好了。
kube-proxy 可以在 “userspace” 模式、 “iptables” 模式或者 “ipvs” 模式下執行。如果您正在使用 “iptables” 模式或者 “ipvs” 模式。您應該看到以下情況之一。
Iptables
u@node$iptables-save|grephostnames
-AKUBE-SEP-57KPRZ3JQVENLNBR-s10.244.3.6/32-mcomment--comment"default/hostnames:"-jMARK--set-xmark0x00004000/0x00004000
-AKUBE-SEP-57KPRZ3JQVENLNBR-ptcp-mcomment--comment"default/hostnames:"-mtcp-jDNAT--to-destination10.244.3.6:9376
-AKUBE-SEP-WNBA2IHDGP2BOBGZ-s10.244.1.7/32-mcomment--comment"default/hostnames:"-jMARK--set-xmark0x00004000/0x00004000
-AKUBE-SEP-WNBA2IHDGP2BOBGZ-ptcp-mcomment--comment"default/hostnames:"-mtcp-jDNAT--to-destination10.244.1.7:9376
-AKUBE-SEP-X3P2623AGDH6CDF3-s10.244.2.3/32-mcomment--comment"default/hostnames:"-jMARK--set-xmark0x00004000/0x00004000
-AKUBE-SEP-X3P2623AGDH6CDF3-ptcp-mcomment--comment"default/hostnames:"-mtcp-jDNAT--to-destination10.244.2.3:9376
-AKUBE-SERVICES-d10.0.1.175/32-ptcp-mcomment--comment"default/hostnames:clusterIP"-mtcp--dport80-jKUBE-SVC-NWV5X2332I4OT4T3
-AKUBE-SVC-NWV5X2332I4OT4T3-mcomment--comment"default/hostnames:"-mstatistic--moderandom--probability0.33332999982-jKUBE-SEP-WNBA2IHDGP2BOBGZ
-AKUBE-SVC-NWV5X2332I4OT4T3-mcomment--comment"default/hostnames:"-mstatistic--moderandom--probability0.50000000000-jKUBE-SEP-X3P2623AGDH6CDF3
-AKUBE-SVC-NWV5X2332I4OT4T3-mcomment--comment"default/hostnames:"-jKUBE-SEP-57KPRZ3JQVENLNBR
KUBE-SERVICES 中應該有 1 條規則,KUBE-SVC-(hash) 中每個端點有 1 或 2 條規則(取決於 SessionAffinity),每個端點中應有 1 條 KUBE-SEP-(hash) 鏈。準確的規則將根據您的確切配置(包括節點、埠組合以及負載均衡器設定)而有所不同。
IPVS
u@node$ipvsadm-ln
ProtLocalAddress:PortSchedulerFlags
->RemoteAddress:PortForwardWeightActiveConnInActConn
...
TCP10.0.1.175:80rr
->10.244.0.5:9376Masq100
->10.244.0.6:9376Masq100
->10.244.0.7:9376Masq100
...
IPVS 代理將為每個伺服器地址(例如叢集 IP、外部 IP、節點埠 IP、負載均衡 IP等)建立虛擬伺服器,併為服務的端點建立一些相應的真實伺服器(如果有)。在這個例子中,伺服器主機(10.0.1.175:80)有 3 個端點(10.244.0.5:9376, 10.244.0.6:9376, 10.244.0.7:9376),你會得到類似上面的結果。
如果走到這一步還沒解決!那只有燒香拜佛了!