本文主要结合Paxos、Raft等算法,介绍etcd集群的工作机制。同时,结合OpenShift中的etcd集群,我们观察etcd在PaaS中发挥的作用。


本文在书写过程中参考了红帽官方文档和一些网上材料,文后给出参考链接。


etcd九连问

etcd是一个可信赖的分布式键值存储服务(注意不是存储),它能够为整个分布式集群存储一些关键数据,协助分布式集群的正常运转。内部包含是一个键值(Key-Value)数据库。请记住一点:etcd是键值数据库,但键值数据库不完全是etcd,键值数据库只是etcd的一部分(后文会详细介绍)。


etcd它是CoreOS(2018年被红帽收购)发起的开源项目。https://github.com/etcd-io/etcd

九连问: etcd的分析与探究_java

针对etcd,几个问题来了。


1.什么是键值数据库?

键值数据库是一种非关系数据库,它使用简单的键值方法来存储数据。键值数据库将数据存储为键值对集合,其中键作为唯一标识符。


2.我们用KV存储服务能干啥?

我们可以用存储服务服务中介提供服务。


3.那么服务中介又是什么呢?

服务中介就是一个包含key/value键值对的字典,key是服务名称,value是服务提供者的地址列表。


4.那么服务中介又能干什么的呢?

服务中介为服务提供者和服务消费者建立联系。


5.服务中介可以提供联系,So What?

服务提供者、服务消费者和服务中介三者,实现了服务发现机制。同时,etcd还提供服务注册中心的功能。


6.服务发现是干啥呢?

服务发现,就是让服务的消费者,找到服务提供者者的机制。


7.服务注册和服务发现的目的是什么呢?

在传统的物理服务器和虚拟机中,应用的增加或减少频率并不高,应用横向弹性伸缩做的也不多。但在微服务和容器化环境中,应用的增加频率和弹性伸缩发生的概率很高。所以服务的地址可能经常会发生变化。例如初始情况100服务调用A服务。当负载增加后,我们对服务A做横向扩展,从1个实例扩展到20个。这个时候,我们难道调用它的100个服务修改配置?这时候,服务注册和服务发现就比较重要。


简而言之,etcd可以作为服务的注册中心,实现服务注册;也可以为服务中介提供支持,从而实现服务发现。所以etcd在微服务中发挥重要的作用。


8.那么,etcd究竟实现了什么业务系统特性?

etcd作为分布式键值存储服务,它必须要保证分布式系统数据的可用性和一致性


9.那么服务注册和发现的逻辑是什么?

我们大致介绍一下服务注册发现的流程。

服务注册

服务提供者的实例(例如OCP中的pod),在启动时或者位置信息发生变化时会向服务注册中心(etcd)注册自身,在停止时会向服务注册表注销自身,如果服务提供者的实例发生故障,在一段时间内不发送心跳之后,也会被服务注册表注销。


服务发现(服务器端模式)

服务消费者对服务提供者的请求,被发往一个中央路由器或者负载均衡器(OCP的SVC或router),中央路由器或者负载均衡器查询服务注册表获取服务提供者的位置信息,并将请求转发给服务提供者。


在OCP中:每创建一个Service会分配一个Service IP地址,称为ClusterIP,这个IP地址是一个虚拟的地址,无法执行ping操作。同时自动在内部DNS注册一条对应的A记录,这就完成了服务注册,注册信息全部保存在Etcd中。


在OCP中,服务发现会使用router或svc。当服务之间的调用都发生在OCP集群内时,使用SVC做服务发现。OCP3内置的SkyDNS。SkyDNS会监听KubernetesAPI,当新创建一个Service,SkyDNS中就会提供<service-name>.<project-name>.svc.cluster.local域名的解析。当服务器请求通过service发过来以后,或转发给OCP node的SkyDNS。如果本地的cache中没有记录,则则Master的SkyDNS主进程请求解析,master SkyDNS会读取etcd中的KV数据。


当服务消费者在OCP外部时,请求会到router上。etcd中的pod信息(作为route的endpoint),会定期同步到haproxy容器中,并生成.map文件,map文件是用来对route分类。然后按照一定频率根据map文件的内容,修改haproxy的配置文件,再重启 HAPorxy 进程来应用新修改了的配置文件。

九连问: etcd的分析与探究_java_02

我们查看一个map文件的内容:

九连问: etcd的分析与探究_java_03

route上记录了FQDN和pod ip(服务提供者)之间的对应。服务消费者通过haproxy中的route记录,找到对应pod的IP。

九连问: etcd的分析与探究_java_04



数据一致性算法


既然在分布式系统中,我们要利用etcd保证数据的可用性和一致性,那么etcd就必然符合数据一致性算法的规范。一致性算法有Paxos、Raft等。


Paxos算法的由来还有个典故“拜占庭将军问题”。由于这种方式较为复杂,很多KV数据库并未使用Paxos算法。而使用Raft算法。etcd使用的是Paxos算法。


Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选人(Candidate):

  • Leader接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。

  • Follower接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。

  • CandidateLeader选举过程中的临时角色。


九连问: etcd的分析与探究_java_05




接下来,我们看etcd server的架构。主要有4部分:

  • HTTP Server:用于处理用户发送的 API 请求以及其它 etcd 节点的同步与心跳信息请求。

  • Store:用于处理 etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 etcd 对用户提供的大多数 API 功能的具体实现。

  • Raft:Raft 强一致性算法的具体实现,是 etcd 的核心。

  • WAL:Write Ahead Log(预写式日志),是 etcd 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd 就通过 WAL 进行持久化存储。WAL 中,所有的数据提交前都会事先记录日志。Snapshot 是为了防止数据过多而进行的状态快照;Entry 表示存储的具体日志内容。


通常,一个用户的请求发送过来,会经由 HTTP Server 转发给 Store 进行具体的事务处理,如果涉及到节点的修改,则交给 Raft 模块进行状态的变更、日志的记录,然后再同步给别的 etcd 节点以确认数据提交,最后进行数据的提交,再次同步。

九连问: etcd的分析与探究_java_06


通常一个etcd集群会有三个节点

九连问: etcd的分析与探究_java_07


OpenShift中的etcd

我的实验环境是OCP3.11,只有一个master。因此只有一个ectd。

OCP3.11的etcd是以容器方式运行的。

九连问: etcd的分析与探究_java_08

首先设置环境变量:

[root@master ~]#  export ETCDCTL_API=3

[root@master ~]# ETCD_ALL_ENDPOINTS=` etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS --write-out=fields   member list | awk '/ClientURL/{printf "%s%s",sep,$3; sep=","}'`


查看etcd的状态:

[root@master ~]# etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS --write-out=table endpoint status

九连问: etcd的分析与探究_java_09

查看etcd的key

[root@master ~]# etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS  get / --prefix --keys-only


九连问: etcd的分析与探究_java_10

九连问: etcd的分析与探究_java_11

查看指定key的Value值。里如果我们查看/kubernetes.io/services/specs/default/router这个key对应的value。Value中显示了router暴露的端口号和service ip。

[root@master ~]# etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS  get /kubernetes.io/services/specs/default/router

九连问: etcd的分析与探究_java_12

那么,etcd在哪保存pod的ip呢?在endpoints对象中。

[root@master ~]# etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS  get /kubernetes.io/services/endpoints/default/docker-registry


九连问: etcd的分析与探究_java_13

如上图,pod ip是:10.128.0.114


docker-registry的pod增加到两个:

九连问: etcd的分析与探究_java_14

马上再次到etcd中查询,endpoint已经变成两个了。

九连问: etcd的分析与探究_java_15


找到节点对应的key:

[root@master ~]# etcdctl  --cert=$ETCD_PEER_CERT_FILE --key=$ETCD_PEER_KEY_FILE --cacert=$ETCD_TRUSTED_CA_FILE --endpoints=$ETCD_LISTEN_CLIENT_URLS get /kubernetes.io/minions  --prefix --keys-only

九连问: etcd的分析与探究_java_16

总之,我们可以根据key,查询到非常详细的信息。