個人K8s還在學習中,相關博客還沒有寫,准備學第二遍再開始學,發現這篇文章挺好,先轉載一下。
下面是一個示意圖,可幫助你調試Kubernetes Deployment(你可以在此處下載它的PDF版本 https://tonybai.com/wp-content/uploads/k8s-deployment-troubleshooting/troubleshooting-kubernetes.pdf)。
當你希望在Kubernetes中部署應用程序時,你通常會定義三個組件:
•一個Deployment - 這是一份用於創建你的應用程序的Pod副本的"食譜";
•一個Service - 一個內部負載均衡器,用於將流量路由到內部的Pod上;
•一個Ingress - 描述如何流量應該如何從集群外部流入到集群內部的你的服務上。
下面讓我們用示意圖快速總結一下要點。
在Kubernetes中,你的應用程序通過兩層負載均衡器暴露服務:內部的和外部的
內部的負載均衡器稱為Service,而外部的負載均衡器稱為Ingress
Pod不會直接部署。Deployment會負責創建Pod並管理它們
假設你要部署一個簡單的"HelloWorld"應用,該應用的YAML文件的內容應該類似下面這樣:
// 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)名重要嗎?它們是否在每一處都應該是一樣的?
在進行調試之前,讓我們回顧一下這三個組件是如何相互關聯的。
讓我們從Deployment和Service開始。
一. 連接Deployment和Service
令人驚訝的消息是,Service和Deployment之間根本沒有連接。
事實是:Service直接指向Pod,並完全跳過了Deployment。
因此,你應該注意的是Pod和Service之間的相互關系。
你應該記住三件事:
•Service selector應至少與Pod的一個標簽匹配;
•Service的targetPort應與Pod中容器的containerPort匹配;
•Service的port可以是任何數字。多個Service可以使用同一端口號,因為它們被分配了不同的IP地址。
下面的圖總結了如何連接端口:
考慮上面被一個服務暴露的Pod
創建Pod時,應為Pod中的每個容器定義containerPort端口
當創建一個Service時,你可以定義port和targetPort,但是哪個用來連接容器呢?
targetPort和containerPort應該始終保持匹配
如果容器暴露3000端口(containerPort),那么targetPort應該匹配這一個端口號
再來看看YAML,標簽和ports/targetPort應該匹配:
// 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:
any-name: my-app
那deployment頂部的track: canary標簽呢?
它也應該匹配嗎?
該標簽屬於deployment,service的選擇器未使用它來路由流量。
換句話說,你可以安全地刪除它或為其分配其他值。
那matchLabels選擇器呢?
它必須始終與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命令連接到service並測試連接。
$ kubectl port-forward service/<service name> 3000:80
•service/ 是服務的名稱- 在上面的YAML中是“my-service”
•3000是你希望在計算機上打開的端口
•80是service通過port字段暴露的端口
如果可以連接,則說明設置正確。
如果不行,則很可能是你填寫了錯誤的標簽或端口不匹配。
二. 連接Service和Ingress
接下來是配置Ingress以將你的應用暴露到集群外部。
Ingress必須知道如何檢索服務,然后檢索Pod並將流量路由給它們。
Ingress按名字和暴露的端口檢索正確的服務。
在Ingress和Service中應該匹配兩件事:
•Ingress的servicePort應該匹配service的port;
•Ingress的serviceName應該匹配服務的name。
下面的圖總結了如何連接端口:
你已經知道servive暴露一個port
Ingress有一個字段叫servicePort
service的port和Ingress的service應該始終保持匹配
如果你為service指定的port是80,那么你也應該將ingress的servicePort改為80
實踐中,你應該查看以下幾行(下面代碼中的my-service和80):
// hello-world.yaml
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,但是這次你應該連接到Ingress控制器,而不是連接到Service。
首先,使用以下命令檢索Ingress控制器的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控制器Pod上的端口80。
如果訪問http://localhost:3000,則應找到提供網頁服務的應用程序。
回顧Port
快速回顧一下哪些端口和標簽應該匹配:
• service selector應與Pod的標簽匹配
• service的targetPort應與Pod中容器的containerPort匹配
• service的端口可以是任何數字。多個服務可以使用同一端口,因為它們分配了不同的IP地址。
• ingress的servicePort應該匹配service的port
• serivce的名稱應與ingress中的serviceName字段匹配
知道如何構造YAML定義只是故事的一部分。
出了問題后該怎么辦?
Pod可能無法啟動,或者正在崩潰。
三. kubernetes deployment故障排除的3個步驟
在深入研究失敗的deployment之前,我們必須對Kubernetes的工作原理有一個明確定義的思維模型。
由於每個deployment中都有三個組件,因此你應該自下而上依次調試所有組件。
•你應該先確保Pods正在運行•然后,專注於讓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。
你如何調查出了什么問題?
有四個有用的命令可以對Pod進行故障排除:
• kubectl logs 有助於檢索Pod容器的日志
• kubectl describe pod 檢索與Pod相關的事件列表很有用
• kubectl get pod 用於提取存儲在Kubernetes中的Pod的YAML定義
• kubectl exec -ti bash 在Pod的一個容器中運行交互式命令很有用
應該使用哪一個呢?
沒有一種萬能的。
相反,我們應該結合着使用它們。
常見Pod錯誤
Pod可能會出現啟動和運行時錯誤。
啟動錯誤包括:
• ImagePullBackoff
• ImageInspectError
• ErrImagePull
• ErrImageNeverPull
• RegistryUnavailable
• InvalidImageName
運行時錯誤包括:
• CrashLoopBackOff
• RunContainerError
• KillContainerError
• VerifyNonRootError
• RunInitContainerError
• CreatePodSandboxError
• ConfigPodSandboxError
• KillPodSandboxError
• SetupNetworkError
• TeardownNetworkError
有些錯誤比其他錯誤更常見。
以下是最常見的錯誤列表以及如何修復它們的方法。
ImagePullBackOff
當Kubernetes無法獲取到Pod中某個容器的鏡像時,將出現此錯誤。
共有三個可能的原因:
•鏡像名稱無效-例如,你拼錯了名稱,或者image不存在
•你為image指定了不存在的標簽
•你嘗試檢索的image屬於一個私有registry,而Kubernetes沒有憑據可以訪問它
前兩種情況可以通過更正image名稱和標記來解決。
針對第三種情況,你應該將私有registry的訪問憑證通過Secret添加到k8s中並在Pod中引用它。
官方文檔中有一個有關如何實現此目標的示例。
CrashLoopBackOff
如果容器無法啟動,則Kubernetes將顯示錯誤狀態為:CrashLoopBackOff。
通常,在以下情況下容器無法啟動:
•應用程序中存在錯誤,導致無法啟動•你未正確配置容器
•Liveness探針失敗太多次
你應該嘗試從該容器中檢索日志以調查其失敗的原因。
如果由於容器重新啟動太快而看不到日志,則可以使用以下命令:
$ kubectl logs <pod-name> --previous
這個命令打印前一個容器的錯誤消息。
RunContainerError
當容器無法啟動時,出現此錯誤。
甚至在容器內的應用程序啟動之前。
該問題通常是由於配置錯誤,例如:
•掛載不存在的卷,例如ConfigMap或Secrets
•將只讀卷安裝為可讀寫
你應該使用kubectl describe pod 命令收集和分析錯誤。
處於Pending狀態的Pod
當創建Pod時,該Pod保持Pending狀態。
為什么?
假設你的調度程序組件運行良好,可能的原因如下:
•集群沒有足夠的資源(例如CPU和內存)來運行Pod
•當前的命名空間具有ResourceQuota對象,創建Pod將使命名空間超過配額
•該Pod綁定到一個處於pending狀態的 PersistentVolumeClaim
最好的選擇是檢查kubectl describe命令輸出的“事件”部分內容:
$ kubectl describe pod <pod name>
對於因ResourceQuotas而導致的錯誤,可以使用以下方法檢查集群的日志:
$ kubectl get events --sort-by=.metadata.creationTimestamp
處於未就緒狀態的Pod
如果Pod正在運行但未就緒(not ready),則表示readiness就緒探針失敗。
當“就緒”探針失敗時,Pod未連接到服務,並且沒有流量轉發到該實例。
就緒探針失敗是應用程序的特定錯誤,因此你應檢查kubectl describe中的“ 事件”部分以識別錯誤。
2. 服務的故障排除
如果你的Pod正在運行並處於就緒狀態,但仍無法收到應用程序的響應,則應檢查服務的配置是否正確。
service旨在根據流量的標簽將流量路由到Pod。
因此,你應該檢查的第一件事是服務關聯了多少個Pod。
你可以通過檢查服務中的端點(endpoint)來做到這一點:
$ kubectl describe service <service-name> | grep Endpoints
端點是一對,並且在服務(至少)以Pod為目標時,應該至少有一個端點。
如果“端點”部分為空,則有兩種解釋:
•你沒有運行帶有正確標簽的Pod(提示:你應檢查自己是否在正確的命名空間中)
•service的selector標簽上有錯字
如果你看到端點列表,但仍然無法訪問你的應用程序,則targetPort可能是你服務中的罪魁禍首。
你如何測試服務?
無論服務類型如何,你都可以使用kubectl port-forward來連接它:
$kubectl port-forward service/<service-name> 3000:80
這里:
• 是服務的名稱•3000 是你希望在計算機上打開的端口
•80 是服務公開的端口
3.Ingress的故障排除
如果你已到達本節,則:
•Pod正在運行並准備就緒
•服務會將流量分配到Pod
但是你仍然看不到應用程序的響應。
這意味着最有可能是Ingress配置錯誤。
由於正在使用的Ingress控制器是集群中的第三方組件,因此有不同的調試技術,具體取決於Ingress控制器的類型。
但是在深入研究Ingress專用工具之前,你可以用一些簡單的方法進行檢查。
Ingress使用serviceName和servicePort連接到服務。
你應該檢查這些配置是否正確。
你可以通過下面命令檢查Ingress配置是否正確:
$kubectl describe ingress <ingress-name>
如果backend一列為空,則配置中必然有一個錯誤。
如果你可以在“backend”列中看到端點,但是仍然無法訪問該應用程序,則可能是以下問題:
•你如何將Ingress暴露於公共互聯網
•你如何將集群暴露於公共互聯網
你可以通過直接連接到Ingress Pod來將基礎結構問題與Ingress隔離開。
首先,獲取你的Ingress控制器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控制器中。你應該調試Ingress。
如果仍然無法使Ingress控制器正常工作,則應開始對其進行調試。
目前有許多不同版本的Ingress控制器。
熱門選項包括Nginx,HAProxy,Traefik等。
你應該查閱Ingress控制器的文檔以查找故障排除指南。
由於Ingress Nginx是最受歡迎的Ingress控制器,因此在下一部分中我們將介紹一些有關調試ingress-nginx的技巧。
調試Ingress Nginx
Ingress-nginx項目有一個Kubectl的官方插件。
你可以用kubectl ingress-nginx來:
•檢查日志,后端,證書等。
•連接到ingress
•檢查當前配置
你應該嘗試的三個命令是:
•kubectl ingress-nginx lint,它會檢查 nginx.conf
•kubectl ingress-nginx backend,以檢查后端(類似於kubectl describe ingress )
•kubectl ingress-nginx logs,查看日志
請注意,你可能需要為Ingress控制器指定正確的名稱空間--namespace 。
四. 總結
如果你不知道從哪里開始,那么在Kubernetes中進行故障排除可能是一項艱巨的任務。
你應該始終牢記從下至上解決問題:從Pod開始,然后通過Service和Ingress向上移動堆棧。
你在本文中了解到的調試技術也可以應用於其他對象,例如:
•failing Job和CronJob
•StatefulSets和DaemonSets
本文翻譯自learnk8s上的文章A visual guide on troubleshooting Kubernetes deployments。
微博:https://weibo.com/bigwhite20xx 微信公眾號:iamtonybai 博客:tonybai.com github: https://github.com/bigwhite