etcd
简介
etcd是什么?
etcd是一个开源的,分布式存储的
,强一致性的
,可靠的键值存储
;来存储分布式系统化或机器集群需要访问的数据。它在网络分区期间优雅地处理Leader选举,并且可以容忍机器故障,即使是在leader节点中。
其底层使用Raft算法保证一致性,主要用于共享配置,服务发现,集群监控,leader选举,分布式锁等场景
目的用来存储分布式系统中的关键数据
读操作可以支持非leader节点读取数据以提高性能,但是写操作仍然需要Leader支持,所有当发生网络分区时,写操作扔可能失败
etcd 默认数据一更新就落盘持久化,数据持久化存储使用 WAL (write ahead log) ,预写式日志。
为什么使用Etcd?
所有的分布式系统都面临一个问题多个节点之间的数据共享问题
,etcd以一致和容错的方式存储元数据,分布式系统使用etcd作为一致性键值存储,用于配置管理,服务发现和协调分布式工作,使用 etcd 的通用分布式模式包括领导选举,[分布式锁][etcd-concurrency]和监控机器活动。
特性
-
接口简单
使用标准的HTTP工具,比如curl,读取值
-
键值存储
与标准文件系统一样,在层次结构组织的目录中,存储数据
etcd的存储格式,仅支持键值(key - value)存储,etcd的键(key)以目录树结构方式组织,就是key的命名和存储类似我们的目录结构
/congfig /databases /feature-flags /verbose-logging /redesign
-
监听变化
监听特定的键或目录的变化,对值得变化做出反应,通知监听客户端
-
强一致性
etcd通过Raft协议保证etcd各个节点数据的一致性,任何时刻,都可以从任意etcd节点查询到正确的数据。
客户端应用写一个 key 时,首先会存储到 etcd Leader 上,然后再通过 Raft 协议复制到 etcd 集群的所有成员中,以此维护各成员(节点)状态的一致性与实现可靠性。
虽然 etcd 是一个强一致性的系统,但也支持从非 Leader 节点读取数据以提高性能,而且写操作仍然需要 Leader 支持,所以当发生网络分区时,写操作仍可能失败。
-
可选的SSL客户端证书认证
-
以每个实例每秒1000次写入为基准
-
用于密钥过期的可选TTL
-
通过Raft协议正确分发
核心竞争力
-
etcd的核心优势是使用简介的方式实现Raft协议
-
etcd在设计的时候重点考虑了如下四个要素:
-
简单
支持Restful风格的Http+json的API
v3版本增加了对gRPC的支持 同时也提供rest gateway进行转化
Go语言编写,跨平台,部署和维护简单
使用Raft算法保证强一致性,Raft算法可理解性好
-
安全
支持TLS客户端安全认证
-
性能
单实例(V3)支持每秒10KQps
-
可靠
使用 Raft 算法充分保证了分布式系统数据的强一致性 etcd 集群是一个分布式系统,由多个节点相互通信构成整体的对外服务,每个节点都存储了完整的数据,并且通过 Raft 协议保证了每个节点维护的数据都是一致的。
-
etcd 和 redis的差异
etcd和redis都支持键值存储,也支持分布式特性,redis支持的数据格式更加丰富,但是他们两个定位和应用场景不一样,关键差异如下:
-
redis在分布式环境下不是强一致性的,可能会丢失数据,或者读取不到最新数据。
-
redis的数据变化监听机制没有etcd完善。
-
因为etcd的强一致性机制,导致性能上要低于redis。
基于上面的关键差异,如果系统没有强一致性的要求,redis比较合适,如果需要存储分布式系统的元数据,辅助分布式系统协调通知,关键配置etcd比较合适,这些场景对读写的吞吐量没有缓存要求那么高,但是对数据一致性要求比较高,
例如:
如果我们开发一个分布式系统,是主从结构,需要实现自动选举一个节点作为主节点,这个选举状态数据的存储引擎,必须高可用,强一致性的,否则每个节点读取到的状态数据都不一致,或者读取不到数据,集群就乱了,不知道谁是主节点。
etcd 和 Zookeeper 是定位类似的项目,跟redis定位不一样
Etcd主要提供以下能力
- 提供存储以及获取数据的接口,通过协议保证Etcd集群中多个节点的数据的强一致性,用于存储元信息以及共享配置
- 提供监听机制,客户端可以监听某个key或者某些key来变更
应用场景
-
分布式系统配置管理
配置文件的存储与分发,将配置文件存储在etcd中,其它节点加载配置文件;如需修改配置文件,则修改后,其他节点只需要重新加载即可;
-
服务注册与发现
客户端请求经过etcd分发给各个服务器,此时如果增加一个服务端并连接到etcd,又于客户端在一直监听着etcd,所以客户端能够快速拉去新增服务端地址,并将客户端请求通过etcd下发到新增的服务端
-
选主,就是选举leader
假设一个 master 节点连接多个 slave 节点,如果 master 节点发生宕机,此时 etcd 会从 slave 节点中选取一个节点作为 master 节点;
-
应用调度
-
分布式锁
常规情况下锁的持有者和释放者是进程中的线程,而在分布式情况下,锁的持有者和释放者可以是微服务或进程;
-
集群监控
客户端会通过 etcd 监控所有的 master、slave 节点,一旦有节点发生宕机,客户端能够及时发现;
单机部署
推荐版本:
- etcd V3.4.15
- etcd V3.3.8
作为本地开发和测试环境,我们不需要部署etcd集群,只要部署一个etcd实例即可
下载安装包
到etcd的github地址,下载最新的安装包:
https://github.com/etcd-io/etcd/releases
https://github.com/etcd-io/etcd/releases/download/v3.4.14/etcd-v3.4.14-linux-amd64.tar.gz
安装包版本举例说明:
- etcd-版本号-darwin-amd64.zip - macos版本
- etcd-版本号-linux-amd64.tar.gz - linux 64位版本
- etcd-版本号-windows-amd64.zip - windows 64位版本
解压缩包后,将得到类似的目录结构:
etcd-v3.2.28-darwin-amd64/
├── Documentation - etcd文档目录
├── etcd - etcd服务端程序
└── etcdctl - etcd客户端程序,用来操作服务端
启动etcd
切换到etcd安装目录
$ ./etcd
打卡命令窗口直接运行etcd程序,就可以启动默认配置的etcd服务器
启动etcd输出类似:
jogindembp:etcd-v3.2.28-darwin-amd64 jogin$ ./etcd
2019-11-14 23:11:46.531199 I | etcdmain: etcd Version: 3.2.28
2019-11-14 23:11:46.531305 I | etcdmain: Git SHA: 2d861f39e
2019-11-14 23:11:46.531312 I | etcdmain: Go Version: go1.8.7
2019-11-14 23:11:46.531318 I | etcdmain: Go OS/Arch: darwin/amd64
........忽略.....
2019-11-14 23:11:46.533058 I | embed: listening for client requests on localhost:2379
提示:etcd服务端处理请求的默认端口是2379
如果不想放到前台,可以在结尾添加&放置到后台运行
测试etcd
可以通过etcdctl命令测试,etcd是否启动成功
例子:
切换到安装目录,执行下面命令
./etcdctl set /config/title tizi365
如果正常的话,会输出:
tizi365
提示:为了方便调试,可以将etcd的安装目录添加到PATH环境变量中,就不需要每次都要切换到etcd安装目录,执行命令。
关闭etcd服务
只要杀掉etcd进程即可
例如:
# 假如60999是etcd进程id
kill 60999
注意:不要使用kill -9 杀掉进程,可能会导致etcd丢失数据。
基本概念
术语 | 描述 |
---|---|
Node(节点) | Node(节点) 是 raft 状态机的一个实例。它有唯一标识,如果它是leader,会内部记录其他节点的信息。 |
Member(成员) | Member(成员是) etcd 的一个实例。它承载一个 node/节点,并为 client (客户端)提供服务。 |
Cluster(集群) | Cluster(集群)由多个 member(成员)组成。每个成员的节点遵循 raft 一致性协议来复制日志。集群从成员中接收提案,提交他们并应用到本地存储。 |
Peer(同伴) | Peer(同伴)是同一个集群中的其他成员。 |
Proposal(提议) | 提议是一个需要完成 raft 协议的请求(例如写请求,配置修改请求)。 |
Client(客户端) | Client(客户端)是集群 HTTP API 的调用者。对于 V3 版本,应该包括 gRPC API。 |
Machine(机器) | 该术语已弃用。在 2.0 版本之前,在 etcd 中的成员备选。 |
etcd HTTP Server | 用于处理用户发送的 API 请求以及其它 etcd 节点的同步与心跳信息请求。 |
etcd Store | 用于处理 etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 etcd 对用户提供的大多数 API 功能的具体实现。 |
Raft | Raft 强一致性算法的具体实现,是 etcd 的核心。 |
WAL | Write Ahead Log(预写式日志),是 etcd 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd 就通过 WAL 进行持久化存储。WAL 中, 所有的数据提交前都会事先记录日志。Snapshot 是为了防止数据过多而进行的状态快照;Entry 表示存储的具体日志内容。 |
集群部署
通常至少部署3个etcd节点(推荐奇数个节点),多个节点之间通过raft一致性算法完成分布式一致性协同,
在etcd整个架构中,有一个非常关键的概念叫做quorum,
quorum 的定义是 (n+1)/2,也就是说超过集群中半数节点组成的一个团体,在 3 个节点的集群中,etcd 可以容许 1 个节点故障,也就是只要有任何 2 个节点可用,etcd 就可以继续提供服务。同理,在 5 个节点的集群中,只要有任何 3 个节点可用,etcd 就可以继续提供服务,这也是 etcd 集群高可用的关键。
集群规划
这里规划一个由3台服务器节点组成的etcd集群,如下表:
节点名字 | 服务器Ip |
---|---|
infra0 | 10.0.1.10 |
infra1 | 10.0.1.11 |
infra2 | 10.0.1.12 |
etcd关键参数说明
参数 | 说明 |
---|---|
--name | etcd节点名字 |
--initial-cluster | etcd启动的时候,通过这个配置找到其他ectd节点的地址列表,格式:'节点名字1=http://节点ip1:2380,节点名字1=http://节点ip1:2380,.....' |
--initial-cluster-state | 初始化的时候,集群的状态 "new" 或者 "existing"两种状态,new代表新建的集群,existing表示加入已经存在的集群。 |
--listen-client-urls | 监听客户端请求的地址列表,格式:'http://localhost:2379', 多个用逗号分隔。 |
--advertise-client-urls | 如果--listen-client-urls配置了,多个监听客户端请求的地址,这个参数可以给出,建议客户端使用什么地址访问etcd。 |
--listen-peer-urls | 服务端节点之间通讯的监听地址,格式:'http://localhost:2380' |
--initial-advertise-peer-urls | 建议服务端之间通讯使用的地址列表。 |
启动节点1
$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
--listen-peer-urls http://10.0.1.10:2380 \
--listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.0.1.10:2379 \
--initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
--initial-cluster-state new
启动节点2
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
--listen-peer-urls http://10.0.1.11:2380 \
--listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.0.1.11:2379 \
--initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
--initial-cluster-state new
提示:每台服务器启动的etcd实例,--initial-cluster 参数都是一样的,列出整个集群所有节点的服务端通讯地址。
开机启动
上面是直接在命令行启动etcd实例,关闭命令窗口,etcd就退出了,推荐使用进程管理软件,启动etcd,例如:centos系统,使用systemd启动etcd,具体如何配置网上找一下systemd的资料即可。
命令行操作
通过etcdctl命令操作etcd服务,读写数据、监听数据。
ectdctl关键参数
输入下面命令可以查看etcdctl命令的帮助信息
etcdctl -h
etcdctl 最关键的参数就是etcd服务的地址是什么?
可以通过 --endpoints参数指定etcd的客户端监听地址列表。
例如:
$ etcdctl --endpoints "http://127.0.0.1:2379,http://127.0.0.1:4001" 子命令
如果你的etcd安装在本地,可以不需要手动指定--endpoints参数。
设置key
etcdctl set key value
$ etcdctl set /config/name tizi365
读取key
etcdctl get key
# 查询一个key
$ etcdctl get /config/name输出
tizi365
删除key
etcdctl rm key
$ etcdctl rm foo
监听key
etcd支持监听key的数据变化
命令格式:
etcdctl watch [-f] [-r] key
可选参数说明:
- -f 除非输入CTRL+C否则一直监控,不退出
- -r 监听key包括key的所有子目录下的数据
例子:
# 监听/config这个目录下的所有内容
$ etcdctl watch -f -r /config
# 输出例子,可以看到/config目录下所有的key的写入操作都被监控到
[set] /config/name
1232
[set] /config/ttl
1232
# 监控指定的key
$ etcdctl watch -f /config/name
开启身份验证
etcd有两种身份验证方式
- 用户名密码
- 证书
新建用户
-
新建用户
etcdctl user add username
-
查看所有用户
etcdctl user list
新建角色并为角色赋权
-
新建角色
etcdctl role add rolename
-
查看所有角色
etcdctl role list
-
为角色赋权
etcdctl role grant-permission rolename --prefix=true readwrite /prefix
-
赋予角色以/prefix为前缀的路径的读写权限
赋予用户角色
-
赋予用户角色
etcdctl user grant-role username rolename
-
查看用户角色
etcdctl user get username
etcd开启auth认证
Etcd开启Basic Auth之后,默认会启用两个角色root和guest,root角色拥有所有权限,guest拥有只读权限,这两个角色都不要删除
-
新建root用户并设置密码
etcdctl user add root
-
开启认证
etcdctl auth enable --user="root" --password=""
访问etcd
etcd 集群访问指南
put
命令用于写:
etcdctl --endpoints=$ENDPOINTS put foo "Hello World!"
get
用于从 etcd 读:
etcdctl --endpoints=$ENDPOINTS get foo
etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo
如何根据前缀获取键
etcdctl --endpoints=$ENDPOINTS put web1 value1
etcdctl --endpoints=$ENDPOINTS put web2 value2
etcdctl --endpoints=$ENDPOINTS put web3 value3
etcdctl --endpoints=$ENDPOINTS get web --prefix # 获取以web开头的键
etcdctl --endpoints=$ENDPOINTS get web --prefix --limit=2 # 使用limit限制数量
如何删除键
etcdctl --endpoints=$ENDPOINTS put key myvalue
etcdctl --endpoints=$ENDPOINTS del key
etcdctl --endpoints=$ENDPOINTS put k1 value1
etcdctl --endpoints=$ENDPOINTS put k2 value2
etcdctl --endpoints=$ENDPOINTS del k --prefix # 删除以k 开头的键值对
如何在事务中进行多个写操作
进行事务性写入的指南
txn
将多个请求包装进单个事务:
etcdctl --endpoints=$ENDPOINTS put user1 bad
etcdctl --endpoints=$ENDPOINTS txn --interactive
compares:
value("user1") = "bad"
success requests (get, put, delete):
del user1
failure requests (get, put, delete):
put user1 good
如何监视键
监视 etcd 键的指南
watch
用于获取未来变更的通知:
etcdctl --endpoints=$ENDPOINTS watch stock1
etcdctl --endpoints=$ENDPOINTS put stock1 1000
etcdctl --endpoints=$ENDPOINTS watch stock --prefix
etcdctl --endpoints=$ENDPOINTS put stock1 10
etcdctl --endpoints=$ENDPOINTS put stock2 20
如何创建租约
在 etcd 中创建租约(lease)的指南
lease
用于带 TTL 的写:
etcdctl --endpoints=$ENDPOINTS lease grant 300
# lease 2be7547fbc6a5afa granted with TTL(300s)
etcdctl --endpoints=$ENDPOINTS put sample value --lease=2be7547fbc6a5afa
etcdctl --endpoints=$ENDPOINTS get sample
etcdctl --endpoints=$ENDPOINTS lease keep-alive 2be7547fbc6a5afa
etcdctl --endpoints=$ENDPOINTS lease revoke 2be7547fbc6a5afa
# or after 300 seconds
etcdctl --endpoints=$ENDPOINTS get sample
如何创建锁
在 etcd 中创建分布式锁的指南
lock
用于分布式锁:
etcdctl --endpoints=$ENDPOINTS lock mutex1
# another client with the same name blocks
etcdctl --endpoints=$ENDPOINTS lock mutex1
如何在etcd集群中实施主节点选举
在 etcd 集群中,实施主节点选举的指南
elect
用于主节点选举:
etcdctl --endpoints=$ENDPOINTS elect one p1
# another client with the same name blocks
etcdctl --endpoints=$ENDPOINTS elect one p2
如何检查集群状态
检查 etcd 集群状态的指南
为每台机器指定初始集群配置:
etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 10.240.0.17:2379 | 4917a7ab173fabe7 | 3.5.0 | 45 kB | true | false | 4 | 16726 | 16726 | |
| 10.240.0.18:2379 | 59796ba9cd1bcd72 | 3.5.0 | 45 kB | false | false | 4 | 16726 | 16726 | |
| 10.240.0.19:2379 | 94df724b66343e6c | 3.5.0 | 45 kB | false | false | 4 | 16726 | 16726 | |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------|
etcdctl --endpoints=$ENDPOINTS endpoint health
10.240.0.17:2379 is healthy: successfully committed proposal: took = 3.345431ms
10.240.0.19:2379 is healthy: successfully committed proposal: took = 3.767967ms
10.240.0.18:2379 is healthy: successfully committed proposal: took = 4.025451ms
如何保存数据库
关于 etcd 数据库快照的指南
snapshot
用于保存 etcd 数据库的时间点(point-in-time)快照:
快照只能从一个 etcd 节点请求,因此 --endpoints
标志应该只包含一个端点。
ENDPOINTS=$HOST_1:2379
etcdctl --endpoints=$ENDPOINTS snapshot save my.db
Snapshot saved at my.db
etcdctl --write-out=table --endpoints=$ENDPOINTS snapshot status my.db
+---------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+---------+----------+------------+------------+
| c55e8b8 | 9 | 13 | 25 kB |
+---------+----------+------------+------------+
如何添加和移除成员
处理 etcd 集群成员的指南
memeber
用于添加、移除、更新成员:
在每台机器上执行:
# For each machine
TOKEN=my-etcd-token-1
CLUSTER_STATE=new
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_3=etcd-node-3
HOST_1=192.168.56.121
HOST_2=192.168.56.122
HOST_3=192.168.56.123
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
在 node 1 上执行:
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2379 \
--listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} \
--initial-cluster-token ${TOKEN}
在 node 2 上执行:
THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2379 \
--listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} \
--initial-cluster-token ${TOKEN}
在 node3 上执行:
THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2379 \
--listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} \
--initial-cluster-token ${TOKEN}
然后使用 member remove
和 member add
命令替换成员:
# 获取 member ID
export ETCDCTL_API=3
HOST_1=192.168.56.121
HOST_2=192.168.56.122
HOST_3=192.168.56.123
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 member list
# 移除成员
MEMBER_ID=300774bba1f38564
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 \
member remove ${MEMBER_ID}
# 添加新成员 node 4
export ETCDCTL_API=3
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_4=etcd-node-4
HOST_1=192.168.56.121
HOST_2=192.168.56.122
HOST_4=192.168.56.124 # 新成员
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379 \
member add ${NAME_4} \
--peer-urls=http://${HOST_4}:2380
下面,使用 --initial-cluster-state existing
标记启动新成员:
# [警告]如果从相同的磁盘空间启动新成员,
# 确保移除旧成员的数据目录
#
# 使用 “existing” 标记重启
TOKEN=my-etcd-token-1
CLUSTER_STATE=existing
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_4=etcd-node-4
HOST_1=192.168.56.121
HOST_2=192.168.56.122
HOST_4=192.168.56.124 # 新成员
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_4}=http://${HOST_4}:2380
THIS_NAME=${NAME_4}
THIS_IP=${HOST_4}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2379 \
--listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} \
--initial-cluster-token ${TOKEN}
python操作etcd
import etcd3
# 建立连接
# client = etcd3.client('172.25.69.180', 2379)
# 添加数据
def put(key,value):
res = client.put(key,value)
print(res)
'''
header {
cluster_id: 14841639068965178418
member_id: 10276657743932975437
revision: 2
raft_term: 2
}
'''
# 获取数据
def get(key):
result = client.get(key)
value = result[0]
if value: # 如果key_value[0]不为空(即存在值)
rv = value.decode() # 对key_value[0]进行解码(假设它是一个字节串),并将解码后的结果赋给rv
else:
rv = value # 如果key_value[0]为空,则将key_value[0]的原始值(可能是None)赋给rv
print(rv)
# 删除某个key
def dele(key):
result = client.delete(key)
print(result) # 删除成功 true 删除失败 false
# 获取满足前缀 key的 value
def get_pre(key):
objs = client.get_prefix(key)
for obj in objs:
result = obj[0].decode()
print(result)
# 获取所有 key的value
def get_all():
objs = client.get_all()
for obj in objs:
res = obj[0].decode()
print(res)
if __name__ == '__main__':
client = etcd3.client('172.25.69.180', 2379)
# put('/cofig/dem','456')
# put('/cofig/dem1','789')
# put('/cofig/dem2','012')
# get('/cofig/dem2')
# dele('/cofig/dem2')
# get_pre('/cofig')
# get_all()