etcd 3.4.3:存储可靠性和安全性研究

注意佩雷夫。:本文的内容并不完全是我们博客的典型内容。但是,众所周知,etcd位于Kubernetes的心脏地带,这就是为什么由独立研究人员在可靠性领域进行的这项研究对于运行此系统的工程师来说很有趣。此外,有趣的是,即使在如此“低”的水平下,已经在生产中证明自己的开源项目也将如何得到改进。



键值(KV)等保管库是基于Raft共识算法的分布式数据库。在 2014年进行的分析,我们发现etcd 0.4.1默认情况下受到所谓的过时读取的影响(由于同步延迟-大约翻译,读操作返回的旧值不相关)。我们决定返回etcd(这次-版本3.4.3),以便再次详细评估其在可靠性和安全性方面的潜力。

我们发现具有成对的“键值”的操作严格可序列化,并且观察者(手表)的过程传递给顺序键中的每个更改。但是,etcd中的锁定从根本上讲是不安全的,并且由于错误而加剧了与它们相关的风险,因此,在等待锁定之后就不会检查租约的相关性您可以在项目博客上的报告中阅读etcd开发人员的评论

这项研究是由Linux基金会一部分Cloud Native Computing Foundation(CNCF)赞助的。它的执行完全符合Jepsen的道德政策

1.背景


Etc KV信息库是一个分布式系统,旨在用作协调基础。像ZookeeperConsul一样,etcd 以键值映射的形式存储少量很少更新的状态(默认情况下最大为8 GB,并在整个数据仓库中提供严格可序列化的读取,写入和微事务,以及诸如,跟踪之类的协调原语(手表)和领导者选择。KubernetesOpenStack等许多分布式系统都使用etcd存储集群元数据,协调数据的协调视图,选择领导者等。

2014年,我们已经对etcd 0.4.1进行了评估。然后我们发现默认情况下,由于优化,它倾向于过时读取。尽管有关Raft原理工作讨论了将读取操作分成线程并通过共识系统传递以确保可行性的必要性,但是etcd本地读取任何领导者,而无需检查较新领导者的最新状态。 etcd开发团队实现了可选的quorum标志,并且在etcd 3.0版API中默认情况下出现除跟踪操作以外的所有操作的线性化

etcd 3.0 API集中在键和值不透明的KV平面地图上不透明字节数组。使用范围查询,您可以模拟层次结构键。用户可以读取,写入和删除密钥,以及监视单个密钥或密钥范围的更新流程。 etcd工具包由租约(生命周期有限的可变对象,通过客户端的心跳请求将其保持在活动状态),锁(绑定到租约的专用命名对象)和领导者的选择提供了补充。

在3.0版中,etcd提供了有限的事务API用于具有许多键的原子操作。在此模型中,事务是具有谓词,真分支和假分支的条件表达式。谓词可以是多个关键比较的结合:根据一个关键的版本,全局修订版etcd或当前关键值,相等或各种不等式。正确和错误的分支可以包括多个读取和写入操作。所有这些都根据谓词估计的结果自动应用。

1.1保证文件的一致性


截至2019年10月,APIetcd 文档指出:“所有API调用均显示出一致的一致性-分布式系统上可用的最强形式的一致性保证。”事实并非如此:一致的一致性严格比线性化性弱,并且线性度在分布式系统中绝对是可以实现的。此外,文档指出“在读取操作期间,etcd不能保证传输[在群集的任何成员上可用的[最新(由查询完成后的外部时钟测量)]值”。这也是一个过于保守的说法:如果etcd提供了线性化能力,则读取操作始终与线性化顺序中的最新提交状态相关联。

该文档还声称etcd保证可序列化隔离:所有操作(甚至影响几个键的操作)都以某种常规顺序执行。作者将可序列化隔离描述为“分布式系统中可用的最强隔离级别”。这(取决于您所说的“隔离级别”的意思)也不是正确的。严格的可序列化性强于简单的可序列化性,而前者在分布式系统中也可以实现。

该文档说,etcd中的所有操作(跟踪除外)默认情况下都是线性的。在这种情况下,线性化被定义为与弱同步全局时钟的一致性。应该注意的是,这样的定义不仅与线性化定义不兼容Herlihy&Wing还暗示了因果关系的违规:处于繁忙时间的节点将尝试读取尚未开始的操作结果。我们假设etcd仍然不是时间机器,并且由于它是基于Raft算法的,因此应该使用公认的线性化定义。

由于etcd中的KV操作是可序列化和线性化的,因此我们认为实际上etcd默认提供严格的序列化。这是有道理的,因为所有键etcd都在单个状态机中,并且Raft提供了对该状态机上所有操作的完整排序。实际上,整个etcd数据集是单个线性化对象。

可选标志serializable 降低读取操作的级别从严格到常规可序列化的一致性,允许读取过时的提交状态。请注意,该标志serializable不会影响故事的可序列化性。KV操作etcd在所有情况下都可以序列化。

2.测试开发


要创建测试套件,我们使用了适当的Jepsen库。分析了etcd 3.4.3版本(截至19年10月的最新版本),适用于由5个节点组成的Debian Stretch集群。我们在这些群集中实现了许多故障,包括网络分区,隔离单个节点,将群集分为多数和少数以及将多数重叠的非过渡性分区。他们“丢弃”并暂停了节点的随机子集,还故意禁用了领导者。引入了几百秒和几毫秒的时间失真(数百秒)(快速的“闪烁”)。由于etcd支持动态更改组件数,因此我们在测试期间随机添加和删除了节点。

测试负载包括用于检查KV操作的寄存器,集和事务测试,以及锁和手表的专用负载。

2.1寄存器


为了评估KV操作期间etcd的可靠性,开发了一种寄存器测试在该测试对单元键执行随机的读取,写入,比较和设置操作使用比较/安装寄存器模型和版本信息,使用Knossos线性化工具评估结果

2.2套


为了量化陈旧的读取,开发一种测试,该测试使用比较并设置事务从单个键读取一组整数,然后向该组添加值。在测试期间,我们还对整个装置进行了并行读取。在完成测试后,结果进行了分析案件的发生时的元件,这是众所周知的存在于所述集合,在读取结果不存在。这些案例用于量化过时的读取和丢失的更新。

2.3追加测试


为了验证严格的可序列化性,开发了一个追加测试在此期间并行读取事务并将值添加到由唯一的整数集组成的列表中。每个列表都存储在一个etcd密钥中,并且在每个事务中进行添加,读取在一个事务中需要更改的每个密钥,然后在第二个事务中写入这些密钥并执行读取操作,这是受保护的以确保自首次读取以来未更改任何记录的密钥。在测试结束时,我们绘制了基于实时优先级的事务之间的关系以及读取和添加操作的关系。检查此图是否存在循环,可以确定操作是否严格可序列化。

尽管etcd防止事务多次写入同一密钥,但是您可以创建每个密钥最多包含一条记录的事务。我们还确保同一事务中的读取操作反映了同一事务中先前的写入操作。

2.4锁


作为协调服务,etcd承诺对分布式锁定提供内置支持。我们以两种方式研究了这些锁。最初,生成了随机的锁定和解锁请求,获得了每个锁定租约,并使用Java客户端中的内置etc客户端将其保持打开状态 keepalive直到释放为止。我们用Knossos测试了结果,看它们是否构成了锁服务的线性化实现。

为了进行更实际的测试(并量化锁定失败的频率),在对内存中的集合进行更新时,我们使用了locks和etcd来组织互斥。并搜索此集中丢失的更新。该测试使我们可以直接确认使用etcd作为互斥量的系统是否可以安全地更新内部状态。

锁定测试的第三版涉及保护租赁密钥,以修改存储在etcd中的集合。

2.5追踪


为了验证手表是否提供有关每次按键更新的信息,在测试中创建了一个按键,并盲目分配了唯一的整数值。同时,客户一次共享此密钥几秒钟。每次启动手表后,客户都从上次停止的版本开始。

在此过程结束时,我们确保每个客户观察到相同的密钥更改序列。

3.结果


3.1从第0版修订开始


跟踪密钥时,客户可以指定初始修订版,即“(从此开始(包括)跟踪的可选修订版”)。如果用户希望使用某个键查看每个操作,则可以指定etcd的第一个修订版。这是什么审核?数据模型词汇表未提供此问题的答案;版本被描述为单调递增的64位计数器,但是不清楚etcd是从0还是从1开始。可以合理地假设倒数是从头开始的(以防万一)。

las,这是错误的。请求第0个修订版会导致etcd从服务器上当前修订版加一个开始广播更新。,但不是最开始的。第一个修订版的请求给出了所有更改。这种现象没有记录在任何地方

我们认为,实际上,这种微妙之处不太可能导致生产中出现问题,因为大多数集群都不会停留在第一版上。另外,etcd会随时间压缩故事,因此,在实际应用中,无论如何,从第一版开始,它很可能不需要读取所有版本。这种行为是合理的,但不会损害文档中的相应描述。

3.2神话般的锁


锁定的API文档指出,锁定的密钥“可以与事务结合使用,以确保仅在拥有锁定时才在etcd中进行更新”。很奇怪,但是它不能为锁本身提供任何保证,并且没有说明它们的用途。

但是,在其他材料中,维护者etcd仍共享有关锁使用的信息。例如,etcd 3.2发行公告描述了etcdctl用于阻止磁盘上文件共享更改的应用程序。另外,在GitHub上的一个问题中,有关特定用途的问题中一位etcd开发人员回答了以下问题:

etcd , ( ) , ( etcd), - :

  1. etcd;
  2. - ( , etcd);
  3. .

就是这样的例子 etcdctl:锁是用来保护团队的put,但并未将锁键绑定到更新。

las,这是不安全的,因为它允许多个客户端同时持有相同的锁。进程的暂停,网络崩溃或分区使问题更加严重,但是,在完全正常的群集中也可能发生此问题,而没有任何外部故障。例如,在此测试运行中,进程号3成功设置了锁定,并且甚至在进程3有机会删除它之前,进程1也并行获得了相同的锁:



在短TTL的租约中,互斥体违规最为明显:仅经过数分钟的测试(即使在健康的群集中),TTL,1、2和3秒也无法提供互斥。进程中止和网络分区导致出现问题的速度甚至更快。

在我们的一种锁测试变体中,etcd互斥锁用于保护一组整数的联合更新(如etcd文档所建议的)。每次更新都会读取当前的内存样本值,大约一秒钟后,会在添加唯一元素的情况下将该集合写回。使用具有2秒TTL的租约,五个并行进程以及一个每五秒钟暂停的进程,我们能够稳定地损失大约18%的已确认更新。

etcd中的内部锁定机制加剧了这个问题。如果一个客户端等待另一个客户端对其进行解锁,丢失其租约,并且在释放该锁之后,则服务器在通知该客户端现在该锁位于其后,不会再次检查租约以确保其仍然有效。

包括额外的租约检查,以及选择更长的TTL和仔细设置选举超时,将减少此问题的发生。但是,不能完全消除互斥锁违规,因为在异步系统中,分布式锁从根本上来说是不安全的。 Martin Kleppmann博士在他的文章中令人信服地描述了这一点关于分布式锁。据他介绍,阻塞服务必须牺牲正确性才能维持异步系统中的生存能力:如果进程在控制阻塞时崩溃,则阻塞服务需要某种方式来强制解除锁定。但是,如果该进程实际上并未失败,而是运行缓慢或暂时不可用,则对其进行解锁可能导致该进程同时被保存在多个位置。

但是,即使分布式阻止服务使用了某种魔术故障检测器,并且实际上可以保证相互排斥,但在某些非本地资源的情况下,使用它仍然是不安全的。假设进程A持有锁时向数据库D发送消息。此后,进程A崩溃,进程B接收到一个锁,并且还向基D发送了一条消息。问题是,来自进程A的消息(由于异步)可能出现在来自进程B的消息之后,这违反了锁旨在提供的互斥性。 。

为避免此问题,有必要依靠以下事实:存储系统本身将支持事务的正确性,或者,如果锁定服务提供了这种机制,则使用防护”令牌,将包含在锁持有人执行的所有操作中。这将确保在当前锁所有者的操作之间不会突然发生前一个锁所有者的操作。例如,在Google的Chubby阻止服务中,这些令牌称为音序器。在etcd中,您可以将锁键修订版用作全局排序的阻止令牌。

另外,etcd中的锁定键可用于保护etcd本身中的事务更新。在交易中检查锁定密钥版本,如果不再持有该锁(即锁密钥版本大于零),则用户可以阻止交易。在我们的测试中,这种方法使我们能够成功隔离读取-修改-写入操作,其中写入是唯一受锁定保护的事务。这种方法提供的隔离类似于弹幕令牌,但是(像弹幕令牌一样)不能保证原子性:在包含许多操作的更新过程中,进程可能崩溃或丢失互斥体,从而使etcd处于逻辑不一致的状态。

项目问题的工作成果:

4。讨论


在我们的测试中,etcd 3.4.3达到了有关KV操作的期望:我们观察到了严格的可序列化的一致性,包括读,写甚至多键事务,尽管进程暂停,崩溃,时钟和网络的操作以及集群成员数量的变化。默认情况下,严格的可序列化行为是在KV操作中实现的;serializable设置了标志的读取性能导致出现过时的读取(如文档中所述)。

监控(手表)正常工作-至少在单个按键上。在压缩历史记录破坏了旧数据之前,手表成功地发布了每个密钥更新。

但是,事实证明,etcd中的锁(像所有分布式锁一样)不提供互斥。不同的进程可以同时持有锁-即使在时钟完全同步的健康集群中也是如此。带有锁定API的文档没有对此进行任何说明,并且所提供的锁定示例也不安全。但是,此补丁发布后,锁的一些问题仍然存在

由于我们的合作,etcd团队对文档进行了许多修改(它们已经出现在GitHub上,并将在项目网站的未来版本中发布)。 GitHub Warranties API页面现在指出,默认情况下etcd 严格可序列化并取消了串行和可序列化是分布式系统中可用的最强一致性级别的主张。关于修订,现在指示开始应该从单元(1)开始,尽管API文档仍然没有说从0修订开始的尝试将导致“输出在当前修订加1之后发生的事件”。而不是预期的“所有事件的发送”。锁定安全性问题的文档正在开发中

从零修订版开始,某些文档更改(例如描述尝试读取时etcd的特殊行为)仍然需要引起注意。

像往常一样,我们强调Jepsen在安全性验证方面更喜欢实验性的方法:我们可以确认错误的存在,但不能确认错误的存在。人们在寻找问题上付出了巨大的努力,但是我们不能证明etcd的一般正确性。

4.1建议


如果在etcd中使用锁,请考虑是否需要它们以确保安全性或通过概率限制并发性来简单地提高性能。可以使用Etcd锁来提高性能,但是将其用于安全目的可能会带来风险。

特别是,如果您使用etcd锁来保护共享资源,例如文件,数据库或服务,则该资源应保证安全而不被阻塞。实现此目的的一种方法是使用单调弹幕令牌。例如,它可能是与当前持有的锁定键关联的etcd版本。共享资源必须确保客户端使用令牌后y为了执行某些操作,任何带有令牌的操作x < y都会被拒绝。这种方法不能确保原子性,但是可以保证锁定框架内的操作按顺序执行,而不是间歇执行。

我们怀疑普通用户不太可能遇到此问题。但是,如果你还是依靠阅读所有从ETCD的变化,从第一个版本,记住,你需要传递1,而不是0作为参数。我们的实验表明,零修订在这种情况下,意思是“当前版本”,而不是“最早。”

最后,锁和etcd(如所有分布式锁)会误导用户:他们可能希望将它们用作常规锁,但是当他们意识到这些锁不提供互斥时,会感到非常惊讶。API文档,博客文章以及GitHub上的问题都没有说明这种风险。我们建议您在etcd文档中包括锁不提供互斥的信息,并提供使用弹幕令牌更新共享资源状态的示例,而不是可能导致更新丢失的示例。

4.2进一步的计划


etcd项目已被认为稳定了好几年:基于它的Raft算法运行良好,用于KV操作的API简单明了。尽管最近有一些附加功能收到了新的API,但其语义相对简单。我们认为,我们已经研究了足够的基本命令,例如getput,事务,阻止和跟踪。但是,还应该执行其他测试。

目前,我们尚未对删除进行足够详细的评估注意:不断创建和删除对象时,可能存在与版本和修订版相关的边界情况。在以后的测试中,我们打算对清除操作进行更仔细的研究。我们也没有测试范围查询或使用多个键的跟踪操作,尽管我们怀疑它们的语义类似于使用单个键的操作。

在测试中,我们使用了进程的暂停,崩溃,时钟的操纵,网络的划分以及集群的组成的更改;在幕后,在一个节点上出现磁盘损坏和其他拜占庭式故障等问题。这些机会可能会在未来的研究中加以探讨。

这项工作得到了Cloud Native Computing Foundation的支持。Linux Foundation的一部分,符合Jepsen的道德政策我们要感谢etcd团队的帮助,尤其是以下代表:克里斯·阿尼什奇克,李圭镐,李翔,李三岳,胡静怡和布兰登·飞利浦。

译者的PS


另请参阅我们的博客:

Source: https://habr.com/ru/post/undefined/


All Articles