开放服务网格 Open Service Mesh 如何开放?

语言: CN / TW / HK

TL;DR

本文从服务网格发展现状、到 Open Service Mesh 源码,分析开放服务网格中的开放是什么以及如何开放。笔者总结其开放体现在以下几点:

  1. 资源提供者(Provider)接口和资源的重新封装: 通过资源提供者接口抽象计算平台的资源,并封装成平台、代理无关的结构化类型 ,并由统一的接口 MeshCataloger 对外提供访问。虽然目前只有 Kubernetes 相关资源的 Provider 实现,但是通过抽象出的接口,可以兼容其他平台,比如虚拟机、物理机。
  2. 服务网格能力接口:对服务网格能力的抽象,定义服务网格的基础功能集。

  3. 代理控制面接口:这一层与反向代理,也就是 sidecar 的实现相关。实际上是反向代理所提供的接口,通过对接口的实现,加上 MeshCataloger 对资源的访问,生成并下发代理所需的配置。

背景

服务网格是什么

服务网格是在 2017年由 Buoyant 的 Willian Morgan 在 What’s a service mesh? And why do I need one? [1] 给出了解释。

服务网格是处理服务间网络通信的基础设施组件,旨在从平台层面提供可观性、安全以及可靠性特性,以进程外的方式提供原本由部分应用层逻辑承载的基础能力,真正实现与业务逻辑的分离。典型的实现是与应用程序一起部署的可扩展网络代理(通常称为 sidecar 模型),来代理服务间的网络通信,也是服务网格特性的接入点。这些代理就组成了服务网格的 数据平面 ,并由 控制平面 进行统一的管理。

图片来自 Pattern: Service Mesh [2]

服务网格现状

过去几年 Istio 有着成为服务网格事实标准的趋势,但时至今日,各种各样的服务网格产品如雨后春笋般层出不穷。CNCF 发布的 2021 Cloud Native survey results [3] 中不难看出,这些网格产品也慢慢被市场所接受,并大有超越 Istio 的态势(部分地区)。

打开 CNCF 的服务网格全景图 [4] ,会发现有不少服务网格的产品。实际上还有很多产品没有列出,比如 HashiCorp Consul Connect、OpenShift Service Mesh、Nginx Service Mesh、Kong Mesh、SOFAMesh 等等,当然还有我司的 Flomesh。此外还有一些云厂商基于开源的网格产品。

这些产品有些使用相同的数据平面,也有些不同,但控制平面各不相同。众多产品为大家提供了更多选择的同时,也由于各个产品间有着很强的隔离性,阻碍了生态系统的发展。这导致很难从一个实现切换到另一个,从控制面到到数据面,以及为其开发的管理后台都有重新开发的成本。此外,还有用户使用习惯的改变,无法做到透明无感知。

针对这种诉求,按照“惯例”就是借助标准 接口 来进行抽象,提供实现的互通性。比如容器网络、运行时、存储有 CNI、CRI、CSI 接口,服务网格也有其抽象接口 Service Mesh Interface [5] (简称SMI),以及其实现也就是今天的主角 Open Service Mesh [6] (简称OSM)。

SMI 与 OSM 简介

在 CNCF 的服务网格全景图中可以看到 SMI 和 OSM。

service mesh landscape

SMI 是什么

SMI 是服务网格的规范,重点关注在 Kubernetes 上运行的服务网格。它定义了一个可以由各种供应商使用的通用标准。允许最终用户的标准化和服务网格技术提供商的创新。SMI 实现了灵活性和互操作性,并涵盖了最常见的服务网格功能。

SMI 提供了 Kubernetes 服务网格的标准接口、常见服务网格场景的基本功能集、支持服务网格新功能的灵活性,以及服务网格技术生态的创新空间。

SMI 的目标是将概念与实现隔离开来,像软件开发过去一直做的那样,将复杂的东西分层抽象。

SMI 规范 [7] 的最新版本是 0.6.0,覆盖了常见的流量访问控制、遥测、管理等基础服务网格能力。与规范对应的是 API,各个网格供应商基于该 API 进行实现。

OSM 是什么

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

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

SMI 规范

SMI 规范为常见服务网格能力提供了一套规范:

  • 流量策略:跨服务应用身份和传输加密等策略。

  • 流量遥测:捕获关键指标,如错误率和延迟。

  • 流量管理:在不同服务之前转移流量。

smi-spec

流量策略

这个规范是用来指定应用的流量表现形式,并与访问控制策略结合来定义特定流量在服务网格中的行为。

比如下面的定义中, /metrics 端点仅对外提供 GET 方式访问,比如 Prometheus。而其他端点,支持所有方式。

kind: TCPRoute
metadata:
name: the-routes
spec:
matches:
ports:
- 8080
---
kind: HTTPRouteGroup
metadata:
name: the-routes
spec:
matches:
- name: metrics
pathRegex: "/metrics"
methods:
- GET
- name: everything
pathRegex: ".*"
methods: ["*"]

下面的定义,则在前面的基础上,允许 Prometheus(使用 prometheus ServiceAccount 部署)访问所有 service-a ServiceAccount 的应用的 /metrics 端点。

kind: TrafficTarget
metadata:
name: path-specific
namespace: default
spec:
destination:
kind: ServiceAccount
name: service-a
namespace: default
rules:
- kind: TCPRoute
name: the-routes
- kind: HTTPRouteGroup
name: the-routes
matches:
- metrics
sources:
- kind: ServiceAccount
name: prometheus
namespace: default

流量管理

访问控制以外的流量管理,更多是体现在流量的拆分上。流量拆分 TrafficSplit 用于实现将流量按百分比拆分到同一应用程序的不同版本。

下面的定义中,将来自Firefox 请求全都路由到 website 的 v2 版本,实现金丝雀发布。在实际场景中的操作流程,参考 SMI 的示例 [10]

kind: TrafficSplit
metadata:
name: ab-test
spec:
service: website
matches:
- kind: HTTPRouteGroup
name: ab-test
backends:
- service: website-v1
weight: 0
- service: website-v2
weight: 100
---
kind: HTTPRouteGroup
metadata:
name: ab-test
matches:
- name: firefox-users
headers:
user-agent: ".*Firefox.*"

流量遥测

流量遥测还是处于很早期的版本 v1alpha1

该规范描述了一种资源,该资源为工具提供了一个通用集成点,这些工具可以通过使用与 HTTP 流量相关的指标而受益。对于即时指标它遵循 metrics.k8s.io 的模式,这些指标可被 CLI 工具、HPA 扩展或自动化金丝雀更新使用。

比如下面定义了从 Pod foo-775b9cbd88-ntxsl 到 Pod baz-577db7d977-lsk2q 的延迟以及成功率指标。

kind: TrafficMetrics
# See ObjectReference v1 core for full spec
resource:
name: foo-775b9cbd88-ntxsl
namespace: foobar
kind: Pod
edge:
direction: to
side: client
resource:
name: baz-577db7d977-lsk2q
namespace: foobar
kind: Pod
timestamp: 2019-04-08T22:25:55Z
window: 30s
metrics:
- 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

OSM 的设计

osm-components

OSM 的控制层面包含了五个核心组件:

  • Proxy Control Plane:代理控制面在操作服务网格中起着关键作用,所有以 sidecar 方式运行的代理,都会与其建立连接,并不断地接受配置的更新。 这个组件实现反向代理所需的接口 。目前 OSM 使用 Envoy 作为其默认的代理实现,因此这个组件实现了 Envoy 的 xDS API。

  • Certificate Manger:证书管理器组件为网格中的服务提供 TLS 证书,这些证书用于使用 mTLS 建立和加密服务之间的连接。

  • Endpoints Providers:端点提供者是与计算平台(Kubernetes 集群、云主机、本地机器)交互的一系列组件的统称。端点提供者将服务名解析为 IP 地址。

  • Mesh Specification:网格规范是现有 SMI 规范 [11] 组件的封装。该组件抽象了为 YAML 定义的特定存储。这个模块实际上是 SMI Spec 的 Kubernetes informers [12] 的封装。

  • Mesh Catalog:是 OSM 的核心组件,它将其他组件的输出组装成新的结构。新的结构可以转换为代理配置并通过代理控制面分发给所有的代理。

这几个抽象的组件,都有接口与其一一对应,

  • Proxy Control Plane: Envoy `AggregatedDiscoveryServiceServer` [13]

  • Certificate Manger: `certificate.Manager` [14]

  • Endpoints Providers: `endpoint.Provider` [15]

  • Mesh Specification: `smi.MeshSpec` [16]

  • Mesh Catalog: `catalog.MeshCataloger` [17]

源码分析

代理控制平面接口

基于 Evnoy 代理的服务网格实现,代理控制面都要实现 `AggregatedDiscoveryServiceServer` 接口 [18] 。目前 OSM 实现的是 V3 版本的接口。深入到代码中,OSM 的 `ads.Server` [19] 实现了 AggregatedDiscoveryServiceServer 接口。

ads.Server 的定义中可以看到其他组件接口 catalog.MeshCatalogercertificate.Manager 的身影。

// Server implements the Envoy xDS Aggregate Discovery Services
type Server struct {
catalog catalog.MeshCataloger
proxyRegistry *registry.ProxyRegistry
xdsHandlers map[envoy.TypeURI]func(catalog.MeshCataloger, *envoy.Proxy, *xds_discovery.DiscoveryRequest, configurator.Configurator, certificate.Manager, *registry.ProxyRegistry) ([]types.Resource, error)
xdsLog map[certificate.CommonName]map[envoy.TypeURI][]time.Time
xdsMapLogMutex sync.Mutex
osmNamespace string
cfg configurator.Configurator
certManager certificate.Manager
ready bool
workqueues *workerpool.WorkerPool
kubecontroller k8s.Controller

// ---
// SnapshotCache implementation structrues below
cacheEnabled bool
ch cachev3.SnapshotCache
srv serverv3.Server
// When snapshot cache is enabled, we (currently) don't keep track of proxy information, however different
// config versions have to be provided to the cache as we keep adding snapshots. The following map
// tracks at which version we are at given a proxy UUID
configVerMutex sync.Mutex
configVersion map[string]uint64

msgBroker *messaging.Broker
}

此处,不得不提一下 messaging.Broker ,OSM 控制平面的“消息总线”。集群中,任何资源(K8s 原生资源、OSM 定义资源、SMI 资源)的变更,都会以事件的方式发布到消息总线。

特定事件消息的订阅方在接收到事件后就会执行特定的逻辑。

这里 ads.Server 在启动 grpc 服务后,便会订阅 ProxyUpdate 事件,收到事件会触发对应的逻辑:通过 catalog.MeshCataloger 的实现获取结构化的数据,进而转换成代理的配置并发送给代理。

在 OSM 中 ProxyUpdate 事件是一些事件的集合,这些事件最终都会被 “当作” ProxyUpdate 事件进行处理。

Proxy-Update

网格目录接口

Mesh Catalog 的接口 catalog.MeshCataloger ,定义了一些用于生成结构化数据的方法。这些结构化的数据,是在 K8s 原生资源、SMI 自定义资源的基础上的封装。作为底层资源和代理控制面的中间层,对上隔离了底层资源的实现,对下统一了资源的对外暴露形式。

// MeshCataloger is the mechanism by which the Service Mesh controller discovers all Envoy proxies connected to the catalog.
type MeshCataloger interface {
// ListOutboundServicesForIdentity list the services the given service identity is allowed to initiate outbound connections to
ListOutboundServicesForIdentity(identity.ServiceIdentity) []service.MeshService

// ListOutboundServicesForMulticlusterGateway lists the upstream services for the multicluster gateway
ListOutboundServicesForMulticlusterGateway() []service.MeshService

// ListInboundServiceIdentities lists the downstream service identities that are allowed to connect to the given service identity
ListInboundServiceIdentities(identity.ServiceIdentity) []identity.ServiceIdentity

// ListOutboundServiceIdentities lists the upstream service identities the given service identity are allowed to connect to
ListOutboundServiceIdentities(identity.ServiceIdentity) []identity.ServiceIdentity

// ListServiceIdentitiesForService lists the service identities associated with the given service
ListServiceIdentitiesForService(service.MeshService) []identity.ServiceIdentity

// ListAllowedUpstreamEndpointsForService returns the list of endpoints over which the downstream client identity
// is allowed access the upstream service
ListAllowedUpstreamEndpointsForService(identity.ServiceIdentity, service.MeshService) []endpoint.Endpoint

// GetIngressTrafficPolicy returns the ingress traffic policy for the given mesh service
GetIngressTrafficPolicy(service.MeshService) (*trafficpolicy.IngressTrafficPolicy, error)

// ListInboundTrafficTargetsWithRoutes returns a list traffic target objects composed of its routes for the given destination service identity
ListInboundTrafficTargetsWithRoutes(identity.ServiceIdentity) ([]trafficpolicy.TrafficTargetWithRoutes, error)

// GetEgressTrafficPolicy returns the Egress traffic policy associated with the given service identity.
GetEgressTrafficPolicy(identity.ServiceIdentity) (*trafficpolicy.EgressTrafficPolicy, error)

// GetKubeController returns the kube controller instance handling the current cluster
GetKubeController() k8s.Controller

// GetOutboundMeshTrafficPolicy returns the outbound mesh traffic policy for the given downstream identity
GetOutboundMeshTrafficPolicy(identity.ServiceIdentity) *trafficpolicy.OutboundMeshTrafficPolicy

// GetInboundMeshTrafficPolicy returns the inbound mesh traffic policy for the given upstream identity and services
GetInboundMeshTrafficPolicy(identity.ServiceIdentity, []service.MeshService) *trafficpolicy.InboundMeshTrafficPolicy
}

接口的实现 `catalog.MeshCatalog` [20] ,也是通过几个接口获取底层的资源:

  • endpoint.Providerendpoint.Endpoint 抽象资源的提供者,在目前 OSM 版本中提供获取 Kubernetes Endpoint 的实现。
  • service.Providerservice.MeshService 抽象资源的提供者,目前同样是提供获取 Kubernetes Service 的实现。
  • smi.MeshSpec :故名思义针对是 SMI 自定义资源,直接使用 SMI 的定义,并没有重新抽象封装。
  • k8s.Controller :用于获取 Kubernetes 原生资源,比如 ServiceAcount、Namespace、Pods 等。
  • policy.Controller
    Egress
    IngressBackend
    
// MeshCatalog is the struct for the service catalog
type MeshCatalog struct {
endpointsProviders []endpoint.Provider
serviceProviders []service.Provider
meshSpec smi.MeshSpec
certManager certificate.Manager
configurator configurator.Configurator

// This is the kubernetes client that operates async caches to avoid issuing synchronous
// calls through kubeClient and instead relies on background cache synchronization and local
// lookups
kubeController k8s.Controller

// policyController implements the functionality related to the resources part of the policy.openrservicemesh.io
// API group, such as egress.
policyController policy.Controller
}

参考资料

[1]

What’s a service mesh? And why do I need one?: https://buoyant.io/what-is-a-service-mesh/

[2]

Pattern: Service Mesh: https://philcalcado.com/2017/08/03/pattern_service_mesh.html

[3]

2021 Cloud Native survey results: https://www.cncf.io/wp-content/uploads/2022/02/CNCF-Annual-Survey-2021.pdf

[4]

CNCF 的服务网格全景图: https://landscape.cncf.io/card-mode?category=service-mesh&grouping=category

[5]

Service Mesh Interface: https://smi-spec.io/

[6]

Open Service Mesh: https://openservicemesh.io/

[7]

SMI 规范: https://github.com/servicemeshinterface/smi-spec

[8]

SMI Spec: https://smi-spec.io/

[9]

Envoy: https://www.envoyproxy.io/

[10]

SMI 的示例: https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha4/traffic-split.md#workflow

[11]

SMI 规范: https://github.com/deislabs/smi-spec

[12]

SMI Spec 的 Kubernetes informers: https://github.com/deislabs/smi-sdk-go

[13]

Envoy AggregatedDiscoveryServiceServer : https://github.com/envoyproxy/go-control-plane/blob/main/envoy/service/discovery/v3/ads.pb.go#L276

[14]

certificate.Manager : https://github.com/openservicemesh/osm/blob/main/pkg/certificate/types.go#L59

[15]

endpoint.Provider : https://github.com/openservicemesh/osm/blob/main/pkg/endpoint/types.go#L16

[16]

smi.MeshSpec : https://github.com/openservicemesh/osm/blob/main/pkg/smi/types.go#L48

[17]

catalog.MeshCataloger : https://github.com/openservicemesh/osm/blob/main/pkg/catalog/types.go#L41

[18]

AggregatedDiscoveryServiceServer 接口: https://github.com/envoyproxy/go-control-plane/blob/main/envoy/service/discovery/v3/ads.pb.go#L276

[19]

ads.Server : https://github.com/openservicemesh/osm/blob/main/pkg/envoy/ads/types.go#L29

[20]

catalog.MeshCatalog : https://github.com/openservicemesh/osm/blob/main/pkg/catalog/types.go#L24