背景假設
當你希望在Kubernetes中部署應用程序時,你通常會定義三個組件:
- 一個Deployment - 這是一份用於創建你的應用程序的Pod副本的"食譜";
- 一個Service - 一個內部負載均衡器,用於將流量路由到內部的Pod上;
- 一個Ingress - 描述如何流量應該如何從集群外部流入到集群內部的你的服務上。
下面讓我們用示意圖快速總結一下要點。
1.在Kubernetes中,你的應用程序通過兩層負載均衡器暴露服務:內部的和外部的
2.內部的負載均衡器稱為Service,而外部的負載均衡器稱為Ingress
3.Pod不會直接部署。Deployment會負責創建Pod並管理它們
假設你要部署一個簡單的"HelloWorld"應用,該應用的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地址。
1.考慮上面被一個服務暴露的Pod
2.創建Pod時,應為Pod中的每個容器定義containerPort端口
3.當創建一個Service時,你可以定義port和targetPort,但是哪個用來連接容器呢?
- targetPort和containerPort應該始終保持匹配
5.如果容器暴露3000端口(containerPort),那么targetPort應該匹配這一個端口號
再來看看YAML,標簽和ports/targetPort應該匹配:
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中應該匹配兩件事:
1.Ingress的 servicePort 應該匹配service的 port
2.Ingress的 serviceName 應該匹配服務的 name
下面的圖總結了如何連接端口:
1.你已經知道servive暴露一個port
2.Ingress有一個字段叫servicePort
3.service的port和Ingress的service應該始終保持匹配
4.如果你為service指定的port是80,那么你也應該將ingress的 servicePort
改為80
實踐中,你應該查看以下幾行(下面代碼中的 my-service 和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,但是這次你應該連接到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
1.service selector應與Pod的標簽匹配
2.service的 targetPort 應與Pod中容器的 containerPort 匹配
3.service的端口可以是任何數字。多個服務可以使用同一端口,因為它們分配了不同的IP地址。
4.ingress的 servicePort 應該匹配 service 的 port
5.serivce的名稱應與 ingress 中的 serviceName 字段匹配
三. k8s deployment故障排除的步驟
由於每個deployment中都有三個組件,因此你應該自下而上依次調試所有組件。
1.你應該先確保Pods正在運行
2.然后,專注於讓service將流量路由到到正確的Pod
3.再檢查是否正確配置了Ingress
1.你應該從底部開始對deployment進行故障排除。首先,檢查Pod是否已就緒並正在運行。
2.如果Pod已就緒,則應調查service是否可以將流量分配給Pod。
3.最后,你應該檢查service與ingress之間的連接。
1. 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
在上述會話中,最后一個Pod處於就緒並正常運行的狀態;但是,前兩個Pod既不處於Running也不是Ready。
有四個有用的命令可以對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錯誤
啟動錯誤包括:
- 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 <pod-name>
命令收集和分析錯誤。
# 處於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
中的 Events 部分以識別錯誤。
# 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
這里:
<service-name>
是服務的名稱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
以檢索端口:
$ 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控制器之一,因此在下一部分中我們將介紹一些有關調試ingress-nginx的技巧。
如果參考 kuboard.cn 上的 Kubernetes 安裝文檔,您安裝的 Ingress 控制器是 nginx ingress
# 調試Ingress Nginx
Ingress-nginx 項目有一個 kubectl 的官方插件
你可以用kubectl ingress-nginx來:
- 檢查日志,后端,證書等
- 連接到ingress
- 檢查當前配置
你應該嘗試的三個命令是:
kubectl ingress-nginx lint
,它會檢查nginx.conf
kubectl ingress-nginx backend
,以檢查后端(類似於kubectl describe ingress <ingress-name>
)kubectl ingress-nginx logs
,查看日志
請注意,你可能需要為Ingress控制器指定正確的名稱空間
--namespace <name>
。
四 總結
如果你不知道從哪里開始,那就從Pod開始,然后通過Service和Ingress向上移動堆棧。