使用外部 Web 服务

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

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

在许多情况下,并非所有基于微服务的应用程序都位于服务网格中。有时,基于微服务的应用程序使用驻留在网格外部的遗留系统提供的功能。您可能希望逐步将这些系统迁移到服务网格。在这些系统迁移之前,必须由网格内的应用程序访问它们。在其他情况下,应用程序使用第三方提供的 Web 服务。

在本博文中,我修改了Istio Bookinfo 示例应用程序以从外部 Web 服务(Google 图书 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 更改为默认阻止出站策略

具有对 Google 图书 Web 服务的 HTTPS 访问权限的 Bookinfo

部署details微服务的新版本v2,该版本从Google 图书 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 图书 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 图书 Web 服务的流量。默认情况下,Istio sidecar 代理(Envoy 代理阻止所有到集群外部目标的流量。要启用此类流量,您必须定义一个网格外部服务条目

启用对 Google 图书 Web 服务的 HTTPS 访问权限

不用担心,定义一个网格外部服务条目并修复您的应用程序。您还必须定义一个虚拟服务,以便通过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 运营商可以动态决定允许微服务访问哪些域。他们可以动态启用和禁用对外部域的流量,而无需重新部署微服务。

对 Google 图书 Web 服务的 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。在该 URL 中,图书 API由路径段/books指定,ISBN编号由路径段/volumes?q=isbn:0486424618指定。但是,在 HTTPS 中,所有 HTTP 详细信息(主机名、路径、标头等)都已加密,sidecar 代理无法执行此类监控和策略执行。Istio 只能通过SNI服务器名称指示)字段(在本例中为www.googleapis.com)了解加密请求的服务器名称。

要允许 Istio 根据 HTTP 详细信息执行出站请求的监控和策略执行,微服务必须发出 HTTP 请求。然后,Istio 打开到目标的 HTTPS 连接(执行 TLS 发起)。根据微服务是在 Istio 服务网格内还是外运行,必须以不同的方式编写或配置微服务的代码。这与 Istio 的设计目标即最大化透明度相矛盾。有时您需要做出妥协……

下图显示了将 HTTPS 流量发送到外部服务的两种选项。在顶部,微服务发送常规的 HTTPS 请求,端到端加密。在底部,相同的微服务在 Pod 内部发送未加密的 HTTP 请求,这些请求会被 sidecar Envoy 代理拦截。sidecar 代理执行 TLS 发起,因此 Pod 和外部服务之间的流量是加密的。

HTTPS traffic to external services, with TLS originated by the microservice vs. by the sidecar proxy
到外部服务的 HTTPS 流量,由微服务发起的 TLS 与由 sidecar 代理发起的 TLS

以下是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环境变量,则请求将不使用 SSL(纯 HTTP)发送到端口 80。

您可以在details v2 的 Kubernetes 部署规范container部分)中将DO_NOT_ENCRYPT环境变量设置为“true”

env:
- name: DO_NOT_ENCRYPT
  value: "true"

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

带有对 Google 图书 Web 服务的 TLS 发起的 Bookinfo

  1. 部署details v2的版本,该版本向Google 图书 API发送 HTTP 请求。DO_NOT_ENCRYPT变量在bookinfo-details-v2.yaml中设置为 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 代理的日志并查看 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 出站流量的监控和访问策略,请查看此博文

对 Google 图书 Web 服务的 TLS 发起的清理

压缩压缩
$ 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无关。外部服务的 TLS 发起将有效,无论是否启用了 Istio 双向 TLS。双向 TLS 保护服务网格内部的服务到服务通信的安全,并为每个服务提供强大的身份。本博文中访问的外部服务使用单向 TLS,这与用于保护 Web 浏览器和 Web 服务器之间通信的机制相同。将 TLS 应用于与外部服务的通信以验证外部服务器的身份并加密流量。

结论

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

分享此文章