
本文將幫助你厘清在Kubernetes中除錯 deployment的思路,下圖是完整的故障排查思路,如果你想獲得更清晰的圖片,請在公眾號后臺(RancherLabs)回復“troubleshooting”,

當你希望在Kubernetes中部署一個應用程式,你通常需要定義三個組件:
-
Deployment——這是創建名為Pods的應用程式副本的方法
-
Serivce——內部負載均衡器,將流量路由到Pods
-
Ingress——可以描述流量如何從集群外部流向Service
接下來,我們通過圖片快速回顧一下,
在Kubernetes中,你的應用程式通過兩層負載均衡器暴露:內部和外部,
內部負載均衡器稱為Service,而外部負載均衡器則稱為Ingress,
Pod未直接部署,因此,Deployment創建Pod并監視它們,
假設你想部署一個簡單的Hello World應用程式,那么對于此類應用程式,其YAML檔案與以下類似:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
name: app
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: app
servicePort: 80
path: /
這個定義很長,容易忽略組件之間的相互關系,
例如:
-
你什么時候應該使用80埠,什么時候使用埠8080?
-
你是否應該為每個服務創建一個新埠,以免它們沖突?
-
標簽(label)名稱重要嗎?是否應該每一處都一樣?
在進行debug之前,我們先來回顧一下這三個組件之間的關系如何,
首先,我們從Deployment和Service開始,
連接Deployment和Service
實際上,Deployment和Service根本沒有連接,相反,該Service直接指向Pod,并完全跳過Deployment,所以,你應該關注的是Pod和Service是如何與彼此關聯的,你應該記住三件事:
-
Service selector至少與Pod的一個標簽匹配
-
Serivce
targetPort應該與Pod內的容器的containerPort相匹配 -
Serviceport可以是任何數字,多個Service可以使用同一個埠,因為它們已經被分配了不同的IP地址
以下圖片總結了如何連接埠:
考慮由Service暴露的pod
當你創建一個pod,你應該在你的Pod中為每個容器定義埠containerPort
當你創建一個Service時,你能夠定義一個port和一個targetPort,但你應該將哪一個連接到容器呢?
targetPort與containerPort應該能夠匹配
如果你的容器暴露埠3000,那么targetPort應該與該數字相匹配,
如果你查看了YAML,標簽與ports或targerPort應該匹配:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
那么在Deployment頂部的track: canary標簽呢?也應該匹配嗎?
那個標簽屬于deployment,并且Service selector不使用它來路由流量,換言之,你可以安全地將其移除或者給它分配不同的值,
那么matchLabelsselector呢?它需要與Pod標簽匹配并且Deployment使用它來跟蹤Pod,
假設你做了一個正確的更改,你應該如何測驗它呢?你可以使用以下命令檢查Pod是否擁有正確的標簽:
kubectl get pods --show-labels
或者如果你有屬于多個應用程式的Pod:
kubectl get pods --selector any-name=my-app --show-labels
其中any-name=my-app是標簽any-name: my-app,依舊存在問題?你也可以連接到Pod,你可以在kubectl中使用命令port-forward連接到Serivce并測驗連接,
kubectl port-forward service/<service name> 3000:80
其中:
-
service/<service name>是serivce的名稱——在當前YAML中,是“my-service”, -
3000是你希望在你的電腦上打開的埠
-
80是Service在port欄位中暴露的埠
如果你能夠連接,那么設定就是正確的,如果你無法連接,你很有可能弄錯了標簽或者埠未匹配,
連接Service和Ingress
暴露應用程式的下一步是配置Ingress,Ingress必須知道如何檢索Service,然后檢索Pod并將流量路由到它們,Ingress通過名稱和暴露的埠來檢索正確的Service,
在Ingress和Service中應該匹配兩件事:
-
Ingress的
servicePort應該與Service的port匹配 -
Ingress的
serviceName應該與Service的name相匹配
以下圖片將總結如何連接埠:
你已經知道該服務暴露了一個埠
Ingress有一個名為servicePort的欄位,
Serviceport和IngressservicePort應該相匹配
如果你決定分配埠80給該service,你應該同時更改servicePort為80
實際操作中,你需要查看這些命令列:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: my-service
servicePort: 80
path: /
你應該如何測驗Ingress是否正常運行呢?你可以使用和之前相同的策略,即kubectl port-forward,但不是連接到service,而是連接到Ingress controller,
首先,使用以下命令為Ingress controller檢索Pod名稱:
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
驗證Ingress Pod(可能在不同的命名空間)并且描述它以檢索埠:
kubectl describe pod nginx-ingress-controller-6fc5bcc \
--namespace kube-system \
| grep Ports
Ports: 80/TCP, 443/TCP, 18080/TCP
最后,連接到Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
此時,每次你在你的電腦上訪問埠3000,請求就會被轉發到在Ingress controller Pod上的埠80,
如果你訪問 http://localhost:3000 ,你應該能找到提供網頁的應用程式,
簡單回顧一下
現在,我們來快速回顧一下什么埠和標簽需要匹配:
-
Service selector應該匹配Pod的標簽
-
Service
targerPort應該匹配在Pod內容器的containerPort -
Service 埠可以是任意數字,多個Service可以使用同個埠,因為它們已經分配了不同的IP地址
-
Ingress的
servicePort應該匹配在Service中的port -
Service的名稱應該匹配在Ingress中的
serviceName的欄位
了解如何構造YAML只是開始,那么,出了問題時會有什么表現?Pod可能無法啟動,或者直接崩潰,
3步排查K8S Deployment故障
在我們深入研究有故障的deployment之前,必須有一個明確定義的模型,以了解Kubernetes的作業方式,
既然在每個deployment中都有那三個組件,你應該從底層開始按順序除錯它們,
-
你應該確保你的Pod正在運行
-
著重關注使Service將流量路由到Pod
-
檢查Ingress是否正確配置
你應該從底層開始排查Deployment故障,首先,檢查Pod是否準備就緒并且正在運行
如果Pod已經準備就緒,你需要檢查Service是否可以將流量分配到Pod,
最后你應該檢查Service和Ingress之間的連接,
1、 故障排查Pod
在大多數情況下,問題出現在Pod本身,所以你應該確保Pod正在運行并準備就緒,應該如何檢查呢?
kubectl get pods
NAME READY STATUS RESTARTS AGE
app1 0/1 ImagePullBackOff 0 47h
app2 0/1 Error 0 47h
app3-76f9fcd46b-xbv4k 1/1 Running 1 47h
以上部分,只有最后一個Pod是正在運行并且準備就緒的,而前兩個Pod既沒有Running也沒有Ready,那么,你應該如何定位是什么出了問題呢?
這里有4個十分有用的命令可以幫助你排查Pod的故障:
-
kubectl logs <pod name>能夠幫助檢索Pod的容器日志 -
kubectl describe pod <pod name>能夠有效地檢索與Pod相關的事件串列 -
kubectl get pod <pod name>對于提取存盤在Kubernetes中的Pod的YAML定義十分有用 -
kubectl exec -ti <pod name> bash可以用于在Pod其中一個容器中運行一個互動式命令
你應該使用哪一個呢?實際上,沒有一種命令是萬能的,你可以根據實際情況結合使用,
常見的Pod錯誤
Pod可能會出現啟動和運行時的錯誤,
啟動錯誤包括:
-
ImagePullBackoff
-
ImageInspectError
-
ErrImagePull
-
ErrImageNeverPull
-
RegistryUnavailable
-
InvalidImageName
運行時錯誤包括:
-
CrashLoopBackOff
-
RunContainerError
-
KillContainerError
-
VerifyNonRootError
-
RunInitContainerError
-
CreatePodSandboxError
-
ConfigPodSandboxError
-
KillPodSandboxError
-
SetupNetworkError
-
TeardownNetworkError
這些錯誤中,有些比其他錯誤更為常見,以下是最常見的錯誤以及如何修復它們:
ImagePullBackOff
當Kubernetes無法檢索Pod其中之一的容器鏡像時,將出現此錯誤,
有三種常見原因:
-
鏡像名稱無效——例如,你錯誤拼寫名稱或鏡像不存在
-
你給這一鏡像指定了一個不存在的tag
-
你所檢索的鏡像是私有倉庫的,并且Kubernetes沒有訪問它的憑據
前兩個原因可以通過更正鏡像名稱和tag解決,最后一個,你需要將憑據添加到“Secret”中的私有鏡像倉庫中,并在Pod中參考它,
官方檔案可以讓你更加清楚:
https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
CrashLoopBackOff
如果容器無法啟動,Kubernetes狀態將顯示CrashLoopBackOff訊息,
通常情況下,容器在以下場景中無法啟動:
-
應用程式中存在錯誤,導致無法啟動
-
你錯誤配置了容器
https://stackoverflow.com/questions/41604499/my-kubernetes-pods-keep-crashing-with-crashloopbackoff-but-i-cant-find-any-lo
-
Liveness探針失敗次數太多
你應該嘗試并檢索該容器的日志以確定出現故障的原因,
如果由于你的容器重啟過快而無法查看日志,你可以使用以下命令:
kubectl logs <pod-name> --previous
它將從之前的容器中列印錯誤資訊,
RunContainerError
容器不能啟動時出現錯誤,甚至在容器內的應用程式啟動之前就無法啟動,
這個問題通常由于錯誤配置導致的,如:
-
安裝一個不存在的volume,如ConfigMap或Secret
-
將只讀volume安裝為可讀寫
你應該使用kubectl describe pod <pod-name>來收集和分析錯誤,
Pod處于Pending狀態
當你創建一個Pod時,Pod保持在Pending狀態,這是為什么呢?假設你的調度組件運行了解,那么有以下幾個原因:
-
集群沒有足夠的資源來運行Pod,如CPU和記憶體
-
當前命名空間有一個ResourceQuota物件并且所創建的Pod會使該命名空間超過資源額度
-
Pod與一個Pending狀態的PersistentVolumeClaim系結,
那么,最好的選擇是使用命令kubectl describe檢查事件:
kubectl describe pod <pod name>
對于由于ResourceQuotas造成的錯誤,可以使用以下方法檢查集群的日志:
kubectl get events --sort-by=.metadata.creationTimestamp
Pod不處于Ready狀態
如果Pod正在運行但是不Ready,這意味著Readiness探針出現故障,當Readiness探針出現故障時,Pod無法附加到Service上,并且流量無法轉發到實體上,
Readiness探針故障是特定于應用程式的錯誤,因此使用kubectl describe來檢查事件部分,以驗證錯誤,
2、 排查Service故障
如果你的Pod正在運行并且準備就緒,但是你依舊無法接收來自應用程式的回應,你應該檢查Service是否配置正確,
Service旨在根據pod的標簽將流量路由到Pod,所以第一件事,你需要檢查Service target多少個Pod,可以通過檢查Service中的Endpoint來完成此步驟:
kubectl describe service <service-name> | grep Endpoints
一個endpoint是一對`
如果“Endpoint”部分是空的,那么有兩種解釋:
-
任何正在運行的Pod沒有正確的label(提示:你需要檢查以下你是否在正確的命名空間內)
-
在Service的selector標簽中有錯別字
如果你看到了endpoint串列,但依舊無法訪問你的應用程式,那么你的Service中的targetPort可能是罪魁禍首,
你應該怎么測驗Service?無論Service型別是什么,都可以使用kubectl port-forward連接到它:
kubectl port-forward service/<service-name> 3000:80
其中:
-
``
`是Service的名稱 -
3000是你想要在電腦上打開的埠 -
80是由Service暴露的埠
3、 排查Ingress故障
如果你走到了這個部分,這意味著:
-
Pod正在運行并且準備就緒
-
Service可以分發流量給Pod
但你依舊無法接收app的回應,那么這很有可能是Ingress配置出現錯誤,
由于使用的Ingress controller是集群中的第三方組件,那么根據Ingress controller的型別會由不同的除錯技術,但是在深入研究Ingress特定的工具之前,你可以使用一些簡單的方法檢查,
Ingress使用serviceName和servicePort連接Service,你應該檢查那些是否正確配置,你可以使用以下命令檢查Ingress是否正確配置:
kubectl describe ingress <ingress-name>
如果Backend列是空的,那么配置中肯定存在錯誤,
如果你能在Backend列中看到endpoint,但依舊無法訪問應用程式,那么可能是以下問題:
-
你將Ingress暴露于公網的方式
-
你將集群暴露于公網的方式
你可以通過直接連接到Ingress Pod將基礎設施問題與Ingress隔離開來,
首先,為你的Ingress Controller檢索Pod(可能位于不同的命名空間中):
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
描述它以檢索埠:
kubectl describe pod nginx-ingress-controller-6fc5bcc
--namespace kube-system \
| grep Ports
最后,連接到Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
此時,每次你在電腦上訪問埠3000,請求將會轉發到Pod上的埠80,
那么,現在能夠正常運行了嗎?
如果正常作業,問題就出在基礎設施,你應該檢查流量如何路由到你的集群,
如果無法正常作業,問題就在Ingress controller,你應該除錯Ingress,
如果仍然無法使Ingress controller正常作業,則應該開始對其進行除錯,市場有許多不同版本的Ingress controller,比較流行的包括Nginx、HAProxy、Traefik等,
你應該查閱Ingress controller的檔案以查找故障排查指南,
既然Ingress Nginx是最流行的Ingress controller,那么在下一個部分我們將介紹一些相關的技巧,
除錯Ingress Nginx
Ingress-nginx有kubectl的官方插件,你可以訪問以下網址查看:
https://kubernetes.github.io/ingress-nginx/kubectl-plugin/
你可以使用kubectl ingress-nginx來進行以下操作:
-
檢查日志、Backend、證書等
-
連接到Ingress
-
檢查當前的配置
你還可以嘗試以下三個命令:
-
kubectl ingress-nginx lint這是用來檢查nginx.conf -
kubectl ingress-nginx backend來檢查Backend(與kubectl describe ingress <ingress-name>類似) -
kubectl ingress-nginx logs來檢查日志
請注意,你需要使用--namespace <name>來指定正確的命名空間,
總 結
如果你毫無頭緒,那么在Kubernetes中進行故障排除可能是一項艱巨的任務,
你應該永遠記住以從下至上的順序解決問題:現檢查Pod,然后向上移動堆疊至Service和Ingress,
而本文中的debug技術在其他地方也是通用的,例如:
-
出現故障的Jobs和CronJobs
-
StatefulSets和DaemonSets
希望大家都沒有bug!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/41209.html
標籤:其他
