使用外部 MongoDB 服务

基于 Istio 的 Bookinfo 示例描述了一个简单的场景。

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

使用外部 TCP 服务 的博文中,我描述了网格内 Istio 应用程序如何通过 TCP 使用外部服务。在这篇文章中,我演示了如何使用外部 MongoDB 服务。您将使用 Istio Bookinfo 示例应用程序,该版本中的书籍评分数据存储在 MongoDB 数据库中。您将在集群外部部署此数据库,并配置 ratings 微服务以使用它。您将学习控制对外部 MongoDB 服务的流量的多种选项及其优缺点。

带有外部评分数据库的 Bookinfo

首先,您需要设置一个 MongoDB 数据库实例,以在 Kubernetes 集群外部保存书籍评分数据。然后,您需要修改 Istio 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 ratings45)。

    $ 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

    压缩
    $ 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 中指定。重要提示:确保在运行以下命令之前应用了默认的目标规则

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

更新后的架构如下所示。请注意,网格内的蓝色箭头表示根据我们添加的虚拟服务配置的流量。根据虚拟服务,流量将发送到 reviews v3ratings 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 线程协议 运行在 TCP 之上,因此您可以像控制对任何其他 外部 TCP 服务 的流量一样控制对 MongoDB 的出口流量。要控制 TCP 流量,必须指定一个包含 MongoDB 主机 IP 地址的 CIDR 表示法中的 IP 块。这里需要注意的是,有时 MongoDB 主机的 IP 不稳定或事先未知。

在 MongoDB 主机 IP 不稳定的情况下,出口流量可以作为 TLS 流量进行控制,或者可以直接路由,绕过 Istio sidecar 代理。

获取 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。如果流量被加密,则 Istio 代理无法解析加密的 MongoDB 协议。

    如果您知道使用了纯 MongoDB 协议,并且没有加密,则可以将协议指定为 MONGO 并让 Istio 代理生成 MongoDB 相关统计信息。另请注意,当指定协议 TCP 时,配置不是特定于 MongoDB 的,而是适用于任何其他基于 TCP 协议的数据库。

    请注意,在 TCP 路由中未使用 MongoDB 的主机,因此您可以使用任何主机,例如 my-mongo.tcp.svc。请注意 STATIC 解析和带有 MongoDB 服务 IP 的端点。定义此类端点后,您可以访问没有域名 的 MongoDB 服务。

  2. 刷新应用程序的网页。现在,应用程序应该显示评分,没有错误。

    Book Ratings Displayed Correctly
    正确显示的书籍评分

    请注意,您会看到两个显示的评论都显示了一星评分,这与预期一致。您将评分设置为一星,以便为自己提供一个视觉线索,证明您的外部数据库确实正在被使用。

  3. 如果您希望通过出口网关引导流量,请继续执行下一节。否则,请执行 清理

通过出口网关引导 TCP 出口流量

在本节中,您将处理需要通过 出口网关 引导流量的情况。sidecar 代理通过匹配 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 出站流量到任意通配符域名

有时您希望将出站流量配置到来自同一域名的多个主机名,例如到来自 *.<your company domain>.com 的所有 MongoDB 服务的流量。您不希望为公司中的每个 MongoDB 服务创建多个配置项。要通过单个配置配置对来自同一域名的所有外部服务的访问,您可以使用通配符主机。

在本节中,您将为通配符域名配置出站流量。我使用了 composedb.com 域中的 MongoDB 实例,因此为 *.com 配置出站流量对我有用(我也可以使用 *.composedb.com)。您可以根据您的 MongoDB 主机选择一个通配符域名。

要为通配符域名配置出站网关流量,您首先需要使用附加的 SNI 代理部署一个自定义出站网关。这是由于 Envoy(标准 Istio 出站网关使用的代理)的当前限制。

准备一个带有 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,以及 Envoy 过滤器以防止恶意应用程序篡改 SNI(过滤器验证应用程序发出的 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. 删除虚拟服务

    压缩
    $ 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

    压缩
    $ 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 身份验证。如果您希望通过指定类似于 *.com 的通配符域名来控制 TLS 层级的 MongoDB 出站流量,并且您需要通过出站网关引导流量,则必须部署一个带有 SNI 代理的自定义出站网关。

请注意,本文中针对 MongoDB 描述的配置和注意事项对于 TCP/TLS 之上的其他非 HTTP 协议来说,基本上是相同的。

分享此文章