写点什么

服务网格接口 SMI 规范解读

作者:Flomesh
  • 2022 年 5 月 16 日
  • 本文字数:7049 字

    阅读完需:约 23 分钟

服务网格接口 SMI 规范解读

服务网格接口(Service Mesh Interface,SMI)是针对在 Kubernetes 上运行的服务网格的规范。 它定义了可以由各种提供商实施的通用标准。 这样既可以实现最终用户的标准化,又可以实现服务网格技术提供商的创新。SMI 实现了灵活性和互操作性,并涵盖了最常见的服务网格功能。SMI 是服务网格规范的统称,实际上由四个组件组成:Traffic Access Control、Traffic Metrics、Traffic Specs 和 Traffic Split 。



SMI 在 2020 年 4 月以 sandbox 方式加入 CNCF,截至本文发出时其最新版本是 v0.6.0。

目标

SMI 的目标是提供一套通用的、可移植的 Service Mesh API 集,Kubernetes 用户可以以与提供商无关的方式使用它们。 这样,用户可以定义使用 Service Mesh 技术的应用程序,而不必与任何特定实现紧密绑定。将概念与实现隔离开来,像软件开发过去一直做的那样,将复杂的系统分层、抽象。


对于 SMI 项目来说,实现服务网格并不是其目标。 它仅仅定义通用规范。同样,定义服务网格的含义范围不是目标,而是通常有用的子集。如果 SMI 提供程序想要添加超出 SMI 规范的提供程序特定的扩展和 API,并受到欢迎。可见,随着时间的推移,随着越来越多的功能作为服务网格的一部分而被普遍接受,这些定义将迁移到 SMI 规范中。

组件

下面结合osm 项目中的 Bookstore Demo 来介绍 SMI 的各个组件。

流量规格

API Group: specs.smi-spec.ioAPI Version: v1alpha4


流量规格(Traffic Specs)定义了一组资源,用来描述流量的表现形式,与访问控制和其他策略结合来定义特定流量在服务网格中的行为。


在服务网格场景下,用户会使用多种不同的网络协议。目前主要是 HTTP,但是也会有其他协议。该规范中的每个资源都各自对应着一种协议。这就允许用户以特定协议的方式来定义流量。


流量规格的定义与任何应用程序或者其他资源无关,只是描述了经过网格的流量类型。通过被更高级的策略引用,比如访问控制、限流等。


值得注意的是,目前并没有关于 gRPC 的规格定义。

HTTPRouteGroup

这个资源用于描述 HTTP/1 和 HTTP/2 流量。它列出了应用程序可以服务的路由。


apiVersion: specs.smi-spec.io/v1alpha4kind: HTTPRouteGroupmetadata:  name: bookstore-service-routes  namespace: bookstorespec:  matches:  - name: books-bought    pathRegex: /books-bought    methods:    - GET    headers:    - "user-agent": ".*-http-client/*.*"    - "client-app": "bookbuyer"  - name: buy-a-book    pathRegex: ".*a-book.*new"    methods:    - GET  - name: update-books-bought    pathRegex: /update-books-bought    methods:    - POST
复制代码


在上面这个示例中,我们为服务 bookstore定义了名为 bookstore-service-routesHTTPRouteGRoup,其中有 3 个路由规则(match):


  • books-bought:匹配请求头 client-appbookbuyeruser-agent 符合正则 .*-http-client/*.* ,使用 GET 方法访问 /books-bought 路径的流量。

  • buy-a-book:匹配使用 GET 方法访问符合正则 .*a-book.*new 路径的流量。在 bookstore 服务中,只有一个路径符合:/buy-a-book/new

  • update-books-bought:匹配使用 POST 方法访问 bookstore 服务的 /update-books-bought 路径。


HTTP 请求头过滤器


这里的 3 个路由规则(条件),实际上都是通过 HTTP 协议的请求头信息来进行判断。这就像是一个 HTTP 请求头的过滤器,过滤出符合条件的请求。再通过与其他 SMI 组件的组合使用,来进行流量的管理。比如与后面要提到 TrafficSplit,来实现特定流量的转移。


请求头过滤器是一个键值对,键是 HTTP 请求头的名称,值便是该请求头匹配条件的正则表达式。


每个路由规则可以有多个请求头过滤器,通过 的方式组合;而多个路由规则之间则是 的方式。


Bookstore Demo 中的服务交互全都是基于 HTTP 协议的,而 SMI 的流量规范中还覆盖了其他协议。

TCPRoute

该资源用于描述一组 4 层 TCP 端口的流量。


kind: TCPRoutemetadata:  name: the-routesspec:  matches:    ports:    - 3306    - 6446
复制代码


上面的例子中,匹配了端口 33066446 的流量。假如没有指定任何端口,则意味着匹配 Kubernetes service 的所有端口。


kind: TCPRoutemetadata:  name: the-routesspec:  matches:    ports: []
复制代码

UDPRoute

这个资源用于描述一组端口的 4 层 UDP 流量,比如下面的定义中描述的是 989990 端口的 UDP 流量:


kind: UDPRoutemetadata:  name: the-routesspec:  matches:    ports:    - 989    - 990
复制代码


同样,假如不指定任何端口时会匹配所有 Kubernetes service 中的端口。

访问控制

Traffic Access Control 提供了用于定义应用访问控制策略的一组资源,在认证授权中属于授权的部分。而身份验证由底层实现处理并表示。


访问控制在 SMI 规范中使用加法的策略,默认情况下所有的流量都是被拒绝的。


API Group: access.smi-spec.ioAPI Version: v1alpha3Compatible with: specs.smi-spec.io/v1alpha4

TrafficTarget

TrafficTarget 将流量定义(规则)和用于定位 Pod 的身份标识关联起来。访问控制是通过引用的流量规格和源服务身份标识列表来实现的。


如果 Pod 的身份标识在列表中,该 Pod 向目标服务的某一个已定义的路由发起的请求是被允许的。假如 Pod 的身份标识不在列表中,或者访问的路由不在引用的流量规格的路由列表中,请求会被拒绝。


服务的身份标识,是通过 Kubernetes 的 service account 体现的。运行 Pod 时会为其指定 service account,这个 service acount 便是运行在 Pod 中的进程的身份标识,也是 Pod 的身份标识。


一个有效的 TrafficTarget 必须有一个目标、至少一个规则和至少一个源。


** 7 层的访问控制:**


kind: TrafficTargetapiVersion: access.smi-spec.io/v1alpha3metadata:  name: bookbuyer-access-bookstore-v1  namespace: bookstorespec:  destination:    kind: ServiceAccount    name: bookstore-v1    namespace: bookstore  rules:  - kind: HTTPRouteGroup    name: bookstore-service-routes    matches:    - buy-a-book    - books-bought  sources:  - kind: ServiceAccount    name: bookbuyer    namespace: bookbuyer
复制代码


在这个示例,定义了这样的访问策略:bookbuyer 命名空间下使用 service account bookbuyer 运行的 Pod(源)可以访问 bookstore 命名空间下使用 service account bookstore-v1 运行的 Pod (目标)的部分路由(路由规则),这些路由就是 bookstore-service-routes HTTPRouteGroup 下的 buy-a-bookbooks-bought。而 bookstore 的其他路由,包括 bookstore-service-routes 中定义的路由 update-books-bought 的访问都会被拒绝。


用表格直观的表示就是:



** 4 层的访问控制:**


kind: TCPRoutemetadata:  name: tcp-portsspec:  matches:    ports:    - 8301    - 8302    - 8300---kind: UDPRoutemetadata:  name: udp-portsspec:  matches:    ports:    - 8301    - 8302---kind: TrafficTargetmetadata:  name: protocal-specificspec:  destination:    kind: ServiceAccount    name: server    namespace: default  rules:  - kind: TCPRoute    name: tcp-ports  - kind: UDPRoute    name: udp-ports  sources:  - kind: ServiceAccount    name: client    namespace: default
复制代码


同样也可以做 4 层的访问控制,比如上面的示例中展示了:在 default 命名空间下,使用 service account client 运行的 Pod 允许访问使用 service account server830183028300 端口。83018302 允许 TCP、UDP 的流量;而 8300 只允许 TCP 的流量,UDP 的流量都会被拦截。

流量分流

API Group: split.smi-spec.ioAPI Version: v1alpha4Compatible with: specs.smi-spec.io/v1alpha4


这个规范中定义了资源 TrafficSplit,用来在多个服务间调整流量的百分比。其属于客户端的策略,将出站流量发送给不同的目标。通过 TrafficSplit 可以实现应用新版本的金丝雀发布,也是服务网格中最重要的功能。


这个规范将一个根服务与多个后端服务关联起来,。这个根服务 spec.service 是用来进行交互的 FQDN(完全限定域名),假如没有 sidecar 代理,客户端同样可以通过其访问目标服务。

TrafficSplit

apiVersion: split.smi-spec.io/v1alpha2kind: TrafficSplitmetadata:  name: bookstore-split  namespace: bookstorespec:  service: bookstore  backends:  - service: bookstore-v1    weight: 50  - service: bookstore-v2    weight: 50
复制代码


在 Bookstore demo 中,有 1 个名为 bookstore 的 Kubernetes service 作为 TrafficSplit 的根服务,其他的服务(如 bookbuyer)通过 bookstore.bookstore.svc.cluster.local:14001 来访问 bookstore 的路由。


同时还部署了 bookstore-v1bookstore-v2 两个 Deployment,以及同名的 service。这两个 service 便作为根服务的后端。


从 上面 TrafficSplit 来看访问 bookstore 的流量,按照 1:1 的比例分发给 bookstore-v1bookstore-v2 两个后端(实际每个后端都可以有多个副本)。请求的分发首先是在 service 之间按照权重进行分流,具体到特定的 service 之后便是在该 service 的多个副本之间均衡(Deployment 的多个 Pod 之间权重相同)。



这里示例将所有访问 bookstore 的请求无差别地进行分流,即所有的路由都是按照权重进行分流。TrafficSplit 支持更高级的分流,可以为不同路由指定分流的方式。我们可以通过 matches 字段指定需要进行分流的路由,使用新增的 canary-test HTTPRouteGroup


apiVersion: split.smi-spec.io/v1alpha2kind: TrafficSplitmetadata:  name: bookstore-canary  namespace: bookstorespec:  service: bookstore  backends:  - service: bookstore-v2    weight: 1  matches:  - kind: HTTPRouteGroup     name: canary-test---kind: HTTPRouteGroupmetadata:  name: canary-testmatches:- name: buy-a-book  pathRegex: ".*a-book.*new"
复制代码


上面我们增加了一个新的 TrafficSplit 定义 bookstore-canary,将所有访问 bookstore/buy-a-book/new 请求全都分流到 bookstore-v2

金丝雀发布流程

假如现在 bookstore 运行的是 v1 的版本,系统中有如下资源:


  • 有着app: bookstoreversion: v1标签的 Deployment bookstore-v1

  • 名为 bookstore 的 service,使用选择器 app: bookstore

  • 名为 bookstore-v1 的 service,使用选择器 app: bookstoreversion: v1

  • 客户端(如 bookbuyer)通过 FQDN bookstore 来访问 bookstore 服务


即将要发布的版本 v2,为了降低发布的风险我们需要逐步地将流量发送给新的版本来验证功能。


  • 发布 v2 之前,需要先创建一个名为 bookstore-rolloutTrafficSplit


apiVersion: split.smi-spec.io/v1alpha2kind: TrafficSplitmetadata:  name: bookstore-rollout  namespace: bookstorespec:  service: bookstore  backends:  - service: bookstore-v1    weight: 100  - service: bookstore-v2    weight: 0
复制代码


  • 发布 v2 版本的 bookstore,创建 Deployment bookstore-v2,使用标签 app: bookstoreversion: v2

  • 创建名为 bookstore-v2 的 service,使用选择器 app: bookstoreversion: v2


此时 bookstore-v2 还不会获得任何流量。


  • 待 bookstore-v2 成功启动之后,调整 bookstore-rolloutbookstore-v2 的权重


apiVersion: split.smi-spec.io/v1alpha2kind: TrafficSplitmetadata:  name: bookstore-rollout  namespace: bookstorespec:  service: bookstore  backends:  - service: bookstore-v1    weight: 90  - service: bookstore-v2    weight: 10
复制代码


  • 验证 v2 版本的功能,并逐步增加 bookstore-v2 的权重,减少 bookstore-v1 的权重。

  • 直至所有的流量都被发送 v2


apiVersion: split.smi-spec.io/v1alpha2kind: TrafficSplitmetadata:  name: bookstore-rollout  namespace: bookstorespec:  service: bookstore  backends:  - service: bookstore-v1    weight: 0  - service: bookstore-v2    weight: 100
复制代码


  • 功能验收完成,删除 bookstore-v1 的 Deployment 和 Service

  • 删除 bookstore-rollout TrafficSplit


这样我们便完成了 bookstore 的版本升级,这是最简单的金丝雀发布用来展示如何使用 TrafficSplit 来实现流量可控的滚动升级。现实场景下,还需要结合路由来针对特定的流量进行分流,这种方式更加高级且高效。


可能会有同学提出疑问,为什么在发布 v2 之前要先创建 TrafficSplit?这是因为 service bookstore 的标签选择器,可以选择 v1 和 v2 的 Pod。如果不先配置分流,Kubernetes service 会将流量发送给 v2 的 Pod。

流量指标

API Group: metrics.smi-spec.ioAPI Version: v1alpha1


该规范描述了一种资源,该资源为消费 HTTP 流量相关指标的工具提供了一个通用的集成点。在 CLI 工具、HPA 缩放、自动金丝雀更新所使用的即时指标上,它遵循 metrics.k8s.io 模式。很多实现都是使用 Prometheus 做存储,这里仅标准化了指标/标签命名。


指标是与资源关联的,这种资源可以是 pod、namespace、deployment 或者 service。所有指标都与生成或服务于测量流量的 Kubernetes 资源相关联。Pod 是其中最小粒度,通常都会将 pod 的指标聚合后作为整个应用的指标。比如,在金丝雀发布时将 deployment 的成功率指标进行聚合。


除了资源以外,指标还包括了 edges,其表示流量的源头和目的地。edge 限制了指标是 resourceedge.resource 间的流量。


通过 API 来获取指标的方法有如下几种:


  • 支持的资源(pod、namespace、deployment 等)作为 APIResourceList 的一部分,支持 listget 操作。

  • 对于支持的资源,可以使用标签选择器来过滤。

  • 子资源允许查询与特定资源关联的所有 edges。

TrafficMetrics

kind: TrafficMetrics# See ObjectReference v1 core for full specresource:  name: foo-775b9cbd88-ntxsl  namespace: foobar  kind: Podedge:  direction: to  side: client  resource:    name: baz-577db7d977-lsk2q    namespace: foobar    kind: Podtimestamp: 2019-04-08T22:25:55Zwindow: 30smetrics:- name: p99_response_latency  unit: seconds  value: 10m- name: p90_response_latency  unit: seconds  value: 10m- name: p50_response_latency  unit: seconds  value: 10m- name: success_count  value: 100- name: failure_count  value: 100
复制代码


在上面的示例中,展示了从 pod foo-775b9cbd88-ntxsl 到 pod baz-577db7d977-lsk2q 的延迟百分比分布、成功/失败数指标。


注意:延迟的默认单位是秒,值10m表示0.1,即 0.1 秒。

TrafficMetricsList

TrafficMetricsList 展示的是与指定资源相关的一组 TrafficMetrics。比如,通过类型来查询所有 Pod 相关的指标:


kind: TrafficMetricsListresource:  kind: Poditems:...
复制代码


或者在类型之上,通过标签选择器对 Pod 进行过滤:


kind: TrafficMetricsListresource:  kind: Podselector:  matchLabels:    app: fooitems:...
复制代码

Kubernetes API

通过 APIService 暴露 metrics.smi-spec.io API。


apiVersion: apiregistration.k8s.io/v1kind: APIServicemetadata:  name: v1alpha1.metrics.smi-spec.iospec:  group: metrics.smi-spec.io/v1alpha1  service:    name: mesh-metrics    namespace: default  version: v1alpha1
复制代码

使用场景:自动金丝雀发布

创建一个金丝雀发布的控制器,可以执行前面提到的“金丝雀发布流程”。


唯一不同的是,控制器会通过 metrics.smi-spec.io API 监控新版本的成功率指标,并自动增加新版本的权重占比。此外,新版本 deployment 的部署和旧版本 deployment 的下线,都是由控制器自动完成的。

实现示例


与很多 metrics 指标监控一样,Prometheus 作为指标的存储,会周期性地从网格 sidecar 拉取指标。途中唯一需要实现的是 Traffic Metrics Shim,它的作用是将 Kubernetes 原生 API 映射到 Prometheus 的查询(PromSQL)。这是服务网格的实现细节。


  1. 发起 metrics.smi-spec.io API 的查询请求:


kubectl get --raw /apis/metrics.smi-spec.io/v1alpha1/namespaces/default/deployments/
复制代码


  1. Kubernetes API Server 将请求转发给 Traffic Metrics Shim

  2. Shim 会将请求映射成 Prometheus 的查询,这里可能会转换成多个 Prometheus 请求:


sum(requests_total{namespace='default',kind='deployment'}) by (name, success)
复制代码


  1. 收到 Prometheus 的查询结果后,shim 会将内容转换成 TrafficMetrics 对象。

总结

SMI 的出现,为“混乱的”服务网格生态提供了规范性的指引。用户可以在不同实现间低成本地切换,避免厂商绑定。


接下来,在一下篇中我们会介绍 SMI 的实现之一:开放服务网格(Open Service Mesh, OSM)。


OSM 是一个轻量级可扩展的云原生服务网格,是简单、完整且独立的服务网格解决方案,它允许用户统一管理、保护并获得针对高度动态微服务环境的开箱即用的可观察性功能。OSM 运行在 Kubernetes 之上控制平面实现了 xDS API 并配置了 SMI API,为应用程序注入 Envoy sidecar 容器作为代理,通过 SMI Spec 来引用网格中的服务。


虽然默认情况下使用 Envoy 作为数据平面,但是设计上提供接口的抽象,支持兼容 xDS 的代理,甚至其他的代理。


目前 OSM 最新版本对 SMI 的支持如下:


发布于: 刚刚阅读数: 2
用户头像

Flomesh

关注

微信订阅号:flomesh 2022.04.07 加入

一站式云原生应用流量管理供应商 官网:https://flomesh.io

评论

发布
暂无评论
服务网格接口 SMI 规范解读_云原生_Flomesh_InfoQ写作社区