蚂蚁大规模 Sigma 集群 Etcd 拆分实践

部署 Kubernetes规划方案 本文总结了Kubernetes集群部署中的关键注意事项,重点关注稳定性与维护成本。官方建议单节点不超过110个Pod,单集群节点不超过500个,Pod总量不超过60,000。超大集群会在etcd、API服务器、调度器等组件产生性能瓶颈,建议根据业务特性、安全隔离要求及规模阈值合理拆分集群。网络方案推荐Calico、Cilium或MacVLAN,节点配置应考虑资源利用率与超卖系数,典型场景下建议96核/512GB规格。整体策略强调在规模与维护复杂度间取得平衡,避免单一超大集群带来的系统性风险。 阅读详情

文|杜克伟(花名:苏麟 )

蚂蚁集团高级开发工程师

负责蚂蚁 Kubernetes 集群的稳定性方面的工作
专注于集群组件变更、稳定性风险保障

本文 15738 字 阅读 20 分钟

前 言

为了支撑蚂蚁业务的迭代升级,蚂蚁基础设施今年启动了 Gzone 全面云化项目。要求 Gzone 需与已经云化的 Rzone 合并部署在同一个集群,Sigma 单集群实际管理的节点规模将超过万台,单集群承担的业务也将更加复杂。

因此我们启动了大规模 Sigma 集群的性能优化方案,在请求延迟上期望能够对齐社区标准,不因规模增长的原因下降。

etcd 作为 Sigma 集群的数据存储数据库,是整个集群的基石,能够直接决定性能天花板。社区建议的单 etcd 集群存储限制是 8G, 而蚂蚁 Sigma 集群的单 etcd 集群存储量早已超过了这个限制,Gzone 上云项目势必会加重 etcd 的负担。

首先,蚂蚁业务混合了流失计算、离线计算和在线业务,混合大量的生命周期在分钟级甚至是秒级的 Pod,单集群每天的 Pod 创建量也提升到了数十万, 都需要 etcd 来支撑;

其次,复杂的业务需求催生了大量的 List (list all、list by namespace、list by label)、watch、create、update、delete 请求,针对 etcd 的存储特性,这些请求性能均会随着 etcd 存储规模的增大而严重衰减,甚至导致 etcd OOM,请求超时等异常;

最后,请求量的增长也加剧了 etcd 由于 compact、defrag 操作对请求 RT P99 的暴涨,甚至请求超时,从而导致集群关键组件调度器、CNI 服务等 Operator 类组件间断性丢失,造成集群不可用。

根据前人的经验,针对 etcd 集群进行数据水平拆分是一个有效的优化手段,典型的拆分是把 Pod 等重要数据单独 etcd 集群来存储,从而降低单 etcd 存储和请求处理的压力,降低请求处理延迟。但是 Pod 资源数据针对 Kubernetes 集群具有特殊性,具有其他资源没有的高要求,尤其是针对已颇具规模正在服务的 K8s 集群进行拆分更是需要万分谨慎小心。

本文主要记录了蚂蚁集团在进行 Pod 资源数据拆分过程中一些实践经验和心得。

抛砖引玉,请大家多多指教!

PART. 1 面临的挑战

从前人的 Pod 数据拆分经验了解到,Pod 数据拆分是一个高危且复杂的流程,原因来自于 Pod 数据自身的特殊性。

Pod 是一组容器的组合,是 Sigma 集群中可调度的最小单位,是业务 workload 的最终承载体。Sigma 集群的最核心最终的交付资源就是 Pod 资源。

Sigma 集群最核心的 SLO 也是 Pod 的创建删除升级等指标。Pod 资源数据可以说是 Sigma 集群最重要的资源数据。同时 Sigma 集群又是由事件驱动的,面向终态体系设计,所以 Pod 资源数据拆分除了考虑基本的前后数据一致性问题外,还要考虑拆分过程中对其他组件的影响。

前人的拆分经验流程中最核心的操作是数据完整性校验和关键服务组件停机。数据完整性校验顾名思义是为了保证数据前后的一致性,而关键服务组件停机是为了避免拆分过程中如果组件不停机造成的非预期后果,可能会有 Pod 非预期删除,Pod 状态被破坏等。但是如果照搬这套流程到蚂蚁 Sigma 集群,问题就来了。

蚂蚁 Sigma 作为蚂蚁集团核心的基础设施,经过 2 年多的发展已经成为拥有 80+ 集群、单集群节点数可达到 1.2w+ 规模的云底座。在如此规模的集群上,运行着蚂蚁内部百万级别的 Pod,其中短运行时长 Pod 每天的创建量在 20w+次。为了满足各种业务发展需求,Sigma 团队与蚂蚁存储、网络、PaaS 等多个云原生团队合作,截止目前 Sigma 共建的第三方组件量已经达到上百个。如果 Pod 拆分要重启组件,需要大量的与业务方的沟通工作,需要多人共同操作。如果操作不慎,梳理不完全漏掉几个组件就有可能造成非预期的后果。

在这里插入图片描述

从蚂蚁 Sigma 集群现状总结一下已有的 Pod 数据拆分经验流程的问题:

  1. 人工操作大量组件重启时间长、易出错

潜在需要重启的组件高达数十个,需要与各个组件 owner 进行沟通确认,梳理出需要重启的组件,需要耗费大量的沟通时间。万一遗漏就可能造成非预期的后果,比如资源残留、脏数据等。

  1. 完全停机持续时间长打破 SLO

数据拆分期间组件完全停机,集群功能完全不可用,且拆分操作极为耗时,根据前人经验,持续时间可能长达 1~2 小时,完全打破了 Sigma 集群对外的 SLO 承诺。

  1. 数据完整性校验手段薄弱

拆分过程中使用 etcd 开源工具 make-mirror 工具来迁移数据,该工具实现比较简单,就是读取一个 etcd 的 key 数据然后重新写到另一个 etcd,不支持断点续传,同时因重新写入 etcd 造成原有 key 的重要字段 revision 被破坏,影响 Pod 数据的 resourceVersion, 可能会造成非预期后果。关于 revision 后文会详细说明。最后的校验手段是检验 keys 的数量是否前后一致,如果中间 key 的数据被破坏,也无法发现。

PART. 2 问题解析

美好的期望

作为一个懒人,不想和那么多的组件 owner 沟通重启问题,大量组件重启也易造成操作遗漏,造成非预期问题。同时是否有更好的数据完整性校验的手段呢?

如果组件不重启,那么整个过程后演变为下面的流程,预期将简化流程,同时保障安全性。

为了达成美好的期望,我们来追本溯源重新 review 整个流程。

在这里插入图片描述

数据拆分是在做什么?

众所周知,etcd 存储了 Kubernetes 集群中的各种资源数据,如 Pod、Services、Configmaps、Deployment 等等。

Kube-apiserver 默认是所有的资源数据都存储在一套 etcd 集群中,随着存储规模的增长,etcd 集群会面临性能瓶颈。以资源维度进行 etcd 的数据拆分来提升 Kube-apiserver 访问 etcd 的性能是业内所共识的经验优化思路,本质是降低单 etcd 集群的数据规模,减少单 etcd 集群的访问 QPS。

针对蚂蚁 Sigma 集群自身的规模和需求,需拆分为 4 个独立的 etcd 集群,分别存储 Pods、Leases、event 和其他资源数据,下面分别简要说明这前三类(Pods、Lease、event)需要拆分出去的资源数据。

在这里插入图片描述

Event 资源

K8s event 资源数据并不是 watch 中的 event,一般是表示关联对象发生的事件,比如 Pod 拉取镜像,容器启动等。在业务上一般是 CI/CD 需要流水式展示状态时间轴,需要频繁拉取 event 资源数据。

event 资源数据本身就是有效期的(默认是 2 小时),除了通过 event 观测资源对象生命周期变化外,一般没有重要的业务依赖,所以说 event 数据一般认为是可以丢弃,不需要保障数据前后一致性的。

因为上述的数据特点,event 的拆分是最为简单的,只需要修改 APIServer 的启动配置,重启 APIServer 即可,不需要做数据迁移,也不需要做老旧数据的清理。整个拆分过程除了 Kube-apiserver 外,不需要任何组件的重启或者修改配置。

Lease资源

Lease 资源一般用于 Kubelet 心跳上报,另外也是社区推荐的 controller 类组件选主的资源类型。

每个 Kubelet 都使用一个 Lease 对象进行心跳上报,默认是每 10s 上报一次。节点越多,etcd 承担的 update 请求越多,节点 Lease 的每分钟更新次数是节点总量的 6 倍,1 万个节点就是每分钟 6 万次,还是非常可观的。Lease 资源的更新对于判断 Node 是否 Ready 非常重要,所以单独拆分出来。

controller 类组件的选主逻辑基本上都是使用的开源的选主代码包,即使用 Lease 选主的组件都是统一的选主逻辑。Kubelet 的上报心跳的代码逻辑更是在我们掌控之中。从代码中分析可知 Lease 资源并不需要严格的数据一致性,只需要在一定时间内保障 Lease 数据被更新过,就不影响使用 Lease 的组件正常功能。

Kubelet 判断 Ready 的逻辑是否在 controller-manager 中的时间默认设置是 40s,即只要对应 Lease 资源在 40s 内被更新过,就不会被判断为 NotReady。而且 40s 这个时间可以调长,只要在这个时间更新就不影响正常功能。使用选主的 controller 类组件的选主 Lease duration 一般为 5s~65s 可以自行设置。

因此 Lease 资源拆分虽和 event 相比要复杂一些,但也是比较简单的。多出来的步骤就是在拆分的过程中,需要把老 etcd 中的 Lease 资源数据同步到新的 etcd 集群中,一般我们使用 etcdctl make-mirror 工具同步数据。此时若有组件更新 Lease 对象,请求可能会落在老 etcd,也可能落在新的 etcd 中。落在老 etcd 中的更新会通过 make-mirror 工具同步到新的 etcd 中,因为 Lease 对象较少,整个过程持续时间很短,也不会存在问题。另外还需要迁移拆分完成后,删除老 etcd 中的 Lease 资源数据,以便释放锁占用的空间,虽然空间很小,但也不要浪费。类似 event 资源拆分,整个拆分过程除了 kube-apiserver 外,同样不需要任何组件的重启或者修改配置。

Pod 资源

Pod 资源可能是我们最熟悉的资源数据了,所有的 workload 最终都是由 Pod 来真实承载。K8s 集群的管理核心就在于 Pod 资源的调度和管理。Pod 资源数据要求严格的数据一致性,Pod 的任何更新产生的 watch event 事件,都不能错过,否则就有可能影响 Pod 资源交付。Pod 资源的特点也正是导致传统 Pod 资源数据拆分过程中需要大规模重启相关组件的原因,后文会解析其中的原因。

社区 kube-apiserver 组件本身早已有按照资源类型设置独立 etcd 存储的配置–etcd-servers-overrides。

–etcd-servers-overrides strings
Per-resource etcd servers overrides, comma separated. The individual override format: group/resource#servers, where servers are URLs, semicolon separated. Note that this applies only to resources compiled into this server binary.

我们常见的资源拆分的简要配置示例如下:

events 拆分配置

–etcd-servers-overrides=/events#https://etcd1.events.xxx:2xxx;https://etcd2.events.xxx:2xxx;https://etcd3.events.xxx:2xxx

leases 拆分配置

–etcd-servers-overrides=coordination.k8s.io/leases#https://etcd1.leases.xxx:2xxx;https://etcd2.leases.xxx:2xxx;https://etcd3.leases.xxx:2xxx

pods 拆分配置

–etcd-servers-overrides=/pods#https://etcd1.pods.xxx.net:2xxx;https://etcd2.pods.xxx:2xxx;https://etcd3.pods.xxx:2xxx

重启组件是必须的吗?

为了了解重启组件是否必须,如果不重启组件有什么影响。我们在测试环境进行了验证,结果我们发现在拆分完成后,新建 Pod 无法被调度,已有 Pod 的无法被删除,finalizier 无法摘除。经过分析后,发现相关组件无法感知到 Pod 创建和删除事件。

那么为什么会出现这种问题呢?要回答这个问题,就需要从 K8s 整个设计核心理念到实现具体细节全部理清楚讲透彻,我们细细道来。

如果 K8s 是一个普通的业务系统,Pod 资源数据拆分只是影响了 kube-apiserver 访问 Pod 资源的存储位置,也就是影响面只到 kube-apiserver 层面的话,就不会存在本篇文章了。

对于普通的业务系统来讲,都会有统一的存储访问层,数据迁移拆分运维操作只会影响到存储访问层的配置而已,更上层的业务系统根本不会感知到。

但, K8s 就是不一样的烟火!

在这里插入图片描述

K8s 集群是一个复杂的系统,是由很多扩展组件相互配合来提供多种多样的能力。

扩展组件是面向终态设计的。面向终态中主要有两个状态概念:期望状态(Desired State) 和当前状态(Current State), 集群中的所有的对象(object) 都有一个期望状态和当前状态。

  • 期望状态简单来说就是我们向集群提交的 object 的 Yaml 数据所描述的终态;

  • 当前状态就是 object 在集群中真实存在的状态。

我们使用的 create、update、patch、delete 等数据请求都是我们针对终态做的修改动作,表达了我们对终态的期望,执行这些动作后,当前集群状态和我们的期望状态是有差异的,集群中的各个 Operators(Controllers)扩展组件通过两者的差异进行不断的调谐(Reconclie) , 驱动 object 从当前状态达到最终状态。

在这里插入图片描述

目前的 Operators 类组件基本上都是使用开源框架进行开发的,所以可以认为其运行组件的代码逻辑是一致统一的。在 Operator 组件内部,最终终态是通过向 kube-apiserver 发送 List 请求获取最终终态的 object yaml 数据,但为了降低 kube-apiserver 的负载压力,在组件启动时 List 请求只执行一次(如果不出现非预期错误),若终态数据 object yaml 在之后有任何变化则是通过 kube-apiserver 主动向 Operator 推送 event(WatchEvent)消息。

从这点讲也可以说 K8s 集群是由 event 驱动的面向终态的设计。

在这里插入图片描述

而 Operator 和 kube-apiserver 之间的 WatchEvent 消息流需要保障任何 event 都不能丢失, 最初的 List 请求返回的 yaml 数据,再加上 WatchEvent 的变更事件组合而成才是 Operator 应该看到的最终状态,也是用户的期望状态。而保障事件不丢失的重要概念则是 resourceVersion。

集群中的每个 object 都有该字段,即使是用户通过 CRD(CustomResourceDefinition) 定义的资源也是有的。

重点来了,上面提到的 resourceVersion 是与 etcd 存储本身独特特性(revision)息息相关的,尤其是针对 Operator 大量使用的 List 请求更是如此。数据的拆分迁移到新的 etcd 存储集群会直接影响到资源对象的 resourceVersion。

那么问题又来了,etcd revision 是什么?与 K8s 资源对象的 resourceVersion 又有什么关联呢?

Etcd 的 3 种 Revision

Etcd 中有三种 Revision,分别是 Revision、CreateRevision 和 ModRevision 下面将这三种 Revision 的关联关系以及特点总结如下:

key-value 写入或者更新时都会有 Revision 字段,并且保证严格递增, 实际上是 etcd 中 MVCC 的逻辑时钟。

在这里插入图片描述

K8s ResourceVersion 与 Etcd Revision

每个从 kube-apiserver 输出的 object 都必然有 resourceVersion 字段,可用于检测 object 是否变化及并发控制。

可从代码注释中看到更多信息:

// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {  
    ...// omit code here
    // An opaque value that represents the internal version of this object that can
  // be used by clients to determine when objects have changed. May be used for optimistic
  // concurrency, change detection, and the watch operation on a resource or set of resources.
  // Clients must treat these values as opaque and passed unmodified back to the server.
  // They may only be valid for a particular resource or set of resources.
  //
  // Populated by the system.
  // Read-only.
  // Value must be treated as opaque by clients and .
  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
  // +optional
  ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
    ...// omit code here
}

kube-apiserver 的请求 verbs 中 create、update、 patch、delete 写操作都会更新 etcd 中的 Revision,更严格的说,会引发 revision 的增长。

现将 K8s 中的 resource object 中的 resourceVersion 字段与 etcd 中的各种 Revision 对应关系总结如下:

在这里插入图片描述

在所有的 kube-apiserver 请求响应中,需要特别注意 List 的响应。List 请求的 resourceVersion 是 etcd 的 Header.Revision, 该值正是 etcd 的 MVCC 逻辑时钟,对 etcd 任何 key 的写操作都是触发 Revision 的单调递增,接影响到 List 请求响应中的 resourceVersion的值。

举例来说,即使是没有任何针对 test-namespace 下面的 Pod 资源的修改动作,如果 List test-namespace 下面的 Pod,响应中的 resourceVersion 也很可能每次都会增长(因为 etcd 中其他 key 有写操作)。

在我们的不停组件 Pod 数据拆分中,我们只禁止了 Pod 的写操作,其他数据并未禁止,在 kube-apiserver 配置更新滚动生效过程中,势必会造成 old etcd 的 Revision 要远大于存储 Pod 数据的 new etcd。这就造成了 List resourceVersion 拆分前后的严重不一致。

resourceVersion 的值在 Operator 中是保障 event 不丢的关键。所以说 etcd 的数据拆分不仅影响到了 kube-apiserver,同时也影响到了众多的 Operator 类组件, 一旦出现变更事件丢失,会造成 Pod 无法交付、出现脏乱数据等问题故障。

在这里插入图片描述

在这里插入图片描述

到现在为止,虽然我们了解到 Operator 拿到的 list resourceVersion 拆分前后不一致,从 old etcd 中返回的 list resourceVersion 要比从 new etcd 要大, 那么和 Operator 丢掉 Pod 更新事件有什么关系呢?

要回答这个问题,就需要从 K8s 的组件协作设计中的 ListAndWatch 说起,势必需要从客户端 Client-go 和服务端 kube-apiserver 来讲。

Client-go 中 ListAndWatch

我们都知道 Operator 组件是通过开源 Client-go 代码包进行事件感知的。

在这里插入图片描述

Operator 中的 Client-go 感知数据对象事件示意图

其中核心关键就是 ListAndWatch 方法,保障 client 不丢失 event 事件的 resourceVersion 就是在该方法中通过 List 请求获取的。

ListAndWatch 第一次会列出所有的对象,并获取资源对象的版本号,然后 watch 资源对象的版本号来查看是否有被变更。首先会将资源版本号设置为 0,list()可能会导致本地的缓存相对于 etcd 里面的内容存在延迟。Reflector 会通过 watch 的方法将延迟的部分补充上,使得本地的缓存数据与 etcd 的数据保持一致。

关键代码如下:

// Run repeatedly uses the reflector's ListAndWatch to fetch all the
// objects and subsequent deltas.
// Run will exit when stopCh is closed.
func (r *Reflector) Run(stopCh <-chan struct{}) {
  klog.V(2).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
  wait.BackoffUntil(func() {
    if err := r.ListAndWatch(stopCh); err != nil {
      utilruntime.HandleError(err)
    }
  }, r.backoffManager, true, stopCh)
  klog.V(2).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
}
// ListAndWatch first lists all items and get the resource version at the moment of call,
// and then use the resource version to watch.
// It returns error if ListAndWatch didn't even try to initialize watch.
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
  var resourceVersion string
  // Explicitly set "0" as resource version - it's fine for the List()
  // to be served from cache and potentially be delayed relative to
  // etcd contents. Reflector framework will catch up via Watch() eventually.
  options := metav1.ListOptions{ResourceVersion: "0"}

  if err := func() error {
    var list runtime.Object
      ... // omit code here
    listMetaInterface, err := meta.ListAccessor(list)
      ... // omit code here
    resourceVersion = listMetaInterface.GetResourceVersion()
        ... // omit code here
    r.setLastSyncResourceVersion(resourceVersion)
    ... // omit code here
    return nil
  }(); err != nil {
    return err
  }
    ... // omit code here
  for {
        ... // omit code here
    options = metav1.ListOptions{
      ResourceVersion: resourceVersion,
      ... // omit code here
    }
    w, err := r.listerWatcher.Watch(options)
        ... // omit code here
    if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
        ... // omit code here
      return nil
    }
  }
}

整理为流程图更为清楚:

在这里插入图片描述

kube-apiserver 中的 Watch 处理

看完客户端的处理逻辑,再来看服务端的处理,关键在 kube-apiserver 对 watch 请求的处理, 对每一个 watch 请求,kube-apiserver 都会新建一个 watcher,启动一个 goroutine watchServer 专门针对该 watch请求进行服务,在这个新建的 watchServer 中向 client 推送资源 event 消息。

在这里插入图片描述

但是重点来了,client 的 watch 请求中参数 watchRV是从 Client-go 中的 List 响应而来,kube-apiserver 只向 client 推送大于 watchRV 的 event 消息,在拆分过程中 client 的 watchRV 有可能远大于 kube-apiserver 本地的 event 的 resourceVersion, 这就是导致 client 丢失 Pod 更新 event 消息的根本原因。

从这一点来说,重启 Operator 组件是必须的,重启组件可以触发 Client-go 的 relist,拿到最新的 Pod list resourceVersion,从而不丢失 Pod 的更新 event 消息。

在这里插入图片描述

PART. 3 问题破局

破解重启问题

到了这里,我们似乎也难逃需要重启组件的命运,但是经过问题解析之后,我们理清了问题原因,其实也就找到了解决问题的方法。

重启组件问题主要涉及到两个主体:客户端 Client-go 和服务端 kube-apiserver,所以解决问题可以从这两个主体出发,寻求问题的突破点。

首先针对客户端 Client-go,关键就在于让 ListAndWatch 重新发起 List 请求拿到 kube-apiserver 的最新的 resourceVersion,从而不丢失后续的 event 消息。如果过能够让 Client-go 在某个特定的时机重新通过 List 请求刷新本地的 resourceVersion,也就解决了问题,但是如果通过更改 Client-go 代码,还是需要组件发布重启才能生效,那么问题就是如何不用修改 Client-go 的代码,就可以重新发起 List 请求。

我们重新 review ListAndWatch 的逻辑流程,可以发现判断是否需要发起 List 请求,关键在于 Watch 方法的返回错误的判断。而 watch 方法返回的错误是根据 kube-apiserver 对 watch 请求的响应决定的,让我们把目光放到服务端 kube-apiserver。

在这里插入图片描述

不一样的 watch 请求处理

kube-apiserver 的 watch 请求处理前文已经介绍过,我们可以通过修改 kube-apiserver 的 watch 请求处理流程,实现与 Client-go 的相互配合,来达到我们的目的。

由上文我们知道 Client-go 的 watchRV 要远大于 kube-apiserver 本地 watch cache 中的 resourceVersion, 可以根据这个特点来实现 kube-apiserver 发送指定错误(TooLargeResourceVersionError),从而触发 Client-go 的 relist 动作。kube-apiserver 组件无可避免的需要重启,更新配置后可以执行我们改造的逻辑。

改造逻辑示意如下:

在这里插入图片描述

技术保障数据一致

前人的经验是通过 etcd make-mirror 工具来实现数据迁移的,优点是简单方便,开源工具开箱即用。缺点是该工作实现简单,就是从一个 etcd 中读取 key,然后重新写入另一个 etcd 中,不支持断点续传,对大数据量耗时长的迁移不友好。另外 etcd key 中的 createRevision 信息也被破坏掉。因此在迁移完成后,需要进行严格的数据完整性检测。

针对上面的问题我们可以换一个思路,我们本质是要做数据迁移的,etcd 本身的存储结构(KeyValue)具有特殊性,我们希望保留数据前后的完整性。所以想到了 etcd 的 snapshot 工具, snapshot 工具本来是用于 etcd 的容灾恢复的,即可以使用一个 etcd 的 snapshot 数据重新创建出新的 etcd 实例。而且通过 snapshot 的数据在新的 etcd 中是能够保持原有的 keyValue 的完整性的,而这正是我们所要的。

// etcd KeyValue 数据结构
type KeyValue struct {
  // key is the key in bytes. An empty key is not allowed.
  Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
  // create_revision is the revision of last creation on this key.
  CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`
  // mod_revision is the revision of last modification on this key.
  ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`
  // version is the version of the key. A deletion resets
  // the version to zero and any modification of the key
  // increases its version.
  Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
  // value is the value held by the key, in bytes.
  Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
  // lease is the ID of the lease that attached to key.
  // When the attached lease expires, the key will be deleted.
  // If lease is 0, then no lease is attached to the key.
  Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
}

迁移数据裁剪

etcd snapshot 数据虽然有我们想要的保持 KeyValue 的完整性,但是重建的 etcd 中存储的数据是老 etcd的全部数据,这个并不是我们想要的。我们当然可以在新建 etcd 后,再来发起冗余数据的清楚工作,但这并不是最好的方法。

我们可以通过改造 etcd snapshot 工具在 snapshot 的过程中实现我们的数据裁剪。etcd 的存储模型中,是有一个 buckets 的列表的, buckets 是 etcd 一个存储概念,对应到关系数据库中可以认为是一个 table,其中的每个 key 就对应的 table 中的一行。其中最重要的 bucket 是名称为 key 的 bucket, 该 bucket 存储了 K8s 中所有资源对象。而 K8s 的所有资源对象的 key 都是有固定格式的,按照 resource 类别和 namespace 区别,每种 resource 都是有固定的前缀。比如 Pod 数据的前缀就是/registry/Pods/。我们在 snapshot 过程中可以根据这个前缀区分出 Pod 数据,把非 Pod 数据裁减掉。

另外根据 etcd 的特性,etcd 做 snapshot 数据的存储大小是 etcd 的硬盘文件大小,其中有两个值 db total size 和 db inuse size, db total size 大小是 etcd 在硬盘中的所占用的存储文件的大小,其中包含了很多已经成为垃圾 key ,但未清理的数据。db inuse size 大小是所有可用的数据的总大小。在不经常使用 etcd defrag 方法整理存储空间时, total 的值一般来讲要远大于 inuse 的值。

在数据裁剪中即使我们裁剪掉非 Pod 数据,整个 snapshot 的数据也不会有任何改变,这时候我们需要通过 defrag 方法来释放掉冗余存储空间。

在下面的示意图中,可以看到 db total 的变化过程,最终我们得到的 snapshot 数据大小就是 Pod 数据的大小,这对我们节约数据传输时间来讲是非常重要的。

在这里插入图片描述

在这里插入图片描述

Pod 禁写的小坑

在前面的拆分流程中,我们提到 K8s 禁止写一类资源的时候,可以通过 MutatingWebhook 来实现,就是直接返回 deny 结果即可,比较简单。这里记录一下我们当时遇到的一个小坑点。

我们最初的 MutatingWebhookConfiguration 配置如下, 但是我们发现 apply 这个配置后,还是能够收到 Pod 的更新 event 消息。

// 第一个版本配置,有问题
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: deny-pods-write
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    url: https://extensions.xxx/always-deny
  failurePolicy: Fail
  name: always-deny.extensions.k8s
  namespaceSelector: {}
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - "*"
    resources:
    - pods
    scope: '*'  
  sideEffects: NoneOnDryRun

经过排查后发现是 Pod 的 status 字段被更新,通过阅读 apiserver 的代码,我们发现与 Pod 存储有关的 resource 不仅仅只有 Pod 一个,还有下面的类型,Pod status 与 Pod 对于 apiserver 的存储来讲是不同的资源。

"pods":             podStorage.Pod,
"pods/attach":      podStorage.Attach,
"pods/status":      podStorage.Status,
"pods/log":         podStorage.Log,
"pods/exec":        podStorage.Exec,
"pods/portforward": podStorage.PortForward,
"pods/proxy":       podStorage.Proxy,
"pods/binding":     podStorage.Binding,

经过调整后,下面的配置是能够禁止 Pod 数据完全更新的配置,注意其中的 resource 配置字段。

这是一个小坑点,记录在此。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: deny-pods-write
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    url: https://extensions.xxx/always-deny
  failurePolicy: Fail
  name: always-deny.extensions.k8s
  namespaceSelector: {}
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - "*"
    resources:
    - pods
    - pods/status
    - pods/binding
    scope: '*'  
  sideEffects: NoneOnDryRun

最后的拆分流程

在解决了前面的问题后,我们最后的拆分流程也就出来了。

示意如下:

在这里插入图片描述

在数据拆分期间,仅有 Pod 数据不可以有写操作,读是可以的,其他资源可以正常读写。整个流程可以通过程序自动化的来实现。

Pod 的禁写操作的时间根据 Pod 数据的大小而所有变化,主要消耗在 Pod 数据 copy 过程上,基本整个过程在几分钟内即可完成。

除了 kube-apiserver 无法避免需要更新存储配置重启外,不需要任何组件重启。同时也节省了大量的与组件 owner 沟通时间,也避免了众多操作过程中的众多不确定性。

整个拆分过程一个人完全可以胜任。

PART. 4 最后的总结

本文从数据拆分的目标出发,借鉴了前人经验,但根据自身的实际情况和要求,突破了之前的经验窠臼,通过技术创新解决了组件重启和数据一致性保障问题,在提升效率的同时也在技术上保障了安全性。

现过程抽丝剥茧介绍了整个思考过程和实现关键点。

在这里插入图片描述

整个思考过程和实现关键点

我们并没有发明创造了什么,只是在现有逻辑和工具基础上,稍加改良从而来完成我们的目标。然而改造和改良过程的背后是需要我们了解底层的细枝末节,这并不是画几个框框就能了解到的。

知其然知其所以然在大部分工作中都是必须的,虽然这会占用我们很多时间,但这些时间是值得的。

最后借用一句古话来结束:

运用之妙,存乎一心
与诸君共勉。

「参考资料」

(1)【etcd storage limit】:

https://etcd.io/docs/v3.3/dev-guide/limit/

(2)【etcd snapshot】:

https://etcd.io/docs/v3.3/op-guide/recovery/

(3)【攀登规模化的高峰 - 蚂蚁集团大规模 Sigma 集群 ApiServer 优化实践】:

https://www.sofastack.tech/blog/climbing-to-the-top-of-scale-ant-groups-large-scale-sigma-cluster-apiserver-optimization-in-practice/
在这里插入图片描述

【信息科学与工程学】计算机科学与自动化——第一百九十八篇 高并发高可用的知识点01 以上补充约 110 行,涵盖分布式协调、服务网格细节、数据库高级特性、性能分析工具、混沌工程、安全渗透、合规审计、研发效能、新兴技术、架构模式、实用工具及经典论文。涵盖数据库索引与锁、NoSQL、前端性能、安全攻防、压测工具深入、监控深入、云原生存储、服务治理扩展、代码质量、DevOps 文化、行业标准、软技能及未来趋势。好的,继续补充更多高并发、高可用、高可靠的知识与技术,涵盖更深层的数据库内部机制、分布式事务、服务治理、运维自动化、安全与前沿技术等领域。 阅读详情

相关推荐

【信息科学与工程学】【运营科学】第二篇 C4信息与通信网络运营 (C4) ——数据中心网络运营04

典型值:λ1​∈[−0.02,−0.01], λ2​∈[−0.01,0], η1​,η3​∈[0.1,0.3], η2​,η4​∈[0.05,0.15]参数:δ1​∈[−0.8,−0.3], δ2​∈[−0.5,−0.2], δ3​∈[−0.4,−0.1], δ4​∈[−0.6,−0.2]参数:φ1​∈[0.1,0.3], φ2​∈[−0.2,−0.05], φ3​∈[−0.1,0.1], φ4​∈[−0.3,−0.1]参数:α∈[0.3,0.6], β∈[0.2,0.4], γ∈[0.01,0.03]

weixin_49199313的博客 1207

Etcd特性学习5——运维操作(最后一篇)

01 make-mirror 在另外一个集群构建一个镜像。同时实时将一个集群的变更同步到另外一个集群中。同时会在终端上打印同步的key个数。 命令格式 etcdctl make-mirror [options] <destination> [flags] 其中的<destination>是镜像目的集群的endpoint列表。 命令示例: etcdctl --endpoints http://127.0.0.1:12379 make-mirror http://127.0.0.1:2

丰年留客的专栏 1973

【信息科学与工程学】计算机科学与自动化-——第十五篇云计算 11 算法篇

在百万级虚拟机、百万级租户的超大规模云环境中,核心算法挑战包括:挑战维度具体问题算法需求技术复杂度规模性​百万级对象并发管理分布式算法、分区算法极高(NP-hard问题)多租户​租户间隔离与资源共享公平性算法、隔离算法高(多目标优化)动态性​资源需求实时变化自适应算法、在线算法高(实时决策)异构性​硬件/负载/需求差异分类算法、匹配算法中高(多维度优化)经济性​资源利用率与成本平衡优化算法、博弈论算法高(多目标博弈)以下是按领域分类的详细算法列表:算法名称数学建模表达核心思想时间复杂度适用场景推理步骤首次适

weixin_49199313的博客 1395

Etcd教程 — 第五章 Etcd之etcdctl的使用

Etcd教程 — 第五章 Etcd之etcdctl的使用

Mr_XiMu的博客 8649

提升仿真效率:Synopsys VCS高级编译选项优化技巧(2023最新版)

本文深入探讨了Synopsys VCS 2023版本中的高级编译选项优化技巧,帮助工程师提升仿真效率。通过并行编译、智能代码分析、精准覆盖率采样等策略,结合真实案例展示如何显著缩短仿真周期并优化资源使用,适用于数字芯片验证和大型SoC设计。

weixin_30338481的博客 113

【技术分享】ArcGIS Pro底图色彩优化实战:从DEM到山体阴影的进阶调色技巧

本文深入探讨了在ArcGIS Pro中优化地形底图色彩的进阶技巧。通过分析DEM与山体阴影的直方图,详细讲解了如何运用图层混合、亮度、对比度及伽马值等核心参数进行精细化调色,从而将灰暗平淡的地图转化为层次分明、视觉美观的专业成果。

5b6n7m8的博客 759

如何合并多个etcd集群的数据

如果多个etcd集群的键值不重复,能否将数据合并?能否像数据库一样,将数据从旧库导出,然后在新库插入就万事大吉呢?答案是不能,etcd并没有提供类似功能,倒是有个备份功能,但是多个集群的备份数据却无法直接合并到一起。难道只能写个循环,将老集群的数据遍历出来,然后再插入新集群吗?这样也太土了吧,于是开始在网上找各种资料,可总是无功而返,看来只能老老实实看etcd官方资料了。功夫不负有心人,终于找到一...

超级码力——Stay hungry, stay foolish! 2872

【流复制环境PostgreSQL-14.1到PostgreSQL-16.1大版本升级】

PostgreSQL大版本会定期添加新特性,这些新特性通常会改变系统表的布局,但内部数据存储格式很少改变。pg_upgrade通过创建新的系统表和重用旧的用户数据文件来执行快速升级。pg_upgrade升级主要有三种用法:1、使用pg_upgrade拷贝升级。2、使用pg_upgrade链接升级(带有- -link选项),- -link较快,但是启动新版本后修改了数据文件,再启动旧版本可能数据损坏,回滚较麻烦,所以尽量避免使用link参数进行升级。

小怪兽的博客 2926

etcd调研

etcd调研

zhouzhenyong的专栏 1487

SAM2与Docker的完美组合:5分钟搞定Linux环境下的图像分割部署

本文详细介绍了如何在Linux环境下通过Docker快速部署SAM2图像分割模型,解决安装过程中的常见问题。文章提供了从镜像选择、CUDA版本匹配到容器构建的全流程指南,特别针对GPU加速环境下的'cudnn_frontend: No execution plans support the graph'等报错提供了实用解决方案,帮助开发者在5分钟内完成高效部署。

fern8的博客 933

halcon3D 点云拟合球体

halon3D 点云拟合球面

weixin_43213429的博客 1639

对etcd进行数据拆分

我们现在是一个k8s集群,多个租户共同使用,包括如果后期有新用户上云,申请到的资源同样也会以node节点的形式加入到现有的master节点,这样当一定规模的服务运行在集群中,势必会增加etcd的负担,整个ku8集群的数据都是存放在etcd中,只有进行合理的拆分,才能提升etcd的性能。 进行etcd拆分的情况如下图: 老版本的etcd仍然提供服务,只存储pod、svc等信息。新版本的etcd提供着存储ku8的events信息。 之前就是因为把所有数据存放到一套etcd集群,出现events信息太多导致et

qq_44397993的博客 1513

拼多多笔试真题-多多送快递(C++/Py/Java /Js/Go)

拼多多技术岗笔试 8月2号真题,拼多多笔试第三题【多多送快递】,提供详细思路讲解以及Java、C++、Python、Go、JavaScript多语言代码实现。拼多多笔试真题【多多送快递】多语言题解。

qq_45776114的博客 292

etcd跨机房部署方案

使用ETCD做为元数据方便快捷,但是谈到跨机房灾备可能就迷糊了,我们在做节日灾备的时候同样遇到了问题, 通过查阅官方文档找到了解决方案,官方提供make-mirror方法,提供数据镜像服务 注意:make-mirror 的使用需要依赖于API版本3, 使用API2的无法通过该工具做数据同步 有关ETCD的编译安装这里就不在说明了, 不明白的可以参考官方说明:https://git...

weixin_30535167的博客 1125

【AI产品思路】用户旅程图与用户全生命周期:到底是什么关系?

用户旅程图(User Journey Map)和用户全生命周期(User Lifecycle)是两种不同的分析框架。用户旅程聚焦单次交易体验的完整过程(如浏览→下单→售后),关注触点、情绪和优化点;而用户生命周期则关注用户与平台的长期关系(从认知到流失),着重提升留存和LTV。两者互为补充:生命周期是时间维度的关系框架,旅程图是行为维度的体验路径。电商运营中,旅程优化改善单次体验,生命周期优化解决长期增长问题,二者结合才能实现体验提升与商业效率的双赢。

qq_25438419的博客 217

《QGIS快速入门与应用基础》230:添加图例工具

本文系统讲解QGIS图例制作的核心方法与优化技巧。首先强调图例与地图框的绑定逻辑,提出三种绑定状态及适用场景。详细拆解三种图例添加方法:布局工具栏一键添加法(新手首选)、菜单栏添加法和右键快捷创建法,并给出单地图框和多地图框两种典型场景的规范技巧。重点解决新手常见的图例杂乱、样式不规范、内容脱节等问题,帮助用户从基础图例制作进阶到符合行业标准的专业图例设计。通过分步操作图示和实用技巧,使读者能够快速掌握图例创建的核心流程与优化方法。

LuckyHanMo的博客 549

【ETCD】【模块划分】etcd 项目模块划分一览

自版本 3.5 起,etcd 项目已经被组织成多个,并托管在一个中。- 包含 API 定义(如协议文件和 proto 生成的库),定义了 etcd 客户端与服务器之间的通信协议。- 包含一些供 etcd 使用的工具包,但并不特定于 etcd 本身。只有那些可能在未来被移出并独立为一个仓库的包才会放在这里。请避免将具有大量自身依赖的代码添加到此处,因为这些代码会自动成为客户端库的依赖(而我们希望保持客户端库的轻量)。- 用于通过网络(grpc)与 etcd 通信的客户端库。推荐所有新的 etcd 使用场景。

zhangshenglu1的博客 1086

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

揭秘 ChatGPT 背后的技术栈:将Kubernetes扩展到2500个节点

我们的Dota项目是在Kubernetes上开始的,随着规模的扩大,我们注意到新的Kubernetes节点经常长时间处于Pending状态。我们主要使用Kubernetes作为批调度系统,并依赖于我们的自动缩放器来动态地扩展和缩小我们的集群——这让我们大大降低了空闲节点的成本,同时在快速迭代的同时仍然提供了低延迟。我们追踪到内核的ARP协议栈。在对fio的性能进行基准测试时,我们看到etcd只能使用大约10%的可用IOPS,因为写延迟为2ms,而etcd执行顺序I/O,这使得它受到延迟的限制。

weixin_44800629的博客 737

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

使用web界面etcd-manager对etcd集群进行管理

1.获取etcd-manager的代码git clone https://github.com/shiguanghuxian/etcd-manage2.安装docker和 docker-composecurl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun# docker-compose 1.27 报错 segm...

reblue520的专栏 4582

攀登规模化的高峰 - 蚂蚁集团大规模 Sigma 集群 ApiServer 优化实践

文|唐博(花名:博易 蚂蚁集团技术专家) ​ 谭崇康(花名:见云 蚂蚁集团高级技术家) 本文 10316 字 阅读 18 分钟 ▼ 蚂蚁集团运行着全球最大的 Kubernetes*(内部称为 Sigma)* 集群之一。Kubernetes 社区官方以 5K node 作为 Kubernetes 规模化的事实标准,而蚂蚁集团在 2019 年的时候,就已经维护着单集群规模超过 1W node 的 Kubernetes 集群。 这不仅仅是单集群节点量级上的差异,更是业务规模的差异,业务多样化和复杂度上

金融级分布式架构 857

etcd 架构原理学习(来自etcd实战)

参考 etcd 实战 极客时间 整体架构 基础模块介绍 client 层: 包含 client v2 和 v3 两个大版本 API 客户端 API 网络层: 主要包含 clent 访问 server 和 server 节点之间的通信协议 clent 访问 server 分为两个版本:v2 API 采用 HTTP/1.x 协议,v3 API 采用 gRPC 协议 server 之间的通信:是指节点间通过 Raft 算法实现数据复制和 Leader 选举等功能时使用的 HTTP 协议 Raft 算

叮当猫的喵i 7679

etcdctl的使用[v3版本]

目录: 1. etcdctl的安装 2. etcdctl V3 3. etcdctl 常用命令 3.1. 指定etcd集群 3.2. 增删改查 3.3. 集群状态 3.4. 集群成员 etcdctl的v3版本与v2版本使用命令有所不同,本文介绍etcdctl v3版本的命令工具的使用方式。 1. etcdctl的安装 etcdctl的二进制文件可以在 git...

胡伟煌的博客 9万+

Kubernetes控制平面组件:Kubernetes如何使用etcd

本文主要对etcd在kubernetes中的应用进行介绍,包括二者的关系、kubernetes对象存储路径、二者如何交互、生产环境如何部署、最佳实践 以及 一些故障案例分析

a1369760658的博客 1570

ETCD集群部署与基本使用

etcd数据库

weixin_44853539的博客 2125

阿里巴巴 Sigma 调度和集群管理系统架构详解

阿里巴巴 Sigma 调度和集群管理系统架构详解划重点阿里巴巴 9 年双 11 经历下来,交易额增长了 280 倍、交易峰值增长 800 多倍、系统数呈现爆发式增长。系统在支撑双 11 过程中的复杂度和支撑难度以指数级形式上升。双 11 峰值的本质是用有限的成本最大化提升用户体验和集群吞吐能力,用合理的代价解决峰值。本文将从阿里巴巴统一调度体系、混部架构、云化架构三个方面详解...

weixin_33742618的博客 647

etcd TLS 全攻略

最近做了一个分布式数据推送项目,第一版是用普通 http 轮询,第二版是 grpc 的 stream 长连接,第三版是用 etcd 的 watch 功能进行监听。其中跨机房的数据备份是用官方提供的 make-mirror,于是乎如何在公网上安全保密的传输就格外重要了。 make-mirror 首先,make-mirror 这个官方工具有个巨大的缺点,就是假如两个 etcd 集群都设了账号密码...

Clockworkai's Blog 2193
上一篇: 来自开发者的点赞,SOFAStack 入选 2021 中国技术品牌影响力企业榜
下一篇: SOFA Weekly |社区开发者的搬砖日常、QA 整理、新手任务计划
SOFAStack
博客等级 码龄7年 747粉丝 389原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值