本位主要用以记录一些etcd的基本操作和常识。
1. 概述
etcd是一个分布式的、可靠
的键值对存储服务。
1.1 主要功能
- 基本的 key-value 存储
- 监听机制
- key 的过期及续约机制,用于监控和服务发现
- 原子 CAS 和 CAD,用于分布式锁和 leader 选举
1.2 分布式特点
- 分布式一致性
- 分布式锁
- leader选举
- 写屏障(write barriers)
1.3 用途
-
配置管理
- kubernetes存储
配置
到etcd,通过etcd的watch
API及时发布配置更改
- kubernetes存储
-
服务注册、发现
- 同一 service 的所有节点注册到相同目录下,节点启动后将自己的信息注册到所属服务的目录中。
- 服务节点定时发送心跳,注册到服务目录中的信息设置一个较短的 TTL,运行正常的服务节点每隔一段时间会去更新信息的 TTL
- 通过名称能查询到服务提供外部访问的 IP 和端口号。比如网关代理服务时能够及时的发现服务中新增节点、丢弃不可用的服务节点,同时各个服务间也能感知对方的存在
-
协调分布式工作
- 分布式锁
- 分布式信号量
2. 工作方式
2.1 选举
etcd各节点有三种角色,分别是:
- leader
处理所有客户端交互,日志复制等,一个任期只有一个。 - follower
完全被动的选民,是只读的。 - candidate
候选人,可以被选举为新领导。
etcd使用了Raft一致性协议来保证数据的强一致性,选举是Raft协议的重要组成。etcd集群如果没有leader将不允许任何数据更新操作。选举完成以后,集群会通过心跳的方式维持 leader 的地位,一旦 leader 失效,会有新的 follower 起来竞选 leader。
2.1.1 选举流程
-
在集群刚启动时,所有节点的状态都为 follower,如果在一段时间周期内(election timeout)没有收到来自 leader 的 heartbeat,触发 leader election,该节点将自己切换为candidate,同时向其他节点发起选举请求,follower会响应这个请求。
如果多个candidate同时发起了选举,导致都没有获得大多数选票时,每一个candidate会随机等待一段时间后重新发起新一轮投票(一般是随机等待150-300ms),可避免多个节点同时竞选。
- 当选举请求在集群中有超半数节点同意后,该candidate就切换为leader,如果没有达成一致,则 candidate 随机选择一个等待间隔(150ms ~ 300ms)再次发起投票。
- leader 节点依靠定时向 follower 发送 heartbeat 来保持其地位.
2.1.2 异常场景
- 任何时候如果其它 follower 在 election timeout 期间都没有收到来自 leader 的 heartbeat,同样会将自己的状态切换为 candidate 并发起选举。每成功选举一次,新 leader 的任期(Term)都会比之前 leader 的任期大 1。
- 集群中的leader故障后,会产生新的leader,之后如果旧的leader恢复,新旧leader之间会比较日志,缺少消息的一方会变为follower。
2.2 存储
etcd的存储机制在v2和v3版本之间有差异,以下只讲v3版本的实现。
在v3版本中,etcd存储分为两部分:
- 内存中的索引:kvindex
- 后端存储,可对接多种存储媒介,当前使用boltdb
2.2.1 kvindex
kvindex基于Google的btree实现,保存在内存中,当client通过key查询value时,会现在kvindex中查询这个key的所有revision,然后通过revision去后端存储(boltdb)查询数据。
2.2.2 boltdb
boltdb中存储的key是reversion,value则是client查询的key在这个reversion下的value,这样在boltdb中存储了这个key的所有版本的值,从而实现多版本机制。
2.2.3 reversion
每一个revision 都由( main ID, sub ID)唯一标识,它也是实现 etcd v3的基础。
同一事务共享maiID,但事务中的每次操作(PUT、DELETE)subID会递增(从0开始)。
mainid和sub id组成了全局唯一的reversion。
2.2.4 压缩历史版本
由于boltdb中保存了所有的版本,数据会越来越多,当数据量很大时,可以使用compact来压缩历史版本,也就是删除一部分历史,保留最近的版本。
compact(4)压缩4以前的 即1、2、3会被删除
2.3 Watch
etcd的watch机制支持watch某个固定的key,也支持watch一个范围。client发起watch时,可以指定一个revision(也可以不指定),如果指定了revision,etcd会从指定的revision返回数据。
2.4 租约-Lease
租约可以指定一对Key value的TTL,是Key有一个有效状态。
3. 使用
3.1 安装部署
可使用docker安装,方便快捷
docker-compose.yml文件内容如下:
version: '2'
services:
etcd:
image: 'bitnami/etcd:latest'
environment:
- "ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379"
- "ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379"
- "ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380"
- "ETCD_INITIAL_ADVERTISE_PEER_URLS=http://0.0.0.0:2380"
- "ALLOW_NONE_AUTHENTICATION=yes"
- "ETCD_INITIAL_CLUSTER=node1=http://0.0.0.0:2380"
- "ETCD_NAME=node1"
- "ETCD_DATA_DIR=/opt/bitnami/etcd/data"
ports:
- 2379:2379
- 2380:2380
e3w:
hostname: e3w
image: soyking/e3w:latest
volumes:
- ./e3w/config.ini:/app/conf/config.default.ini
ports:
- "61101:8080"
按上面的内容执行后会启动两个容器,1个是etcd节点,还有1个etcd的web ui:e3w。注意:要提前准备好e3w的配置文件。
[app]
port=8080
auth=false
[etcd]
root_key=/ui
dir_value=
addr=etcd:2379,etcd:22379,etcd:32379
username=
password=
cert_file=
key_file=
ca_file=
web ui操作和查询到的key会自动加个前缀,即配置文件中的root_key。所以别奇怪为什么查不到你写入的数据。如果开启了权限验证,需要配置username和password。
3.2 命令行操作
命令行操作非常简单,帮助信息清晰易懂。
几个常用命令:
-
获取
etcdctl get <key>
-
更新
etcdctl put <key> <value>
-
删除
etcdctl del <key>
权限控制相关:
-
root用户存在时才能开启权限控制
etcdctl user add root # 默认带root角色 etcdctl auth enable
-
开启权限控制后执行命令需要用–user指定用户
etcdctl get <key> --user=<用户>
-
使用新用户执行命令,提示没有权限,需要添加指定key的读写权限
(开启权限控制后,命令后面都需要加–user=<用户>,下面的命令已省略)
# 创建role:rw_test etcdctl role add rw_test # 给role添加权限: 对/test开头的key有readwrite权限 etcdctl role grant-permission rw_test readwrite /test --prefix=true # 给用户添加role etcdctl user grant-role <用户> rw_test
3.3 Python操作
python操作etcd需要使用第三方库:python-etcd3
pip install etcd3 # 注意这里不是python-etcd3
常用操作:
-
连接
client = etcd3.client(host, port, user, password)
-
读取
client.get(key, serializable=False) client.get_prefix(key_prefix, revision=None, **kwargs) # 可以指定revision,limit等参数
-
watch
client.watch(key, start_revision=None, **kwargs) # 可以指定revision等参数 client.watch_prefix(key, start_revision=None, **kwargs) # 可以指定revision等参数 client.add_watch_prefix_callback(key_prefix, callback, start_revision=None, **kwargs) # 可以指定revision等参数 # WatchResponse对象中的header属性可以看到revision信息