取用外部 Web 服務

描述一個基於 Istio 的 Bookinfo 範例的簡單情境。

2018 年 1 月 31 日 | 作者:Vadim Eisenberg

在許多情況下,並非所有基於微服務的應用程式部分都位於服務網格中。 有時,基於微服務的應用程式會使用網格外部的舊系統所提供的功能。 您可能想要逐步將這些系統遷移到服務網格。 在這些系統遷移之前,網格內部的應用程式必須能夠存取它們。 在其他情況下,應用程式會使用第三方提供的 Web 服務。

在這篇部落格文章中,我修改了 Istio Bookinfo 範例應用程式,以從外部 Web 服務 (Google Books API) 擷取書籍詳細資訊。 我將展示如何透過使用網格外部服務條目在 Istio 中啟用出口 HTTPS 流量。 我提供了兩種出口 HTTPS 流量的選項,並描述了每個選項的優缺點。

初始設定

為了示範取用外部 Web 服務的情境,我從一個已安裝 Istio 的 Kubernetes 叢集開始。 然後,我部署Istio Bookinfo 範例應用程式。 此應用程式使用 details 微服務來擷取書籍詳細資訊,例如頁數和出版商。 原始的 details 微服務提供書籍詳細資訊,而不需查詢任何外部服務。

這篇部落格文章中的範例命令適用於 Istio 1.0+,無論是否啟用相互 TLS。 Bookinfo 組態檔位於 Istio 發行封存檔的 samples/bookinfo 目錄中。

以下是來自原始Bookinfo 範例應用程式的應用程式端到端架構副本。

The Original Bookinfo Application
原始的 Bookinfo 應用程式

請執行部署應用程式確認應用程式可從叢集外部存取套用預設目的地規則章節中的步驟,以及將 Istio 變更為預設封鎖出口原則

Bookinfo 使用 HTTPS 存取 Google Books Web 服務

部署新版本的 details 微服務,v2,它會從 Google Books API 擷取書籍詳細資訊。 執行下列命令;它會將服務容器的 DO_NOT_ENCRYPT 環境變數設定為 false。 此設定會指示已部署的服務使用 HTTPS (而不是 HTTP) 來存取外部服務。

壓縮
$ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo-details-v2.yaml@ --dry-run -o yaml | kubectl set env --local -f - 'DO_NOT_ENCRYPT=false' -o yaml | kubectl apply -f -

應用程式更新後的架構現在看起來如下

The Bookinfo Application with details V2
具有 details V2 的 Bookinfo 應用程式

請注意,Google Books Web 服務位於 Istio 服務網格之外,其邊界以虛線標示。

現在將所有傳送到 details 微服務的流量,導向到 details 版本 v2

壓縮
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-details-v2.yaml@

請注意,虛擬服務依賴您在套用預設目的地規則章節中建立的目的地規則。

判斷入口 IP 和連接埠之後,存取應用程式的網頁。

糟糕…您看到的不是書籍詳細資訊,而是顯示擷取產品詳細資訊時發生錯誤的訊息

The Error Fetching Product Details Message
擷取產品詳細資訊時發生錯誤訊息

好消息是您的應用程式沒有當機。 透過良好的微服務設計,您不會有錯誤傳播。 在您的案例中,失敗的 details 微服務不會導致 productpage 微服務失敗。 儘管 details 微服務發生故障,應用程式的大部分功能仍然提供。 您有優雅的服務降級:如您所見,評論和評分都正確顯示,且應用程式仍然有用。

那麼可能發生了什麼問題?啊…答案是我忘記告訴您要啟用從網格內部到外部服務 (在本例中為 Google Books Web 服務) 的流量。 依預設,Istio Sidecar Proxy (Envoy Proxy) 會封鎖所有前往叢集外部目的地的流量。 若要啟用此類流量,您必須定義一個網格外部服務條目

啟用 HTTPS 存取 Google Books Web 服務

別擔心,請定義一個網格外部服務條目並修復您的應用程式。 您還必須定義一個虛擬服務,以執行透過 SNI 到外部服務的路由。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: googleapis
spec:
  hosts:
  - www.googleapis.com
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  location: MESH_EXTERNAL
  resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: googleapis
spec:
  hosts:
  - www.googleapis.com
  tls:
  - match:
    - port: 443
      sni_hosts:
      - www.googleapis.com
    route:
    - destination:
        host: www.googleapis.com
        port:
          number: 443
      weight: 100
EOF

現在存取應用程式的網頁會顯示書籍詳細資訊,而不會出現錯誤

Book Details Displayed Correctly
書籍詳細資訊正確顯示

您可以查詢您的服務條目

$ kubectl get serviceentries
NAME         AGE
googleapis   8m

您可以刪除您的服務條目

$ kubectl delete serviceentry googleapis
serviceentry "googleapis" deleted

並在輸出中看到服務條目已刪除。

刪除服務條目後存取網頁會產生您之前遇到的相同錯誤,即擷取產品詳細資訊時發生錯誤。 如您所見,服務條目是動態定義的,就像許多其他 Istio 組態成品一樣。 Istio 運算子可以動態決定它們允許微服務存取的網域。 他們可以即時啟用和停用前往外部網域的流量,而無需重新部署微服務。

清除 HTTPS 存取 Google Books Web 服務

壓縮壓縮
$ kubectl delete serviceentry googleapis
$ kubectl delete virtualservice googleapis
$ kubectl delete -f @samples/bookinfo/networking/virtual-service-details-v2.yaml@
$ kubectl delete -f @samples/bookinfo/platform/kube/bookinfo-details-v2.yaml@

Istio 的 TLS 發起

這個故事有個但書。 假設您想要監控您的微服務使用哪些特定Google API (書籍日曆工作 等)。假設您想要強制執行一項原則,只允許使用書籍 API。假設您想要監控您的微服務存取的書籍識別碼。對於這些監控和原則任務,您需要知道 URL 路徑。例如,考慮 URL www.googleapis.com/books/v1/volumes?q=isbn:0486424618。在該 URL 中,書籍 API 由路徑區段 /books 指定,ISBN 號碼由路徑區段 /volumes?q=isbn:0486424618 指定。但是,在 HTTPS 中,所有 HTTP 詳細資訊 (主機名稱、路徑、標頭等) 都經過加密,且 Sidecar Proxy 無法進行此類監控和原則強制執行。Istio 只能透過 SNI (伺服器名稱指示) 欄位得知加密請求的伺服器名稱,在本例中為 www.googleapis.com

為了讓 Istio 根據 HTTP 詳細資訊執行出口請求的監控和原則強制執行,微服務必須發出 HTTP 請求。然後 Istio 會開啟與目的地的 HTTPS 連線 (執行 TLS 發起)。微服務的程式碼必須根據微服務是在 Istio 服務網格內部還是外部執行而以不同的方式撰寫或組態。這與 Istio 的最大化透明度的設計目標相矛盾。有時您需要妥協…

下圖顯示將 HTTPS 流量傳送到外部服務的兩個選項。在頂部,微服務會傳送經過端到端加密的一般 HTTPS 請求。在底部,相同的微服務會在 Pod 內部傳送未加密的 HTTP 請求,這些請求會被 Sidecar Envoy Proxy 攔截。Sidecar Proxy 執行 TLS 發起,因此 Pod 和外部服務之間的流量會被加密。

HTTPS traffic to external services, with TLS originated by the microservice vs. by the sidecar proxy
透過微服務發起 TLS 與透過 Sidecar Proxy 發起的 TLS 將 HTTPS 流量傳送到外部服務

以下是在 Bookinfo details 微服務程式碼中使用 Ruby net/http 模組支援這兩種模式的方式

uri = URI.parse('https://www.googleapis.com/books/v1/volumes?q=isbn:' + isbn)
http = Net::HTTP.new(uri.host, ENV['DO_NOT_ENCRYPT'] === 'true' ? 80:443)
...
unless ENV['DO_NOT_ENCRYPT'] === 'true' then
     http.use_ssl = true
end

當定義 DO_NOT_ENCRYPT 環境變數時,會對連接埠 80 執行不使用 SSL (純 HTTP) 的請求。

您可以在details v2 的 Kubernetes 部署規格container 區段中,將 DO_NOT_ENCRYPT 環境變數設定為「true」。

env:
- name: DO_NOT_ENCRYPT
  value: "true"

在下一節中,您將設定 TLS 發起以存取外部 Web 服務。

Bookinfo 使用 TLS 發起到 Google Books Web 服務

  1. 部署一個將 HTTP 請求傳送到 Google Books APIdetails v2 版本。在bookinfo-details-v2.yaml中,DO_NOT_ENCRYPT 變數設定為 true。

    壓縮
    $ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo-details-v2.yaml@
    
  2. 將傳送到 details 微服務的流量,導向到 details 版本 v2

    壓縮
    $ kubectl apply -f @samples/bookinfo/networking/virtual-service-details-v2.yaml@
    
  3. www.google.apis 建立一個網格外部服務條目、一個將目的地連接埠從 80 重寫為 443 的虛擬服務,以及一個執行 TLS 發起的目的地規則。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: googleapis
    spec:
      hosts:
      - www.googleapis.com
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: https
        protocol: HTTPS
      resolution: DNS
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: rewrite-port-for-googleapis
    spec:
      hosts:
      - www.googleapis.com
      http:
      - match:
        - port: 80
        route:
        - destination:
            host: www.googleapis.com
            port:
              number: 443
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: originate-tls-for-googleapis
    spec:
      host: www.googleapis.com
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 443
          tls:
            mode: SIMPLE # initiates HTTPS when accessing www.googleapis.com
    EOF
    
  4. 存取應用程式的網頁,並驗證書籍詳細資訊是否顯示無誤。

  5. 啟用 Envoy 的存取記錄

  6. 檢查 details v2 的 Sidecar Proxy 的記錄,並查看 HTTP 請求。

    $ kubectl logs $(kubectl get pods -l app=details -l version=v2 -o jsonpath='{.items[0].metadata.name}') istio-proxy | grep googleapis
    [2018-08-09T11:32:58.171Z] "GET /books/v1/volumes?q=isbn:0486424618 HTTP/1.1" 200 - 0 1050 264 264 "-" "Ruby" "b993bae7-4288-9241-81a5-4cde93b2e3a6" "www.googleapis.com:80" "172.217.20.74:80"
    EOF
    

    請注意記錄中的 URL 路徑,可以監控路徑,並可根據路徑套用存取原則。若要深入了解 HTTP 出口流量的監控和存取原則,請查看這篇部落格文章

清除 TLS 發起到 Google Books Web 服務

壓縮壓縮
$ kubectl delete serviceentry googleapis
$ kubectl delete virtualservice rewrite-port-for-googleapis
$ kubectl delete destinationrule originate-tls-for-googleapis
$ kubectl delete -f @samples/bookinfo/networking/virtual-service-details-v2.yaml@
$ kubectl delete -f @samples/bookinfo/platform/kube/bookinfo-details-v2.yaml@

與 Istio 相互 TLS 的關係

請注意,此處的 TLS 發起與 Istio 應用的相互 TLS 無關。無論是否啟用 Istio 相互 TLS,外部服務的 TLS 發起都會正常運作。相互 TLS 可保護服務網格內部的服務間通訊,並為每個服務提供強大的身分驗證。這篇部落格文章中的外部服務是使用單向 TLS 存取的,其機制與保護網路瀏覽器和網路伺服器之間通訊的機制相同。TLS 會應用於與外部服務的通訊,以驗證外部伺服器的身分並加密流量。

結論

在這篇部落格文章中,我示範了 Istio 服務網格中的微服務如何透過 HTTPS 使用外部網路服務。預設情況下,Istio 會封鎖所有流向叢集外部主機的流量。若要啟用此類流量,必須為服務網格建立網格外部服務條目。可以透過發出 HTTPS 請求,或透過發出 HTTP 請求並由 Istio 執行 TLS 發起,來存取外部網站。當微服務發出 HTTPS 請求時,流量會進行端對端加密,但是 Istio 無法監控 HTTP 詳細資訊,例如請求的 URL 路徑。當微服務發出 HTTP 請求時,Istio 可以監控請求的 HTTP 詳細資訊並強制執行基於 HTTP 的存取原則。但是,在這種情況下,微服務和 Sidecar Proxy 之間的流量是未加密的。在具有非常嚴格安全要求的組織中,部分流量未加密可能會被禁止。

分享這篇文章