使用 Go 重新实现一套 Service Mesh

更新时间 7/12/2023

本文介绍了 Service Mesh (服务网格)的发展现状及其优越性和局限性,并阐述 Apache APISIX 的创始团队 API7.ai 用 Go 语言重新实现 Service Mesh 的原因以及具体实现方法。Amesh 是 Apache APISIX 的服务网格库,旨在将 Service Mesh 和 API Gateway 结合,提供一个统一的控制平面来管理微服务的流量和 API 的访问控制。本文对 Amesh 的部分功能实现原理进行了详细说明,主要包括 Amesh 如何让配置在 APISIX 生效、Amesh 如何拦截流量、Amesh 如何扩展 APISIX 原生能力三个方面。

作者张晋涛,API7.ai 云原生技术专家,Apache APISIX PMC,Kubernetes Ingress-NGINX maintainer,Microsoft MVP,《K8S 生态周报》的维护者。 联合作者曾琦,API7.ai 全球增长实习生。

Service Mesh 现状

云原生正在推动数字化转型。但越来越多的应用程序和服务使用不同的技术堆栈进行部署,这对系统的交付和管理性能提出了更高的要求。Service Mesh 提供了一个解决方案——它创建了一个专用层,用于处理服务之间的通信,以确保服务的一致性和可靠性。

云原生计算基金会 CNCF 在 2021 年底对云原生社区进行了一项微型调查,以了解各组织采用 Service Mesh 的具体情况。在 253 名受访者中,70% 所在的组织在生产或开发环境中采用了 Service Mesh,19% 正在评估。Service Mesh 方案百花齐放,LinkerdIstio 占据了主要市场。

cncf

云原生应用公司 Solo 对 2022 年 Service Mesh 的使用情况进行了调查,并发布年度调查报告。该报告深入探讨了各大组织采用微服务、使用 Kubernetes 和 Istio 的情况。调查发现,89% 的组织称使用 Service Mesh 对应用程序的可靠性产生了非常积极的影响。近一半的公司(49%)称其正在使用 Service Mesh,38% 的公司正在对 Service Mesh 进行评估。调查结果表明,许多组织正在采用微服务和 Service Mesh,以提高应用程序的可靠性和效率。

solo

Service Mesh 正在被越来越多的组织采纳或评估。然而,在 Service Mesh 方案采纳过程中也产生了诸多挑战,例如:

挑战类型挑战类别占比
工程专业知识和经验短缺非技术挑战47%
架构和技术复杂性非技术挑战41%
缺乏指导、蓝图和最佳实践非技术挑战36%
集成技术挑战32%
可靠性和一致性技术挑战26%
定义策略技术挑战22%
监控和跟踪技术挑战22%

为什么要重新实现 Service Mesh

背景

API7.ai 是一家提供 API 全生命周期管理服务和产品的公司,旨在为企业提供高效、安全、可靠的 API 管理解决方案。作为 Apache APISIX 的创始团队,API7.ai 致力于将 API 管理推向更高的境界,提升企业的 API 治理能力。

Apache APISIX 是 Apache 基金会的顶级开源项目,是一款高性能易扩展的云原生 API 网关。与传统的 API 网关相比,Apache APISIX 具有更高的性能和更好的扩展性,可以满足企业对于 API 管理的不断增长的需求。

api7

重新实现 Service Mesh 的原因

重新实现 Service Mesh 的原因是为了解决现有 Service Mesh 实现的一些问题:

  • Envoy 进行二次开发的复杂度较高,使得开发和维护 Service Mesh 变得复杂和困难

    相比之下,Apache APISIX 可以使用任意语言进行插件开发,并且包含近 100 种开箱即用的插件,从而可以更轻松地实现 API 网关和 Service Mesh 的常见功能,简化复杂度。

  • 引入 Service Mesh 后,网络损耗是一个需要考虑的问题

    Apache APISIX 的性能更优,可以降低引入 Istio Service Mesh 后的网络损耗。与 Istio 的数据面 Envoy 相比,Apache APISIX 的数据面处理速度更快,可以更快地响应客户端请求,并减少不必要的网络开销。这对于需要高性能的企业来说尤为重要。

  • 构建基于 Apache APISIX 的 Service Mesh 生态是一个自然的发展方向

    Apache APISIX 已经拥有大量国内外用户,从 API 网关到 Service Mesh 是用户的诉求。通过使用 Apache APISIX 作为 Service Mesh 的数据面,可以更好地管理和控制微服务架构中的 API,提高服务的可观察性、可靠性和安全性。同时,Apache APISIX 生态系统的不断发展也将为用户提供更多的选择和支持,促进 Service Mesh 技术的发展和应用。

  • 提供统一技术栈的解决方案,满足客户需求

    通过重新实现 Service Mesh,API7.ai 可以提供一种集成度更高、性能更优、可扩展性更强的解决方案,帮助客户在 API 管理方面取得更大的成功。同时,重新实现 Service Mesh 还可以更好地支持企业的多云和混合云场景,提高企业的 API 治理能力,并为未来的发展留下更大的空间。

为什么用 Go 实现 Service Mesh

选择 Go 语言实现 Service Mesh 有以下几个方面的优势:

  1. 广泛的库和工具支持:

    Go 语言拥有丰富的标准库和第三方库,可以方便地实现 Service Mesh 的核心功能,如流量管理、负载均衡、服务发现等。同时,很多知名的开源项目也是使用 Go 语言实现的,如 Kubernetes、Prometheus、Grafana、Jaeger 等,可以方便地集成到 Service Mesh 中,提供完整的监控、追踪和日志功能。

  2. 易于部署:

    Go 编译出的二进制文件可以很方便的通过容器部署,很适合在 Service Mesh 场景中使用。

  3. 性能优势:

    Go 语言运行时具有轻量级、低延迟、高并发和内存管理等优势,这使得它在 Service Mesh 场景下具有更好的性能表现。在 Service Mesh 中,会有大量的网络请求和数据传输,而 Go 语言的高并发和低延迟可以更好地满足这些需求,提高服务的响应速度和吞吐量。

  4. 生态丰富:

    Docker、Kubernetes、Istio 等项目均通过 Go 语言实现,这使得 Go 语言成为云原生生态中的一部分,并且与这些项目的集成更加方便。同时,Go 语言还有许多开源库和工具,可以方便地实现 Service Mesh 中的各种功能。

  5. 开发效率高:

    Go 语言具有简单、直观的语法和良好的并发支持,使得开发人员可以更快地实现 Service Mesh 的功能,同时保持代码的清晰和易读性。此外,Go 语言的测试框架和工具链也非常完善,可以更方便地进行单元测试和集成测试,提高代码的质量和稳定性。

如何用 Go 实现 Service Mesh

通用架构

在使用 Go 语言实现 Service Mesh 时,首先要确认架构。当前最流行的架构方案是基于 Sidecar 模型的。以下是这套架构的主要组成部分:

  1. Sidecar 模型:

    Sidecar 模型是 Service Mesh 中最广泛应用的模型之一,它通过在每个服务实例旁边(即 Sidecar)插入一个专门的代理来处理服务间通信和网络相关的问题。

  2. Webhook 自动注入:

    为了方便使用,可以通过 Webhook 实现 Sidecar 代理的自动注入。Webhook 是一种机制,可以在 Kubernetes 集群中根据特定事件(如 Pod 的创建或更新)自动调用一段预定义的代码。利用 Webhook,可以在 Pod 创建或更新时自动注入 Sidecar 代理,减少人工干预和配置错误。

  3. 流量代理和拦截:

    在 Sidecar 代理中,可以实现流量代理和拦截的功能。流量代理是指在 Sidecar 代理中实现负载均衡、服务发现等功能,将服务请求转发到正确的目标服务上。流量拦截是指在 Sidecar 代理中实现流量控制、安全策略等功能,对请求进行拦截和过滤,保证服务的安全性和稳定性。

  4. Controller:

    Controller 是 Service Mesh 的控制平面,负责处理配置翻译和服务发现等问题。可以使用 Istio 等 Service Mesh 控制器,来管理和配置 Sidecar 代理,以便它们能够协同工作,提供服务发现、负载均衡、故障恢复和安全性等功能。

配置规范SMI

SMI(Service Mesh Interface)是一套中立的 Service Mesh 配置规范,旨在为不同的 Service Mesh 提供一致的 API 接口,使得开发人员可以更方便地管理和控制 Service Mesh 中的各种功能和策略。SMI 定义了一组 API 和 CRD(Custom Resource Definition)规范,包括 TrafficTarget、TrafficSplit、TrafficSpec 等资源对象,用于描述流量目标、流量划分和流量规范等概念。通过 SMI,不同的 Service Mesh 实现可以提供一致的 API 接口,使得开发人员可以更容易地编写跨 Service Mesh 的应用程序。

SMI

目前,众多开源项目已经适配了 SMI 规范,如 Istio、Linkerd、Consul 等,这使得开发人员可以在不同的 Service Mesh 中使用相同的 API 接口,从而提高了应用程序的可移植性和互操作性。特别地,Open Service Mesh(OSM)是一个基于 SMI 规范的 Service Mesh 实现,它通过 SMI 规范提供了一组标准的 API 接口,可以与不同的 Service Mesh 实现进行集成,从而提供更灵活、可扩展的 Service Mesh 解决方案。

尽管很多开源项目都对 SMI 规范做了适配,但是 SMI 规范目前最为主要的实现—— Open Service Mesh 项目被归档了。

我们有哪些收获

首先,我们认识到全新采用 Sidecar 模型的通用型 Service Mesh 很难在短时间内展露头角。虽然 Sidecar 模型在 Service Mesh 中应用广泛,但是实现一套通用的 Service Mesh 还需要时间和经验的积累。这个结论并不适用于企业内自研的方案,因为每个企业的需求和场景都是不同的。

其次,我们发现 SMI 规范在度过“甜蜜期”后,后续投入很少。虽然 SMI 规范是一个有价值的 Service Mesh 配置规范,但是在实际应用中,它也面临着一些挑战和限制。例如,SMI 规范并不涵盖所有的 Service Mesh 功能和策略,因此一些特定的需求和场景可能需要自定义扩展。Gateway API(GAMMA)成为了主要的方向。Gateway API 是一个 Kubernetes 设计的新 API 规范,可以用于配置和管理网关和负载均衡器等网络设备。它提供了一种通用的方式来管理和控制服务网格中的流量和策略,可以与 Service Mesh 集成,提供更灵活、可扩展的网络解决方案。

此外,我们认识到借力社区会更利于开源项目的发展。在开源项目中,社区和用户的参与非常重要,可以为项目提供更多的反馈和支持。例如,Apache APISIX 具有良好的社区支持和用户基础,可以为项目的发展提供更多的动力和资源。同时,Apache APISIX 的优势在于数据面,可以为 Service Mesh 提供更高效、可靠、安全的数据代理服务。使用 Apache APISIX 替换 Envoy 成为 Istio 的数据面是一条相对有效的路径。Istio 是一个非常流行的 Service Mesh 实现,具有广泛的社区和用户基础。使用 Apache APISIX 替换 Envoy 作为 Istio 的数据面,可以为 Istio 提供更高效、可靠、安全的数据代理服务,同时提高 Istio 的性能和可扩展性。

Amesh 的开发及实现

什么是 Amesh

Amesh(https://github.com/api7/amesh)是 Apache APISIX 的服务网格库。它适配了 xDS 协议,可以从诸如 Istio 的控制平面中接收数据,并生成 APISIX 所需的数据结构,使得 APISIX 能够在服务网格领域作为数据面发挥作用。依靠 Amesh,APISIX 可以工作在服务网格模式下,不使用传统的 etcd 作为数据中心,而是使用 shdict 与 Amesh 库直接进行数据交换,避免了额外的性能损耗,使得大规模部署成为可能。

通过使用 Amesh,可以在服务网格领域获得 APISIX 具备的高性能、丰富的流量管理功能、易扩展性等多种优势。

Amesh 如何实现

Amesh 是一个开源项目,旨在将 Service Mesh 和 API Gateway 结合起来,提供一种简单而强大的方式来管理和保护微服务。下图为 Amesh 架构图:

Amesh-architecture

  1. Amesh 支持 xDS API:

    xDS API 是一种通用的 API,用于配置和管理 Service Mesh 中的各种资源。Amesh 支持 EDS、RDS、SDS、LDS 等 xDS API,这使得它能够与多种 Service Mesh 平台进行集成。

  2. Amesh 将通过 xDS API 下发的数据转义为 APISIX 的配置:

    APISIX 是一个高性能的 API 网关,它支持多种协议和数据格式。通过将 xDS API 下发的数据转义为 APISIX 的配置,Amesh 可以让 APISIX 生效,并通过 APISIX 对微服务进行管理和保护。

  3. Amesh 可以让 Istio 感知到自己的存在:

    在部署 Amesh 时,可以注入相应的 yaml 文件。这个文件包含了部署 Amesh 所需的所有资源和配置信息,可以帮助用户轻松地将 Amesh 部署到自己的环境中。通过注入这个文件,用户可以快速地启动和配置 Amesh,并开始使用它提供的微服务管理和保护功能。

Amesh 如何让配置在 APISIX 生效

为了让 Amesh 的配置在 APISIX 中生效,首先需要将 Amesh 构建为 .so 文件,并将 Amesh 作为共享库挂载至 APISIX。在这个过程中,Amesh 使用了 NGINX lua_shared_dict,这是一种能够在 NGINX 中开辟内存空间的技术。通过将 Amesh 构建为共享库,并将其挂载至 APISIX,我们可以让 Amesh 的配置在 APISIX 中生效。

为实现 Amesh 和 APISIX 之间的数据交换,Amesh 使用了 NGINX FFI(Foreign Function Interface)技术。它使得不同编程语言之间可以进行互操作。通过 FFI,Amesh 与 APISIX 之间可以通过 C 接口进行调用,并使用相同的内存空间进行数据交换。

1func (s *SharedDictStorage) Store(key, value string) {
2    if s.zone == nil {
3        log.Warnw("zone is nil")
4        return
5    }
6
7    var keyCStr = C.CString(key)
8    defer C.free(unsafe.Pointer(keyCStr))
9    var keyLen = C.size_t(len(key))
10
11    var valueCStr = C.CString(value)
12    defer C.free(unsafe.Pointer(valueCStr))
13    var valueLen = C.size_t(len(value))
14
15    errMsgBuf := make([]*C.char, 1)
16    var forcible = 0
17
18    C.ngx_lua_ffi_shdict_store(s.zone, 0x0004,
19        (*C.uchar)(unsafe.Pointer(keyCStr)), keyLen,
20        4,
21        (*C.uchar)(unsafe.Pointer(valueCStr)), valueLen,
22        0, 0, 0,
23        (**C.char)(unsafe.Pointer(&errMsgBuf[0])),
24        (*C.int)(unsafe.Pointer(&forcible)),
25    )
26}

Amesh 如何拦截流量

函数会判断是否有需要拦截的端口。如果没有,则直接返回。

如果需要拦截的端口是 *,表示需要拦截所有端口的流量。此时,函数会插入一些规则来确保 SSH 流量不会被重定向。然后,函数会遍历需要排除的端口,并为它们插入规则,将流量直接返回。最后,函数会将所有流量重定向到 InboundRedirectChain 中。

如果需要拦截的端口不是 *,则表示只需要拦截特定端口的流量。函数会遍历所有需要拦截的端口,并为它们插入规则,将流量重定向到 InboundRedirectChain 中。

1func (ic *iptablesConstructor) insertInboundRules() {
2    if ic.cfg.InboundPortsInclude == "" {
3        return
4    }
5    ic.iptables.AppendRuleV4(log.UndefinedCommand, PreRoutingChain, "nat", "-p", "tcp", "-j", InboundChain)
6
7    if ic.cfg.InboundPortsInclude == "*" {
8        // Makes sure SSH is not redirected
9        ic.iptables.AppendRuleV4(log.UndefinedCommand, InboundChain, "nat", "-p", "tcp", "--dport", "22", "-j", "RETURN")
10        if ic.cfg.InboundPortsExclude != "" {
11            for _, port := range split(ic.cfg.InboundPortsExclude) {
12                ic.iptables.AppendRuleV4(log.UndefinedCommand, InboundChain, "nat", "-p", "tcp", "--dport", port, "-j", "RETURN")
13            }
14        }
15        ic.iptables.AppendRuleV4(log.UndefinedCommand, InboundChain, "nat", "-p", "tcp", "-j", InboundRedirectChain)
16    } else {
17        for _, port := range split(ic.cfg.InboundPortsInclude) {
18            ic.iptables.AppendRuleV4(
19                log.UndefinedCommand, InboundChain, "nat", "-p", "tcp", "--dport", port, "-j", InboundRedirectChain,
20            )
21        }
22    }
23}

Amesh 如何扩展 APISIX 原生能力

为了扩展 APISIX 的原生能力,Amesh 项目创建了一个自定义资源 AmeshPluginConfig,该资源可以贴合 APISIX 的原生语义,并且可以配置时同步生效。同时,AmeshPluginConfig 还支持 APISIX 原生及扩展功能。

1func NewAmeshPluginConfigController(cli client.Client, scheme *runtime.Scheme,
2    clientset clientset.Interface, kubeClient kubernetes.Interface,
3    podInformer v1informer.PodInformer,
4    ameshPluginConfigInformer ameshv1alpha1informer.AmeshPluginConfigInformer) *AmeshPluginConfigReconciler {
5
6    eventBroadcaster := record.NewBroadcaster()
7    eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
8
9    c := &AmeshPluginConfigReconciler{
10        Client:    cli,
11        clientset: clientset,
12        recorder:  eventBroadcaster.NewRecorder(scheme, v1.EventSource{Component: "AmeshPluginConfigReconciler"}),
13
14        Log:    ctrl.Log.WithName("controllers").WithName("AmeshPluginConfig"),
15        Scheme: scheme,
16
17        podInformer:   podInformer,
18        selectorCache: utils.NewSelectorCache(ameshPluginConfigInformer.Lister()),
19        pluginsCache:  map[string]*types.PodPluginConfig{},
20    }
21}

总结

Service Mesh 是一个解决多种云原生环境下服务通信与管理的方案。它凭借着自身优势正在被越来越多的组织采纳或评估。然而,Service Mesh 也面临着诸多挑战。Envoy 进行二次开发的复杂度较高,使得开发和维护 Service Mesh 变得复杂和困难。同时,客户也希望能够有统一技术栈的解决方案。因此,API7.ai 决定充分发挥 APISIX 的优势,对 Istio 中的 Envoy 进行替代。

微信咨询

获取方案