Categories
程式開發

超長可視化指南!你必須了解的K8S部署的調試思路


本文將幫助你釐清在Kubernetes中調試 deployment的思路。下圖是完整的故障排查思路,如果你想獲得更清晰的圖片,請在公眾號後台(非評論區/非小助手)回复“troubleshooting”。

超長可視化指南!你必須了解的K8S部署的調試思路 1

當你希望在Kubernetes中部署一個應用程序,你通常需要定義三個組件:

  • Deployment——這是創建名為Pods的應用程序副本的方法
  • Serivce——內部負載均衡器,將流量路由到Pods
  • Ingress——可以描述流量如何從集群外部流向Service

接下來,我們通過圖片快速回顧一下。

超長可視化指南!你必須了解的K8S部署的調試思路 2

在Kubernetes中,你的應用程序通過兩層負載均衡器暴露:內部和外部。

超長可視化指南!你必須了解的K8S部署的調試思路 3

內部負載均衡器稱為Service,而外部負載均衡器則稱為Ingress。

超長可視化指南!你必須了解的K8S部署的調試思路 4

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是如何與彼此關聯的。你應該記住三件事:

  1. Service selector至少與Pod的一個標籤匹配
  2. Serivce targetPort應該與Pod內的容器的containerPort相匹配
  3. Service port可以是任何數字。多個Service可以使用同一個端口,因為它們已經被分配了不同的IP地址

以下圖片總結瞭如何連接端口:

超長可視化指南!你必須了解的K8S部署的調試思路 5

考慮由Service暴露的pod

超長可視化指南!你必須了解的K8S部署的調試思路 6

當你創建一個pod,你應該在你的Pod中為每個容器定義端口containerPort

超長可視化指南!你必須了解的K8S部署的調試思路 7

當你創建一個Service時,你能夠定義一個port和一個targetPort。但你應該將哪一個連接到容器呢?

超長可視化指南!你必須了解的K8S部署的調試思路 8

targetPortcontainerPort應該能夠匹配

超長可視化指南!你必須了解的K8S部署的調試思路 9

如果你的容器暴露端口3000,那麼targetPort應該與該數字相匹配。

如果你查看了YAML,標籤與portstargerPort應該匹配:

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不使用它來路由流量。換言之,你可以安全地將其移除或者給它分配不同的值。

那麼matchLabels selector呢?它需要與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/ 3000:80

其中:

  • service/是serivce的名稱——在當前YAML中,是“my-service”。
  • 3000是你希望在你的電腦上打開的端口
  • 80是Service在port字段中暴露的端口

如果你能夠連接,那麼設置就是正確的。如果你無法連接,你很有可能弄錯了標籤或者端口未匹配。

連接Service和Ingress

暴露應用程序的下一步是配置Ingress。 Ingress必須知道如何檢索Service,然後檢索Pod並將流量路由到它們。 Ingress通過名稱和暴露的端口來檢索正確的Service。

在Ingress和Service中應該匹配兩件事:

  1. Ingress的servicePort應該與Service的port匹配
  2. Ingress的serviceName應該與Service的name相匹配

以下圖片將總結如何連接端口:

超長可視化指南!你必須了解的K8S部署的調試思路 10

你已經知道該服務暴露了一個端口

超長可視化指南!你必須了解的K8S部署的調試思路 11

Ingress有一個名為servicePort的字段。

超長可視化指南!你必須了解的K8S部署的調試思路 12

Service port和Ingress servicePort應該相匹配

超長可視化指南!你必須了解的K8S部署的調試思路 13

如果你決定分配端口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,你應該能找到提供網頁的應用程序。

簡單回顧一下

現在,我們來快速回顧一下什麼端口和標籤需要匹配:

  1. Service selector應該匹配Pod的標籤
  2. Service targerPort應該匹配在Pod內容器的containerPort
  3. Service 端口可以是任意數字。多個Service可以使用同個端口,因為它們已經分配了不同的IP地址
  4. Ingress的servicePort應該匹配在Service中的port
  5. Service的名稱應該匹配在Ingress中的serviceName的字段

了解如何構造YAML只是開始。那麼,出了問題時會有什麼表現? Pod可能無法啟動,或者直接崩潰。

3步排查K8S Deployment故障

在我們深入研究有故障的deployment之前,必須有一個明確定義的模型,以了解Kubernetes的工作方式。

既然在每個deployment中都有那三個組件,你應該從底層開始按順序調試它們。

  1. 你應該確保你的Pod正在運行
  2. 著重關注使Service將流量路由到Pod
  3. 檢查Ingress是否正確配置

超長可視化指南!你必須了解的K8S部署的調試思路 14

你應該從底層開始排查Deployment故障。首先,檢查Pod是否準備就緒並且正在運行

超長可視化指南!你必須了解的K8S部署的調試思路 15

如果Pod已經準備就緒,你需要檢查Service是否可以將流量分配到Pod。

超長可視化指南!你必須了解的K8S部署的調試思路 16

最後你應該檢查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的容器日誌
  • 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其中之一的容器鏡像時,將出現此錯誤。

有三種常見原因:

  • 鏡像名稱無效——例如,你錯誤拼寫名稱或鏡像不存在
  • 你給這一鏡像指定了一個不存在的tag
  • 你所檢索的鏡像是私有倉庫的,並且Kubernetes沒有訪問它的憑據

前兩個原因可以通過更正鏡像名稱和tag解決。最後一個,你需要將憑據添加到“Secret”中的私有鏡像倉庫中,並在Pod中引用它。

官方文檔可以讓你更加清楚:

https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/

CrashLoopBackOff

如果容器無法啟動,Kubernetes狀態將顯示CrashLoopBackOff消息。

通常情況下,容器在以下場景中無法啟動:

你應該嘗試並檢索該容器的日誌以確定出現故障的原因。

如果由於你的容器重啟過快而無法查看日誌,你可以使用以下命令:

kubectl logs  --previous

它將從之前的容器中打印錯誤信息。

RunContainerError

容器不能啟動時出現錯誤,甚至在容器內的應用程序啟動之前就無法啟動。

這個問題通常由於錯誤配置導致的,如:

  • 安裝一個不存在的volume,如ConfigMap或Secret
  • 將只讀volume安裝為可讀寫

你應該使用kubectl describe pod 來收集和分析錯誤。

Pod處於Pending狀態

當你創建一個Pod時,Pod保持在Pending狀態。這是為什麼呢?假設你的調度組件運行了解,那麼有以下幾個原因:

  • 集群沒有足夠的資源來運行Pod,如CPU和內存
  • 當前命名空間有一個ResourceQuota對象並且所創建的Pod會使該命名空間超過資源額度
  • Pod與一個Pending狀態的PersistentVolumeClaim綁定。

那麼,最好的選擇是使用命令kubectl describe檢查事件:

kubectl describe pod 

對於由於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  | grep Endpoints

一個endpoint是一對,並且當Service(至少)target一個pod時。至少有一對。

如果“Endpoint”部分是空的,那麼有兩種解釋:

  1. 任何正在運行的Pod沒有正確的label(提示:你需要檢查以下你是否在正確的命​​名空間內)
  2. 在Service的selector標籤中有錯別字

如果你看到了endpoint列表,但依舊無法訪問你的應用程序,那麼你的Service中的targetPort可能是罪魁禍首。

你應該怎麼測試Service?無論Service類型是什麼,都可以使用kubectl port-forward連接到它:

kubectl port-forward service/ 3000:80

其中:

  • 是Service的名稱
  • 3000是你想要在電腦上打開的端口
  • 80是由Service暴露的端口

3、 排查Ingress故障

如果你走到了這個部分,這意味著:

  • Pod正在運行並且準備就緒
  • Service可以分發流量給Pod

但你依舊無法接收app的響應。那麼這很有可能是Ingress配置出現錯誤。

由於使用的Ingress controller是集群中的第三方組件,那麼根據Ingress controller的類型會由不同的調試技術。但是在深入研究Ingress特定的工具之前,你可以使用一些簡單的方法檢查。

Ingress使用serviceNameservicePort連接Service。你應該檢查那些是否正確配置。你可以使用以下命令檢查Ingress是否正確配置:

kubectl describe ingress 

如果 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 類似)
  • kubectl ingress-nginx logs來檢查日誌

請注意,你需要使用--namespace 來指定正確的命名空間。

總結

如果你毫無頭緒,那麼在Kubernetes中進行故障排除可能是一項艱鉅的任務。

你應該永遠記住以從下至上的順序解決問題:現檢查Pod,然後向上移動堆棧至Service和Ingress。

而本文中的debug技術在其他地方也是通用的,例如:

  • 出現故障的Jobs和CronJobs
  • StatefulSets和DaemonSets

希望大家都沒有bug!

本文轉載自公眾號RancherLabs(ID:RancherLabs)。

原文鏈接

https://mp.weixin.qq.com/s/-vQUbP5c1bubWGKwy9orVw