Istio從懵圈到熟練:二分之一活的微服務
作者 | 聲東 阿里雲售後技術專家
<關注公眾號,回覆 排查 即可下載電子書>
《深入淺出 Kubernetes》一書共彙集 12 篇技術文章,幫助你一次搞懂 6 個核心原理,吃透基礎理論,一次學會 6 個典型問題的華麗操作!
Istio is the future!基本上,我相信對雲原生技術趨勢有些微判斷的同學,都會有這個覺悟。其背後的邏輯其實是比較簡單的:當容器叢集,特別是 Kubernetes 成為事實上的標準之後,應用必然會不斷的複雜化,服務治理肯定會成為強需求。
Istio 的現狀是,聊的人很多,用的人其實很少。所以導致我們能看到的文章,講道理的很多,講實際踩坑經驗的極少。阿里雲售後團隊作為一線踩坑團隊,分享問題排查經驗,我們責無旁貸。這篇文章,我就跟大家聊一個簡單 Istio 問題的排查過程,權當拋磚。
二分之一活的微服務
問題是這樣的,使用者在自己的測試叢集裡安裝了 Istio,並依照官方文件部署 bookinfo 應用來上手 Istio。部署之後,使用者執行 kubectl get pods 命令,發現所有的 Pod 都只有二分之一個容器是 READY 的。
# kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-68868454f5-94hzd 1/2 Running 0 1m
productpage-v1-5cb458d74f-28nlz 1/2 Running 0 1m
ratings-v1-76f4c9765f-gjjsc 1/2 Running 0 1m
reviews-v1-56f6855586-dplsf 1/2 Running 0 1m
reviews-v2-65c9df47f8-zdgbw 1/2 Running 0 1m
reviews-v3-6cf47594fd-cvrtf 1/2 Running 0 1m
如果從來都沒有注意過 READY 這一列的話,我們大概會有兩個疑惑:2 在這裡是什麼意思,以及 1/2 到底意味著什麼。
簡單來講,這裡的 READY 列,給出的是每個 Pod 內部容器的 Readiness,即就緒狀態。每個叢集節點上的 kubelet 會根據容器本身 Readiness 規則的定義,分別是 tcp、http 或 exec 的方式,來確認對應容器的 Readiness 情況。
更具體一點,kubelet 作為執行在每個節點上的程式,以 tcp/http 的方式(節點網路名稱空間到 Pod 網路名稱空間)訪問容器定義的介面,或者在容器的 namespace 裡執行 exec 定義的命令,來確定容器是否就緒。
這裡的 2 說明這些 Pod 裡都有兩個容器,1/2 則表示,每個 Pod 裡只有一個容器是就緒的,即通過 Readiness 測試的。關於 2 這一點,我們下一節會深入講,這裡我們先看一下,為什麼所有的 Pod 裡,都有一個容器沒有就緒。
使用 kubectl 工具拉取第一個 details pod 的編排模板,可以看到這個 Pod 裡兩個容器,只有一個定義了 readiness probe。對於未定義 readiness probe 的容器, kubelet 認為,只要容器裡的程式開始執行,容器就進入就緒狀態了。所以 1/2 個就緒 Pod,意味著,有定義 readiness probe 的容器,沒有通過 kubelet 的測試。
沒有通過 readiness probe 測試的是 istio-proxy 這個容器。它的 readiness probe 規則定義如下:
readinessProbe:
failureThreshold: 30
httpGet:
path: /healthz/ready
port: 15020
scheme: HTTP
initialDelaySeconds: 1
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 1
我們登入這個 Pod 所在的節點,用 curl 工具來模擬 kubelet 訪問下邊的 uri,測試 istio-proxy 的就緒狀態。
# curl http://172.16.3.43:15020/healthz/ready -v
* About to connect() to 172.16.3.43 port 15020 (#0)
* Trying 172.16.3.43...
* Connected to 172.16.3.43 (172.16.3.43) port 15020 (#0)
> GET /healthz/ready HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 172.16.3.43:15020
> Accept: */*>
< HTTP/1.1 503 Service Unavailable< Date: Fri, 30 Aug 2019 16:43:50 GMT
< Content-Length: 0
< *
Connection #0 to host 172.16.3.43 left intact
繞不過去的大圖
上一節我們描述了問題現象,但是留下一個問題,就是 Pod 裡的容器個數為什麼是 2。雖然每個 Pod 本質上至少有兩個容器:一個是佔位符容器 pause,另一個是真正的工作容器,但是我們在使用 kubectl 命令獲取 Pod 列表的時候,READY 列是不包括 pause 容器的。
這裡的另外一個容器,其實就是服務網格的核心概念 sidercar。其實把這個容器叫做 sidecar,某種意義上是不能反映這個容器的本質的。Sidecar 容器本質上是反向代理,它本來是一個 Pod 訪問其他服務後端 Pod 的負載均衡。
然而,當我們為叢集中的每一個 Pod,都“隨身”攜帶一個反向代理的時候,Pod 和反向代理就變成了服務網格。正如下邊這張經典大圖所示。這張圖實在有點難畫,所以只能借用,繞不過去。
所以 sidecar 模式,其實是“自帶通訊員”模式。這裡比較有趣的是,在我們把 sidecar 和 Pod 繫結在一塊的時候,sidecar 在出流量轉發時扮演著反向代理的角色,而在入流量接收的時候,可以做超過反向代理職責的一些事情。這點我們會在其他文章裡討論。
Istio 在 Kubernetes 基礎上實現了服務網格,Isito 使用的 sidecar 容器就是第一節提到的,沒有就緒的容器。所以這個問題,其實就是服務網格內部,所有的 sidecar 容器都沒有就緒。
代理與代理的生命週期管理
上一節我們看到,Istio 中的每個 Pod,都自帶了反向代理 sidecar。我們遇到的問題是,所有的 sidecar 都沒有就緒。我們也看到 readiness probe 定義的,判斷 sidecar 容器就緒的方式就是訪問下邊這個介面:
http://<pod ip>:15020/healthz/ready
接下來,我們深入看下 Pod,以及其 sidecar 的組成及原理。在服務網格里,一個 Pod 內部除了本身處理業務的容器之外,還有 istio-proxy 這個 sidecar 容器。正常情況下,istio-proxy 會啟動兩個程式:pilot-agent 和 Envoy。
如下圖,Envoy 是實際上負責流量管理等功能的代理,從業務容器出、入的資料流,都必須要經過 Envoy;而 pilot-agent 負責維護 Envoy 的靜態配置,以及管理 Envoy 的生命週期。這裡的動態配置部分,我們在下一節會展開來講。
我們可以使用下邊的命令進入 Pod 的 istio-proxy 容器做進一步排查。這裡的一個小技巧,是我們可以以使用者 1337,使用特權模式進入 istio-proxy 容器,如此就可以使用 iptables 等只能在特權模式下執行的命令。
docker exec -ti -u 1337 --privileged <istio-proxy container id> bash
這裡的 1337 使用者,其實是 sidecar 映象裡定義的一個同名使用者 istio-proxy,預設 sidecar 容器使用這個使用者。如果我們在以上命令中,不使用使用者選項 u,則特權模式實際上是賦予 root 使用者的,所以我們在進入容器之後,需切換到 root 使用者執行特權命令。
進入容器之後,我們使用 netstat 命令檢視監聽,我們會發現,監聽 readiness probe 埠 15020 的,其實是 pilot-agent 程式。
istio-proxy@details-v1-68868454f5-94hzd:/$ netstat -lnpt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:15090 0.0.0.0:* LISTEN 19/envoy
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN 19/envoy
tcp 0 0 0.0.0.0:9080 0.0.0.0:* LISTEN -
tcp6 0 0 :::15020 :::* LISTEN 1/pilot-agent
我們在istio-proxy內部訪問readiness probe介面,一樣會得到503的錯誤。
就緒檢查的實現
瞭解了 sidecar 的代理,以及管理代理生命週期的 pilot-agent 程式,我們可以稍微思考一下 pilot-agent 應該怎麼去實現 healthz/ready 這個介面。顯然,如果這個介面返回 OK 的話,那不僅意味著 pilot-agent 是就緒的,而必須確保代理是工作的。
實際上 pilot-agent 就緒檢查介面的實現正是如此。這個介面在收到請求之後,會去呼叫代理 Envoy 的 server_info 介面。呼叫所使用的 IP 是 Localhost。這個非常好理解,因為這是同一個 Pod 內部程式通訊。使用的埠是 Envoy 的 proxyAdminPort,即 15000。
有了以上的知識準備之後,我們來看下 istio-proxy 這個容器的日誌。實際上,在容器日誌裡,一直在重複輸出一個報錯,這句報錯分為兩部分,其中 Envoy proxy is NOT ready 這部分是 pilot agent 在響應 healthz/ready 介面的時候輸出的資訊,即 Envoy 代理沒有就緒;而剩下的 config not received from Pilot (is Pilot running?): cds updates: 0 successful, 0 rejected; lds updates: 0 successful, 0 rejected 這部分,是 pilot-agent 通過 proxyAdminPort 訪問 server_info 的時候帶回的資訊,看起來是 Envoy 沒有辦法從 Pilot 獲取配置。
Envoy proxy is NOT ready: config not received from Pilot (is Pilot running?): cds updates: 0 successful, 0 rejected; lds updates: 0 successful, 0 rejected.
到這裡,建議大家回退看下上一節的插圖,在上一節我們選擇性的忽略是 Pilot 到 Envoy 這條虛線,即動態配置。這裡的報錯,實際上是 Envoy 從控制面 Pilot 獲取動態配置失敗。
控制面和資料面
目前為止,這個問題其實已經很清楚了。在進一步分析問題之前,我聊一下我對控制面和資料面的理解。控制面資料面模式,可以說無處不在。我們這裡舉兩個極端的例子。
第一個例子,是 DHCP 伺服器。我們都知道,在區域網中的電腦,可以通過配置 DHCP 來獲取 IP 地址,這個例子中,DHCP 伺服器統一管理,動態分配 IP 地址給網路中的電腦,這裡的 DHCP 伺服器就是控制面,而每個動態獲取 IP 的電腦就是資料面。
第二個例子,是電影劇本,和電影的演出。劇本可以認為是控制面,而電影的演出,包括演員的每一句對白,電影場景佈置等,都可以看做是資料面。
我之所以認為這是兩個極端,是因為在第一個例子中,控制面僅僅影響了電腦的一個屬性,而第二個例子,控制面幾乎是資料面的一個完整的抽象和拷貝,影響資料面的方方面面。Istio 服務網格的控制面是比較靠近第二個例子的情況,如下圖:
Istio 的控制面 Pilot 使用 gRPC 協議對外暴露介面 istio-pilot.istio-system:15010,而 Envoy 無法從 Pilot 處獲取動態配置的原因,是在所有的 Pod 中,叢集 DNS 都無法使用。
簡單的原因
這個問題的原因其實比較簡單,在 sidecar 容器 istio-proxy 裡,Envoy 不能訪問 Pilot 的原因是叢集 DNS 無法解析 istio-pilot.istio-system 這個服務名字。在容器裡看到 resolv.conf 配置的 DNS 伺服器是 172.19.0.10,這個是叢集預設的 kube-dns 服務地址。
istio-proxy@details-v1-68868454f5-94hzd:/$ cat /etc/resolv.conf
nameserver 172.19.0.10
search default.svc.cluster.local svc.cluster.local cluster.local localdomain
但是客戶刪除重建了 kube-dns 服務,且沒有指定服務 IP,這導致,實際上叢集 DNS 的地址改變了,這也是為什麼所有的 sidecar 都無法訪問 Pilot。
# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 172.19.9.54 <none> 53/UDP,53/TCP 5d
最後,通過修改 kube-dns 服務,指定 IP 地址,sidecar 恢復正常。
# kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-68868454f5-94hzd 2/2 Running 0 6d
nginx-647d5bf6c5-gfvkm 2/2 Running 0 2d
nginx-647d5bf6c5-wvfpd 2/2 Running 0 2d
productpage-v1-5cb458d74f-28nlz 2/2 Running 0 6d
ratings-v1-76f4c9765f-gjjsc 2/2 Running 0 6d
reviews-v1-56f6855586-dplsf 2/2 Running 0 6d
reviews-v2-65c9df47f8-zdgbw 2/2 Running 0 6d
reviews-v3-6cf47594fd-cvrtf 2/2 Running 0 6d
結論
這其實是一個比較簡單的問題,排查過程其實也就幾分鐘。但是寫這篇文章,有點感覺是在看長安十二時辰,短短几分鐘的排查過程,寫完整背後的原理,前因後果,卻花了幾個小時。這是 Istio 文章的第一篇,希望在大家排查問題的時候,有所幫助。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69975341/viewspace-2694571/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Istio 從懵圈到熟練:二分之一活的微服務微服務
- K8s 從懵圈到熟練-叢集伸縮原理K8S
- K8s 從懵圈到熟練 – 映象拉取這件小事K8S
- K8s 從懵圈到熟練 – 叢集網路詳解K8S
- Kubernetes 從懵圈到熟練:叢集服務的三個要點和一種實現
- Git從入門到熟練掌握Git
- 從微服務治理的角度看RSocket,. Envoy和. Istio微服務
- 從架構到元件,深挖istio如何連線、管理和保護微服務2.0?架構元件微服務
- 從微服務到雲原生微服務
- 招聘要求裡的「熟練使用 SQL」 你熟練麼?SQL
- 【連載】微服務網格Istio(一)微服務
- 0和1的熟練
- 從“挖光纜”到“剪網線”|螞蟻金服異地多活的微服務體系微服務
- 如何使用Istio 1.6管理多叢集中的微服務?微服務
- ABP VNext從單體切換到微服務微服務
- 從程式碼到部署微服務實戰(一)微服務
- 愈發熟練的 CSS 技巧CSS
- Istio旨在成為容器化微服務的網格管道微服務
- [SuperSocket2.0]SuperSocket 2.0從入門到懵逼
- Proxyless的多活流量和微服務治理微服務
- MySQL基礎教程---Chapter13 熟練使用事務MySqlAPT
- 基於阿里雲 ASK 的 Istio 微服務應用部署初探阿里微服務
- 為微服務構建服務網格的Istio自身卻走向微服務的反面單體架構 – Christian Posta微服務架構
- 微服務18:微服務治理之異地多活容災微服務
- 跨語言微服務框架-Istio簡紹和概念微服務框架
- SpringBoot、Kubernetes和Istio微服務網格演示原始碼Spring Boot微服務原始碼
- Kubernetes,Istio和Java MicroProfile微服務雲原生案例 - heidloffJava微服務
- 微服務斷路器模式實現:Istio vs Hystrix微服務模式
- linux沒熟練內容Linux
- 從單體架構到分散式微服務架構的思考架構分散式微服務
- 微服務 | Spring Cloud(一):從單體SSM 到 Spring Cloud微服務SpringCloudSSM
- 從Monolith到微服務:理論與實踐 - Kent BeckMono微服務
- 178-ABP VNext從單體切換到微服務微服務
- 從開發到部署微服務保姆級視訊教程微服務
- 知新 | koa框架入門到熟練第一章框架
- 《PHP 微服務練兵》系列教程PHP微服務
- 你真的熟練使用webpack嗎?Web
- 突破能力瓶頸 用一套遊戲設計方法論完成從“熟練工”到“專家”的蛻變遊戲設計