溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

應(yīng)用容器對(duì)Envoy Sidecar的啟動(dòng)依賴(lài)問(wèn)題怎么解決

發(fā)布時(shí)間:2022-01-05 18:05:12 來(lái)源:億速云 閱讀:115 作者:柒染 欄目:云計(jì)算

今天就跟大家聊聊有關(guān)應(yīng)用容器對(duì)Envoy Sidecar的啟動(dòng)依賴(lài)問(wèn)題怎么解決,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

下面將介紹用戶(hù)從 Spring Cloud,Dubbo 等傳統(tǒng)微服務(wù)框架遷移到 Istio 服務(wù)網(wǎng)格時(shí)的一些經(jīng)驗(yàn),以及在使用 Istio 過(guò)程中可能遇到的一些常見(jiàn)問(wèn)題的解決方法。

故障現(xiàn)象

該問(wèn)題的表現(xiàn)是安裝了 sidecar proxy 的應(yīng)用在啟動(dòng)后的一小段時(shí)間內(nèi)無(wú)法通過(guò)網(wǎng)絡(luò)訪問(wèn) pod 外部的其他服務(wù),例如外部的 HTTP,MySQLRedis等服務(wù)。如果應(yīng)用沒(méi)有對(duì)依賴(lài)服務(wù)的異常進(jìn)行容錯(cuò)處理,該問(wèn)題還常常會(huì)導(dǎo)致應(yīng)用啟動(dòng)失敗。下面我們以該問(wèn)題導(dǎo)致的一個(gè)典型故障的分析過(guò)程為例對(duì)該問(wèn)題的原因進(jìn)行說(shuō)明。

典型案例:某運(yùn)維同學(xué)反饋:昨天晚上 Istio 環(huán)境中應(yīng)用的心跳檢測(cè)報(bào) connect reset,然后服務(wù)重啟了。懷疑是 Istio 環(huán)境中網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致了服務(wù)重啟。

故障分析

根據(jù)運(yùn)維同學(xué)的反饋,該 pod 曾多次重啟。因此我們先用 kubectl logs --previous 命令查詢(xún) awesome-app 容器最后一次重啟前的日志,以從日志中查找其重啟的原因。

kubectl logs --previous awesome-app-cd1234567-gzgwg -c awesome-app

從日志中查詢(xún)到了其重啟前最后的錯(cuò)誤信息如下:

Logging system failed to initialize using configuration from 'http://log-config-server:12345/******/logback-spring.xml'
java.net.ConnectException: Connection refused (Connection refused)
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)

從錯(cuò)誤信息可以得知,應(yīng)用進(jìn)程在啟動(dòng)時(shí)試圖通過(guò) HTTP 協(xié)議從配置中心拉取 logback 的配置信息,但該操作由于網(wǎng)絡(luò)異常失敗了,導(dǎo)致應(yīng)用進(jìn)程啟動(dòng)失敗,最終導(dǎo)致容器重啟。

是什么導(dǎo)致了網(wǎng)絡(luò)異常呢?我們?cè)儆?Kubectl get pod 命令查詢(xún) Pod 的運(yùn)行狀態(tài),嘗試找到更多的線索:

kubectl get pod awesome-app-cd1234567-gzgwg  -oyaml

命令輸出的 pod 詳細(xì)內(nèi)容如下,該 yaml 片段省略了其他無(wú)關(guān)的細(xì)節(jié),只顯示了 lastState 和 state 部分的容器狀態(tài)信息。

containerStatuses:
  - containerID: 
    lastState:
      terminated:
        containerID: 
        exitCode: 1
        finishedAt: 2020-09-01T13:16:23Z
        reason: Error
        startedAt: 2020-09-01T13:16:22Z
    name: awesome-app
    ready: true
    restartCount: 2
    state:
      running:
        startedAt: 2020-09-01T13:16:36Z
  - containerID: 
    lastState: {}
    name: istio-proxy
    ready: true
    restartCount: 0
    state:
      running:
        startedAt: 2020-09-01T13:16:20Z
  hostIP: 10.0.6.161

從該輸出可以看到 pod 中的應(yīng)用容器 awesome-app 重啟了兩次。整理該 pod 中 awesome-app 應(yīng)用容器和 istio-proxy sidecar 容器的啟動(dòng)和終止的時(shí)間順序,可以得到下面的時(shí)間線:

  1. 2020-09-01T13:16:20Z istio-proxy 啟動(dòng)

  2. 2020-09-01T13:16:22Z awesome-app 上一次啟動(dòng)時(shí)間

  3. 2020-09-01T13:16:23Z awesome-app 上一次異常退出時(shí)間

  4. 2020-09-01T13:16:36Z awesome-app 最后一次啟動(dòng),以后就一直正常運(yùn)行

可以看到在 istio-proxy 啟動(dòng)2秒后,awesome-app 啟動(dòng),并于1秒后異常退出。結(jié)合前面的日志信息,我們知道這次啟動(dòng)失敗的直接原因是應(yīng)用訪問(wèn)配置中心失敗導(dǎo)致。在 istio-proxy 啟動(dòng)16秒后,awesome-app 再次啟動(dòng),這次啟動(dòng)成功,之后一直正常運(yùn)行。

istio-proxy 啟動(dòng)和 awesome-app 上一次異常退出的時(shí)間間隔很短,只有2秒鐘,因此我們基本可以判斷此時(shí) istio-proxy 尚未啟動(dòng)初始化完成,導(dǎo)致 awesome-app 不能通過(guò)istio-proxy 連接到外部服務(wù),導(dǎo)致其啟動(dòng)失敗。待 awesome-app 于 2020-09-01T13:16:36Z 再次啟動(dòng)時(shí),由于 istio-proxy 已經(jīng)啟動(dòng)了較長(zhǎng)時(shí)間,完成了從 pilot 獲取動(dòng)態(tài)配置的過(guò)程,因此 awesome-app 向 pod 外部的網(wǎng)絡(luò)訪問(wèn)就正常了。

如下圖所示,Envoy 啟動(dòng)后會(huì)通過(guò) xDS 協(xié)議向 pilot 請(qǐng)求服務(wù)和路由配置信息,Pilot 收到請(qǐng)求后會(huì)根據(jù) Envoy 所在的節(jié)點(diǎn)(pod或者VM)組裝配置信息,包括 Listener、Route、Cluster等,然后再通過(guò) xDS 協(xié)議下發(fā)給 Envoy。根據(jù) Mesh 的規(guī)模和網(wǎng)絡(luò)情況,該配置下發(fā)過(guò)程需要數(shù)秒到數(shù)十秒的時(shí)間。由于初始化容器已經(jīng)在 pod 中創(chuàng)建了 Iptables rule 規(guī)則,因此這段時(shí)間內(nèi)應(yīng)用向外發(fā)送的網(wǎng)絡(luò)流量會(huì)被重定向到 Envoy ,而此時(shí) Envoy 中尚沒(méi)有對(duì)這些網(wǎng)絡(luò)請(qǐng)求進(jìn)行處理的監(jiān)聽(tīng)器和路由規(guī)則,無(wú)法對(duì)此進(jìn)行處理,導(dǎo)致網(wǎng)絡(luò)請(qǐng)求失敗。(關(guān)于 Envoy sidecar 初始化過(guò)程和 Istio 流量管理原理的更多內(nèi)容,可以參考這篇文章 Istio流量管理實(shí)現(xiàn)機(jī)制深度解析) 應(yīng)用容器對(duì)Envoy Sidecar的啟動(dòng)依賴(lài)問(wèn)題怎么解決

解決方案

在應(yīng)用啟動(dòng)命令中判斷 Envoy 初始化狀態(tài)

從前面的分析可以得知,該問(wèn)題的根本原因是由于應(yīng)用進(jìn)程對(duì) Envoy sidecar 配置初始化的依賴(lài)導(dǎo)致的。因此最直接的解決思路就是:在應(yīng)用進(jìn)程啟動(dòng)時(shí)判斷 Envoy sidecar 的初始化狀態(tài),待其初始化完成后再啟動(dòng)應(yīng)用進(jìn)程。

Envoy 的健康檢查接口 localhost:15020/healthz/ready 會(huì)在 xDS 配置初始化完成后才返回 200,否則將返回 503,因此可以根據(jù)該接口判斷 Envoy 的配置初始化狀態(tài),待其完成后再啟動(dòng)應(yīng)用容器。我們可以在應(yīng)用容器的啟動(dòng)命令中加入調(diào)用 Envoy 健康檢查的腳本,如下面的配置片段所示。在其他應(yīng)用中使用時(shí),將 start-awesome-app-cmd 改為容器中的應(yīng)用啟動(dòng)命令即可。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: awesome-app-deployment
spec:
  selector:
    matchLabels:
      app: awesome-app
  replicas: 1
  template:
    metadata:
      labels:
        app: awesome-app
    spec:
      containers:
      - name: awesome-app
        image: awesome-app
        ports:
        - containerPort: 80
        command: ["/bin/bash", "-c"]
        args: ["while [[ \"$(curl -s -o /dev/null -w ''%{http_code}'' localhost:15020/healthz/ready)\" != '200' ]]; do echo Waiting for Sidecar;sleep 1; done; echo Sidecar available; start-awesome-app-cmd"]

該流程的執(zhí)行順序如下:

  1. Kubernetes 啟動(dòng) 應(yīng)用容器。

  2. 應(yīng)用容器啟動(dòng)腳本中通過(guò) curl get localhost:15020/healthz/ready 查詢(xún) Envoy sidcar 狀態(tài),由于此時(shí) Envoy sidecar 尚未就緒,因此該腳本會(huì)不斷重試。

  3. Kubernetes 啟動(dòng) Envoy sidecar。

  4. Envoy sidecar 通過(guò) xDS 連接 Pilot,進(jìn)行配置初始化。

  5. 應(yīng)用容器啟動(dòng)腳本通過(guò) Envoy sidecar 的健康檢查接口判斷其初始化已經(jīng)完成,啟動(dòng)應(yīng)用進(jìn)程。

該方案雖然可以規(guī)避依賴(lài)順序的問(wèn)題,但需要對(duì)應(yīng)用容器的啟動(dòng)腳本進(jìn)行修改,對(duì) Envoy 的健康狀態(tài)進(jìn)行判斷。更理想的方案應(yīng)該是應(yīng)用對(duì) Envoy sidecar 不感知。

通過(guò) pod 容器啟動(dòng)順序進(jìn)行控制

通過(guò)閱讀 Kubernetes 源碼 ,我們可以發(fā)現(xiàn)當(dāng) pod 中有多個(gè)容器時(shí),Kubernetes 會(huì)在一個(gè)線程中依次啟動(dòng)這些容器,如下面的代碼片段所示:

	// Step 7: start containers in podContainerChanges.ContainersToStart.
	for _, idx := range podContainerChanges.ContainersToStart {
		start("container", containerStartSpec(&pod.Spec.Containers[idx]))
  }

因此我們可以在向 pod 中注入 Envoy sidecar 時(shí)將 Envoy sidecar 放到應(yīng)用容器之前,這樣 Kubernetes 會(huì)先啟動(dòng) Envoy sidecar,再啟動(dòng)應(yīng)用容器。但是還有一個(gè)問(wèn)題,Envoy 啟動(dòng)后我們并不能立即啟動(dòng)應(yīng)用容器,還需要等待 xDS 配置初始化完成。這時(shí)我們就可以采用容器的 postStart lifecycle hook來(lái)達(dá)成該目的。Kubernetes 會(huì)在啟動(dòng)容器后調(diào)用該容器的 postStart hook,postStart hook 會(huì)阻塞 pod 中的下一個(gè)容器的啟動(dòng),直到 postStart hook 執(zhí)行完成。因此如果在 Envoy sidecar 的 postStart hook 中對(duì) Envoy 的配置初始化狀態(tài)進(jìn)行判斷,待完成初始化后再返回,就可以保證 Kubernetes 在 Envoy sidecar 配置初始化完成后再啟動(dòng)應(yīng)用容器。該流程的執(zhí)行順序如下:

  1. Kubernetes 啟動(dòng) Envoy sidecar 。

  2. Kubernetes 執(zhí)行 postStart hook。

  3. postStart hook 通過(guò) Envoy 健康檢查接口判斷其配置初始化狀態(tài),直到 Envoy 啟動(dòng)完成 。

  4. Kubernetes 啟動(dòng)應(yīng)用容器。

Istio 已經(jīng)在 1.7 中合入了該修復(fù)方案,參見(jiàn) Allow users to delay application start until proxy is ready #24737。

插入 sidecar 后的 pod spec 如下面的 yaml 片段所示。postStart hook 配置的 pilot-agent wait 命令會(huì)持續(xù)調(diào)用 Envoy 的健康檢查接口 '/healthz/ready' 檢查其狀態(tài),直到 Envoy 完成配置初始化。這篇文章Delaying application start until sidecar is ready中介紹了更多關(guān)于該方案的細(xì)節(jié)。

apiVersion: v1
kind: Pod
metadata:
  name: sidecar-starts-first
spec:
  containers:
  - name: istio-proxy
    image: 
    lifecycle:
      postStart:
        exec:
          command:
          - pilot-agent
          - wait
  - name: application
    image: my-application

該方案在不對(duì)應(yīng)用進(jìn)行修改的情況下比較完美地解決了應(yīng)用容器和 Envoy sidecar 初始化的依賴(lài)問(wèn)題。但是該解決方案對(duì) Kubernetes 有兩個(gè)隱式依賴(lài)條件:Kubernetes 在一個(gè)線程中按定義順序依次啟動(dòng) pod 中的多個(gè)容器,以及前一個(gè)容器的 postStart hook 執(zhí)行完畢后再啟動(dòng)下一個(gè)容器。這兩個(gè)前提條件在目前的 Kuberenetes 代碼實(shí)現(xiàn)中是滿(mǎn)足的,但由于這并不是 Kubernetes的 API 規(guī)范,因此該前提在將來(lái) Kubernetes 升級(jí)后很可能被打破,導(dǎo)致該問(wèn)題再次出現(xiàn)。

Kubernetes 支持定義 pod 中容器之間的依賴(lài)關(guān)系

為了徹底解決該問(wèn)題,避免 Kubernetes 代碼變動(dòng)后該問(wèn)題再次出現(xiàn),更合理的方式應(yīng)該是由 Kubernetes 支持顯式定義 pod 中一個(gè)容器的啟動(dòng)依賴(lài)于另一個(gè)容器的健康狀態(tài)。目前 Kubernetes 中已經(jīng)有一個(gè) issue Support startup dependencies between containers on the same Pod #65502 對(duì)該問(wèn)題進(jìn)行跟蹤處理。如果 Kubernetes 支持了該特性,則該流程的執(zhí)行順序如下:

  1. Kubernetes 啟動(dòng) Envoy sidecar 容器。

  2. Kubernetes 通過(guò) Envoy sidecar 容器的 readiness probe 檢查其狀態(tài),直到 readiness probe 反饋 Envoy sidecar 已經(jīng) ready,即已經(jīng)初始化完畢。

  3. Kubernetes 啟動(dòng)應(yīng)用容器。

解耦應(yīng)用服務(wù)之間的啟動(dòng)依賴(lài)關(guān)系

以上幾個(gè)解決方案的思路都是控制 pod 中容器的啟動(dòng)順序,在 Envoy sidecar 初始化完成后再啟動(dòng)應(yīng)用容器,以確保應(yīng)用容器啟動(dòng)時(shí)能夠通過(guò)網(wǎng)絡(luò)正常訪問(wèn)其他服務(wù)。但這些方案只是『頭痛醫(yī)頭,腳痛醫(yī)腳』,是治標(biāo)不治本的方法。因?yàn)榧词?pod 中對(duì)外的網(wǎng)絡(luò)訪問(wèn)沒(méi)有問(wèn)題,應(yīng)用容器依賴(lài)的其他服務(wù)也可能由于尚未啟動(dòng),或者某些問(wèn)題而不能在此時(shí)正常提供服務(wù)。要徹底解決該問(wèn)題,我們需要解耦應(yīng)用服務(wù)之間的啟動(dòng)依賴(lài)關(guān)系,使應(yīng)用容器的啟動(dòng)不再?gòu)?qiáng)依賴(lài)其他服務(wù)。

在一個(gè)微服務(wù)系統(tǒng)中,原單體應(yīng)用中的各個(gè)業(yè)務(wù)模塊被拆分為多個(gè)獨(dú)立進(jìn)程(服務(wù))。這些服務(wù)的啟動(dòng)順序是隨機(jī)的,并且服務(wù)之間通過(guò)不可靠的網(wǎng)絡(luò)進(jìn)行通信。微服務(wù)多進(jìn)程部署、跨進(jìn)程網(wǎng)絡(luò)通信的特定決定了服務(wù)之間的調(diào)用出現(xiàn)異常是一個(gè)常見(jiàn)的情況。為了應(yīng)對(duì)微服務(wù)的該特點(diǎn),微服務(wù)的一個(gè)基本的設(shè)計(jì)原則是 "design for failure",即需要以?xún)?yōu)雅的方式應(yīng)對(duì)可能出現(xiàn)的各種異常情況。當(dāng)在微服務(wù)進(jìn)程中不能訪問(wèn)一個(gè)依賴(lài)的外部服務(wù)時(shí),需要通過(guò)重試、降級(jí)、超時(shí)、斷路等策略對(duì)異常進(jìn)行容錯(cuò)處理,以盡可能保證系統(tǒng)的正常運(yùn)行。

Envoy sidecar 初始化期間網(wǎng)絡(luò)暫時(shí)不能訪問(wèn)的情況只是放大了微服務(wù)系統(tǒng)未能正確處理服務(wù)依賴(lài)的問(wèn)題,即使解決了 Envoy sidecar 的依賴(lài)順序,該問(wèn)題依然存在。例如在本案例中,配置中心也是一個(gè)獨(dú)立的微服務(wù),當(dāng)一個(gè)依賴(lài)配置中心的微服務(wù)啟動(dòng)時(shí),配置中心有可能尚未啟動(dòng),或者尚未初始化完成。在這種情況下,如果在代碼中沒(méi)有對(duì)該異常情況進(jìn)行處理,也會(huì)導(dǎo)致依賴(lài)配置中心的微服務(wù)啟動(dòng)失敗。在一個(gè)更為復(fù)雜的系統(tǒng)中,多個(gè)微服務(wù)進(jìn)程之間可能存在網(wǎng)狀依賴(lài)關(guān)系,如果沒(méi)有按照 "design for failure" 的原則對(duì)微服務(wù)進(jìn)行容錯(cuò)處理,那么只是將整個(gè)系統(tǒng)啟動(dòng)起來(lái)就將是一個(gè)巨大的挑戰(zhàn)。對(duì)于本例而言,可以采用一個(gè)類(lèi)似這樣的簡(jiǎn)單容錯(cuò)策略:先用一個(gè)缺省的 logback 配置啟動(dòng)應(yīng)用進(jìn)程,并在啟動(dòng)后對(duì)配置中心進(jìn)行重試,待連接上配置中心后,再使用配置中心下發(fā)的配置對(duì) logback 進(jìn)行設(shè)置。

應(yīng)用容器對(duì) Envoy Sidecar 啟動(dòng)依賴(lài)問(wèn)題的典型表現(xiàn)是應(yīng)用容器在剛啟動(dòng)的一小段時(shí)間內(nèi)調(diào)用外部服務(wù)失敗。原因是此時(shí) Envoy sidecar 尚未完成 xDS 配置的初始化,因此不能為應(yīng)用容器轉(zhuǎn)發(fā)網(wǎng)絡(luò)請(qǐng)求。該調(diào)用失敗可能導(dǎo)致應(yīng)用容器不能正常啟動(dòng)。此問(wèn)題的根本原因是微服務(wù)應(yīng)用中對(duì)依賴(lài)服務(wù)的調(diào)用失敗沒(méi)有進(jìn)行合理的容錯(cuò)處理。對(duì)于遺留系統(tǒng),為了盡量避免對(duì)應(yīng)用的影響,我們可以通過(guò)在應(yīng)用啟動(dòng)命令中判斷 Envoy 初始化狀態(tài)的方案,或者升級(jí)到 Istio 1.7 來(lái)緩解該問(wèn)題。但為了徹底解決服務(wù)依賴(lài)導(dǎo)致的錯(cuò)誤,建議參考 "design for failure" 的設(shè)計(jì)原則,解耦微服務(wù)之間的強(qiáng)依賴(lài)關(guān)系,在出現(xiàn)暫時(shí)不能訪問(wèn)一個(gè)依賴(lài)的外部服務(wù)的情況時(shí),通過(guò)重試、降級(jí)、超時(shí)、斷路等策略進(jìn)行處理,以盡可能保證系統(tǒng)的正常運(yùn)行。

看完上述內(nèi)容,你們對(duì)應(yīng)用容器對(duì)Envoy Sidecar的啟動(dòng)依賴(lài)問(wèn)題怎么解決有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI