Istio 從懵圈到熟練:二分之一活的微服務

1.png

做者 | 聲東  阿里雲售後技術專家nginx

<關注阿里巴巴雲原生公衆號,回覆 排查 便可下載電子書> docker

《深刻淺出 Kubernetes》一書共聚集 12 篇技術文章,幫助你一次搞懂 6 個核心原理,吃透基礎理論,一次學會 6 個典型問題的華麗操做!後端

Istio is the future!基本上,我相信對雲原生技術趨勢有些微判斷的同窗,都會有這個覺悟。其背後的邏輯實際上是比較簡單的:當容器集羣,特別是 Kubernetes 成爲事實上的標準以後,應用必然會不斷的複雜化,服務治理確定會成爲強需求。bash

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 狀況。less

更具體一點,kubelet 做爲運行在每一個節點上的進程,以 tcp/http 的方式(節點網絡命名空間到 Pod 網絡命名空間)訪問容器定義的接口,或者在容器的 namespace 裏執行 exec 定義的命令,來肯定容器是否就緒。dom

2.png

這裏的 2 說明這些 Pod 裏都有兩個容器,1/2 則表示,每一個 Pod 裏只有一個容器是就緒的,即經過 Readiness 測試的。關於 2 這一點,咱們下一節會深刻講,這裏咱們先看一下,爲何全部的 Pod 裏,都有一個容器沒有就緒。curl

使用 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 的負載均衡。

3.png

然而,當咱們爲集羣中的每個 Pod,都「隨身」攜帶一個反向代理的時候,Pod 和反向代理就變成了服務網格。正以下邊這張經典大圖所示。這張圖實在有點難畫,因此只能借用,繞不過去。

4.png

因此 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 的生命週期。這裏的動態配置部分,咱們在下一節會展開來說。

5.png

咱們可使用下邊的命令進入 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。

6.png

有了以上的知識準備以後,咱們來看下 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 服務網格的控制面是比較靠近第二個例子的狀況,以下圖:

7.png

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 文章的第一篇,但願在你們排查問題的時候,有所幫助。

第 3 期雲原生網絡研討會邀您參加

5 月 28 日,阿里雲技術專家將爲你們帶來《如何爲雲原生應用帶來穩定高效的部署能力?》,屆時將會介紹阿里經濟體大規模應用上雲過程當中遇到的核心部署問題、採起的對應解決方案,以及這些方案沉澱爲通用化能力輸出開源後,如何幫助阿里雲上的用戶提高應用部署發佈的效率與穩定性。

聽衆可獲取如下收益:

• 瞭解阿里經濟體大規模應用上雲的實踐經驗,如何解決原生 K8s workload 不知足場景需求的問題;
• 做爲外部用戶,如何體驗和使用上阿里經濟體上雲所沉澱下來的應用部署發佈能力;
• 演示阿里巴巴針對大規模 K8s 集羣如何作到 DaemonSet 高可用的灰度升級(即將開源!)

點擊連接便可預定直播:https://yq.aliyun.com/live/2898

阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的公衆號。」

相關文章
相關標籤/搜索