使用外部 Web 服务

HTTPS 流量的出口规则

此博客文章于 2018 年 8 月 9 日更新。它反映并使用了 Istio 1.0 的新功能 v1alpha3 流量管理 API。如果您需要使用旧版本,请按照文档进行操作 使用外部 Web 服务归档版

在许多情况下,在 service mesh 中的微服务序并不是应用程序的全部,有时, 网格内部的微服务需要使用在服务网格外部的遗留系统提供的功能,虽然我们希望逐步将这些系统迁移到服务网格中。 但是在迁移这些系统之前,必须让服务网格内的应用程序能访问它们。还有其他情况, 应用程序使用外部组织提供的 Web 服务,通常是通过万维网提供的服务。

在这篇博客文章中,我修改了Istio Bookinfo 示例应用程序让它可以 从外部 Web 服务(Google Books APIs )获取图书详细信息。 我将展示如何使用 mesh-external service entries 在 Istio 中启用外部 HTTPS 流量。最后, 我解释了当前与 Istio 出口流量控制相关的问题。

初始设定

为了演示使用外部 Web 服务的场景,我首先使用安装了 Istio 的 Kubernetes 集群, 然后我部署 Istio Bookinfo 示例应用程序, 此应用程序使用 details 微服务来获取书籍详细信息,例如页数和发布者, 原始 details 微服务提供书籍 详细信息,无需咨询任何外部服务。

此博客文章中的示例命令适用于 Istio 1.0+,无论启用或不启用双向 TLS。 Bookinfo 配置文件位于 Istio 发行存档的 samples/bookinfo 目录中。

以下是原始 Bookinfo 示例应用程序中应用程序端到端体系结构的副本。

原 Bookinfo 应用程序
原 Bookinfo 应用程序

执行部署应用程序确认应用正在运行,以及 应用默认目标规则中的步骤部分。

Bookinfo 使用 HTTPS 访问 Google 图书网络服务

让我们添加一个新版本的 details 微服务,_v2_,从Google Books APIs中获取图书详细信息。 它设定了服务容器的 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 -

现在,应用程序的更新架构如下所示:

Bookinfo 的 details V2 应用程序
Bookinfo 的 details V2 应用程序

请注意,Google Book 服务位于 Istio 服务网格之外,其边界由虚线标记。

现在让我们将指向 details 微服务的所有流量定向到 _details v2_:

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

请注意,VirtualService 依赖于您在应用默认目标规则部分中创建的目标规则。

确定 ingress 的 IP 和端口之后, 让我们访问应用程序的网页。

糟糕…页面显示 _Error fetching product details_,而不是书籍详细信息:

获取产品详细信息的错误消息
获取产品详细信息的错误消息

好消息是我们的应用程序没有崩溃, 通过良好的微服务设计,我们没有让故障扩散。在我们的例子中, 失败的 details 微服务不会导致 productpage 微服务失败, 尽管 details 微服务失败, 仍然提供了应用程序的大多数功能, 我们有优雅的服务降级:正如您所看到的,评论和评级正确显示, 应用程序仍然有用。

那可能出了什么问题? 啊……答案是我忘了启用从网格内部到外部服务的流量,在本例中是 Google Book Web 服务。 默认情况下,Istio sidecar 代理(Envoy proxies阻止到集群外目的地的所有流量, 要启用此类流量,我们必须定义mesh-external service entry

启用对 Google Books 网络服务的 HTTPS 访问

不用担心,让我们定义网格外部 ServiceEntry 并修复我们的应用程序。您还必须定义 virtual service 使用 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

现在访问应用程序的网页会显示书籍详细信息而不会出现错误:

正确显示书籍详细信息
正确显示书籍详细信息

您可以查询您的 ServiceEntry

$ kubectl get serviceentries
NAME         AGE
googleapis   8m

您可以删除您的 ServiceEntry

$ kubectl delete serviceentry googleapis
serviceentry "googleapis" deleted

并在输出中看到删除了 ServiceEntry

删除 ServiceEntry 后访问网页会产生我们之前遇到的相同错误,即 Error fetching product details, 正如我们所看到的,,与许多其他 Istio 配置一样,ServiceEntry动态定义的 , Istio 运算符可以动态决定 它们允许微服务访问哪些域, 他们可以动态启用和禁用外部域的流量,而无需重新部署微服务。

清除对 Google 图书网络服务的 HTTPS 访问权限

$ 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。 在该网址中,路径段指定了图书 API /books 和路径段的 ISBN 代码 /volumes?q=isbn:0486424618。但是,在 HTTPS 中,所有 HTTP 详细信息(主机名,路径,标头等)都是加密的 sidecar 代理的这种监督和策略执行是无法实现的。Istio 只能通过 SNI(_Server Name Indication_)得知加密请求中的主机名称,在这里就是 www.googleapis.com

为了允许 Istio 基于域执行出口请求的过滤,微服务必须发出 HTTP 请求, 然后,Istio 打开到目标的 HTTPS 连接(执行 TLS 发起), 根据微服务是在 Istio 服务网格内部还是外部运行, 微服务的代码必须以不同方式编写或以不同方式配置, 这与最大化透明度 的 Istio 设计目标相矛盾, 有时我们需要妥协……

下图显示了如何执行外部服务的 HTTPS 流量, 在顶部,Istio 服务网格外部的微服务发送常规 HTTPS 请求, 端到端加密, 在底部,Istio 服务网格内的相同微服务必须在 pod 内发送未加密的 HTTP 请求, 这些请求被 sidecar Envoy 代理拦截 , sidecar 代理执行 TLS 发起,因此 pod 和外部服务之间的流量被加密。

对外发起 HTTPS 流量的两种方式:微服务自行发起,或由 Sidecar 代理发起
对外发起 HTTPS 流量的两种方式:微服务自行发起,或由 Sidecar 代理发起

以下是我们如何在 Bookinfo 的 details 微服务代码 中使用 Ruby net/http 模块

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

请注意,默认的 HTTPS 端口 443 的取值是 URI.parse 通过对 URI (https://) 的解析得来的, 当在 Istio 服务网格内运行时,微服务必须向端口 443 发出 HTTP 请求,该端口是外部服务侦听的端口。 当定义 WITH_ISTIO 环境变量时,请求在没有 SSL(普通 HTTP )的情况下执行。

我们将 WITH_ISTIO 环境变量设置为 “true” details 的部署配置文件, container 部分:

env:
- name: DO_NOT_ENCRYPT
  value: "true"

在下一节中,您将配置 TLS 发起以访问外部 Web 服务。

具有 TLS 的 Bookinfo 起源于 Google Books 网络服务

  1. 部署 details v2 版本,将 HTTP 请求发送到 Google Books API。 在 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 创建网格外部 ServiceEntry,并执行目标规则以执行 TLS 发起。

    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: googleapis
    spec:
      hosts:
      - www.googleapis.com
      ports:
      - number: 443
        name: http-port-for-tls-origination
        protocol: HTTP
      resolution: DNS
    ---
    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 # 访问 edition.cnn.com 时启动 HTTPS
    EOF

    注意,前缀为 http- ServiceEntr 指定了端口为 443,其协议指定为 HTTP。 请注意,您不需要使用端口 443 发送 TLS 发起的 HTTP 请求。 出口流量的 TLS 显示了如何使用端口重写执行 TLS 发起。

  4. 访问应用程序的网页,并验证是否显示图书详细信息。

  5. 检查 details v2 的 sidecar 代理的日志,并查看 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:443" "172.217.20.74:443"
    EOF

    请注意日志中的 URL 路径,可以监视路径并根据它来应用访问策略。要了解有关 HTTP 出口流量的监控和访问策略 的更多信息,请查看归档博客之出口流量监控之日志

清除 TLS 原始数据到 Google Books 网络服务

$ kubectl delete serviceentry 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, 这是用于保护 Web 浏览器和 Web 服务器之间通信的相同机制 , TLS 应用于与外部服务的通信, 以验证外部服务器的身份并加密流量。

结论

在这篇博文中,我演示了 Istio 服务网格中的微服务如何通过 HTTPS 使用外部 Web 服务, 默认情况下, Istio 会阻止集群外主机的所有流量, 要启用此类流量,请使用 mesh-external,必须为服务网格创建 ServiceEntry , 可以通过 HTTPS 访问外部站点,当微服务发出 HTTPS 请求时,流量是端到端加密的,但是 Istio 无法监视 HTTP 详细信息, 例如请求的 URL 路径。当微服务发出 HTTP 请求时,Istio 可以监视请求的 HTTP 详细信息并强制执行基于 HTTP 的访问策略。 但是,在这种情况下,微服务和 sidecar 代理之间的流量是未加密的。在具有非常严格的安全要求的组织中, 可以禁止未加密的部分流量。

See also

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

在 Istio 中配置从网格内访问外部服务的流量路由。

描述出口网关如何配置对外部服务发起双向 TLS。

介绍如何为入口网关配置 SNI 直通。

Istio v1alpha3 路由 API 介绍,动机及其设计原则。

介绍更安全,低风险的部署和发布到生产。