根据etcd官网的介绍:
A distributed, reliable key-value store for the most critical data of a distributed system。分布式,可靠的键值存储,用于分布式系统中最关键的数据。
在分布式系统中,各种服务配置信息的管理共享和服务发现是一个很基本也是很重要的问题,无论你调用服务还是调度容器,都需要知道对应的服务实例和容器节点地址信息。etcd 就是这样一款实现了元数据信息可靠存储的组件。
etcd 可集中管理配置信息。服务端将配置信息存储于 etcd,客户端通过 etcd 得到服务配置信息,etcd 监听配置信息的改变,发现改变通知客户端。
而 etcd 满足 CAP 理论中的 CP(一致性和分区容错性) 指标,由此我们知道,etcd 解决了分布式系统中一致性存储的问题。
ETCD_VER=v3.4.4
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GITHUB_URL}
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /usr/local/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /usr/local/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /usr/local
rm -f /usr/local/etcd-${ETCD_VER}-linux-amd64.tar.gz
ln /usr/local/etcd-${ETCD_VER}-linux-amd64/etcd /usr/local/bin/etcd
ln /usr/local/etcd-${ETCD_VER}-linux-amd64/etcdctl /usr/local/bin/etcdctl
etcd --version
etcdctl version
在每个台机器上设置etcd集群成员参数:
TOKEN=cr7-etcd
CLUSTER_STATE=new
NAME_1=etcd1
NAME_2=etcd2
NAME_3=etcd3
HOST_1=192.168.1.91
HOST_2=192.168.1.92
HOST_3=192.168.1.93
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
分别在每台机器上运行初始化ectd集群,初始化成功后会在运行该命令的目录下生成data.etcd目录,用于存放etcd节点相关信息,只要该目录存在,停止后可以重新用该命令启动:
# etcd1
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}
# etcd2
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}
# etcd3
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}
后台启动可以加上:
nohup .... 2>&1 &
参数说明:
编辑/etc/profile,将etcd集群成员参数写入环境变量:
export ETCDCTL_API=3
HOST_1=192.168.1.91
HOST_2=192.168.1.92
HOST_3=192.168.1.93
export ENDPOINTS=$HOST_1:2379,$HOST_2:2379,$HOST_3:2379
#编辑完成后
source /etc/profile
#查看etcd集群成员
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS member list --write-out=table
+------------------+---------+-------+--------------------------+--------------------------+------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER |
+------------------+---------+-------+--------------------------+--------------------------+------------+
| 597745dd5a1f190a | started | etcd3 | http://192.168.1.93:2380 | http://192.168.1.93:2379 | false |
| 6a3e67577dbd3de0 | started | etcd2 | http://192.168.1.92:2380 | http://192.168.1.92:2379 | false |
| d2b20b971efea8ef | started | etcd1 | http://192.168.1.91:2380 | http://192.168.1.91:2379 | false |
+------------------+---------+-------+--------------------------+--------------------------+------------+
#检查集群健康状态
[root@etcd1 ~]# etcdctl endpoint health --cluster --endpoints=$ENDPOINTS
http://192.168.1.91:2379 is healthy: successfully committed proposal: took = 7.289365ms
http://192.168.1.92:2379 is healthy: successfully committed proposal: took = 13.173445ms
http://192.168.1.93:2379 is healthy: successfully committed proposal: took = 14.812638ms
#查看集群详细状态
[root@etcd1 ~]# 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 |
+-------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 192.168.1.91:2379 | d2b20b971efea8ef | 3.4.4 | 25 kB | false | false | 8 | 64 | 64 | |
| 192.168.1.92:2379 | 6a3e67577dbd3de0 | 3.4.4 | 25 kB | true | false | 8 | 64 | 64 | |
| 192.168.1.93:2379 | 597745dd5a1f190a | 3.4.4 | 25 kB | false | false | 8 | 64 | 64 | |
+-------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
etcdctl --endpoints=$ENDPOINTS put foo "Hello World"
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS get foo
foo
Hello World
通过前缀获取数据:
#分别插入web1,web2,web3 三条数据
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS put web1 value1
OK
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS put web2 value2
OK
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS put web3 value3
OK
#通过web前缀获取这三条数据
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS get web --prefix
web1
value1
web2
value2
web3
value3
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS del foo
1 #删除了1条数据
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS del web --prefix
3 #删除了3条数据
#先插入一条数据
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS put user1 bad
OK
#开启事务
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS txn --interactive
#输入判断条件,两次回车
compares:
value("user1") = "good"
#如果user1 = good,则执行del user1
success requests (get, put, del):
del user1
#如果user1 != good,则执行put user1 verygood
failure requests (get, put, del):
put user1 verygood
FAILURE
OK
#由于user1原先不等于good,所以执行put user1 verygood
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS get user1
user1
verygood #现在user1的值已经被改为verygood
watch用于获取监听信息的更改,并且支持持续地监听。在窗口1开启监听:
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS watch
另外开启一个窗口2写入数据:
etcdctl --endpoints=$ENDPOINTS put stock1 1000
此时窗口1会收到更新信息:
stock1
PUT
stock1
1000
也支持前缀监听:
etcdctl --endpoints=$ENDPOINTS watch stock --prefix
etcdctl --endpoints=$ENDPOINTS put stock1 10
etcdctl --endpoints=$ENDPOINTS put stock2 20
lease用于设置key的TTL时间。
etcdctl --endpoints=$ENDPOINTS lease grant 300
# lease 2be7547fbc6a5afa granted with TTL(300s)
#创建数据,并指定lease
etcdctl --endpoints=$ENDPOINTS put sample value --lease=2be7547fbc6a5afa
#此时还可以获取到数据
etcdctl --endpoints=$ENDPOINTS get sample
#重置租约时间到原先指定的300s,会重复刷新
etcdctl --endpoints=$ENDPOINTS lease keep-alive 2be7547fbc6a5afa
#立即释放
etcdctl --endpoints=$ENDPOINTS lease revoke 2be7547fbc6a5afa
#租约到期或者直接revoke,就获取不到这个key了
etcdctl --endpoints=$ENDPOINTS get sample
窗口1给key1加锁:
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS lock key1
key1/28ef77fb1b464b49
窗口2也想给key1加锁,此时会卡住,直到窗口1释放锁以后,窗口2才能给key1加锁:
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS lock key1
snapshot只能指定其中一个etcd节点:
[root@etcd1 snap]# ENDPOINTS=$HOST_1:2379
[root@etcd1 snap]# etcdctl --endpoints=$ENDPOINTS snapshot save my.db
{"level":"info","ts":1614828932.1646311,"caller":"snapshot/v3_snapshot.go:110","msg":"created temporary db file","path":"my.db.part"}
{"level":"info","ts":1614828932.166052,"caller":"snapshot/v3_snapshot.go:121","msg":"fetching snapshot","endpoint":"192.168.1.91:2379"}
{"level":"info","ts":1614828932.1835077,"caller":"snapshot/v3_snapshot.go:134","msg":"fetched snapshot","endpoint":"192.168.1.91:2379","took":0.018725853}
{"level":"info","ts":1614828932.1836193,"caller":"snapshot/v3_snapshot.go:143","msg":"saved","path":"my.db"}
Snapshot saved at my.db
#快照存放在data.etcd/member/snap
[root@etcd1 snap]# ls
db my.db
[root@etcd1 snap]# etcdctl --write-out=table --endpoints=$ENDPOINTS snapshot status my.db
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| 4c02b18b | 36 | 44 | 25 kB |
+----------+----------+------------+------------+
删除老成员,添加新成员(在原先etcd节点上操作):
# 获取成员ID
export ETCDCTL_API=3
HOST_1=192.168.1.91
HOST_2=192.168.1.92
HOST_3=192.168.1.93
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 member list
# 移除成员
MEMBER_ID=278c654c9a6dfd3b #移除node3
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 \
member remove ${MEMBER_ID}
# 添加新成员 (etcd4)
export ETCDCTL_API=3
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_4=etcd-node-4
HOST_1=192.168.1.91
HOST_2=192.168.1.92
HOST_4=192.168.1.94 # new member
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379 \
member add ${NAME_4} \
--peer-urls=http://${HOST_4}:2380
在新etcd节点操作:
#如果新成员在相同的磁盘上启动,确保移除data目录
TOKEN=cr7-etcd
CLUSTER_STATE=existing #加入新集群
NAME_1=etcd1
NAME_2=etcd2
NAME_4=etcd4
HOST_1=192.168.1.91
HOST_2=192.168.1.92
HOST_4=192.168.1.94 # new member
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}
root 是 etcd 的超级管理员,拥有 etcd 的所有权限,在开启角色认证之前为们必须要先建立好 root 用户。还需要注意的是 root 用户必须拥有 root 的角色,允许在 etcd 的所有操作。(有一个特殊用户root,一个特殊角色root。)
#创建root用户,root用户自动有最高root权限
[root@etcd1 ~]# etcdctl --endpoints=$ENDPOINTS user add root
#设置密码123456
Password of root:
Type password of root again for confirmation:
User root created
#创建一个普通权限,可以对foo这key进行读写操作
#创建一个role
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} role add user1-role
Role user1-role created
#给role赋予权限
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} role grant-permission user1-role readwrite foo
Role user1-role updated
#查看创建的role
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} role get user1-role
Role user1-role
KV Read:
foo
KV Write:
foo
#创建用户并关联权限
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} user add user1
#设置密码123
Password of user1:
Type password of user1 again for confirmation:
User user1 created
#将user1-role的权限关联到user1
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} user grant-role user1 user1-role
Role user1-role is granted to user user1
#启用认证
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} auth enable
Authentication Enabled
#此时不指定用户将无法进行任何操作
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} put foo bar
{"level":"warn","ts":"2021-03-04T22:53:30.740+0800","caller":"clientv3/retry_interceptor.go:61","msg":"retrying of unary invoker failed","target":"endpoint://client-b379b941-6eec-46a3-87ac-76417ae9ea5f/192.168.1.91:2379","attempt":0,"error":"rpc error: code = InvalidArgument desc = etcdserver: user name is empty"}
Error: etcdserver: user name is empty
#使用用户user1可以对foo进行读写操作
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} put foo bar --user=user1:123
OK
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} get foo --user=user1:123
foo
bar
#但是user1无法对其他key进行操作
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} put foo2 bar2 --user=user1:123
{"level":"warn","ts":"2021-03-04T22:53:57.474+0800","caller":"clientv3/retry_interceptor.go:61","msg":"retrying of unary invoker failed","target":"endpoint://client-f67861bf-ce78-48b2-90a6-1f91ce508936/192.168.1.91:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied
#使用root用户关闭认证
[root@etcd1 ~]# etcdctl --endpoints=${ENDPOINTS} auth disable --user=root:123456
Authentication Disabled