使用外部 Web 服务

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

Jan 31, 2018 | By Vadim Eisenberg

在许多情况下,在 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 )来访问外部服务。

Zip
$ 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_:

Zip
$ 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 访问权限

ZipZip
$ 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, ENV['DO_NOT_ENCRYPT'] === 'true' ? 80:443)
...
unless ENV['DO_NOT_ENCRYPT'] === 'true' then
     http.use_ssl = true
end

当定义 WITH_ISTIO 环境变量时,在没有 SSL(普通 HTTP )的情况下请求会通过 80 端口执行。

我们将 details v2 的部署配置文件 的环境变量 DO_NOT_ENCRYPT 设置为 _“true”_。 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。

    Zip
    $ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo-details-v2.yaml@
    
  2. 将指向 details 微服务的流量定向到 _details v2_。

    Zip
    $ kubectl apply -f @samples/bookinfo/networking/virtual-service-details-v2.yaml@
    
  3. www.google.apis 创建网格外部 ServiceEntry,virtual service 将目标端口从 80 重写为 443,并执行 TLS 的 destination rule

    $ 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 # 访问 edition.cnn.com 时启动 HTTPS
    EOF
    
  4. 访问应用程序的网页,并验证显示的书籍详细信息没有错误。

  5. 开启 Envoy 访问记录功能

  6. 检查 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:80" "172.217.20.74:80"
    EOF
    

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

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

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

结论

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