從零開始入門 K8s:可觀測性,你的應用健康嗎?

一、需求來源

首先來看一下,整個需求的來源:當把應用遷移到 Kubernetes 之後,要如何去保障應用的健康與穩定呢?其實很簡單,可以從兩個方面來進行增強:

  1. 首先是提高應用的可觀測性;

  2. 第二是提高應用的可恢復能力。

從可觀測性上來講,可以在三個方面來去做增強:

  1. 首先是應用的健康狀態上面,可以實時地進行觀測;

  2. 第二個是可以獲取應用的資源使用情況;

  3. 第三個是可以拿到應用的實時日誌,進行問題的診斷與分析。

當出現了問題之後,首先要做的事情是要降低影響的範圍,進行問題的調試與診斷。最後當出現問題的時候,理想的狀況是:可以通過和 K8s 集成的自愈機制進行完整的恢復。

二、Liveness 與 Readiness

本小節爲大家介紹 Liveness probe 和 eadiness probe。

應用健康狀態-初識 Liveness 與 Readiness

Liveness probe 也叫就緒指針,用來判斷一個 pod 是否處在就緒狀態。當一個 pod 處在就緒狀態的時候,它才能夠對外提供相應的服務,也就是說接入層的流量才能打到相應的 pod。當這個 pod 不處在就緒狀態的時候,接入層會把相應的流量從這個 pod 上面進行摘除。

來看一下簡單的一個例子,如下圖其實就是一個 Readiness 就緒的一個例子:

當這個 pod 指針判斷一直處在失敗狀態的時候,其實接入層的流量不會打到現在這個 pod 上。

當這個 pod 的狀態從 FAIL 的狀態轉換成 success 的狀態時,它才能夠真實地承載這個流量。

Liveness 指針也是類似的,它是存活指針,用來判斷一個 pod 是否處在存活狀態。當一個 pod 處在不存活狀態的時候,會出現什麼事情呢?

這個時候會由上層的判斷機制來判斷這個 pod 是否需要被重新拉起。那如果上層配置的重啓策略是 restart always 的話,那麼此時這個 pod 會直接被重新拉起。

應用健康狀態-使用方式

接下來看一下 Liveness 指針和 Readiness 指針的具體的用法。

探測方式

Liveness 指針和 Readiness 指針支持三種不同的探測方式:

  1. 第一種是 httpGet。它是通過發送 http Get 請求來進行判斷的,當返回碼是 200-399 之間的狀態碼時,標識這個應用是健康的;

  2. 第二種探測方式是 Exec。它是通過執行容器中的一個命令來判斷當前的服務是否是正常的,當命令行的返回結果是 0,則標識容器是健康的;

  3. 第三種探測方式是 tcpSocket 。它是通過探測容器的 IP 和 Port 進行 TCP 健康檢查,如果這個 TCP 的鏈接能夠正常被建立,那麼標識當前這個容器是健康的。

探測結果

從探測結果來講主要分爲三種:

  • 第一種是 success,當狀態是 success 的時候,表示 container 通過了健康檢查,也就是 Liveness probe 或 Readiness probe 是正常的一個狀態;

  • 第二種是 Failure,Failure 表示的是這個 container 沒有通過健康檢查,如果沒有通過健康檢查的話,那麼此時就會進行相應的一個處理,那在 Readiness 處理的一個方式就是通過 service。service 層將沒有通過 Readiness 的 pod 進行摘除,而 Liveness 就是將這個 pod 進行重新拉起,或者是刪除。

  • 第三種狀態是 Unknown,Unknown 是表示說當前的執行的機制沒有進行完整的一個執行,可能是因爲類似像超時或者像一些腳本沒有及時返回,那麼此時 Readiness-probe 或 Liveness-probe 會不做任何的一個操作,會等待下一次的機制來進行檢驗。

那在 kubelet 裏面有一個叫 ProbeManager 的組件,這個組件裏面會包含 Liveness-probe 或 Readiness-probe,這兩個 probe 會將相應的 Liveness 診斷和 Readiness 診斷作用在 pod 之上,來實現一個具體的判斷。

應用健康狀態-Pod Probe Spec

下面介紹這三種方式不同的檢測方式的一個 yaml 文件的使用。

首先先看一下 exec,exec 的使用其實非常簡單。如下圖所示,大家可以看到這是一個 Liveness probe,它裏面配置了一個 exec 的一個診斷。接下來,它又配置了一個 command 的字段,這個 command 字段裏面通過 cat 一個具體的文件來判斷當前 Liveness probe 的狀態,當這個文件裏面返回的結果是 0 時,或者說這個命令返回是 0 時,它會認爲此時這個 pod 是處在健康的一個狀態。

那再來看一下這個 httpGet,httpGet 裏面有一個字段是路徑,第二個字段是 port,第三個是 headers。這個地方有時需要通過類似像 header 頭的一個機制做 health 的一個判斷時,需要配置這個 header,通常情況下,可能只需要通過 health 和 port 的方式就可以了。

第三種是 tcpSocket,tcpSocket 的使用方式其實也比較簡單,你只需要設置一個檢測的端口,像這個例子裏面使用的是 8080 端口,當這個 8080 端口 tcp connect 審覈正常被建立的時候,那 tecSocket,Probe 會認爲是健康的一個狀態。

此外還有如下的五個參數,是 Global 的參數。

  • 第一個參數叫 initialDelaySeconds,它表示的是說這個 pod 啓動延遲多久進行一次檢查,比如說現在有一個 Java 的應用,它啓動的時間可能會比較長,因爲涉及到 jvm 的啓動,包括 Java 自身 jar 的加載。所以前期,可能有一段時間是沒有辦法被檢測的,而這個時間又是可預期的,那這時可能要設置一下 initialDelaySeconds;

  • 第二個是 periodSeconds,它表示的是檢測的時間間隔,正常默認的這個值是 10 秒;

  • 第三個字段是 timeoutSeconds,它表示的是檢測的超時時間,當超時時間之內沒有檢測成功,那它會認爲是失敗的一個狀態;

  • 第四個是 successThreshold,它表示的是:當這個 pod 從探測失敗到再一次判斷探測成功,所需要的閾值次數,默認情況下是 1 次,表示原本是失敗的,那接下來探測這一次成功了,就會認爲這個 pod 是處在一個探針狀態正常的一個狀態;

  • 最後一個參數是 failureThreshold,它表示的是探測失敗的重試次數,默認值是 3,表示的是當從一個健康的狀態連續探測 3 次失敗,那此時會判斷當前這個pod的狀態處在一個失敗的狀態。

應用健康狀態-Liveness 與 Readiness 總結

接下來對 Liveness 指針和 Readiness 指針進行一個簡單的總結。

介紹

Liveness 指針是存活指針,它用來判斷容器是否存活、判斷 pod 是否 running。如果 Liveness 指針判斷容器不健康,此時會通過 kubelet 殺掉相應的 pod,並根據重啓策略來判斷是否重啓這個容器。如果默認不配置 Liveness 指針,則默認情況下認爲它這個探測默認返回是成功的。

Readiness 指針用來判斷這個容器是否啓動完成,即 pod 的 condition 是否 ready。如果探測的一個結果是不成功,那麼此時它會從 pod 上 Endpoint 上移除,也就是說從接入層上面會把前一個 pod 進行摘除,直到下一次判斷成功,這個 pod 纔會再次掛到相應的 endpoint 之上。

檢測失敗

對於檢測失敗上面來講 Liveness 指針是直接殺掉這個 pod,而 Readiness 指針是切掉 endpoint 到這個 pod 之間的關聯關係,也就是說它把這個流量從這個 pod 上面進行切掉。

適用場景

Liveness 指針適用場景是支持那些可以重新拉起的應用,而 Readiness 指針主要應對的是啓動之後無法立即對外提供服務的這些應用。

注意事項

在使用 Liveness 指針和 Readiness 指針的時候有一些注意事項。因爲不論是 Liveness 指針還是 Readiness 指針都需要配置合適的探測方式,以免被誤操作。

  • 第一個是調大超時的閾值,因爲在容器裏面執行一個 shell 腳本,它的執行時長是非常長的,平時在一臺 ecs 或者在一臺 vm 上執行,可能 3 秒鐘返回的一個腳本在容器裏面需要 30 秒鐘。所以這個時間是需要在容器裏面事先進行一個判斷的,那如果可以調大超時閾值的方式,來防止由於容器壓力比較大的時候出現偶發的超時;

  • 第二個是調整判斷的一個次數,3 次的默認值其實在比較短週期的判斷週期之下,不一定是最佳實踐,適當調整一下判斷的次數也是一個比較好的方式;

  • 第三個是 exec,如果是使用 shell 腳本的這個判斷,調用時間會比較長,比較建議大家可以使用類似像一些編譯性的腳本 Golang 或者一些 C 語言、C++ 編譯出來的這個二進制的 binary 進行判斷,那這種通常會比 shell 腳本的執行效率高 30% 到 50%;

  • 第四個是如果使用 tcpSocket 方式進行判斷的時候,如果遇到了 TLS 的服務,那可能會造成後邊 TLS 裏面有很多這種未健全的 tcp connection,那這個時候需要自己對業務場景上來判斷,這種的鏈接是否會對業務造成影響。

三、問題診斷

接下來給大家講解一下在 K8s 中常見的問題診斷。

應用故障排查-瞭解狀態機制

首先要了解一下 K8s 中的一個設計理念,就是這個狀態機制。因爲 K8s 是整個的一個設計是面向狀態機的,它裏面通過 yaml 的方式來定義的是一個期望到達的一個狀態,而真正這個 yaml 在執行過程中會由各種各樣的 controller來負責整體的狀態之間的一個轉換。

比如說上面的圖,實際上是一個 Pod 的一個生命週期。剛開始它處在一個 pending 的狀態,那接下來可能會轉換到類似像 running,也可能轉換到 Unknown,甚至可以轉換到 failed。然後,當 running 執行了一段時間之後,它可以轉換到類似像 successded 或者是 failed,然後當出現在 unknown 這個狀態時,可能由於一些狀態的恢復,它會重新恢復到 running 或者 successded 或者是 failed 。

其實 K8s 整體的一個狀態就是基於這種類似像狀態機的一個機制進行轉換的,而不同狀態之間的轉化都會在相應的 K8s對象上面留下來類似像 Status 或者像 Conditions 的一些字段來進行表示。

像下面這張圖其實表示的就是說在一個 Pod 上面一些狀態位的一些展現。

比如說在 Pod 上面有一個字段叫 Status,這個 Status 表示的是 Pod 的一個聚合狀態,在這個裏面,這個聚合狀態處在一個 pending 狀態。

然後再往下看,因爲一個 pod 裏面有多個 container,每個 container 上面又會有一個字段叫 State,然後 State 的狀態表示當前這個 container 的一個聚合狀態。那在這個例子裏面,這個聚合狀態處在的是 waiting 的狀態,那具體的原因是因爲什麼呢?是因爲它的鏡像沒有拉下來,所以處在 waiting 的狀態,是在等待這個鏡像拉取。然後這個 ready 的部分呢,目前是 false,因爲它這個進行目前沒有拉取下來,所以這個 pod 不能夠正常對外服務,所以此時 ready 的狀態是未知的,定義爲 false。如果上層的 endpoint 發現底層這個 ready 不是 true 的話,那麼此時這個服務是沒有辦法對外服務的。

再往下是 condition,condition 這個機制表示是說:在 K8s 裏面有很多這種比較小的這個狀態,而這個狀態之間的聚合會變成上層的這個 Status。那在這個例子裏面有幾個狀態,第一個是 Initialized,表示是不是已經初始化完成?那在這個例子裏面已經是初始化完成的,那它走的是第二個階段,是在這個 ready 的狀態。因爲上面幾個 container 沒有拉取下來相應的鏡像,所以 ready 的狀態是 false。

然後再往下可以看到這個 container 是否 ready,這裏可以看到是 false,而這個狀態是 PodScheduled,表示說當前這個 pod 是否是處在一個已經被調度的狀態,它已經 bound 在現在這個 node 之上了,所以這個狀態也是 true。

那可以通過相應的 condition 是 true 還是 false 來判斷整體上方的這個狀態是否是正常的一個狀態。而在 K8s 裏面不同的狀態之間的這個轉換都會發生相應的事件,而事件分爲兩種: 一種叫做 normal 的事件,一種是 warning 事件。大家可以看見在這第一條的事件是有個 normal 事件,然後它相應的 reason 是 scheduler,表示說這個 pod 已經被默認的調度器調度到相應的一個節點之上,然後這個節點是 cn-beijing192.168.3.167 這個節點之上。

再接下來,又是一個 normal 的事件,表示說當前的這個鏡像在 pull 相應的這個 image。然後再往下是一個 warning 事件,這個 warning 事件表示說 pull 這個鏡像失敗了。

以此類推,這個地方表示的一個狀態就是說在 K8s 裏面這個狀態機制之間這個狀態轉換會產生相應的事件,而這個事件又通過類似像 normal 或者是 warning 的方式進行暴露。開發者可以通過類似像通過這個事件的機制,可以通過上層 condition Status 相應的一系列的這個字段來判斷當前這個應用的具體的狀態以及進行一系列的診斷。

應用故障排查-常見應用異常

本小節介紹一下常見應用的一些異常。首先是 pod 上面,pod 上面可能會停留幾個常見的狀態。

Pod 停留在 Pending

第一個就是 pending 狀態,pending 表示調度器沒有進行介入。此時可以通過 kubectl describe pod 來查看相應的事件,如果由於資源或者說端口占用,或者是由於 node selector 造成 pod 無法調度的時候,可以在相應的事件裏面看到相應的結果,這個結果裏面會表示說有多少個不滿足的 node,有多少是因爲 CPU 不滿足,有多少是由於 node 不滿足,有多少是由於 tag 打標造成的不滿足。

Pod 停留在 waiting

那第二個狀態就是 pod 可能會停留在 waiting 的狀態,pod 的 states 處在 waiting 的時候,通常表示說這個 pod 的鏡像沒有正常拉取,原因可能是由於這個鏡像是私有鏡像,但是沒有配置 Pod secret;那第二種是說可能由於這個鏡像地址是不存在的,造成這個鏡像拉取不下來;還有一個是說這個鏡像可能是一個公網的鏡像,造成鏡像的拉取失敗。

Pod 不斷被拉取並且可以看到 crashing

第三種是 pod 不斷被拉起,而且可以看到類似像 backoff 。這個通常表示說 pod 已經被調度完成了,但是啓動失敗,那這個時候通常要關注的應該是這個應用自身的一個狀態,並不是說配置是否正確、權限是否正確,此時需要查看的應該是 pod 的具體日誌。

Pod 處在 Runing 但是沒有正常工作

第四種 pod 處在 running 狀態,但是沒有正常對外服務。那此時比較常見的一個點就可能是由於一些非常細碎的配置,類似像有一些字段可能拼寫錯誤,造成了 yaml 下發下去了,但是有一段沒有正常地生效,從而使得這個 pod 處在 running 的狀態沒有對外服務,那此時可以通過 apply-validate-f pod.yaml 的方式來進行判斷當前 yaml 是否是正常的,如果 yaml 沒有問題,那麼接下來可能要診斷配置的端口是否是正常的,以及 Liveness 或 Readiness 是否已經配置正確。

Service 無法正常的工作

最後一種就是 service 無法正常工作的時候,該怎麼去判斷呢?那比較常見的 service 出現問題的時候,是自己的使用上面出現了問題。因爲 service 和底層的 pod 之間的關聯關係是通過 selector 的方式來匹配的,也就是說 pod 上面配置了一些 label,然後 service 通過 match label 的方式和這個 pod 進行相互關聯。如果這個 label 配置的有問題,可能會造成這個 service 無法找到後面的 endpoint,從而造成相應的 service 沒有辦法對外提供服務,那如果 service 出現異常的時候,第一個要看的是這個 service 後面是不是有一個真正的 endpoint,其次來看這個 endpoint 是否可以對外提供正常的服務。

四、應用遠程調試

本節講解的是在 K8s 裏面如何進行應用的遠程調試,遠程調試主要分爲 pod 的遠程調試以及 service 的遠程調試。還有就是針對一些性能優化的遠程調試。

應用遠程調試 - Pod 遠程調試

首先把一個應用部署到集羣裏面的時候,發現問題的時候,需要進行快速驗證,或者說修改的時候,可能需要類似像登陸進這個容器來進行一些診斷。

比如說可以通過 exec 的方式進入一個 pod。像這條命令裏面,通過 kubectl exec-it pod-name 後面再填寫一個相應的命令,比如說 /bin/bash,表示希望到這個 pod 裏面進入一個交互式的一個 bash。然後在 bash 裏面可以做一些相應的命令,比如說修改一些配置,通過 supervisor 去重新拉起這個應用,都是可以的。

那如果指定這一個 pod 裏面可能包含着多個 container,這個時候該怎麼辦呢?怎麼通過 pod 來指定 container 呢?其實這個時候有一個參數叫做 -c,如上圖下方的命令所示。-c 後面是一個 container-name,可以通過 pod 在指定 -c 到這個 container-name,具體指定要進入哪個 container,後面再跟上相應的具體的命令,通過這種方式來實現一個多容器的命令的一個進入,從而實現多容器的一個遠程調試。

應用遠程調試 - Servic 遠程調試

那麼 service 的遠程調試該怎麼做呢?service 的遠程調試其實分爲兩個部分:

  • 第一個部分是說我想將一個服務暴露到遠程的一個集羣之內,讓遠程集羣內的一些應用來去調用本地的一個服務,這是一條反向的一個鏈路;

  • 還有一種方式是我想讓這個本地服務能夠去調遠程的服務,那麼這是一條正向的鏈路。

在反向列入上面有這樣一個開源組件,叫做 Telepresence,它可以將本地的應用代理到遠程集羣中的一個 service 上面,使用它的方式非常簡單。

首先先將 Telepresence 的一個 Proxy 應用部署到遠程的 K8s 集羣裏面。然後將遠程單一個 deployment swap 到本地的一個 application,使用的命令就是 Telepresence-swap-deployment 然後以及遠程的 DEPLOYMENT_NAME。通過這種方式就可以將本地一個 application 代理到遠程的 service 之上、可以將應用在遠程集羣裏面進行本地調試,這個有興趣的同學可以到 GitHub 上面來看一下這個插件的使用的方式。

第二個是如果本地應用需要調用遠程集羣的服務時候,可以通過 port-forward 的方式將遠程的應用調用到本地的端口之上。比如說現在遠程的裏面有一個 API server,這個 API server 提供了一些端口,本地在調試 Code 時候,想要直接調用這個 API server,那麼這時,比較簡單的一個方式就是通過 port-forward 的方式。

它的使用方式是 kubectl port-forward,然後 service 加上遠程的 service name,再加上相應的 namespace,後面還可以加上一些額外的參數,比如說端口的一個映射,通過這種機制就可以把遠程的一個應用代理到本地的端口之上,此時通過訪問本地端口就可以訪問遠程的服務。

開源的調試工具 - kubectl-debug

最後再給大家介紹一個開源的調試工具,它也是 kubectl 的一個插件,叫 kubectl-debug。我們知道在 K8s 裏面,底層的容器 runtime 比較常見的就是類似像 docker 或者是 containerd,不論是 docker 還是 containerd,它們使用的一個機制都是基於 Linux namespace 的一個方式進行虛擬化和隔離的。

通常情況下 ,並不會在鏡像裏面帶特別多的調試工具,類似像 netstat telnet 等等這些 ,因爲這個會造成應用整體非常冗餘。那麼如果想要調試的時候該怎麼做呢?其實這個時候就可以依賴類似於像 kubectl-debug 這樣一個工具。

kubectl-debug 這個工具是依賴於 Linux namespace 的方式來去做的,它可以 datash 一個 Linux namespace 到一個額外的 container,然後在這個 container 裏面執行任何的 debug 動作,其實和直接去 debug 這個 Linux namespace 是一致的。這裏有一個簡單的操作,給大家來介紹一下:

這個地方其實已經安裝好了 kubectl-debug,它是 kubectl 的一個插件。所以這個時候,你可以直接通過 kubectl-debug 這條命令來去診斷遠程的一個 pod。像這個例子裏面,當執行 debug 的時候,實際上它首先會先拉取一些鏡像,這個鏡像裏面實際上會默認帶一些診斷的工具。當這個鏡像啓用的時候,它會把這個 debug container 進行啓動。與此同時會把這個 container 和相應的你要診斷的這個 container 的 namespace 進行掛靠,也就說此時這個 container 和你是同 namespace 的,類似像網絡站,或者是類似像內核的一些參數,其實都可以在這個 debug container 裏面實時地進行查看。

像這個例子裏面,去查看類似像 hostname、進程、netstat 等等,這些其實都是和這個需要 debug 的 pod 是在同一個環境裏面的,所以你之前這三條命令可以看到裏面相關的信息。

如果此時進行 logout 的話,相當於會把相應的這個 debug pod 殺掉,然後進行退出,此時對應用實際上是沒有任何的影響的。那麼通過這種方式可以不介入到容器裏面,就可以實現相應的一個診斷。

此外它還支持額外的一些機制,比如說我給設定一些 image,然後類似像這裏面安裝了的是 htop,然後開發者可以通過這個機制來定義自己需要的這個命令行的工具,並且通過這種 image 的方式設置進來。那麼這個時候就可以通過這種機制來調試遠程的一個 pod。

總結

  • 關於 Liveness 和 Readiness 的指針。Liveness probe 就是保活指針,它是用來看 pod 是否存活的,而 Readiness probe 是就緒指針,它是判斷這個 pod 是否就緒的,如果就緒了,就可以對外提供服務。這個就是 Liveness 和 Readiness 需要記住的部分;

  • 應用診斷的三個步驟:首先 describe 相應的一個狀態;然後提供狀態來排查具體的一個診斷方向;最後來查看相應對象的一個 event 獲取更詳細的一個信息;

  • 提供 pod 一個日誌來定位應用的自身的一個狀態;

  • 遠程調試的一個策略,如果想把本地的應用代理到遠程集羣,此時可以通過 Telepresence 這樣的工具來實現,如果想把遠程的應用代理到本地,然後在本地進行調用或者是調試,可以用類似像 port-forward 這種機制來實現。

本文轉載自阿里巴巴雲原生微信公衆號(ID:Alicloudnative)。

相關閱讀:

從零開始入門 K8s:應用存儲和持久化數據卷:存儲快照與拓撲調度

從零開始入門 K8s:應用存儲和持久化數據卷的核心知識

從零開始入門 K8s:應用配置管理

從零開始入門 K8s:應用編排與管理:Job & DaemonSet

從零開始入門 K8s:應用編排與管理

從零開始入門 K8s:K8s 的應用編排與管理

從零開始入門 K8s:詳解 Pod 及容器設計模式

從零開始入門 K8s:詳解 K8s 容器基本概念

從零開始入門 K8s:詳解 K8s 核心概念

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章