You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
18 KiB
Markdown

2 years ago
# 01 | etcd的前世今生为什么Kubernetes使用etcd
你好,我是唐聪。
今天是专栏课程的第一讲我们就从etcd的前世今生讲起。让我们一起穿越回2013年看看etcd最初是在什么业务场景下被设计出来的
2013年有一个叫CoreOS的创业团队他们构建了一个产品Container Linux它是一个开源、轻量级的操作系统侧重自动化、快速部署应用服务并要求应用程序都在容器中运行同时提供集群化的管理方案用户管理服务就像单机一样方便。
他们希望在重启任意一节点的时候,用户的服务不会因此而宕机,导致无法提供服务,因此需要运行多个副本。但是多个副本之间如何协调,如何避免变更的时候所有副本不可用呢?
为了解决这个问题CoreOS团队需要一个协调服务来存储服务配置信息、提供分布式锁等能力。怎么办呢当然是分析业务场景、痛点、核心目标然后是基于目标进行方案选型评估是选择社区开源方案还是自己造轮子。这其实就是我们遇到棘手问题时的通用解决思路CoreOS团队同样如此。
假设你是CoreOS团队成员你认为在这样的业务场景下理想中的解决方案应满足哪些目标呢
如果你有过一些开发经验,应该能想到一些关键点了,我根据自己的经验来总结一下,一个协调服务,理想状态下大概需要满足以下五个目标:
1. **可用性角度:高可用**。协调服务作为集群的控制面存储,它保存了各个服务的部署、运行信息。若它故障,可能会导致集群无法变更、服务副本数无法协调。业务服务若此时出现故障,无法创建新的副本,可能会影响用户数据面。
2. **数据一致性角度:提供读取“最新”数据的机制**。既然协调服务必须具备高可用的目标就必然不能存在单点故障single point of failure而多节点又引入了新的问题即多个节点之间的数据一致性如何保障比如一个集群3个节点A、B、C从节点A、B获取服务镜像版本是新的但节点C因为磁盘 I/O异常导致数据更新缓慢若控制端通过C节点获取数据那么可能会导致读取到过期数据服务镜像无法及时更新。
3. **容量角度:低容量、仅存储关键元数据配置。**协调服务保存的仅仅是服务、节点的配置信息(属于控制面配置),而不是与用户相关的数据。所以存储上不需要考虑数据分片,无需过度设计。
4. **功能:增删改查,监听数据变化的机制**。协调服务保存了服务的状态信息,若服务有变更或异常,相比控制端定时去轮询检查一个个服务状态,若能快速推送变更事件给控制端,则可提升服务可用性、减少协调服务不必要的性能开销。
5. **运维复杂度:可维护性。**在分布式系统中往往会遇到硬件Bug、软件Bug、人为操作错误导致节点宕机以及新增、替换节点等运维场景都需要对协调服务成员进行变更。若能提供API实现平滑地变更成员节点信息就可以大大降低运维复杂度减少运维成本同时可避免因人工变更不规范可能导致的服务异常。
了解完理想中的解决方案目标我们再来看CoreOS团队当时为什么选择了从0到1开发一个新的协调服务呢
如果使用开源软件当时其实是有ZooKeeper的但是他们为什么不用ZooKeeper呢我们来分析一下。
从高可用性、数据一致性、功能这三个角度来说ZooKeeper是满足CoreOS诉求的。然而当时的ZooKeeper不支持通过API安全地变更成员需要人工修改一个个节点的配置并重启进程。
若变更姿势不正确则有可能出现脑裂等严重故障。适配云环境、可平滑调整集群规模、在线变更运行时配置是CoreOS的期望目标而ZooKeeper在这块的可维护成本相对较高。
其次ZooKeeper是用 Java 编写的部署较繁琐占用较多的内存资源同时ZooKeeper RPC的序列化机制用的是Jute自己实现的RPC API。无法使用curl之类的常用工具与之互动CoreOS期望使用比较简单的HTTP + JSON。
因此CoreOS决定自己造轮子那CoreOS团队是如何根据系统目标进行技术方案选型的呢
## etcd v1和v2诞生
首先我们来看服务高可用及数据一致性。前面我们提到单副本存在单点故障,而多副本又引入数据一致性问题。
因此为了解决数据一致性问题需要引入一个共识算法确保各节点数据一致性并可容忍一定节点故障。常见的共识算法有Paxos、ZAB、Raft等。CoreOS团队选择了易理解实现的Raft算法它将复杂的一致性问题分解成Leader选举、日志同步、安全性三个相对独立的子问题只要集群一半以上节点存活就可提供服务具备良好的可用性。
其次我们再来看数据模型Data Model和API。数据模型参考了ZooKeeper使用的是基于目录的层次模式。API相比ZooKeeper来说使用了简单、易用的REST API提供了常用的Get/Set/Delete/Watch等API实现对key-value数据的查询、更新、删除、监听等操作。
key-value存储引擎上ZooKeeper使用的是Concurrent HashMap而etcd使用的是则是简单内存树它的节点数据结构精简后如下含节点路径、值、孩子节点信息。这是一个典型的低容量设计数据全放在内存无需考虑数据分片只能保存key的最新版本简单易实现。
![](https://static001.geekbang.org/resource/image/ff/41/ff4ee032739b9b170af1b2e2ba530e41.png?wh=1024*904)
```
type node struct {
Path string //节点路径
Parent *node //关联父亲节点
Value string //key的value值
ExpireTime time.Time //过期时间
Children map[string]*node //此节点的孩子节点
}
```
最后我们再来看可维护性。Raft算法提供了成员变更算法可基于此实现成员在线、安全变更同时此协调服务使用Go语言编写无依赖部署简单。
![](https://static001.geekbang.org/resource/image/dd/70/dd253e4fc19885fa6f00c278762ba270.png?wh=1920*1402)
基于以上技术方案和架构图CoreOS团队在2013年8月对外发布了第一个测试版本v0.1API v1版本命名为etcd。
那么etcd这个名字是怎么来的呢其实它源于两个方面unix的“/etc”文件夹和分布式系统(“D”istribute system)的D组合在一起表示etcd是用于存储分布式配置的信息存储服务。
v0.1版本实现了简单的HTTP Get/Set/Delete/Watch API但读数据一致性无法保证。v0.2版本支持通过指定consistent模式从Leader读取数据并将Test And Set机制修正为CAS(Compare And Swap)解决原子更新的问题同时发布了新的API版本v2这就是大家熟悉的etcd v2版本第一个非stable版本。
下面我用一幅时间轴图给你总结一下etcd v1/v2关键特性。
![](https://static001.geekbang.org/resource/image/d0/0e/d0af3537c0eef89b499a82693da23f0e.png?wh=1920*727)
## 为什么Kubernetes使用etcd?
这张图里我特别标注出了Kubernetes的发布时间点这个非常关键。我们必须先来说说这个事儿也就是Kubernetes和etcd的故事。
2014年6月Google的Kubernetes项目诞生了我们前面所讨论到Go语言编写、etcd高可用、Watch机制、CAS、TTL等特性正是Kubernetes所需要的它早期的0.4版本使用的正是etcd v0.2版本。
Kubernetes是如何使用etcd v2这些特性的呢举几个简单小例子。
当你使用Kubernetes声明式API部署服务的时候Kubernetes的控制器通过etcd Watch机制会实时监听资源变化事件对比实际状态与期望状态是否一致并采取协调动作使其一致。Kubernetes更新数据的时候通过CAS机制保证并发场景下的原子更新并通过对key设置TTL来存储Event事件提升Kubernetes集群的可观测性基于TTL特性Event事件key到期后可自动删除。
Kubernetes项目使用etcd除了技术因素也与当时的商业竞争有关。CoreOS是Kubernetes容器生态圈的核心成员之一。
当时Docker容器浪潮正席卷整个开源技术社区CoreOS也将容器集成到自家产品中。一开始与Docker公司还是合作伙伴然而Docker公司不断强化Docker的PaaS平台能力强势控制Docker社区这与CoreOS核心商业战略出现了冲突也损害了Google、RedHat等厂商的利益。
最终CoreOS与Docker分道扬镳并推出了rkt项目来对抗Docker然而此时Docker已深入人心CoreOS被Docker全面压制。
以Google、RedHat为首的阵营基于Google多年的大规模容器管理系统Borg经验结合社区的建议和实践构建以Kubernetes为核心的容器生态圈。相比Docker的垄断、独裁Kubernetes社区推行的是民主、开放原则Kubernetes每一层都可以通过插件化扩展在Google、RedHat的带领下不断发展壮大etcd也进入了快速发展期。
在2015年1月CoreOS发布了etcd第一个稳定版本2.0支持了quorum read提供了严格的线性一致性读能力。7月基于etcd 2.0的Kubernetes第一个生产环境可用版本v1.0.1发布了Kubernetes开始了新的里程碑的发展。
etcd v2在社区获得了广泛关注GitHub star数在2015年6月就高达6000+超过500个项目使用被广泛应用于配置存储、服务发现、主备选举等场景。
下图我从构建分布式系统的核心要素角度给你总结了etcd v2核心技术点。无论是NoSQL存储还是SQL存储、文档存储其实大家要解决的问题都是类似的基本就是图中总结的数据模型、复制、共识算法、API、事务、一致性、成员故障检测等方面。
希望通过此图帮助你了解从0到1如何构建、学习一个分布式系统要解决哪些技术点在心中有个初步认识后面的课程中我会再深入介绍。
![](https://static001.geekbang.org/resource/image/cd/f0/cde3f155f51bfd3d7fd78fe8e7ac9bf0.png?wh=1920*943)
## etcd v3诞生
然而随着Kubernetes项目不断发展v2版本的瓶颈和缺陷逐渐暴露遇到了若干性能和稳定性问题Kubernetes社区呼吁支持新的存储、批评etcd不可靠的声音开始不断出现。
具体有哪些问题呢?我给你总结了如下图:
![](https://static001.geekbang.org/resource/image/88/d1/881db1b7d05dc40771e9737f3117f5d1.png?wh=1920*577)
下面我分别从功能局限性、Watch事件的可靠性、性能、内存开销来分别给你剖析etcd v2的问题。
首先是**功能局限性问题。**它主要是指etcd v2不支持范围和分页查询、不支持多key事务。
第一etcd v2不支持范围查询和分页。分页对于数据较多的场景是必不可少的。在Kubernetes中在集群规模增大后Pod、Event等资源可能会出现数千个以上但是etcd v2不支持分页不支持范围查询大包等expensive request会导致严重的性能乃至雪崩问题。
第二etcd v2不支持多key事务。在实际转账等业务场景中往往我们需要在一个事务中同时更新多个key。
然后是**Watch机制可靠性问题**。Kubernetes项目严重依赖etcd Watch机制然而etcd v2是内存型、不支持保存key历史版本的数据库只在内存中使用滑动窗口保存了最近的1000条变更事件当etcd server写请求较多、网络波动时等场景很容易出现事件丢失问题进而又触发client数据全量拉取产生大量expensive request甚至导致etcd雪崩。
其次是**性能瓶颈问题**。etcd v2早期使用了简单、易调试的HTTP/1.x API但是随着Kubernetes支撑的集群规模越来越大HTTP/1.x协议的瓶颈逐渐暴露出来。比如集群规模大时由于HTTP/1.x协议没有压缩机制批量拉取较多Pod时容易导致APIServer和etcd出现CPU高负载、OOM、丢包等问题。
另一方面etcd v2 client会通过HTTP长连接轮询Watch事件当watcher较多的时候因HTTP/1.x不支持多路复用会创建大量的连接消耗server端过多的socket和内存资源。
同时etcd v2支持为每个key设置TTL过期时间client为了防止key的TTL过期后被删除需要周期性刷新key的TTL。
实际业务中很有可能若干key拥有相同的TTL可是在etcd v2中即使大量key TTL一样你也需要分别为每个key发起续期操作当key较多的时候这会显著增加集群负载、导致集群性能显著下降。
最后是**内存开销问题。**etcd v2在内存维护了一颗树来保存所有节点key及value。在数据量场景略大的场景如配置项较多、存储了大量Kubernetes Events 它会导致较大的内存开销同时etcd需要定时把全量内存树持久化到磁盘。这会消耗大量的CPU和磁盘 I/O资源对系统的稳定性造成一定影响。
为什么etcd v2有以上若干问题Consul等其他竞品依然没有被Kubernetes支持呢
一方面当时包括Consul在内没有一个开源项目是十全十美完全满足Kubernetes需求。而CoreOS团队一直在聆听社区的声音并积极改进解决社区的痛点。用户吐槽etcd不稳定他们就设计实现自动化的测试方案模拟、注入各类故障场景及时发现修复Bug以提升etcd稳定性。
另一方面用户吐槽性能问题针对etcd v2各种先天性缺陷问题他们从2015年就开始设计、实现新一代etcd v3方案去解决以上痛点并积极参与Kubernetes项目负责etcd v2到v3的存储引擎切换推动Kubernetes项目的前进。同时设计开发通用压测工具、输出Consul、ZooKeeper、etcd性能测试报告证明etcd的优越性。
etcd v3就是为了解决以上稳定性、扩展性、性能问题而诞生的。
在内存开销、Watch事件可靠性、功能局限上它通过引入B-tree、boltdb实现一个MVCC数据库数据模型从层次型目录结构改成扁平的key-value提供稳定可靠的事件通知实现了事务支持多key原子更新同时基于boltdb的持久化存储显著降低了etcd的内存占用、避免了etcd v2定期生成快照时的昂贵的资源开销。
性能上首先etcd v3使用了gRPC API使用protobuf定义消息消息编解码性能相比JSON超过2倍以上并通过HTTP/2.0多路复用机制减少了大量watcher等场景下的连接数。
其次使用Lease优化TTL机制每个Lease具有一个TTL相同的TTL的key关联一个LeaseLease过期的时候自动删除相关联的所有key不再需要为每个key单独续期。
最后是etcd v3支持范围、分页查询可避免大包等expensive request。
2016年6月etcd 3.0诞生随后Kubernetes 1.6发布默认启用etcd v3助力Kubernetes支撑5000节点集群规模。
下面的时间轴图我给你总结了etcd3重要特性及版本发布时间。从图中你可以看出从3.0到未来的3.5更稳、更快是etcd的追求目标。
![](https://static001.geekbang.org/resource/image/5f/6d/5f1bf807db06233ed51d142917798b6d.png?wh=1920*826)
从2013年发布第一个版本v0.1到今天的3.5.0-pre从v2到v3etcd走过了7年的历程etcd的稳定性、扩展性、性能不断提升。
发展到今天在GitHub上star数超过34K。在Kubernetes的业务场景磨炼下它不断成长走向稳定和成熟成为技术圈众所周知的开源产品而**v3方案的发布也标志着etcd进入了技术成熟期成为云原生时代的首选元数据存储产品。**
## 小结
最后我们来小结下今天的内容我们从如下几个方面介绍了etcd的前世今生并在过程中详细解读了为什么Kubernetes使用etcd
* etcd诞生背景 etcd v2源自CoreOS团队遇到的服务协调问题。
* etcd目标我们通过实际业务场景分析得到理想中的协调服务核心目标高可用、数据一致性、Watch、良好的可维护性等。而在CoreOS团队看来高可用、可维护性、适配云、简单的API、良好的性能对他们而言是非常重要的ZooKeeper无法满足所有诉求因此决定自己构建一个分布式存储服务。
* 介绍了v2基于目录的层级数据模型和API并从分布式系统的角度给你详细总结了etcd v2技术点。etcd的高可用、Watch机制与Kubernetes期望中的元数据存储是匹配的。etcd v2在Kubernetes的带动下获得了广泛的应用但也出现若干性能和稳定性、功能不足问题无法满足Kubernetes项目发展的需求。
* CoreOS团队未雨绸缪从问题萌芽时期就开始构建下一代etcd v3存储模型分别从性能、稳定性、功能上等成功解决了Kubernetes发展过程中遇到的瓶颈也捍卫住了作为Kubernetes存储组件的地位。
希望通过今天的介绍, 让你对etcd为什么有v2和v3两个大版本etcd如何从HTTP/1.x API到gRPC API、单版本数据库到多版本数据库、内存树到boltdb、TTL到Lease、单key原子更新到支持多key事务的演进过程有个清晰了解。希望你能有所收获在后续的课程中我会和你深入讨论各个模块的细节。
## 思考题
最后我给你留了一个思考题。分享一下在你的项目中你主要使用的是哪个etcd版本来解决什么问题呢使用的etcd v2 API还是v3 API呢在这过程中是否遇到过什么问题
感谢你的阅读,欢迎你把思考和观点写在留言区,也欢迎你把这篇文章分享给更多的朋友一起阅读。