在現代微服務架構中,服務間的通訊是一個充滿挑戰的領域。無論你使用 Java、NodeJS 還是 Golang 開發微服務,最終都需要透過網路與其他服務進行通訊。而網路環境對應用程式來説充滿了不確定性:服務發現、超時處理、重試機制、斷路器以及安全性等問題都需要妥善處理。

Istio 正是為解決這些挑戰而生的開放原始碼服務網格解決方案。它能夠協助處理雲端和微服務環境中的服務間連線問題,而與不受程式語言或框架的限制。Istio 建立在名為 Envoy 的開放原始碼代理之上,為應用程式提供了強大的網路功能支援。

微服務架構面臨的挑戰

當 ACME 公司嘗試採用微服務、自動化測試、容器化和持續整合與佈署(CI/CD)時,他們將核心系統中的模組 A 和 B 拆分為獨立服務,並建立了新的服務 C。這些服務被封裝成容器並佈署到 Kubernetes 平台上。然而,他們很快就遇到了一些挑戰:

  1. 服務回應時間不一致:在使用者使用高峰期,某些服務出現間歇性問題,無法處理任何流量。此外,當服務 B 出現問題時,服務 A 也會受到影響,但僅限於特定請求。

  2. 佈署風險:自動化佈署時,有時會引入未被自動化測試捕捉的錯誤。他們採用藍綠佈署方式,希望降低佈署風險,但結果卻更像是一次性的「大爆炸」式發布。

  3. 安全性實作不一致:不同團隊對安全性的處理方式完全不同。團隊 A 偏好使用證書和私鑰的安全連線,團隊 B 建立了自己的自定義框架,而團隊 C 則認為不需要額外的安全措施。

這些挑戰在服務導向架構中相當常見,包括:

  • 防止故障跨越隔離邊界
  • 建立能夠應對環境變化的應用程式/服務
  • 建立能夠在部分故障條件下執行的系統
  • 瞭解系統在不斷變化和演進中的整體狀態
  • 控制系統的執行時行為
  • 隨著攻擊面擴大而實施強大的安全性
  • 降低系統變更的風險
  • 強制執行關於誰或什麼可以使用系統元件以及何時使用的策略

雲端基礎設施的不可靠性

雖然作為雲端基礎設施的消費者,我們看不到實際的硬體,但雲端由數百萬個硬體和軟體元件組成。這些元件形成了我們可以透過自助服務 API 設定的虛擬化基礎設施。這些元件中的任何一個都可能發生故障。在過去,我們盡一切努力使基礎設施高度可用,並在此基礎上構建應用程式,假設基礎設施具有可用性和可靠性。但在雲端環境中,我們必須假設基礎設施是短暫的,有時會不可用。

讓我們看一個簡單的例子:假設一個「偏好設定服務」負責管理客戶偏好,並需要呼叫「客戶服務」。當「偏好設定服務」呼叫「客戶服務」更新客戶資料時,可能會遇到嚴重的延遲。這種情況可能由多種原因造成:

  • 客戶服務過載與執行緩慢
  • 客戶服務存在錯誤
  • 網路防火牆減慢了流量
  • 網路擁塞減慢了流量
  • 網路硬體故障,需要重新路由流量
  • 客戶服務硬體上的網路卡出現故障

問題在於,「偏好設定服務」無法區分這是否是「客戶服務」的故障。在具有數百萬硬體和軟體元件的雲端環境中,這類別情況經常發生。

使服務互動更具彈性

「偏好設定服務」可以嘗試幾種方法來處理這種情況:

  1. 重試請求:雖然在過載情況下,這可能會加劇下游問題,而與無法確定之前的嘗試是否已經成功。
  2. 設定超時:在某個閾值後超時請求並丟擲錯誤。
  3. 嘗試不同例項:重試到「客戶服務」的不同例項,可能在不同的可用區域。
  4. 斷路:如果「客戶服務」長時間出現問題,可以選擇暫時停止呼叫它,進入冷卻期。

為了緩解這些情況並提高應用程式對意外故障的彈性,已經發展出一些模式:

  • 客戶端負載平衡:給客戶端可能的端點列表,讓它決定呼叫哪一個。
  • 服務發現:一種機制,用於查詢定期更新的特定邏輯服務的健康端點列表。
  • 斷路器:對看似行為不當的服務在一段時間內減少負載。
  • 隔板模式:使用明確的閾值(連線、執行緒、工作階段等)限制客戶端資源使用。
  • 超時:在呼叫服務時強制執行時間限制。
  • 重試:重試失敗的請求。
  • 重試預算:對重試施加約束,例如在給定時間內限制重試次數。
  • 截止時間:為請求提供上下文,説明回應在多長時間內仍然有用。

這些模式統稱為「應用程式網路」,它們與網路堆積積疊較低層的類別似結構有很多重疊,但它們在訊息層而非封包層運作。

實時瞭解系統狀態

快速前進的一個重要方面是確保我們朝著正確的方向前進。我們嘗試快速佈署,以便測試客戶對它們的反應,但如果服務緩慢或不可用,客戶將沒有機會做出反應(或會避開我們的服務)。當我們對服務進行更改時,我們是否瞭解它們將產生什麼影響(正面或負面)?在進行更改之前,我們是否知道系統執行狀況?

瞭解服務架構的關鍵訊息至關重要,例如哪些服務相互通訊、典型服務負載是什麼、預期看到多少故障、服務故障時會發生什麼、服務健康狀況等。每次我們透過佈署新程式碼或設定進行更改時,都有可能對關鍵指標產生負面影響。當網路和基礎設施不可靠性出現,或者我們佈署了有錯誤的新程式碼時,我們能否確信我們對實際情況有足夠的瞭解,相信系統不會當機?透過指標、日誌和追蹤來觀察系統是執行服務架構的關鍵部分。

解決方案:從應用程式函式庫到基礎設施

應用程式函式庫的侷限性

最早解決雲端環境中應用程式和服務執行問題的是大型網際網路公司,它們投入大量時間和資源構建函式庫和框架。例如,Google 構建了 Stubby,Twitter 構建了 Finagle,Netflix 在 2012 年將其微服務函式庫開放原始碼給社群。NetflixOSS 函式庫針對 Java 開發者,處理雲原生關注點:

  • Hystrix:斷路和隔板
  • Ribbon:客戶端負載平衡
  • Eureka:服務註冊和發現
  • Zuul:動態邊緣代理

這些函式庫只能用於 Java 專案,需要在應用程式中引入依賴並使用它們。例如,使用 NetflixOSS Hystrix 需要在依賴控制系統中引入 Hystrix:

com.netflix.hystrix hystrix-core x.y.z

使用 Hystrix 時,我們用基本的 Hystrix 類別 HystrixCommand 包裝命令:

public class CommandHelloWorld extends HystrixCommand {
    private final String name;

    public CommandHelloWorld(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        // 實際例子會在這裡進行網路呼叫
        return "Hello " + name + "!";
    }
}

雖然將應用程式彈性的實作分散到應用程式本身可以消除中央瓶頸,但也引入了新的挑戰:

  1. 技術限制:如果要引入新服務,將受到其他人和團隊做出的實作決策的約束。例如,使用 NetflixOSS Hystrix 必須使用 Java 或根據 JVM 的技術。

  2. 多語言支援問題:引入新語言或框架時,可能需要尋找不同的函式庫來實作彈性模式,或尋找類別似的套件。每種語言都需要尋找、認證並引入到開發堆積積疊中,而這些函式庫會有不同的實作和假設。

  3. 維護困難:跨多種程式語言和框架維護一系列函式庫需要很大的紀律性,確保所有實作一致與正確非常困難。同時推播更新和變更也是一項艱鉅的任務。

將關注點轉移到基礎設施

這些基本的應用程式網路關注點不特定於任何特定的應用程式、語言或框架。重試、超時、客戶端負載平衡、斷路等也不是差異化的應用程式功能。我們真正想要的是一種與技術無關的方式來實作這些關注點,並減輕應用程式自身實作它們的負擔。

應用程式感知的服務代理

使用代理是將這些橫向關注點移入基礎設施的一種方式。代理是一個中間基礎設施元件,可以處理連線並將它們重定向到適當的後端。我們經常使用代理(無論是否知道)來處理網路流量、強制安全性和平衡後端伺服器的工作負載。

對於這個問題,我們需要一個能夠代表我們的服務執行應用程式網路的代理。這個服務代理需要理解應用程式結構,如訊息和請求,而不像傳統的基礎設施代理那樣只理解連線和封包。換句話説,我們需要一個第 7 層代理。

認識 Envoy 代理

Envoy 是一個在開放原始碼社群中脫穎而出的服務代理,它功能多樣、效能優異與功能強大。Envoy 是在 Lyft 開發的,作為其 SOA 基礎設施的一部分,能夠實作重試、超時、斷路、客戶端負載平衡、服務發現、安全性和指標收集等網路關注點,而不依賴於特定的語言或框架。Envoy 在應用程式之外執行,與應用程式分離。

Envoy 的強大之處不僅限於這些應用層彈性方面。Envoy 還捕捉許多應用程式網路指標,如每秒請求數、失敗數、斷路事件等。透過使用 Envoy,我們可以自動獲得服務間通訊的可見性,這正是我們開始看到許多意外複雜性的地方。Envoy 代理為解決服務架構中的橫向可靠性和可觀測性關注點奠定了基礎,使我們能夠將這些關注點從應用程式轉移到基礎設施中。

我們可以在應用程式旁邊佈署服務代理,以獲得這些功能(彈性和可觀測性),但與應用程式分離,同時保持非常特定於應用程式的精確度。在這個模型中,希望與系統其餘部分通訊的應用程式首先將其請求傳遞給 Envoy,然後 Envoy 處理上游通訊。

服務代理還可以收集分散式追蹤跨度,以便我們可以將特定請求所採取的所有步驟拼接在一起。我們可以看到每個步驟花了多長時間,並尋找系統中潛在的瓶頸或錯誤。如果所有應用程式都透過自己的代理與外部世界通訊,並且所有進入應用程式的流量都透過我們的代理,我們就可以在不更改任何應用程式碼的情況下為應用程式獲得一些重要功能。這個代理 + 應用程式組合形成了稱為服務網格的通訊匯流排的基礎。

我們可以將 Envoy 等服務代理與應用程式的每個例項一起佈署為單個原子單元。例如,在 Kubernetes 中,我們可以在單個 Pod 中與應用程式一起佈署服務代理。這種佈署模式稱為 Sidecar 模式,其中服務代理被佈署來補充主要應用程式例項。

什麼是服務網格?

服務代理如 Envoy 有助於為在雲環境中執行的服務架構增加重要功能。每個應用程式可能有自己的要求或設定,説明代理應如何根據其工作負載目標行為。隨著應用程式和服務數量的增加,設定和管理大量代理可能變得困難。此外,在每個應用程式例項旁邊放置這些代理為構建有趣的高階功能開闢了機會,否則我們將不得不在應用程式本身中實作這些功能。

服務網格是一種分散式應用程式基礎設施,負責以透明、與應用程式分離的方式代表應用程式處理網路流量。服務代理形成了資料平面,所有流量都透過它處理和觀察。資料平面負責建立、保護和控制透過網格的流量。資料平面的行為由控制平面設定。控制平面是網格的大腦,為操作者提供 API 來操作網路行為。資料平面和控制平面共同提供任何雲原生架構中必要的重要功能:

  • 服務彈性
  • 可觀測性訊號
  • 流量控制能力
  • 安全性
  • 策略執行

服務網格透過實作重試、超時和斷路器等功能,使服務通訊對故障具有彈性。它還能夠透過處理服務發現、自適應和區域感知負載平衡以及健康檢查等事項來處理不斷發展的基礎設施拓撲。由於所有流量都流經網格,操作者可以明確控制和引導流量。例如,如果我們想佈署應用程式的新版本,我們可能希望只將其暴露給一小部分(比如 1%)的實時流量。有了服務網格,我們就有能力做到這一點。

當然,服務網格中控制的反面是瞭解其當前行為。由於流量流經網格,我們能夠透過跟蹤指標(如請求峰值、延遲、吞吐量、故障等)來捕捉有關網路行為的詳細訊號。我們可以使用這些遙測資料來描繪系統中正在發生的情況。最後,由於服務網格控制應用程式之間網路通訊的兩端,它可以強制執行強大的安全性,如具有相互認證的傳輸層加密:特別是使用相互傳輸層安全(mTLS)協定。

服務網格為服務操作者提供所有這些功能,幾乎不需要或根本不需要應用程式碼更改、依賴或侵入。某些功能需要與應用程式碼進行少量合作,但我們可以避免大型、複雜的函式庫依賴。使用服務網格,無論你使用什麼應用程式框架或程式語言來構建應用程式,這些功能都會一致與正確地實作,使服務團隊能夠在實施和交付變更以測試其假設和交付價值時快速、安全與自信地行動。

Istio 服務網格簡介

Istio 是由 Google、IBM 和 Lyft 建立的開放原始碼服務網格實作。它幫助你以透明的方式為服務架構增加彈性和可觀測性。使用 Istio,應用程式不需要知道它們是服務網格的一部分:每當它們與外部世界互動時,Istio 代表它們處理網路。無論你使用的是微服務、單體應用還是介於兩者之間的任何東西,Istio 都能帶來許多好處。Istio 的資料平面使用 Envoy 代理,並幫助你設定應用程式,使其旁邊佈署一個服務代理(Envoy)例項。Istio 的控制平面由幾個元件組成,為終端使用者/操作者提供 API、為代理提供設定 API、安全設定、策略宣告等。

Istio 最初是為在 Kubernetes 上執行而構建的,但從佈署平台不可知的角度編寫。這意味著你可以在 Kubernetes、OpenShift 甚至傳統佈署環境(如虛擬機器)等佈署平台上使用根據 Istio 的服務網格。在後面的章節中,我們將看到這對跨雲(包括私有資料中心)的混合佈署有多麼強大。

由於流量在服務網格中流經 Istio 服務代理,Istio 在每個應用程式中都有控制點,可以影響和引導其網路行為。這允許服務操作者控制流量流動並實施精細的發布,如金絲雀發布、暗發布、漸進式推出和 A/B 風格測試。

Istio 的一個重要求是安全性。Istio 預設啟用安全性。由於 Istio 控制應用程式網路徑的每一端,它可以預設透明地加密流量。事實上,更進一步,Istio 可以管理金鑰和證書的發行、安裝和輪換,使服務開箱即用地獲得相互 TLS。如果你曾經歷過為相互 TLS 安裝和設定證書的痛苦,你會欣賞這種操作的簡單性和這種功能的強大之處。Istio 可以分配工作負載身份並將其嵌入到證書中。Istio 還可以使用不同工作負載的身份來進一步實施強大的存取控制策略。

最後,但同樣重要的是,使用 Istio,你可以實施配額、速率限制和組織策略。使用 Istio 的策略執行,你可以建立非常精細的規則,説明哪些服務允許相互互動,哪些不允許。這在跨雲(公共和本地)佈署服務時變得特別重要。使用 Istio,我們可以強制執行諸如「雲端服務不能與本地應用程式通訊和使用資料」之類別的策略。

Istio 是服務網格的強大實作。它的功能使你能夠簡化在雲原生服務架構中執行和操作,可能跨越混合環境。

佈署 Istio 到 Kubernetes

讓我們使用 Kubernetes 容器平台佈署 Istio 和範例應用程式。Kubernetes 是一個非常強大的容器平台,能夠在一組稱為 Kubernetes 節點的主機器上排程和協調容器。這些節點是能夠執行容器的主機器,但 Kubernetes 處理這些機制。

安裝 Istio

首先,我們需要存取 Kubernetes 發行版。在本文中,我們使用 Docker Desktop,它在主機電腦上提供了一個能夠執行 Docker 和 Kubernetes 的精簡虛擬機器。

設定好 Docker Desktop 並啟用 Kubernetes 後,我們應該能夠連線到 Kubernetes 叢集:

$ kubectl get nodes
NAME      STATUS   ROLES    AGE   VERSION
docker-desktop   Ready    master   15h   v1.21.1

接下來,我們要將 Istio 安裝到 Kubernetes 發行版中。我們使用 istioctl 命令列工具來安裝 Istio。從 Istio 發布頁面下載 Istio 1.13.0 發行版,或使用以下指令碼:

curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.13.0 sh -

下載並解壓後,我們可以探索發行版的內容,包括範例、安裝資源和命令列介面。特別關注 bin 目錄,其中包含用於與 Istio 互動的 istioctl 工具。

讓我們驗證 istioctl 工具是否正常工作:

$ ./bin/istioctl version
no running Istio pods in "istio-system"
1.13.0

最後,讓我們驗證 Kubernetes 叢集是否滿足任何先決條件:

$ istioctl x precheck
✔ No issues found when checking the cluster. Istio is safe to install or upgrade!

現在,讓我們使用 istioctl 進行基本安裝:

$ istioctl install --set profile=demo -y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Egress gateways installed
✔ Installation complete

執行此命令後,我們可能需要等待幾分鐘,讓 Docker 映像正確下載並佈署成功。讓我們看 istio-system 名稱空間中安裝了哪些元件:

$ kubectl get pod -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
istio-egressgateway-55d547456b-q2ldq   1/1     Running   0          92s
istio-ingressgateway-7654895f97-2pb62  1/1     Running   0          93s
istiod-5b9d44c58b-vvrpb                1/1     Running   0          99s

最後,我們需要安裝控制平面支援元件。這些元件不是嚴格要求的,但對於任何真正的 Istio 佈署都應該安裝:

$ kubectl apply -f ./samples/addons

現在,如果我們檢查 istio-system 名稱空間,我們會看到已安裝的支援元件:

$ kubectl get pod -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
grafana-784c89f4cf-8w8f4               1/1     Running   0          ...
istio-egressgateway-96cf6b468-9n65h    1/1     Running   0          ...
istio-ingressgateway-57b94d999-48vmn   1/1     Running   0          ...
istiod-58c5fdd87b-lr4jf                1/1     Running   0          ...
jaeger-7f78b6fb65-rvfr7                1/1     Running   0          ...
kiali-dc84967d9-vb9b4                  1/1     Running   0          ...
prometheus-7bfddb8dbf-rxs4m            2/2     Running   0          ...

瞭解 Istio 控制平面

控制平面為服務網格的使用者提供了控制、觀察、管理和設定網格的方式。對於 Istio,控制平面提供以下功能:

  • 為操作者指定所需路由/彈性行為的 API
  • 為資料平面提供消費設定的 API
  • 為資料平面提供服務發現抽象
  • 指定使用策略的 API
  • 證書發行和輪換
  • 工作負載身份分配
  • 統一遙測收集
  • 服務代理 sidecar 注入
  • 網路邊界規範以及如何存取它們

這些責任的大部分由名為 istiod 的單一控制平面元件實作。

Istiod

Istio 的控制平面責任由 istiod 元件實作。istiod(有時稱為 Istio Pilot)負責將使用者/操作者指定的高階 Istio 設定轉換為每個資料平面服務代理的特定代理設定。

例如,透過設定資源,我們可以指定如何允許流量進入叢集,如何將其路由到服務的特定版本,如何在進行新佈署時轉移流量,以及服務呼叫者應如何處理超時、重試和斷路等彈性方面。istiod 接收這些設定,解釋它們,並將它們作為特定服務代理的設定公開。Istio 使用 Envoy 作為其服務代理,因此這些設定被轉換為 Envoy 設定。

例如,對於嘗試與目錄服務通訊的服務,如果請求中有標頭 x-dark-launch,我們可能希望將流量傳送到服務的 v2。我們可以使用以下設定為 Istio 表達這一點:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-service
spec:
  hosts:
    - catalog.prod.svc.cluster.local
  http:
    - match:
        - headers:
            x-dark-launch:
              exact: "v2"
      route:
        - destination:
            host: catalog.prod.svc.cluster.local
            subset: v2
    - route:
        - destination:
            host: catalog.prod.svc.cluster.local
            subset: v1

這個設定指定,根據標頭比對,當有標頭 x-dark-launch 等於 v2 時,我們希望將請求路由到目錄服務的 v2 佈署;對於所有其他請求,我們將路由到目錄服務的 v1。

Istio 讀取 Istio 特定的設定物件,如前面設定中的 VirtualService,並將它們轉換為 Envoy 的原生設定。istiod 透過其資料平面 API 將此設定意圖作為 Envoy 設定公開給服務代理。

身份管理

使用 Istio 服務網格,服務代理與每個應用程式例項一起執行,所有應用程式流量都透過這些代理。當應用程式希望向另一個服務發出請求時,傳送方和接收方的代理直接相互通訊。

Istio 的核心功能之一是能夠為每個工作負載例項分配身份,並加密服務之間呼叫的傳輸,因為它位於請求路徑的兩端(發起和終止)。為此,Istio 使用 X.509 證書來加密流量。工作負載身份嵌入在這些證書中,遵循 SPIFFE(Secure Production Identity Framework For Everyone)規範。這使 Istio 能夠提供強大的相互認證(mTLS),而應用程式不需要了解證書、公鑰/私鑰等。istiod 處理證書的認證、簽名和交付,以及用於啟用這種形式安全的證書輪換。

入口和出口閘道器

為了讓我們的應用程式和服務提供有意義的功能,它們需要與叢集外部的應用程式互動。這些可能是現有的單體應用程式、現成的軟體、訊息佇列、資料函式庫和第三方合作夥伴系統。為此,操作者需要設定 Istio 以允許流量進入叢集,並非常具體地説明允許哪些流量離開叢集。建模和理解允許進入和離開叢集的流量是良好的實踐,並提高了我們的安全態勢。

提供此功能的 Istio 元件是 istio-ingressgatewayistio-egressgateway。這些元件實際上是能夠理解 Istio 設定的 Envoy 代理。雖然它們在技術上不是控制平面的一部分,但它們在服務網格的任何實際使用中都是不可或缺的。這些元件位於資料平面中,設定方式與應用程式一起生活的 Istio 服務代理非常相似。唯一的實際區別是它們獨立於任何應用程式工作負載,只是為了讓流量進入和離開叢集。

在服務網格中佈署第一個應用程式

ACME 公司正在重做其網站以及支援庫存和結帳的系統。該公司決定使用 Kubernetes 作為其佈署平台的核心,並將其應用程式構建到 Kubernetes API 而不是特定的雲供應商。ACME 正在尋求解決雲環境中服務通訊的一些挑戰,所以當其首席架構師瞭解到 Istio 時,公司決定使用它。ACME 的應用程式是一個線上網店,由典型的企業應用程式服務組成。

首先,讓我們在 Kubernetes 中建立一個名稱空間,我們將在其中佈署我們的服務:

$ kubectl create namespace istioinaction
$ kubectl config set-context $(kubectl config current-context) --namespace=istioinaction

現在我們在 istioinaction 名稱空間中,讓我們看我們要佈署的內容。catalog-service 的 Kubernetes 資源檔案如下:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: catalog
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 3000
  selector:
    app: catalog
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: catalog
    version: v1
  name: catalog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: catalog
      version: v1
  template:
    metadata:
      labels:
        app: catalog
        version: v1
    spec:
      containers:
        - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: istioinaction/catalog:latest
          imagePullPolicy: IfNotPresent
          name: catalog
          ports:
            - containerPort: 3000
              name: http
              protocol: TCP
          securityContext:
            privileged: false

在佈署之前,我們希望注入 Istio 服務代理,以便此服務可以參與服務網格。我們可以使用 istioctl kube-inject 命令來實作這一點:

$ istioctl kube-inject -f services/catalog/kubernetes/catalog.yaml

這個命令會將 Kubernetes 資源檔案豐富化,增加 Istio 服務代理的 sidecar 佈署和一些額外元件。

為了啟用自動注入,我們可以標記 istioinaction 名稱空間:

$ kubectl label namespace istioinaction istio-injection=enabled

現在讓我們建立目錄佈署:

$ kubectl apply -f services/catalog/kubernetes/catalog.yaml
serviceaccount/catalog created
service/catalog created
deployment.apps/catalog created

如果我們詢問 Kubernetes 佈署了哪些 Pod,我們會看到類別似這樣的內容:

$ kubectl get pod
NAME                      READY   STATUS    RESTARTS   AGE
catalog-7c96f7cc66-flm8g  2/2     Running   0          1m

注意 READY 列中的 2/2:這意味著 Pod 中有兩個容器,兩個都處於 Ready 狀態。其中一個容器是應用程式容器,在這種情況下是 catalog。另一個容器是 istio-proxy sidecar。

此時,我們可以從 Kubernetes 叢集內部使用主機名 catalog.istioinaction 查詢目錄服務:

$ kubectl run -i -n default --rm --restart=Never dummy \
  --image=curlimages/curl --command -- \
  sh -c 'curl -s http://catalog.istioinaction/items/1'
{
  "id": 1,
  "color": "amber",
  "department": "Eyewear",
  "name": "Elinor Glasses",
  "price": "282.00"
}

接下來,我們佈署 webapp 服務,它聚合來自其他服務的資料並在瀏覽器中直觀地顯示它:

$ kubectl apply -f services/webapp/kubernetes/webapp.yaml
serviceaccount/webapp created
service/webapp created
deployment.apps/webapp created

如果我們列出 Kubernetes 叢集中的 Pod,我們會看到我們的新 webapp 佈署,其中 2/2 容器正在執行:

$ kubectl get pod
NAME                       READY   STATUS    RESTARTS   AGE
catalog-759767f98b-mcqcm   2/2     Running   0          3m59s
webapp-8454b8bbf6-b8g7j    2/2     Running   0          50s

最後,讓我們呼叫新的 webapp 服務並驗證它是否有效:

$ kubectl run -i -n default --rm --restart=Never dummy \
  --image=curlimages/curl --command -- \
  sh -c 'curl -s http://webapp.istioinaction/api/catalog/items/1'

如果此命令正確完成,您應該看到與直接呼叫目錄服務時相同的 JSON 回應。此外,我們可以透過將應用程式轉發到本地主機來在瀏覽器中視覺化 webapp 服務背後所有服務的內容:

$ kubectl port-forward deploy/webapp 8080:8080

您可以在瀏覽器中開啟 http://localhost:8080 檢視 Web 應用程式 UI。

到目前為止,我們只是佈署了帶有 Istio 服務代理的目錄和 webapp 服務。每個服務都有自己的 sidecar 代理,所有進出各個服務的流量都透過各自的 sidecar 代理。

探索 Istio 的強大功能:彈性、可觀測性和流量控制

在前面的例子中,我們必須將 webapp 服務本地轉發,因為到目前為止,我們沒有辦法將流量引入叢集。使用 Istio,我們可以使用 Istio 入口閘道器將流量引入叢集,這樣我們就可以呼叫我們的 Web 應用程式:

$ kubectl apply -f ch2/ingress-gateway.yaml
gateway.networking.istio.io/coolstore-gateway created
virtualservice.networking.istio.io/webapp-virtualservice created

此時,我們已經使 Istio 在 Kubernetes 叢集的邊緣知道 webapp 服務,我們可以呼叫它。讓我們看是否可以存取我們的服務。首先,我們需要取得 Istio 閘道器正在監聽的端點。在 Docker Desktop 上,它預設為 http://localhost:80:

$ curl http://localhost/api/catalog/items/1

如果您在自己的 Kubernetes 叢集上執行(例如,在公共雲上),您可以透過列出 istio-system 名稱空間中的 Kubernetes 服務來找到公共雲的外部端點:

$ URL=$(kubectl -n istio-system get svc istio-ingressgateway \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ curl $URL/api/catalog/items/1

如果您無法使用負載平衡器,另一種方法是使用 kubectl 轉發到本地機器:

$ kubectl port-forward deploy/istio-ingressgateway \
  -n istio-system 8080:8080

Istio 可觀測性

由於 Istio 服務代理位於連線的兩端(每個服務都有自己的服務代理),Istio 可以收集大量關於應用程式之間發生的情況的遙測和洞察。Istio 的服務代理作為 sidecar 與每個應用程式一起佈署,因此它收集的洞察來自應用程式「外部」。在大多數情況下,這意味著應用程式不需要特定於函式庫或框架的實作來實作這種級別的可觀測性。

Istio 為兩個主要類別的可觀測性建立遙測。第一個是頂線指標,如每秒請求數、故障數和尾部延遲百分位數。瞭解這些值可以提供對系統中問題開始出現的位置的深入洞察。其次,Istio 可以促進分散式追蹤,如 OpenTracing.io。Istio 可以將跨度傳送到分散式追蹤後端,而無需應用程式擔心它。這樣,我們可以深入瞭解特定服務互動期間發生的情況,檢視延遲發生的位置,並取得有關整體呼叫延遲的訊息。

頂級指標

我們首先看一些可以開箱即用的 Istio 可觀測性功能。在前面的部分中,我們增加了兩個 Kubernetes 佈署並將它們注入了 Istio sidecar 代理。然後我們增加了一個 Istio 入口閘道器,這樣我們就可以從叢集外部存取我們的服務。為了取得指標,我們將使用 Prometheus 和 Grafana。

讓我們使用 istioctl 將 Grafana 轉發到我們的本地機器,這樣我們就可以看到儀錶板:

$ istioctl dashboard grafana
http://localhost:3000

這應該會自動開啟您的預設瀏覽器;如果沒有,請開啟瀏覽器並轉到 http://localhost:3000。您應該會到達 Grafana 主螢幕。在左上角,選擇 Home 儀錶板以顯示我們可以切換到的其他儀錶板的下拉列表。

Istio 有一組開箱即用的儀錶板,提供有關在 Istio 中安裝和執行的服務的一些基本詳細訊息。使用這些儀錶板,我們可以看到我們已安裝並在網格中執行的服務以及一些 Istio 控制平面元件。在儀錶板列表中,點選 Istio Service Dashboard。

儀錶板應顯示所選特定服務的一些頂級指標。在儀錶板頂部附近的 Service 下拉框中,確保選擇了 webapp.istioinaction.svc.cluster.local 服務。

我們看到像 Client Request Volume 和 Client Success Rate 這樣的指標,但值大多是空的或「N/A」。在命令列 shell 中,讓我們向服務傳送一些流量並觀察發生的情況:

$ while true; do curl http://localhost/api/catalog; sleep .5; done

按 Ctrl-C 結束此迴圈。現在,如果您檢視 Grafana 儀錶板,您應該會看到一些有趣的流量(您可能需要重新整理儀錶板)。

我們的服務收到了一些流量,我們有 100% 的成功率,並且我們經歷了 P50、P90 和 P99 尾部延遲。向下滾動儀錶板,您可以看到有關哪些服務和客戶端正在呼叫 webapp 服務以及該行為是什麼樣的其他有趣指標。

您會注意到我們沒有向我們的應用程式碼增加任何檢測。雖然我們應該始終大量檢測我們的應用程式,但我們在這裡看到的是應用程式在網路上實際做了什麼,無論應用程式認為發生了什麼。從黑盒角度來看,我們可以觀察應用程式及其協作者在網格中的行為方式 - 而我們所做的只是增加 Istio sidecar 代理。要獲得對透過叢集的個別呼叫的更全面的檢視,我們可以檢視分散式追蹤等內容,以跟蹤單個請求在命中多個服務時的情況。

使用開放追蹤進行分散式追蹤

我們可以使用 Istio 來處理大部分繁重的工作,以便開箱即用地獲得分散式追蹤。Istio 安裝附帶的一個附加元件是 Jaeger 追蹤儀錶板,我們可以這樣開啟它:

$ istioctl dashboard jaeger
http://localhost:16686

現在,讓我們使用我們的 Web 瀏覽器導航到 http://localhost:16686,這應該會帶我們到 Jaeger Web 控制枱。左上窗格中 Service 下拉列表中的服務應該是 istio-ingressgateway.istio-system。如果不是,請點選下拉列表並選擇 istio-ingressgateway.istio-system。然後點選左側窗格底部的 Find Traces。您應該會看到一些分散式追蹤條目。如果沒有,重新執行命令列中的流量生成客戶端:

$ while true; do curl http://localhost/api/catalog; sleep .5; done

按 Ctrl-C 結束迴圈。

您應該會看到最近進入叢集的呼叫以及它們生成的分散式追蹤跨度。點選其中一個跨度條目會顯示特定呼叫的詳細訊息。從 istio-ingressgateway,呼叫先到 webapp 服務,然後到 catalog 服務。

雖然 Istio 可以在服務之間和追蹤引擎之間傳播追蹤 ID 和中繼資料,但應用程式負責在自身內部傳播追蹤中繼資料。追蹤中繼資料通常由一組 HTTP 標頭(對於 HTTP 和 HTTPS 流量)組成,應用程式負責將傳入標頭與任何傳出請求相關聯。換句話説,Istio 無法知道特定服務或應用程式內部發生的情況,因此它無法知道特定傳入請求應與特定傳出請求相關聯(因果關係)。它依賴應用程式知道這一點並正確地將標頭注入到任何傳出請求中。從那裡,Istio 可以捕捉這些跨度並將它們傳送到追蹤引擎。

Istio 的彈性

如前所述,透過網路通訊以幫助完成其業務邏輯的應用程式必須意識到並考慮分散式計算的謬誤:它們需要處理網路不可預測性。在過去,我們嘗試透過在應用程式中包含大量網路變通程式碼來做一些事情,如重試、超時、斷路等。Istio 可以使我們不必將這種網路程式碼直接寫入我們的應用程式,並為服務網格中的所有應用程式提供一致的預設彈性期望。

這種彈性方面之一是在間歇性/暫時性網路錯誤中重試請求。例如,如果網路出現故障,我們的應用程式可能會看到這些錯誤並透過重試請求繼續。在我們的範例架構中,我們將透過從我們的目錄服務驅動行為來模擬這一點。

如果我們像前面部分那樣呼叫我們的 webapp 伺服器端點,呼叫會成功回傳。但是,如果我們希望所有呼叫都失敗,我們可以使用一個指令碼,將不良行為注入到應用程式中。執行以下命令會導致所有 webappcatalog 的呼叫 100% 的時間都以 HTTP 500 錯誤回應失敗:

$ ./bin/chaos.sh 500 100

如果您現在查詢目錄專案,將回傳 HTTP 500:

$ curl -v http://localhost/api/catalog

為了演示 Istio 自動為應用程式執行重試的能力,讓我們將目錄服務設定為在我們呼叫 webapp 伺服器端點時 50% 的時間生成錯誤:

$ ./bin/chaos.sh 500 50

現在我們可以測試服務回應:

$ while true; do curl http://localhost/api/catalog ; \ sleep .5; done

按 Ctrl-C 結束此迴圈。

此命令的輸出應該是來自 webapp 服務的間歇性成功和失敗。實際上,失敗是由於 webapp 與目錄服務通訊時造成的(目錄服務行為不當)。讓我們看如何使用 Istio 使 webappcatalog 之間的網路更具彈性。

使用 Istio VirtualService,我們可以指定有關與網格中服務互動的規則。以下是 catalogVirtualService 定義的範例:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
    - catalog
  http:
    - route:
        - destination:
            host: catalog
      retries:
        attempts: 3
        perTryTimeout: 2s

使用此定義,我們指定對目錄服務的請求最多可以重試三次,每次嘗試的超時間為兩秒。如果我們實施此規則,我們可以使用 Istio 在遇到故障時自動重試。讓我們建立此規則並重新執行我們的測試客戶端指令碼:

$ kubectl apply -f ch2/catalog-virtualservice.yaml
virtualservice.networking.istio.io/catalog created

現在嘗試再次執行客戶端指令碼:

$ while true; do curl http://localhost/api/catalog ; \ sleep .5; done

按 Ctrl-C 結束此迴圈。

您應該會看到較少的異常冒泡到客戶端。使用 Istio,在不觸及任何應用程式碼的情況下,我們可以在透過網路通訊時增加一定程度的彈性。

讓我們停用目錄服務中的故障:

$ ./bin/chaos.sh 500 delete

這應該會停止目錄的任何不良行為回應。

Istio 的流量路由

我們將在本文中看到的最後一個 Istio 功能是能夠對服務網格中的請求進行非常精細的控制,無論它們在呼叫圖中有多深。到目前為止,我們已經看了一個簡單的架構,由 webapp 服務提供一個外觀,覆寫它在後端通訊的任何服務。目前它談話的唯一服務是 catalog。假設我們想要向目錄服務增加一些新功能。

在這個例子中,我們將向有效載荷增加一個標誌,以指示目錄中特定專案是否有影像可用。我們希望將此訊息公開給能夠理解此標誌的終端呼叫者(如能夠理解此標誌的使用者介面,或可以使用該標誌決定是否用更多影像訊息豐富專案的服務等)。

目錄服務的 V1 在其回應中具有以下屬性:

{
  "id": 1,
  "color": "amber",
  "department": "Eyewear",
  "name": "Elinor Glasses",
  "price": "282.00"
}

對於目錄的 v2,我們增加了一個名為 imageUrl 的新屬性:

{
  "id": 1,
  "color": "amber",
  "department": "Eyewear",
  "name": "Elinor Glasses",
  "price": "282.00",
  "imageUrl": "http://lorempixel.com/640/480"
}
# Istio 的資料平面:Envoy 代理

## Envoy 在服務網格中的角色

Envoy 代理是一個功能強大的應用層代理,能夠為應用程式提供應用層級的行為控制。在 Istio 服務網格架構中,Envoy 扮演著資料平面的核心角色,負責處理所有服務間的網路通訊。

當我們在雲端環境中佈署服務時,常會面臨網路故障、拓撲變更和彈性擴充等挑戰。Envoy 能夠一致與正確地解決這些問題,提供可靠的服務間通訊機制。

### Envoy  Istio 控制平面的互動

Istio 的控制平面元件 istiod 使用 Kubernetes API 讀取 Istio 設定(如虛擬服務),然後透過 xDS API 動態設定服務代理。這種架構使 Istio 能夠抽象化服務登入檔,並為 Envoy  xDS API 提供實作。

 Istio 佈署在 Kubernetes 上時,istiod 實作了這個 API 並抽象化 Envoy 與特定服務註冊實作的關聯。Istio 使用 Kubernetes 的服務登入檔進行服務發現,而 Envoy 代理完全不需要了解這些實作細節。

## Envoy 的遙測與監控能力

Envoy 能夠產生大量的指標和遙測資料,這些資料需要被送到適當的地方進行分析。Istio 設定資料平面與時間序列系統(如 Prometheus)整合,並可以將分散式追蹤資料傳送到 OpenTracing 引擎。

例如,Istio 可以與 Jaeger 追蹤引擎整合,也可以使用 Zipkin。這種整合能力讓維運人員更容易監控和診斷服務網格中的問題。

## TLS 安全性與證書管理

Envoy 可以終止和發起到服務網格中服務的 TLS 流量。為了實作這一點,我們需要支援基礎設施來建立、簽署和輪換證書。Istio 透過 istiod 元件提供這些功能。

istiod 為應用程式提供特定的證書,這些證書可用於建立服務間的相互 TLS 連線,確保流量安全。

## Istio  Envoy 的協同優勢

Istio 的元件與 Envoy 代理共同構成了一個強大的服務網格實作。兩者都擁有蓬勃發展的社群,並針對下一代服務架構進行了最佳化。

從本章開始,我們假設 Envoy 作為資料平面,因此所有學到的知識都可以應用到後續章節。從現在開始,我們將 Envoy 稱為 Istio 服務代理,其功能透過 Istio  API 呈現,但請理解許多功能實際上來自 Envoy 並由其實作。