OpenKruise :SidecarSet 助力 Mesh 容器热升级

做者| 赵明山(立衡)

头图.jpggit

前言


OpenKruise 是阿里云开源的云原生应用自动化管理套件,也是当前托管在 Cloud Native Computing Foundation ( CNCF ) 下的 Sandbox 项目。它来自阿里巴巴多年来容器化、云原生的技术沉淀,是阿里内部生产环境大规模应用的基于 Kubernetes 之上的标准扩展组件,也是紧贴上游社区标准、适应互联网规模化场景的技术理念与最佳实践。

OpenKruise 在 2021.5.20 发布了最新的 v0.9.0版本( ChangeLog ),上一篇文章咱们介绍了新增 Pod 重启、删除防御等重磅功能,今天向你们介绍另外一个核心特性,即 SidecarSet 基于上一个版本扩展了特别针对 Service Mesh 场景的支持。
github

背景:如何独立升级 Mesh 容器


SidecarSet 是 Kruise 提供的独立管理 Sidecar 容器的 workload。用户经过 SidecarSet 可以便利的完成对 Sidecar 容器的自动注入独立升级,详情请参考:OpenKruise 官网
api

默认状况下,Sidecar 的独立升级顺序是先中止旧版本的容器,而后再建立新版本的容器。这种方式尤为适合不影响 Pod 服务可用性的 Sidecar 容器,例如日志收集 agent ,可是对于不少代理或运行时的 Sidecar 容器,如 Istio Envoy,这种升级方法就有问题了。Envoy 做为 Pod 中的一个 Proxy 容器代理了全部的流量,这种场景下若是直接重启升级,Pod 服务的可用性必然会受到影响,所以须要考虑应用自身的发布和容量状况,没法彻底独立于应用作 Sidecar 的发布。
1--1.pngapp

阿里巴巴集团内部拥有上万的 Pod 都是基于 Service Mesh 来实现相互间的通讯,因为 Mesh 容器升级会致使业务 Pod 的不可用,于是 Mesh 容器的升级将会极大阻碍 Service Mesh 的迭代。针对这种场景,咱们同集团内部的 Service Mesh 团队一块儿合做实现了 Mesh 容器的热升级能力。本文将重点介绍在实现 mesh 容器热升级能力的过程当中 SidecarSet 是扮演了怎样的重要角色。
tcp

SidecarSet 助力 Mesh 容器无损热升级

Mesh 容器不能像日志采集类容器直接原地升级,其缘由在于:Mesh 容器必需要不间断地对外提供服务,而独立升级方式会致使 Mesh 服务存在一段不可用时间。虽然社区中已有一些知名的 Mesh 服务如 Envoy 、Mosn 等默认可以提供平滑升级的能力,可是这些升级方式没法与云原生进行恰当地结合,且 kubernetes 自己也缺少对此类 Sidecar 容器的升级方案。
ide

OpenKruise SidecarSet 为此类 Mesh 容器提供了 Sidecar 热升级机制,可以经过云原生的方式助力 Mesh 容器实现无损热升级。post

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
  name: hotupgrade-sidecarset
spec:
  selector:
    matchLabels:
      app: hotupgrade
  containers:
    - name: sidecar
      image: openkruise/hotupgrade-sample:sidecarv1
      imagePullPolicy: Always
      lifecycle:
        postStart:
          exec:
            command:
              - /bin/sh
              - /migrate.sh
      upgradeStrategy:
        upgradeType: HotUpgrade
        hotUpgradeEmptyImage: openkruise/hotupgrade-sample:empty
  • upgradeType : HotUpgrade 表明该 sidecar 容器的类型是 Hot upgrade ,即热升级方案。
  • HotUpgradeEmptyImage : 当热升级 Sidecar 容器时,业务需要提供一个 empty 容器用于热升级过程当中的容器切换。Empty 容器同 Sidecar 容器具备相同的配置(镜像地址除外),例如 command , lifecycle , probe 等。

SidecarSet 热升级机制主要包含注入热升级 Sidecar 容器和 Mesh 容器平滑升级两个过程。
性能

注入热升级 Sidecar 容器

针对热升级类型的 Sidecar 容器,在 Pod 建立时 SidecarSet Webhook 将会注入两个容器:
ui

  • {Sidecar.name} -1: 以下图所示 envoy -1,这个容器表明正在实际工做的 sidecar 容器,例如:envoy :1.16.0
  • {Sidecar.name} -2: 以下图所示 envoy-2,这个容器是业务提供的 HotUpgradeEmptyImage 容器,例如:empty :1.0

2-2.png

上述 Empty 容器在 Mesh 容器运行过程当中,并无作任何实际的工做。
阿里云

Mesh 容器平滑升级

热升级流程主要分为一下三个步骤:

  1. Upgrade: 将 Empty 容器替换为最新版本的 Sidecar 容器,例如:envoy-2.Image = envoy:1.17.0
  2. Migration : 执行 Sidecar 容器的 PostStartHook 脚本,完成 mesh 服务的平滑升级
  3. Reset: Mesh 服务平滑升级后,将老版本 Sidecar 容器替换为 Empty 容器,例如:envoy-1.Image = empty : 1.0

仅需上述三个步骤便可完成热升级中的所有流程,若对 Pod 执行屡次热升级,则重复执行上述三个步骤便可。

3-3.png

Migration 核心逻辑

SidecarSet 热升级机制不只完成了 Mesh 容器的切换,而且提供了新老版本的协调机制( PostStartHook ),可是至此还只是万里长征的第一步,Mesh 容器同时还须要提供 PostSartHook 脚原本完成 Mesh 服务自身的平滑升级(上述 Migration 过程),如:Envoy 热重启、Mosn 无损重启。

Mesh 容器通常都是经过监听固定端口来对外提供服务,此类 Mesh 容器的migration 过程能够归纳为:经过 UDS 传递 ListenFD 和中止 Accpet 、开始排水。针对不支持热重启的 Mesh 容器能够参考此过程完成改造,逻辑图以下:
4-4.png

热升级 Migration Demo

不一样 Mesh 容器对外提供的服务以及内部实现逻辑各有差别,进而具体的 Migration也有所不一样,上述逻辑只是对其中一些要点作了一些总结,但愿能对有须要的各位有所裨益,同时在 Github 上面咱们也提供了一个热升级 Migration Demo 以供参考,下面将对其中的一些关键代码进行介绍。

1. 协商机制

Mesh 容器启动逻辑首先就须要判断第一次启动仍是热升级平滑迁移过程,为了减小Mesh 容器沟通成本,Kruise 在两个 sidecar 容器中注入了两个环境变量 SIDECARSET_VERSION 和 SIDECARSET_VERSION_ALT ,经过判断两个环境变量的值来判断是不是热升级过程以及当前 sidecar 容器是新版本仍是老版本。

// return two parameters:
// 1. (bool) indicates whether it is hot upgrade process
// 2. (bool ) when isHotUpgrading=true, the current sidecar is newer or older
func isHotUpgradeProcess() (bool, bool) {
    // 当前sidecar容器的版本
    version := os.Getenv("SIDECARSET_VERSION")
    // 对端sidecar容器的版本
    versionAlt := os.Getenv("SIDECARSET_VERSION_ALT")
    // 当对端sidecar容器version是"0"时,代表当前没有在热升级过程
    if versionAlt == "0" {
        return false, false
    }
    // 在热升级过程当中
    versionInt, _ := strconv.Atoi(version)
    versionAltInt, _ := strconv.Atoi(versionAlt)
    // version是单调递增的int类型,新版本的version值会更大
    return true, versionInt > versionAltInt
}

2. ListenFD 迁移

经过 Unix Domain Socket 实现 ListenFD 在不一样容器间的迁移,此步一样也是热升级中很是关键的一步,代码示例以下:

// 为了代码的简洁,全部的失败都将不捕获

/* 老版本sidecar经过Unix Domain Socket迁移ListenFD到新版本sidecar */
// tcpLn *net.TCPListener
f, _ := tcpLn.File()
fdnum := f.Fd()
data := syscall.UnixRights(int(fdnum))
// 与新版本sidecar容器经过 Unix Domain Socket创建连接
raddr, _ := net.ResolveUnixAddr("unix", "/dev/shm/migrate.sock")
uds, _ := net.DialUnix("unix", nil, raddr)
// 经过UDS,发送ListenFD到新版本sidecar容器
uds.WriteMsgUnix(nil, data, nil)
// 中止接收新的request,而且开始排水阶段,例如:http2 GOAWAY
tcpLn.Close()

/* 新版本sidecar接收ListenFD,而且开始对外服务 */
// 监听 UDS
addr, _ := net.ResolveUnixAddr("unix", "/dev/shm/migrate.sock")
unixLn, _ := net.ListenUnix("unix", addr)
conn, _ := unixLn.AcceptUnix()
buf := make([]byte, 32)
oob := make([]byte, 32)
// 接收 ListenFD
_, oobn, _, _, _ := conn.ReadMsgUnix(buf, oob)
scms, _ := syscall.ParseSocketControlMessage(oob[:oobn])
if len(scms) > 0 {
    // 解析FD,并转化为 *net.TCPListener 
    fds, _ := syscall.ParseUnixRights(&(scms[0]))
    f := os.NewFile(uintptr(fds[0]), "")
    ln, _ := net.FileListener(f)
    tcpLn, _ := ln.(*net.TCPListener)
    // 基于接收到的Listener开始对外提供服务,以http服务为例
    http.Serve(tcpLn, serveMux)
}

已知 Mesh 容器热升级案例

阿里云服务网格( Alibaba Cloud Service Mesh,简称 ASM)提供了一个全托管式的服务网格平台,兼容社区 Istio 开源服务网格。当前,基于 OpenKruise SidecarSet 的热升级能力,ASM 实现了数据平面 Sidecar 热升级能力( Beta ),用户能够在应用无感的状况下完成服务网格的数据平面版本升级,正式版也将于近期上线。除热升级能力外,ASM 还支持配置诊断、操做审计、访问日志、监控、服务注册接入等能力,全方位提高服务网格使用体验,欢迎您前往试用。

总结

云原生中 Mesh 容器的热升级一直都是迫切却又棘手的问题,本文中的方案也只是阿里巴巴集团在此问题上的一次探索,在反馈社区的同时也但愿可以抛砖引玉,引起各位对此中场景的思考。同时,咱们也欢迎更多的同窗参与到 OpenKruise 社区来,共同建设一个场景更加丰富、完善的 K8s 应用管理、交付扩展能力,可以面向更加规模化、复杂化、极致性能的场景。

5-5.png