使用外部 MongoDB 服務

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

2018 年 11 月 16 日 | 作者:Vadim Eisenberg

使用外部 TCP 服務部落格文章中,我描述了網格內的 Istio 應用程式如何透過 TCP 使用外部服務。在這篇文章中,我將示範如何使用外部 MongoDB 服務。您將使用Istio Bookinfo 範例應用程式,該版本將書籍評分資料持久儲存在 MongoDB 資料庫中。您將此資料庫部署在叢集外部,並設定 ratings 微服務來使用它。您將學習控制外部 MongoDB 服務流量的多種選項及其優缺點。

使用外部評分資料庫的 Bookinfo

首先,您在 Kubernetes 叢集外部設定一個 MongoDB 資料庫實例,以保存書籍評分資料。然後,您修改Bookinfo 範例應用程式以使用您的資料庫。

設定評分資料庫

對於此任務,您設定一個MongoDB實例。您可以使用任何 MongoDB 實例;我使用了Compose for MongoDB

  1. 為您的 admin 使用者的密碼設定一個環境變數。為了防止密碼保留在 Bash 歷史記錄中,請在使用history -d執行命令後立即從歷史記錄中移除該命令。

    $ export MONGO_ADMIN_PASSWORD=<your MongoDB admin password>
    
  2. 為您將建立的新使用者(即 bookinfo)的密碼設定一個環境變數。使用history -d從歷史記錄中移除該命令。

    $ export BOOKINFO_PASSWORD=<password>
    
  3. 為您的 MongoDB 服務設定環境變數 MONGODB_HOSTMONGODB_PORT

  4. 建立 bookinfo 使用者

    $ cat <<EOF | mongo --ssl --sslAllowInvalidCertificates $MONGODB_HOST:$MONGODB_PORT -u admin -p $MONGO_ADMIN_PASSWORD --authenticationDatabase admin
    use test
    db.createUser(
       {
         user: "bookinfo",
         pwd: "$BOOKINFO_PASSWORD",
         roles: [ "read"]
       }
    );
    EOF
    
  5. 建立一個 _集合_ 以保存評分。以下命令將兩個評分都設定為等於 1,以便在 Bookinfo _ratings_ 服務使用您的資料庫時提供視覺提示(預設的 Bookinfo _ratings_ 為 45)。

    $ cat <<EOF | mongo --ssl --sslAllowInvalidCertificates $MONGODB_HOST:$MONGODB_PORT -u admin -p $MONGO_ADMIN_PASSWORD --authenticationDatabase admin
    use test
    db.createCollection("ratings");
    db.ratings.insert(
      [{rating: 1},
       {rating: 1}]
    );
    EOF
    
  6. 檢查 bookinfo 使用者是否可以取得評分

    $ cat <<EOF | mongo --ssl --sslAllowInvalidCertificates $MONGODB_HOST:$MONGODB_PORT -u bookinfo -p $BOOKINFO_PASSWORD --authenticationDatabase test
    use test
    db.ratings.find({});
    EOF
    

    輸出應類似於

    MongoDB server version: 3.4.10
    switched to db test
    { "_id" : ObjectId("5b7c29efd7596e65b6ed2572"), "rating" : 1 }
    { "_id" : ObjectId("5b7c29efd7596e65b6ed2573"), "rating" : 1 }
    bye
    

Bookinfo 應用程式的初始設定

為了示範使用外部資料庫的情境,您首先需要有一個安裝了 Istio 的 Kubernetes 叢集。然後,您部署Istio Bookinfo 範例應用程式套用預設目的地規則,並將 Istio 變更為預設封鎖出口的策略

此應用程式使用 ratings 微服務來取得書籍評分,介於 1 到 5 之間的數字。評分會顯示為每個評論的星號。ratings 微服務有多個版本。您將在下一個小節中部署使用MongoDB 作為評分資料庫的版本。

本部落格文章中的範例命令適用於 Istio 1.0。

提醒您,以下是Bookinfo 範例應用程式中應用程式的端對端架構。

The original Bookinfo application
原始的 Bookinfo 應用程式

在 Bookinfo 應用程式中使用外部資料庫

  1. 部署使用 MongoDB 資料庫的 ratings 微服務規格 (ratings v2)

    Zip
    $ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml@
    serviceaccount "bookinfo-ratings-v2" created
    deployment "ratings-v2" created
    
  2. MONGO_DB_URL 環境變數更新為您的 MongoDB 值

    $ kubectl set env deployment/ratings-v2 "MONGO_DB_URL=mongodb://bookinfo:$BOOKINFO_PASSWORD@$MONGODB_HOST:$MONGODB_PORT/test?authSource=test&ssl=true"
    deployment.extensions/ratings-v2 env updated
    
  3. 將所有發往 reviews 服務的流量路由到其 v3 版本。您這樣做是為了確保 reviews 服務始終呼叫 ratings 服務。此外,將所有發往 ratings 服務的流量路由到使用您的資料庫的 ratings v2

    透過新增兩個虛擬服務來指定上述兩個服務的路由。這些虛擬服務在 Istio 版本封存的 samples/bookinfo/networking/virtual-service-ratings-mongodb.yaml 中指定。_**重要:**_ 確保您在執行以下命令之前套用了預設的目的地規則

    Zip
    $ kubectl apply -f @samples/bookinfo/networking/virtual-service-ratings-db.yaml@
    

更新後的架構如下所示。請注意,網格內的藍色箭頭標記了根據我們新增的虛擬服務設定的流量。根據虛擬服務,流量會傳送到 _reviews v3_ 和 _ratings v2_。

The Bookinfo application with ratings v2 and an external MongoDB database
具有 ratings v2 和外部 MongoDB 資料庫的 Bookinfo 應用程式

請注意,MongoDB 資料庫位於 Istio 服務網格之外,或者更準確地說,位於 Kubernetes 叢集之外。服務網格的邊界以虛線標記。

存取網頁

確定入口 IP 和埠後,存取應用程式的網頁。

由於您尚未設定出口流量控制,因此 Istio 會封鎖對 MongoDB 服務的存取。這就是為什麼每個評論下方顯示的訊息是 _「評分服務目前無法使用」_ 而不是評分星號。

The Ratings service error messages
評分服務錯誤訊息

在以下章節中,您將使用 Istio 中不同的出口控制選項,設定對外部 MongoDB 服務的出口存取。

TCP 的出口控制

由於MongoDB Wire Protocol 在 TCP 之上執行,您可以將對 MongoDB 的出口流量控制為對任何其他外部 TCP 服務的流量。為了控制 TCP 流量,必須指定 CIDR 表示法中的 IP 區塊,其中包含您的 MongoDB 主機的 IP 位址。這裡的注意事項是,有時 MongoDB 主機的 IP 不穩定或事先未知。

如果 MongoDB 主機的 IP 不穩定,則出口流量可以作為 TLS 流量進行控制,或者可以直接路由流量,繞過 Istio Sidecar Proxy。

取得您的 MongoDB 資料庫實例的 IP 位址。作為一個選項,您可以使用 host 命令

$ export MONGODB_IP=$(host $MONGODB_HOST | grep " has address " | cut -d" " -f4)

在沒有閘道的情況下控制 TCP 出口流量

如果您不需要透過出口閘道路由流量,例如,如果您沒有要求所有離開網格的流量都必須透過閘道離開,請按照本節中的說明進行操作。或者,如果您想透過出口閘道路由流量,請繼續透過出口閘道直接路由 TCP 出口流量

  1. 定義一個 TCP 網格外部服務條目

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: mongo
    spec:
      hosts:
      - my-mongo.tcp.svc
      addresses:
      - $MONGODB_IP/32
      ports:
      - number: $MONGODB_PORT
        name: tcp
        protocol: TCP
      location: MESH_EXTERNAL
      resolution: STATIC
      endpoints:
      - address: $MONGODB_IP
    EOF
    

    請注意,由於MongoDB 協定在 TLS 之上執行的情況下流量可以加密,因此指定了協定 TCP 而不是 MONGO。如果流量已加密,則加密的 MongoDB 協定無法由 Istio Proxy 剖析。

    如果您知道使用未加密的純 MongoDB 協定,您可以將協定指定為 MONGO,並讓 Istio Proxy 產生與 MongoDB 相關的統計資料。另請注意,當指定協定 TCP 時,配置不是 MongoDB 特有的,而是與任何其他基於 TCP 的協定的資料庫相同。

    請注意,您的 MongoDB 的主機未使用在 TCP 路由中,因此您可以使用任何主機,例如 my-mongo.tcp.svc。請注意 STATIC 解析和具有您的 MongoDB 服務 IP 的端點。定義此類端點後,您可以存取沒有網域名稱的 MongoDB 服務。

  2. 重新整理應用程式的網頁。現在,應用程式應顯示沒有錯誤的評分

    Book Ratings Displayed Correctly
    正確顯示書籍評分

    請注意,如預期,您會看到兩個顯示的評論的評分都是一顆星。您將評分設定為一顆星,以便讓您視覺上清楚地知道您的外部資料庫確實正在使用中。

  3. 如果您想透過出口閘道路由流量,請繼續下一節。否則,請執行清理

透過出口閘道直接路由 TCP 出口流量

在本節中,您將處理需要透過出口閘道路由流量的情況。Sidecar Proxy 會透過比對 MongoDB 主機的 IP(長度為 32 的 CIDR 區塊)將來自 MongoDB 用戶端的 TCP 連線路由到出口閘道。出口閘道會透過其主機名稱將流量轉發到 MongoDB 主機。

  1. 部署 Istio 出口閘道.

  2. 如果您沒有執行上一節中的步驟,請立即執行。

  3. 您可能希望在您的 MongoDB 用戶端的 Sidecar 代理程式與出口閘道之間啟用相互 TLS 驗證,以讓出口閘道監控來源 Pod 的身分,並根據該身分啟用 Mixer 原則強制執行。啟用相互 TLS 也會加密流量。如果您不想啟用相互 TLS,請繼續前往Sidecar 代理程式與出口閘道之間的相互 TLS章節。否則,請繼續前往下一節。

設定從 Sidecar 到出口閘道的 TCP 流量

  1. 定義 EGRESS_GATEWAY_MONGODB_PORT 環境變數以保留某個用於將流量導向出口閘道的埠,例如 7777。您必須選擇一個網格中沒有其他服務使用的埠。

    $ export EGRESS_GATEWAY_MONGODB_PORT=7777
    
  2. 將選取的埠新增至 istio-egressgateway 服務。您應該使用安裝 Istio 時使用的相同值,特別是您必須指定先前設定的 istio-egressgateway 服務的所有埠。

    $ helm template install/kubernetes/helm/istio/ --name istio-egressgateway --namespace istio-system -x charts/gateways/templates/deployment.yaml -x charts/gateways/templates/service.yaml --set gateways.istio-ingressgateway.enabled=false --set gateways.istio-egressgateway.enabled=true --set gateways.istio-egressgateway.ports[0].port=80 --set gateways.istio-egressgateway.ports[0].name=http --set gateways.istio-egressgateway.ports[1].port=443 --set gateways.istio-egressgateway.ports[1].name=https --set gateways.istio-egressgateway.ports[2].port=$EGRESS_GATEWAY_MONGODB_PORT --set gateways.istio-egressgateway.ports[2].name=mongo | kubectl apply -f -
    
  3. 檢查 istio-egressgateway 服務是否確實具有選取的埠

    $ kubectl get svc istio-egressgateway -n istio-system
    NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                   AGE
    istio-egressgateway   ClusterIP   172.21.202.204   <none>        80/TCP,443/TCP,7777/TCP   34d
    
  4. 停用 istio-egressgateway 服務的相互 TLS 驗證

    $ kubectl apply -f - <<EOF
    apiVersion: authentication.istio.io/v1alpha1
    kind: Policy
    metadata:
      name: istio-egressgateway
      namespace: istio-system
    spec:
      targets:
      - name: istio-egressgateway
    EOF
    
  5. 為您的 MongoDB 服務建立出口 Gateway,以及目標規則和虛擬服務,以將流量導向出口閘道,並從出口閘道導向外部服務。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: istio-egressgateway
    spec:
      selector:
        istio: egressgateway
      servers:
      - port:
          number: $EGRESS_GATEWAY_MONGODB_PORT
          name: tcp
          protocol: TCP
        hosts:
        - my-mongo.tcp.svc
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mongo
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
      - name: mongo
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: mongo
    spec:
      host: my-mongo.tcp.svc
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: direct-mongo-through-egress-gateway
    spec:
      hosts:
      - my-mongo.tcp.svc
      gateways:
      - mesh
      - istio-egressgateway
      tcp:
      - match:
        - gateways:
          - mesh
          destinationSubnets:
          - $MONGODB_IP/32
          port: $MONGODB_PORT
        route:
        - destination:
            host: istio-egressgateway.istio-system.svc.cluster.local
            subset: mongo
            port:
              number: $EGRESS_GATEWAY_MONGODB_PORT
      - match:
        - gateways:
          - istio-egressgateway
          port: $EGRESS_GATEWAY_MONGODB_PORT
        route:
        - destination:
            host: my-mongo.tcp.svc
            port:
              number: $MONGODB_PORT
          weight: 100
    EOF
    
  6. 驗證出口流量是否導向出口閘道.

Sidecar 代理程式與出口閘道之間的相互 TLS

  1. 刪除先前的設定

    $ kubectl delete gateway istio-egressgateway --ignore-not-found=true
    $ kubectl delete virtualservice direct-mongo-through-egress-gateway --ignore-not-found=true
    $ kubectl delete destinationrule egressgateway-for-mongo mongo --ignore-not-found=true
    $ kubectl delete policy istio-egressgateway -n istio-system --ignore-not-found=true
    
  2. istio-egressgateway 服務強制執行相互 TLS 驗證

    $ kubectl apply -f - <<EOF
    apiVersion: authentication.istio.io/v1alpha1
    kind: Policy
    metadata:
      name: istio-egressgateway
      namespace: istio-system
    spec:
      targets:
      - name: istio-egressgateway
      peers:
      - mtls: {}
    EOF
    
  3. 為您的 MongoDB 服務建立出口 Gateway,以及目標規則和虛擬服務,以將流量導向出口閘道,並從出口閘道導向外部服務。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: istio-egressgateway
    spec:
      selector:
        istio: egressgateway
      servers:
      - port:
          number: 443
          name: tls
          protocol: TLS
        hosts:
        - my-mongo.tcp.svc
        tls:
          mode: MUTUAL
          serverCertificate: /etc/certs/cert-chain.pem
          privateKey: /etc/certs/key.pem
          caCertificates: /etc/certs/root-cert.pem
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mongo
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
      - name: mongo
        trafficPolicy:
          loadBalancer:
            simple: ROUND_ROBIN
          portLevelSettings:
          - port:
              number: 443
            tls:
              mode: ISTIO_MUTUAL
              sni: my-mongo.tcp.svc
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: mongo
    spec:
      host: my-mongo.tcp.svc
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: direct-mongo-through-egress-gateway
    spec:
      hosts:
      - my-mongo.tcp.svc
      gateways:
      - mesh
      - istio-egressgateway
      tcp:
      - match:
        - gateways:
          - mesh
          destinationSubnets:
          - $MONGODB_IP/32
          port: $MONGODB_PORT
        route:
        - destination:
            host: istio-egressgateway.istio-system.svc.cluster.local
            subset: mongo
            port:
              number: 443
      - match:
        - gateways:
          - istio-egressgateway
          port: 443
        route:
        - destination:
            host: my-mongo.tcp.svc
            port:
              number: $MONGODB_PORT
          weight: 100
    EOF
    
  4. 繼續下一節。

驗證出口流量是否導向出口閘道

  1. 再次重新整理應用程式的網頁,並驗證評分是否仍然正確顯示。

  2. 啟用 Envoy 的存取記錄

  3. 檢查出口閘道的 Envoy 日誌,並查看一行對應到您對 MongoDB 服務的請求。如果 Istio 部署在 istio-system 命名空間中,則用於列印日誌的命令為

    $ kubectl logs -l istio=egressgateway -n istio-system
    [2019-04-14T06:12:07.636Z] "- - -" 0 - "-" 1591 4393 94 - "-" "-" "-" "-" "<Your MongoDB IP>:<your MongoDB port>" outbound|<your MongoDB port>||my-mongo.tcp.svc 172.30.146.119:59924 172.30.146.119:443 172.30.230.1:59206 -
    

清理 TCP 出口流量控制

$ kubectl delete serviceentry mongo
$ kubectl delete gateway istio-egressgateway --ignore-not-found=true
$ kubectl delete virtualservice direct-mongo-through-egress-gateway --ignore-not-found=true
$ kubectl delete destinationrule egressgateway-for-mongo mongo --ignore-not-found=true
$ kubectl delete policy istio-egressgateway -n istio-system --ignore-not-found=true

TLS 的出口控制

在現實生活中,大多數與外部服務的通訊都必須加密,並且 MongoDB 協定在 TLS 之上運行。此外,TLS 用戶端通常會傳送 伺服器名稱指示 (SNI) 作為其交握的一部分。如果您的 MongoDB 伺服器執行 TLS,並且您的 MongoDB 用戶端傳送 SNI 作為交握的一部分,您可以像控制任何其他具有 SNI 的 TLS 流量一樣控制 MongoDB 出口流量。使用 TLS 和 SNI,您不需要指定 MongoDB 伺服器的 IP 位址。您改為指定其主機名稱,這更方便,因為您不必依賴 IP 位址的穩定性。您也可以指定萬用字元作為主機名稱的前綴,例如允許存取來自 *.com 網域的任何伺服器。

若要檢查您的 MongoDB 伺服器是否支援 TLS,請執行

$ openssl s_client -connect $MONGODB_HOST:$MONGODB_PORT -servername $MONGODB_HOST

如果以上命令列印出伺服器傳回的憑證,則該伺服器支援 TLS。如果沒有,您必須像先前章節所述在 TCP 層級控制 MongoDB 出口流量。

在沒有閘道的情況下控制 TLS 出口流量

如果您不需要出口閘道,請依照本節中的指示操作。如果您想透過出口閘道導向流量,請繼續前往透過出口閘道導向 TCP 出口流量

  1. 為 MongoDB 服務建立 ServiceEntry

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: mongo
    spec:
      hosts:
      - $MONGODB_HOST
      ports:
      - number: $MONGODB_PORT
        name: tls
        protocol: TLS
      resolution: DNS
    EOF
    
  2. 重新整理應用程式的網頁。應用程式應該會顯示評分且沒有錯誤。

清理 TLS 的出口設定

$ kubectl delete serviceentry mongo

透過出口閘道導向 TLS 出口流量

在本節中,您將處理需要透過出口閘道導向流量的情況。Sidecar 代理程式會透過比對 MongoDB 主機的 SNI,將來自 MongoDB 用戶端的 TLS 連線路由到出口閘道。出口閘道會將流量轉送至 MongoDB 主機。請注意,Sidecar 代理程式會將目的地埠重寫為 443。出口閘道會接受埠 443 上的 MongoDB 流量,依 SNI 比對 MongoDB 主機,並再次重寫埠以成為 MongoDB 伺服器的埠。

  1. 部署 Istio 出口閘道.

  2. 為 MongoDB 服務建立 ServiceEntry

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: mongo
    spec:
      hosts:
      - $MONGODB_HOST
      ports:
      - number: $MONGODB_PORT
        name: tls
        protocol: TLS
      - number: 443
        name: tls-port-for-egress-gateway
        protocol: TLS
      resolution: DNS
      location: MESH_EXTERNAL
    EOF
    
  3. 重新整理應用程式的網頁,並驗證評分是否正確顯示。

  4. 為您的 MongoDB 服務建立出口 Gateway,以及目標規則和虛擬服務,以將流量導向出口閘道,並從出口閘道導向外部服務。

    如果您想要在應用程式 Pod 的 Sidecar 代理程式與出口閘道之間啟用相互 TLS 驗證,請使用以下命令。(您可能希望啟用相互 TLS 以讓出口閘道監控來源 Pod 的身分,並根據該身分啟用 Mixer 原則強制執行。)

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: istio-egressgateway
    spec:
      selector:
    istio: egressgateway
      servers:
      - port:
      number: 443
      name: tls
      protocol: TLS
    hosts:
    - $MONGODB_HOST
    tls:
      mode: MUTUAL
      serverCertificate: /etc/certs/cert-chain.pem
      privateKey: /etc/certs/key.pem
      caCertificates: /etc/certs/root-cert.pem
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mongo
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
      - name: mongo
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN
      portLevelSettings:
      - port:
          number: 443
        tls:
          mode: ISTIO_MUTUAL
          sni: $MONGODB_HOST
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: direct-mongo-through-egress-gateway
    spec:
      hosts:
      - $MONGODB_HOST
      gateways:
      - mesh
      - istio-egressgateway
      tls:
      - match:
    - gateways:
      - mesh
      port: $MONGODB_PORT
      sni_hosts:
      - $MONGODB_HOST
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: mongo
        port:
          number: 443
      tcp:
      - match:
    - gateways:
      - istio-egressgateway
      port: 443
    route:
    - destination:
        host: $MONGODB_HOST
        port:
          number: $MONGODB_PORT
      weight: 100
    EOF
    
  5. 驗證流量是否透過出口閘道導向

清理透過出口閘道導向 TLS 出口流量

$ kubectl delete serviceentry mongo
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-mongo-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-mongo

啟用 MongoDB TLS 出口流量至任意萬用字元網域

有時候您想要設定從相同網域到多個主機名稱的出口流量,例如從 *.<您的公司網域>.com 到所有 MongoDB 服務的流量。您不希望建立多個設定項目,每個 MongoDB 服務一個。若要透過單一設定設定存取來自相同網域的所有外部服務,您可以使用萬用字元主機。

在本節中,您將設定萬用字元網域的出口流量。我使用了 composedb.com 網域上的 MongoDB 執行個體,因此設定 *.com 的出口流量對我來說有效 (我也可以使用 *.composedb.com)。您可以根據您的 MongoDB 主機選擇萬用字元網域。

若要設定萬用字元網域的出口閘道流量,您首先需要部署一個具有額外 SNI 代理程式的自訂出口閘道。由於標準 Istio 出口閘道使用的代理程式 Envoy 的目前限制,因此需要這樣做。

準備一個具有 SNI 代理程式的新出口閘道

在本小節中,除了標準 Istio Envoy 代理程式之外,您還會部署一個具有 SNI 代理程式的出口閘道。您可以使用任何能夠根據任意、未預先設定的 SNI 值路由流量的 SNI 代理程式;我們使用 Nginx 來實現此功能。

  1. 為 Nginx SNI 代理程式建立設定檔。如果需要,您可能想要編輯該檔案以指定其他 Nginx 設定。

    $ cat <<EOF > ./sni-proxy.conf
    user www-data;
    
    events {
    }
    
    stream {
      log_format log_stream '\$remote_addr [\$time_local] \$protocol [\$ssl_preread_server_name]'
      '\$status \$bytes_sent \$bytes_received \$session_time';
    
      access_log /var/log/nginx/access.log log_stream;
      error_log  /var/log/nginx/error.log;
    
      # tcp forward proxy by SNI
      server {
        resolver 8.8.8.8 ipv6=off;
        listen       127.0.0.1:$MONGODB_PORT;
        proxy_pass   \$ssl_preread_server_name:$MONGODB_PORT;
        ssl_preread  on;
      }
    }
    EOF
    
  2. 建立 Kubernetes ConfigMap 以保留 Nginx SNI 代理程式的設定

    $ kubectl create configmap egress-sni-proxy-configmap -n istio-system --from-file=nginx.conf=./sni-proxy.conf
    
  3. 以下命令將產生 istio-egressgateway-with-sni-proxy.yaml 以進行編輯和部署。

    $ cat <<EOF | helm template install/kubernetes/helm/istio/ --name istio-egressgateway-with-sni-proxy --namespace istio-system -x charts/gateways/templates/deployment.yaml -x charts/gateways/templates/service.yaml -x charts/gateways/templates/serviceaccount.yaml -x charts/gateways/templates/autoscale.yaml -x charts/gateways/templates/role.yaml -x charts/gateways/templates/rolebindings.yaml --set global.mtls.enabled=true --set global.istioNamespace=istio-system -f - > ./istio-egressgateway-with-sni-proxy.yaml
    gateways:
      enabled: true
      istio-ingressgateway:
        enabled: false
      istio-egressgateway:
        enabled: false
      istio-egressgateway-with-sni-proxy:
        enabled: true
        labels:
          app: istio-egressgateway-with-sni-proxy
          istio: egressgateway-with-sni-proxy
        replicaCount: 1
        autoscaleMin: 1
        autoscaleMax: 5
        cpu:
          targetAverageUtilization: 80
        serviceAnnotations: {}
        type: ClusterIP
        ports:
          - port: 443
            name: https
        secretVolumes:
          - name: egressgateway-certs
            secretName: istio-egressgateway-certs
            mountPath: /etc/istio/egressgateway-certs
          - name: egressgateway-ca-certs
            secretName: istio-egressgateway-ca-certs
            mountPath: /etc/istio/egressgateway-ca-certs
        configVolumes:
          - name: sni-proxy-config
            configMapName: egress-sni-proxy-configmap
        additionalContainers:
        - name: sni-proxy
          image: nginx
          volumeMounts:
          - name: sni-proxy-config
            mountPath: /etc/nginx
            readOnly: true
    EOF
    
  4. 部署新的出口閘道

    $ kubectl apply -f ./istio-egressgateway-with-sni-proxy.yaml
    serviceaccount "istio-egressgateway-with-sni-proxy-service-account" created
    role "istio-egressgateway-with-sni-proxy-istio-system" created
    rolebinding "istio-egressgateway-with-sni-proxy-istio-system" created
    service "istio-egressgateway-with-sni-proxy" created
    deployment "istio-egressgateway-with-sni-proxy" created
    horizontalpodautoscaler "istio-egressgateway-with-sni-proxy" created
    
  5. 驗證新的出口閘道是否正在執行。請注意,Pod 有兩個容器 (一個是 Envoy 代理程式,另一個是 SNI 代理程式)。

    $ kubectl get pod -l istio=egressgateway-with-sni-proxy -n istio-system
    NAME                                                  READY     STATUS    RESTARTS   AGE
    istio-egressgateway-with-sni-proxy-79f6744569-pf9t2   2/2       Running   0          17s
    
  6. 建立靜態位址等於 127.0.0.1 (localhost) 的服務項目,並停用導向新服務項目的流量上的相互 TLS

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: sni-proxy
    spec:
      hosts:
      - sni-proxy.local
      location: MESH_EXTERNAL
      ports:
      - number: $MONGODB_PORT
        name: tcp
        protocol: TCP
      resolution: STATIC
      endpoints:
      - address: 127.0.0.1
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: disable-mtls-for-sni-proxy
    spec:
      host: sni-proxy.local
      trafficPolicy:
        tls:
          mode: DISABLE
    EOF
    

使用新的出口閘道設定存取 *.com

  1. *.com 定義 ServiceEntry

    $ cat <<EOF | kubectl create -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: mongo
    spec:
      hosts:
      - "*.com"
      ports:
      - number: 443
        name: tls
        protocol: TLS
      - number: $MONGODB_PORT
        name: tls-mongodb
        protocol: TLS
      location: MESH_EXTERNAL
    EOF
    
  2. *.com、埠 443、協定 TLS 建立出口 Gateway、設定閘道 SNI 的目標規則,以及防止惡意應用程式竄改 SNI 的 Envoy 篩選器 (篩選器會驗證應用程式發出的 SNI 是否為回報給 Mixer 的 SNI)。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: istio-egressgateway-with-sni-proxy
    spec:
      selector:
        istio: egressgateway-with-sni-proxy
      servers:
      - port:
          number: 443
          name: tls
          protocol: TLS
        hosts:
        - "*.com"
        tls:
          mode: MUTUAL
          serverCertificate: /etc/certs/cert-chain.pem
          privateKey: /etc/certs/key.pem
          caCertificates: /etc/certs/root-cert.pem
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: mtls-for-egress-gateway
    spec:
      host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local
      subsets:
        - name: mongo
          trafficPolicy:
            loadBalancer:
              simple: ROUND_ROBIN
            portLevelSettings:
            - port:
                number: 443
              tls:
                mode: ISTIO_MUTUAL
    ---
    # The following filter is used to forward the original SNI (sent by the application) as the SNI of the mutual TLS
    # connection.
    # The forwarded SNI will be reported to Mixer so that policies will be enforced based on the original SNI value.
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: forward-downstream-sni
    spec:
      filters:
      - listenerMatch:
          portNumber: $MONGODB_PORT
          listenerType: SIDECAR_OUTBOUND
        filterName: forward_downstream_sni
        filterType: NETWORK
        filterConfig: {}
    ---
    # The following filter verifies that the SNI of the mutual TLS connection (the SNI reported to Mixer) is
    # identical to the original SNI issued by the application (the SNI used for routing by the SNI proxy).
    # The filter prevents Mixer from being deceived by a malicious application: routing to one SNI while
    # reporting some other value of SNI. If the original SNI does not match the SNI of the mutual TLS connection, the
    # filter will block the connection to the external service.
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: egress-gateway-sni-verifier
    spec:
      workloadLabels:
        app: istio-egressgateway-with-sni-proxy
      filters:
      - listenerMatch:
          portNumber: 443
          listenerType: GATEWAY
        filterName: sni_verifier
        filterType: NETWORK
        filterConfig: {}
    EOF
    
  3. 將前往 *.com 的流量路由到出口閘道,並從出口閘道路由到 SNI 代理程式。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: direct-mongo-through-egress-gateway
    spec:
      hosts:
      - "*.com"
      gateways:
      - mesh
      - istio-egressgateway-with-sni-proxy
      tls:
      - match:
        - gateways:
          - mesh
          port: $MONGODB_PORT
          sni_hosts:
          - "*.com"
        route:
        - destination:
            host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local
            subset: mongo
            port:
              number: 443
          weight: 100
      tcp:
      - match:
        - gateways:
          - istio-egressgateway-with-sni-proxy
          port: 443
        route:
        - destination:
            host: sni-proxy.local
            port:
              number: $MONGODB_PORT
          weight: 100
    EOF
    
  4. 再次重新整理應用程式的網頁,並驗證評分是否仍然正確顯示。

  5. 啟用 Envoy 的存取記錄

  6. 檢查出口閘道的 Envoy 代理程式的日誌。如果 Istio 部署在 istio-system 命名空間中,則用於列印日誌的命令為

    $ kubectl logs -l istio=egressgateway-with-sni-proxy -c istio-proxy -n istio-system
    

    您應該會看到類似以下的行

    [2019-01-02T17:22:04.602Z] "- - -" 0 - 768 1863 88 - "-" "-" "-" "-" "127.0.0.1:28543" outbound|28543||sni-proxy.local 127.0.0.1:49976 172.30.146.115:443 172.30.146.118:58510 <your MongoDB host>
    [2019-01-02T17:22:04.713Z] "- - -" 0 - 1534 2590 85 - "-" "-" "-" "-" "127.0.0.1:28543" outbound|28543||sni-proxy.local 127.0.0.1:49988 172.30.146.115:443 172.30.146.118:58522 <your MongoDB host>
    
  7. 檢查 SNI 代理程式的日誌。如果 Istio 部署在 istio-system 命名空間中,則用於列印日誌的命令為

    $ kubectl logs -l istio=egressgateway-with-sni-proxy -n istio-system -c sni-proxy
    127.0.0.1 [23/Aug/2018:03:28:18 +0000] TCP [<your MongoDB host>]200 1863 482 0.089
    127.0.0.1 [23/Aug/2018:03:28:18 +0000] TCP [<your MongoDB host>]200 2590 1248 0.095
    

了解發生的情況

在本節中,您使用萬用字元網域設定了到 MongoDB 主機的出口流量。雖然對於單一 MongoDB 主機而言,使用萬用字元網域沒有好處 (可以指定確切的主機名稱),但對於叢集中應用程式存取符合某些萬用字元網域的多個 MongoDB 主機的情況,這可能會有所幫助。例如,如果應用程式存取 mongodb1.composedb.commongodb2.composedb.commongodb3.composedb.com,則可以使用適用於萬用字元網域 *.composedb.com 的單一設定來設定出口流量。

我將把它留給讀者作為練習,以驗證當您設定應用程式使用另一個具有符合本節中使用的萬用字元網域的主機名稱的 MongoDB 執行個體時,不需要其他 Istio 設定。

清理 MongoDB TLS 出口流量至任意萬用字元網域的設定

  1. 刪除 *.com 的設定項目

    $ kubectl delete serviceentry mongo
    $ kubectl delete gateway istio-egressgateway-with-sni-proxy
    $ kubectl delete virtualservice direct-mongo-through-egress-gateway
    $ kubectl delete destinationrule mtls-for-egress-gateway
    $ kubectl delete envoyfilter forward-downstream-sni egress-gateway-sni-verifier
    
  2. 刪除 egressgateway-with-sni-proxy 部署的設定項目

    $ kubectl delete serviceentry sni-proxy
    $ kubectl delete destinationrule disable-mtls-for-sni-proxy
    $ kubectl delete -f ./istio-egressgateway-with-sni-proxy.yaml
    $ kubectl delete configmap egress-sni-proxy-configmap -n istio-system
    
  3. 移除您建立的設定檔

    $ rm ./istio-egressgateway-with-sni-proxy.yaml
    $ rm ./nginx-sni-proxy.conf
    

清理

  1. 刪除 bookinfo 使用者

    $ cat <<EOF | mongo --ssl --sslAllowInvalidCertificates $MONGODB_HOST:$MONGODB_PORT -u admin -p $MONGO_ADMIN_PASSWORD --authenticationDatabase admin
    use test
    db.dropUser("bookinfo");
    EOF
    
  2. 刪除 ratings 集合

    $ cat <<EOF | mongo --ssl --sslAllowInvalidCertificates $MONGODB_HOST:$MONGODB_PORT -u admin -p $MONGO_ADMIN_PASSWORD --authenticationDatabase admin
    use test
    db.ratings.drop();
    EOF
    
  3. 取消設定您使用的環境變數

    $ unset MONGO_ADMIN_PASSWORD BOOKINFO_PASSWORD MONGODB_HOST MONGODB_PORT MONGODB_IP
    
  4. 移除虛擬服務

    Zip
    $ kubectl delete -f @samples/bookinfo/networking/virtual-service-ratings-db.yaml@
    Deleted config: virtual-service/default/reviews
    Deleted config: virtual-service/default/ratings
    
  5. 取消部署 ratings v2-mongodb

    Zip
    $ kubectl delete -f @samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml@
    deployment "ratings-v2" deleted
    

結論

在這篇部落格文章中,我示範了 MongoDB 出口流量控制的各種選項。您可以在適用的情況下,在 TCP 或 TLS 層級控制 MongoDB 出口流量。在 TCP 和 TLS 案例中,您可以根據您組織的安全需求,將流量從 Sidecar 代理程式直接導向外部 MongoDB 主機,或透過出口閘道導向流量。在後一種情況下,您還可以決定在 Sidecar 代理程式與出口閘道之間套用或停用相互 TLS 驗證。如果您想在 TLS 層級透過指定類似 *.com 的萬用字元網域來控制 MongoDB 出口流量,並且您需要透過出口閘道導向流量,則必須部署具有 SNI 代理程式的自訂出口閘道。

請注意,這篇部落格文章中針對 MongoDB 描述的設定和考量,對於 TCP/TLS 之上的其他非 HTTP 協定而言幾乎相同。

分享這篇文章