Etcd 是一个使用一致性哈希算法(Raft)在分布式环境下的 key/value 存储服务。利用 Etcd 的特性,应用程序能够在集群中共享信息、配置或做服务发现,Etcd 会在集群的各个节点中复制这些数据并保证这些数据始终正确。html
System Requirements >= 8v CPU + 16GB RAM + 50GB SSDnode
静态就是在配置服务以前已经知道了节点的地址和集群的大小git
############################ # Build the latest version ############################ # 1.下载项目并编译 $ git clone https://github.com/etcd-io/etcd.git && cd etcd $ ./build To build a vendored etcd from the master branch via go get: # 2.设置GOPATH环境变量 $ export GOPATH='/Users/example/go' $ go get -v go.etcd.io/etcd $ go get -v go.etcd.io/etcd/etcdctl # 3.启动服务 $ ./bin/etcd $ $GOPATH/bin/etcd # 4.简单使用 $ ./bin/etcdctl put foo bar OK
################################## # Running etcd in standalone mode ################################## # 1.设置启动的Node地址 $ export NODE1='172.16.176.52' # 2.建立一个逻辑存储 $ docker volume create --name etcd-data # 3.启动etcd服务 # 正式的ectd端口是2379用于客户端链接,而2380用于伙伴通信 # --data-dir: 到数据目录的路径 # --initial-advertise-peer-urls: 集群中节点间通信的URL地址 # --listen-peer-urls: 集群中节点间通信的URL地址 # --advertise-client-urls: 客户端监听的URL地址 # --listen-client-urls: 客户端监听的URL地址 # --initial-cluster: 启动初始化集群配置 $ docker run -p 2379:2379 -p 2380:2380 --name etcd \ --volume=etcd-data:/etcd-data \ quay.io/coreos/etcd:latest \ /usr/local/bin/etcd \ --data-dir=/etcd-data --name node1 \ --initial-advertise-peer-urls http://${NODE1}:2380 \ --listen-peer-urls http://0.0.0.0:2380 \ --advertise-client-urls http://${NODE1}:2379 \ --listen-client-urls http://0.0.0.0:2379 \ --initial-cluster node1=http://${NODE1}:2380 # 4.列出如今集群中的服务状态 $ etcdctl --endpoints=http://${NODE1}:2379 member list
################################ # Running a 3 node etcd cluster ################################ # node1 docker run -p 2379:2379 -p 2380:2380 --name etcd-node-1 \ --volume=/var/lib/etcd:/etcd-data \ quay.io/coreos/etcd:latest \ /usr/local/bin/etcd \ --data-dir=/etcd-data \ --initial-advertise-peer-urls "http://10.20.30.1:2380" \ --listen-peer-urls "http://0.0.0.0:2380" \ --advertise-client-urls "http://10.20.30.1:2379" \ --listen-client-urls "http://0.0.0.0:2379" \ --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \ --initial-cluster-state "new" \ --initial-cluster-token "my-etcd-token" # node2 docker run -p 2379:2379 -p 2380:2380 --name etcd-node-2 \ --volume=/var/lib/etcd:/etcd-data \ quay.io/coreos/etcd:latest \ /usr/local/bin/etcd \ --data-dir=/etcd-data \ --initial-advertise-peer-urls "http://10.20.30.2:2380" \ --listen-peer-urls "http://0.0.0.0:2380" \ --advertise-client-urls "http://10.20.30.2:2379" \ --listen-client-urls "http://0.0.0.0:2379" \ --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \ --initial-cluster-state "new" \ --initial-cluster-token "my-etcd-token" # node3 docker run -p 2379:2379 -p 2380:2380 --name etcd-node-3 \ --volume=/var/lib/etcd:/etcd-data \ quay.io/coreos/etcd:latest \ /usr/local/bin/etcd \ --data-dir=/etcd-data \ --initial-advertise-peer-urls "http://10.20.30.3:2380" \ --listen-peer-urls "http://0.0.0.0:2380" \ --advertise-client-urls "http://10.20.30.3:2379" \ --listen-client-urls "http://0.0.0.0:2379" \ --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \ --initial-cluster-state "new" \ --initial-cluster-token "my-etcd-token" # run etcdctl using API version 3 docker exec etcd /bin/sh -c "export ETCDCTL_API=3 && /usr/local/bin/etcdctl put foo bar"
# 编辑docker-compose.yml文件 version: "3.6" services: node1: image: quay.io/coreos/etcd volumes: - node1-data:/etcd-data expose: - 2379 - 2380 networks: cluster_net: ipv4_address: 172.16.238.100 environment: - ETCDCTL_API=3 command: - /usr/local/bin/etcd - --data-dir=/etcd-data - --name - node1 - --initial-advertise-peer-urls - http://172.16.238.100:2380 - --listen-peer-urls - http://0.0.0.0:2380 - --advertise-client-urls - http://172.16.238.100:2379 - --listen-client-urls - http://0.0.0.0:2379 - --initial-cluster - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380 - --initial-cluster-state - new - --initial-cluster-token - docker-etcd node2: image: quay.io/coreos/etcd volumes: - node2-data:/etcd-data networks: cluster_net: ipv4_address: 172.16.238.101 environment: - ETCDCTL_API=3 expose: - 2379 - 2380 command: - /usr/local/bin/etcd - --data-dir=/etcd-data - --name - node2 - --initial-advertise-peer-urls - http://172.16.238.101:2380 - --listen-peer-urls - http://0.0.0.0:2380 - --advertise-client-urls - http://172.16.238.101:2379 - --listen-client-urls - http://0.0.0.0:2379 - --initial-cluster - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380 - --initial-cluster-state - new - --initial-cluster-token - docker-etcd node3: image: quay.io/coreos/etcd volumes: - node3-data:/etcd-data networks: cluster_net: ipv4_address: 172.16.238.102 environment: - ETCDCTL_API=3 expose: - 2379 - 2380 command: - /usr/local/bin/etcd - --data-dir=/etcd-data - --name - node3 - --initial-advertise-peer-urls - http://172.16.238.102:2380 - --listen-peer-urls - http://0.0.0.0:2380 - --advertise-client-urls - http://172.16.238.102:2379 - --listen-client-urls - http://0.0.0.0:2379 - --initial-cluster - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380 - --initial-cluster-state - new - --initial-cluster-token - docker-etcd volumes: node1-data: node2-data: node3-data: networks: cluster_net: driver: bridge ipam: driver: default config: - subnet: 172.16.238.0/24
# 使用启动集群 docker-compose up -d # 以后使用以下命令登陆到任一节点测试etcd集群 docker exec -it node1 bash # etcdctl member list 422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:2379 ed635d2a2dbef43d, started, node2, http://172.16.238.101:2380, http://172.16.238.101:2379 daf3fd52e3583ffe, started, node3, http://172.16.238.102:2380, http://172.16.238.102:2379
--name #指定节点名称 --data-dir #指定节点的数据存储目录,用于保存日志和快照 --addr #公布的 IP 地址和端口;默认为 127.0.0.1:2379 --bind-addr #用于客户端链接的监听地址;默认为–addr 配置 --peers #集群成员逗号分隔的列表;例如 127.0.0.1:2380,127.0.0.1:2381 --peer-addr #集群服务通信的公布的 IP 地址;默认为 127.0.0.1:2380 -peer-bind-addr #集群服务通信的监听地址;默认为-peer-addr 配置 --wal-dir #指定节点的 wal 文件的存储目录,若指定了该参数 wal 文件会和其余数据文件分开存储 --listen-client-urls #监听 URL;用于与客户端通信 --listen-peer-urls #监听 URL;用于与其余节点通信 --initial-advertise-peer-urls #告知集群其余节点 URL --advertise-client-urls #告知客户端 URL --initial-cluster-token #集群的 ID --initial-cluster #集群中全部节点 --initial-cluster-state new #表示从无到有搭建 etcd 集群 --discovery-srv #用于 DNS 动态服务发现,指定 DNS SRV 域名 --discovery #用于 etcd 动态发现,指定 etcd 发现服务的 URL
etcd 的数据存储有点像 PG 数据库的存储方式github
etcd 目前支持 V2 和 V3 两个大版本,这两个版本在实现上有比较大的不一样,一方面是对外提供接口的方式,另外一方面就是底层的存储引擎,V2 版本的实例是一个纯内存的实现,全部的数据都没有存储在磁盘上,而 V3 版本的实例就支持了数据的持久化。算法
咱们都知道 etcd 为咱们提供了 key/value 的服务目录存储。docker
# 设置键值对 $ etcdctl set name escape # 获取方式 $ etcdctl get name escape
使用 etcd 以后,咱们会疑问数据都存储到的那里呢?数据默认会存放在 /var/lib/etcd/default/ 目录。咱们会发现数据所在的目录,会被分为两个文件夹中,分别是 snap 和 wal目录。数据库
# 目录结构 $ tree /var/lib/etcd/default/ default └── member ├── snap │ ├── 0000000000000006-0000000000046ced.snap │ ├── 0000000000000006-00000000000493fe.snap │ ├── 0000000000000006-000000000004bb0f.snap │ ├── 0000000000000006-000000000004e220.snap │ └── 0000000000000006-0000000000050931.snap └── wal └── 0000000000000000-0000000000000000.wal
使用 WAL 进行数据的存储使得 etcd 拥有两个重要功能,那就是故障快速恢复和数据回滚/重作。安全
既然有了 WAL 实时存储了全部的变动,为何还须要 snapshot 呢?随着使用量的增长,WAL 存储的数据会暴增。为了防止磁盘很快就爆满,etcd 默认每 10000 条记录作一次 snapshot 操做,通过 snapshot 之后的 WAL 文件就能够删除。而经过 API 能够查询的历史 etcd 操做默认为 1000 条。bash
首次启动时,etcd 会把启动的配置信息存储到 data-dir 参数指定的数据目录中。配置信息包括本地节点的ID、集群ID和初始时集群信息。用户须要避免 etcd 从一个过时的数据目录中从新启动,由于使用过时的数据目录启动的节点会与集群中的其余节点产生不一致。因此,为了最大化集群的安全性,一旦有任何数据损坏或丢失的可能性,你就应该把这个节点从集群中移除,而后加入一个不带数据目录的新节点。服务器
保证一致性的共识算法
在每个分布式系统中,etcd 每每都扮演了很是重要的地位,因为不少服务配置发现以及配置的信息都存储在 etcd 中,因此整个集群可用性的上限每每就是 etcd 的可用性,而使用 3 ~ 5 个 etcd 节点构成高可用的集群每每都是常规操做。
正是由于 etcd 在使用的过程当中会启动多个节点,如何处理几个节点之间的分布式一致性就是一个比较有挑战的问题了。解决多个节点数据一致性的方案其实就是共识算法,etcd 使用的就是 Raft 共识算法。
Raft 从一开始就被设计成一个易于理解和实现的共识算法,它在容错和性能上与 Paxos 协议比较相似,区别在于它将分布式一致性的问题分解成了几个子问题,而后一一进行解决。
每个 Raft 集群中都包含多个服务器,在任意时刻,每一台服务器只可能处于 Leader、Follower 以及 Candidate 三种状态;在处于正常的状态时,集群中只会存在一个 Leader 状态,其他的服务器都是 Follower 状态。
全部的 Follower 节点都是被动的,它们不会主动发出任何的请求,只会响应 Leader 和 Candidate 发出的请求。对于每个用户的可变操做,都会被路由给 Leader 节点进行处理,除了 Leader 和 Follower 节点以外,Candidate 节点其实只是集群运行过程当中的一个临时状态。
Raft 集群中的时间也被切分红了不一样的几个任期(Term),每个任期都会由 Leader 的选举开始,选举结束后就会进入正常操做的阶段,直到 Leader 节点出现问题才会开始新一轮的选择。
每个服务器都会存储当前集群的最新任期,它就像是一个单调递增的逻辑时钟,可以同步各个节点之间的状态,当前节点持有的任期会随着每个请求被传递到其余的节点上。Raft 协议在每个任期的开始时都会从一个集群中选出一个节点做为集群的 Leader 节点,这个节点会负责集群中的日志的复制以及管理工做。
咱们将 Raft 协议分红三个子问题:节点选举、日志复制以及安全性。
服务发现是 etcd 服务的主要的用途之一
服务发现要解决的也是分布式系统中最多见的问题之一,即在同一个分布式集群中的进程或服务,要如何才能找到对方并创建链接。本质上来讲,服务发现就是想要了解集群中是否有进程在监听 UDP 或 TCP 端口,而且经过名字就能够查找和链接。要解决服务发现的问题,须要有下面三大支柱,缺一不可。
平常开发集群管理功能中,若是要设计能够动态调整集群大小。那么首先就要支持服务发现,就是说当一个新的节点启动时,能够将本身的信息注册给 master,而后让 master 把它加入到集群里,关闭以后也能够把本身从集群中删除。etcd 提供了很好的服务注册与发现的基础功,咱们采用 etcd 来作服务发现时,能够把精力用于服务自己的业务处理上。
etcd 在键的组织上采用了层次化的空间结构,相似于文件系统中目录的概念,数据库操做围绕对键值和目录的 CRUD 完整生命周期的管理。etcdctl 是一个命令行的客户端,它提供了一下简洁的命令,可理解为命令工具集,能够方便咱们在对服务进行测试或者手动修改数据库内容。etcdctl 与其余 xxxctl 的命令原理及操做相似,如 systemctl 等等。
# 列出etcd集群中的实例 $ etcdctl member list # 添加etcd集群中的实例 $ etcdctl member add <实例> # 删除etcd集群中的实例 $ etcdctl member remove <实例> # 更新etcd集群中的实例 $ etcdctl member update <实例>
--name #指定节点名称
Etcd 集群备份和数据恢复以及优化运维
etcd 被设计为能承受集群自动从临时失败(例如机器重启)中恢复,并且对于一个有 N 个成员的集群能允许 (N-1)/2 的持续失败。当一个成员持续失败时,不论是由于硬件失败或者磁盘损坏,它丢失到集群的访问。若是集群持续丢失超过 (N-1)/2 的成员,则它只能悲惨的失败,无可救药的失去法定人数(quorum)。一旦法定人数丢失,集群没法达到一致性而致使没法继续接收更新。为了从灾难失败中恢复数据,etcd v3 提供快照和修复工具来重建集群而不丢失 v3 键数据。
因为 v3 版本的 etcd 证书是基于 IP 的,因此每次新增 etcd 节点都须要从新制做证书。为了咱们更方便的使用,能够查看这个连接来 etcd 证书制做。详情: https://github.com/cloudflare...
恢复集群,首先须要来自 etcd 成员的键空间的快照。快速能够是用 etcdctl snapshot save 命令从活动成员中获取。或者是从 etcd 数据目录复制 member/snap/db 文件。例如,下列命令快照在 $ENDPOINT 上服务的键空间到文件 snapshot.db。
# 集群备份etcd数据快照 # $ENDPOINT => http://10.20.30.1:2379 $ ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db # 在单节点etcd上执行下面的命令就能够对etcd进行数据备份 # 每两个小时备份一次数据并上传到S3上,并保留最近两天的数据 $ ETCDCTL_API=3 etcdctl snapshot save /var/lib/etcd_backup/etcd_$(date "+%Y%m%d%H%M%S").db
为了恢复集群,使用以前任意节点上备份的快照 "db" 文件。恢复的手,可使用 etcdctl snapshot restore 命令来恢复 etcd 数据目录,此时全部成员应该使用相同的快照恢复。由于恢复数据死后,会覆盖某些快照元数据(特别是成员ID和集群ID)信息,集群内的成员可能会丢失它以前的标识。所以为了从快照启动集群,恢复必须启动一个新的逻辑集群。
在恢复时,快照完整性的检验是可选的。若是快照是经过 etcdctl snapshot save 获得的话,使用 etcdctl snapshot restore 命令恢复的时候,会检查 hash 值的完整性。若是快照是从数据目录复制而来,则没有完整性校验,所以它只能经过使用 --skip-hash-check 来恢复。
下面为一个 3 成员的集群建立新的 etcd 数据目录:
$ etcdctl snapshot restore snapshot.db \ --name m1 \ --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-advertise-peer-urls http://host1:2380 $ etcdctl snapshot restore snapshot.db \ --name m2 \ --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-advertise-peer-urls http://host2:2380 $ etcdctl snapshot restore snapshot.db \ --name m3 \ --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-advertise-peer-urls http://host3:2380
下一步, 用新的数据目录启动 etcd 服务,如今恢复的集群可使用并提供来自快照的键空间服务。
$ etcd \ --name m1 \ --listen-client-urls http://host1:2379 \ --advertise-client-urls http://host1:2379 \ --listen-peer-urls http://host1:2380 & $ etcd \ --name m2 \ --listen-client-urls http://host2:2379 \ --advertise-client-urls http://host2:2379 \ --listen-peer-urls http://host2:2380 & $ etcd \ --name m3 \ --listen-client-urls http://host3:2379 \ --advertise-client-urls http://host3:2379 \ --listen-peer-urls http://host3:2380 &
做者: Escape
https://escapelife.github.io/...