介 绍


Etcd是一个开源的分布式键值存储,它由CoreOS团队开发,现在由Cloud Native Computing Foundation负责管理。这个词的发音是“et-cee-dee”,表示在多台机器上分发Unix系统的“/etc”目录,其中包含了大量的全局配置文件。它是许多分布式系统的主干,为跨服务器集群存储数据提供可靠的方式。它适用于各种操作系统,包括Linux、BSD和OS X。

Etcd具有下面这些属性:

  • 完全复制:集群中的每个节点都可以使用完整的存档

  • 高可用性:Etcd可用于避免硬件的单点故障或网络问题

  • 一致性:每次读取都会返回跨多主机的最新写入

  • 简单:包括一个定义良好、面向用户的API(gRPC)

  • 安全:实现了带有可选的客户端证书身份验证的自动化TLS

  • 快速:每秒10000次写入的基准速度

  • 可靠:使用Raft算法实现了存储的合理分布

Etcd的工作原理

在理解Etcd的工作机制之前,我们先定义三个关键概念:leaders、elections以及terms。在一个基于Raft的系统中,集群使用election为给定的term选择leader。

Leader处理所有需要集群一致协商的客户端请求。不需要一致协商的请求(如读取)可以由任何集群成员处理。Leader负责接受新的更改,将信息复制到follower节点,并在follower验证接受后提交更改。每个集群在任何给定的时间内只能有一个leader。

如果leader挂了或者不再响应了,那么其他节点将在预定的时间超时之后开启一个新的term来创建新election。每个节点维护一个随机的election计时器,该计时器表示节点在调用新的election以及选择自己作为候选之前需要等待的时间。

如果节点在超时发生之前没有收到leader的消息,则该节点将通过启动新的term、将自己标记为候选,并要求其他节点投票来开始新的election。每个节点投票给请求其投票的第一个候选。如果候选从集群中的大多数节点处获得了选票,那么它就成为了新的leader。但是,如果存在多个候选且获得了相同数量的选票,那么现有的election term将在没有leader的情况下结束,而新的term将以新的随机选举计时器开始。

如上所述,任何更改都必须连接到leader节点。Etcd没有立即接受和提交更改,而是使用Raft算法确保大多数节点都同意更改。Leader将提议的新值发送到集群中的每个节点。然后,节点发送一条消息确认收到了新值。如果大多数节点确认接收,那么leader提交新值,并向每个节点发送将该值提交到日志的消息。这意味着每次更改都需要得到集群节点的仲裁才能提交。

Kubernetes中的Etcd

自从2014年成为Kubernetes的一部分以来,Etcd社区呈现指数级的增长。CoreOS、谷歌、Redhat、IBM、思科、华为等等均是Etcd的贡献成员。其中AWS、谷歌云平台和Azure等大型云提供商成功在生产环境中使用了Etcd。

Etcd在Kubernetes中的工作是为分布式系统安全存储关键数据。它最著名的是Kubernetes的主数据存储,用于存储配置数据、状态和元数据。由于Kubernetes通常运行在几台机器的集群上,因此它是一个分布式系统,需要Etcd这样的分布式数据存储。

Etcd使得跨集群存储数据和监控更改变得更加容易,它允许来自Kubernetes集群的任何节点读取和写入数据。Kubernetes使用Etcd的watch功能来监控系统实际(actual)状态或期望(desired)状态的变化。如果这两个状态不同,Kubernetes会做出一些改变来调和这两个状态。kubectl命令的每次读取都从Etcd存储的数据中检索,所做的任何更改(kubectl apply)都会在Etcd中创建或更新条目,每次崩溃都会触发etcd中值的修改。

部署以及硬件建议

出于测试或开发目的,Etcd可以在笔记本电脑或轻量云上运行。然而,在生产环境中运行Etcd集群时,我们应该考虑Etcd官方文档提供的指导。它为良好稳定的生产部署提供了一个良好的起点。需要留意的是:

  • Etcd会将数据写入磁盘,因此强烈推荐使用SSD

  • 始终使用奇数个集群数量,因为需要通过仲裁来更新集群的状态

  • 出于性能考虑,集群通常不超过7个节点

让我们回顾一下在Kubernetes中部署Etcd集群所需的步骤。之后,我们将演示一些基本的CLI命令以及API调用。我们将结合Kubernetes的概念(如StatefulSets和PersistentVolume)进行部署。

预先准备

在继续demo之前,我们需要准备:

  • 一个谷歌云平台的账号:免费的tier应该足够了。你也可以选择大多数其他云提供商,只需进行少量修改即可。

  • 一个运行Rancher的服务器

启动Rancher实例

在你控制的服务器上启动Rancher实例。这里有一个非常简单直观的入门指南:https://rancher.com/quick-start/

使用Rancher部署GKE集群

参照本指南使用Rancher在GCP账户中设置和配置Kubernetes集群:

https://rancher.com/docs/rancher/v2.x/en/cluster-provisioning/hosted-kubernetes-clusters/gke/

在运行Rancher实例的同一服务器上安装Google Cloud SDK以及kubelet命令。按照上面提供的链接安装SDK,并通过Rancher UI安装kubelet。

使用gcloud init和gcloud auth login,确保gcloud命令能够访问你的GCP账户。

集群部署后,输入下面的命令检查基本的kubectl功能:

Etcd超全解:原理阐释及部署设置的最佳实践_第1张图片

在部署Etcd集群(通过kubectl或在Rancher的UI中导入YAML文件)之前,我们需要配置一些项。在GCE中,默认的持久化磁盘是pd-standard。我们将为Etcd部署配置pd-ssd。这不是强制性的,不过根据Etcd的建议,SSD是非常好的选择。查看此链接可以了解其他云提供商的存储类:

https://kubernetes.io/docs/concepts/storage/storage-classes/

让我们检查一下GCE提供的可用存储类。正如预期的那样,我们看到了一个默认的结果,叫做standard:

Etcd超全解:原理阐释及部署设置的最佳实践_第2张图片

应用下面这个YAML文件,更新zone的值来匹配你的首选项,这样我们就可以使用SSD存储了:

Etcd超全解:原理阐释及部署设置的最佳实践_第3张图片

我们再一次检查,可以看到,除了默认standard类之外,ssd也可以使用了:

Etcd超全解:原理阐释及部署设置的最佳实践_第4张图片

现在我们可以继续部署Etcd集群了。我们将创建一个带有3个副本的StatefulSet,每个副本都有一个ssd storageClass的专用卷。我们还需要部署两个服务,一个用于内部集群通信,一个用于通过API从外部访问集群。

在搭建集群时,我们需要将一些参数传递给Etcd二进制文件再到数据存储中。Listen-client-urls和listen-peer-urls选项指定Etcd服务器用于接受传入连接的本地地址。指定0.0.0.0作为IP地址意味着Etcd将监听所有可用接口上的连接。Advertise-client-urls和initial-advertise-peer-urls参数指定了在Etcd客户端或者其他Etcd成员联系etcd服务器时应该使用的地址。

下面的YAML文件定义了我们的两个服务以及Etcd StatefulSe图:

# etcd-sts.yaml---
apiVersion: v1
kind: Service
metadata:
name: etcd-client
spec:
type: LoadBalancer
ports:
- name: etcd-client
port: 2379
protocol: TCP
targetPort: 2379
selector:
app: etcd
---
apiVersion: v1
kind: Service
metadata:
name: etcd
spec:
clusterIP: None
ports:
- port: 2379
name: client
- port: 2380
name: peer
selector:
app: etcd
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: etcd
labels:
app: etcd
spec:
serviceName: etcd
replicas: 3
template:
metadata:
name: etcd
labels:
app: etcd
spec:
containers:
- name: etcd
image: quay.io/coreos/etcd:latest
ports:
- containerPort: 2379
name: client
- containerPort: 2380
name: peer
volumeMounts:
- name: data
mountPath: /var/run/etcd
command:
- /bin/sh
- -c
- | PEERS="etcd-0=http://etcd-0.etcd:2380,etcd-1=http://etcd-1.etcd:2380,etcd-2=http://etcd-2.etcd:2380"
exec etcd --name ${HOSTNAME} \
--listen-peer-urls http://0.0.0.0:2380 \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://${HOSTNAME}.etcd:2379 \
--initial-advertise-peer-urls http://${HOSTNAME}:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster ${PEERS} \
--initial-cluster-state new \
--data-dir /var/run/etcd/default.etcd
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: ssd
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi

输入下列命令应用YAML:

Etcd超全解:原理阐释及部署设置的最佳实践_第5张图片

在应用YAML文件后,我们可以在Rancher提供的不同选项卡中定义资源:

Etcd超全解:原理阐释及部署设置的最佳实践_第6张图片

Etcd超全解:原理阐释及部署设置的最佳实践_第7张图片

Etcd超全解:原理阐释及部署设置的最佳实践_第8张图片

与Etcd交互

与Etcd交互的方式主要有两种:使用etcdctl命令或者直接通过RESTful API。我们将简要介绍这两种方法,不过你还可以通过访问这里和这里的完整文档找到更加深入的信息和示例。

Etcdctl是一个和Etcd服务器交互的命令行接口。它可以用于执行各种操作,如设置、更新或者删除键、验证集群健康情况、添加或删除Etcd节点以及生成数据库快照。默认情况下,etcdctl使用v2 API与Etcd服务器通信来获得向后兼容性。如果希望etcdctl使用v3 API和Etcd通信,则必须通过ETCDCTL_API环境变量将版本设置为3。

对于API,发送到Etcd服务器的每一个请求都是一个gRPC远程过程调用。这个gRPC网关提供一个RESTful代理,能够将HTTP/JSON请求转换为gRPC消息。

让我们来找到API调用所需的外部IP:

Etcd超全解:原理阐释及部署设置的最佳实践_第9张图片

我们应该还能找到3个pods的名称,这样我们就可以使用etcdctl命令:

Etcd超全解:原理阐释及部署设置的最佳实践_第10张图片

我们检查Etcd版本。为此我们可以使用API或CLI(v2和v3).根据你选择的方法, 输出的结果将略有不同。

使用此命令可直接与API联系:

Etcd超全解:原理阐释及部署设置的最佳实践_第11张图片

检查API版本为v2的etcdctl客户端,输入:

Etcd超全解:原理阐释及部署设置的最佳实践_第12张图片

检查API版本为v3的etcdctl客户端,则输入:

Etcd超全解:原理阐释及部署设置的最佳实践_第13张图片

接下来,列出集群成员,就像我们上面做的那样:

{"members":[{"id":"2e80f96756a54ca9","name":"etcd-0","peerURLs":["http://etcd-0.etcd:2380"],"clientURLs":["http://etcd-0.etcd:2379"]},{"id":"7fd61f3f79d97779","name":"etcd-1","peerURLs":["http://etcd-1.etcd:2380"],"clientURLs":["http://etcd-1.etcd:2379"]},{"id":"b429c86e3cd4e077","name":"etcd-2","peerURLs":["http://etcd-2.etcd:2380"],"clientURLs":["http://etcd-2.etcd:2379"]}]}

V2版本的etcdctl:

Etcd超全解:原理阐释及部署设置的最佳实践_第14张图片

V3版本的etcdctl:

Etcd超全解:原理阐释及部署设置的最佳实践_第15张图片

在Etcd中设置和检索值

下面我们将介绍的最后一个示例是在Etcd集群中全部3个pods上创建一个键并检查其值。然后我们会杀掉leader,在我们的场景中是etcd-0,然后来看看新的leader是如何选出来的。最后,在集群恢复之后,我们将在所有成员上验证之前创建的键的值。我们会看到,没有数据丢失的情况发生,集群只是换了一个leader而已。

我们可以通过输入下面的命令来验证集群最初是健康的:

Etcd超全解:原理阐释及部署设置的最佳实践_第16张图片

接下来,验证当前leader。最后一个字段表明etcd-0是我们集群中的leader:

Etcd超全解:原理阐释及部署设置的最佳实践_第17张图片

使用该API,我们将创建一个名为message的键并给它分配一个值,请记住在下面的命令中把IP地址替换为你在集群中通过下面命令获取到的地址:

Etcd超全解:原理阐释及部署设置的最佳实践_第18张图片

无论查询哪个成员,键都具有相同的值。这帮助我们验证值是否已经复制到其他节点并提交到日志。

Etcd超全解:原理阐释及部署设置的最佳实践_第19张图片

演示高可用性和恢复

接下来,我们可以杀掉Etcd集群leader。这样我们可以看到新的leader是如何选出的,以及集群如何从degraded状态中恢复过来。删除与上面发现的Etcd leader相关的pod:

Etcd超全解:原理阐释及部署设置的最佳实践_第20张图片

下面我们检查一下集群的健康情况:

failed to check the health of member 2e80f96756a54ca9 on http://etcd-0.etcd:2379: Get http://etcd-0.etcd:2379/health: dial tcp: lookup etcd-0.etcd on 10.15.240.10:53: no such host
member 2e80f96756a54ca9 is unreachable: [http://etcd-0.etcd:2379] are all unreachable
member 7fd61f3f79d97779 is healthy: got healthy result from http://etcd-1.etcd:2379
member b429c86e3cd4e077 is healthy: got healthy result from http://etcd-2.etcd:2379cluster is degraded
command terminated with exit code 5

上面的信息表明,由于失去了leader节点,集群出于degrade状态。

一旦Kubernetes通过启动新实例来响应删除的pod,Etcd集群应该就恢复过来了:

Etcd超全解:原理阐释及部署设置的最佳实践_第21张图片

输入下面指令,我们可以看到新的leader已经选出来了:

Etcd超全解:原理阐释及部署设置的最佳实践_第22张图片

在我们的例子中,etcd-1节点被选为leader

如果我们再一次检查message键的值,会发现没有出现数据的损失:

Etcd超全解:原理阐释及部署设置的最佳实践_第23张图片

结 论

Etcd是一种非常强大、高可用以及可靠的分布式键值存储,专门为特定用例设计。常见的例子包括存储数据哭连接细节、缓存设置、特性标记等等。它被设计成顺序一致的,因此在整个集群中每个事件都是以相同的顺序存储。

我们了解了如何在Rancher的帮助下用Kubernetes建立并运行etcd集群。之后,我们能够使用一些基本的Etcd命令进行操作。为了更好的了解这个项目,键是如何组织的,如何为键设置TTLs,或者如何备份所有数据,参考官方的Etcd repo会是个不错的选择:

https://github.com/etcd-io/etcd/tree/master/Documentation