3.1 ETCD安装
3.1.1 直接安装
以MAC系统为例,讲述2种按照方法,第一种很简单,是Mac自带的:
#用brew安装非常方便,没安装的自行安装Homebrew,通过下面命令可以查看安装包brew search etcd#安装brew install etcd#查看版本etcd --version#启动,如果没有--enable-v2=true,就不用使用v2的接口etcd --enable-v2=true
不过这种方式可能会安装失败,我这把失败时提示日志目前没有权限,根据提示执行相关命令即可,我这边的提示如下:
sudo chown -R $(whoami) /usr/local/var/log
3.1.2 源码安装
我个人更推荐下面这种安装方式:
ETCD_VER=v3.4.14 # choose either URLGOOGLE_URL=https://storage.googleapis.com/etcdGITHUB_URL=https://github.com/etcd-io/etcd/releases/downloadDOWNLOAD_URL=${GOOGLE_URL} rm -f /tmp/etcd-${ETCD_VER}-darwin-amd64.ziprm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-darwin-amd64.zip -o /tmp/etcd-${ETCD_VER}-darwin-amd64.zipunzip /tmp/etcd-${ETCD_VER}-darwin-amd64.zip -d /tmp && rm -f /tmp/etcd-${ETCD_VER}-darwin-amd64.zipmv /tmp/etcd-${ETCD_VER}-darwin-amd64/* /tmp/etcd-download-test && rm -rf mv /tmp/etcd-${ETCD_VER}-darwin-amd64 #输出etcd版本/tmp/etcd-download-test/etcd --version/tmp/etcd-download-test/etcdctl version #这里是把etcd和etcdctl copy到bin目录下面cp /tmp/etcd-download-test/etcd /usr/local/bincp /tmp/etcd-download-test/etcdctl /usr/local/bin
然后执行:
#安装etcdsh etcd_install.sh#查看版本etcd --version#启动,如果没有--enable-v2=true,就不用使用v2的接口etcd --enable-v2=true
3.2 集群部署
3.2.1 部署流程
下面我们可以部署一个etcd集群,我把代码还是写到文件中,第一个脚本为不支持在 Docs 外粘贴 block,内容如下(启动etcd需要很多参数,这些参数我都已经注释说明,更多参数详见:https://www.cnblogs.com/linuxws/p/11194403.html):
TOKEN=token-01CLUSTER_STATE=newNAME_1=etcd-01NAME_2=etcd-02NAME_3=etcd-03HOST_1=127.0.0.1HOST_2=127.0.0.1HOST_3=127.0.0.1PORT_API_1=2379PORT_PEER_1=2380PORT_API_2=2479PORT_PEER_2=2480PORT_API_3=2579PORT_PEER_3=2580 CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_PEER_1},${NAME_2}=http://${HOST_2}:${PORT_PEER_2},${NAME_3}=http://${HOST_3}:${PORT_PEER_3} # For every machineTHIS_NAME=${NAME_1}THIS_IP=${HOST_1}THIS_PORT_API=${PORT_API_1}THIS_PORT_PEER=${PORT_PEER_1}# 用于杀死进程lsof -i:2379 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9 # --enable-v2 支持v2接口,可以省略# --data-dir 数据存储目录,可以省略# --name 节点名称,必须# --initial-advertise-peer-urls 数据在集群内进行交互的url,必须# --listen-peer-urls 集群节点之间通信监听的url,必须# --advertise-client-urls 客户通过该地址与本member交互信息,可以省略# --listen-client-urls 监听客户端请求的url,必须# --initial-cluster 初始启动的集群配置,必须# --initial-cluster-state 初始化集群状态,取值为new和existing,可以省略# --initial-cluster-token 集群初始化token,可以省略etcd --enable-v2=true --data-dir=data.${THIS_NAME} --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} --listen-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \ --advertise-client-urls http://${THIS_IP}:${THIS_PORT_API} --listen-client-urls http://${THIS_IP}:${THIS_PORT_API} \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
第二个脚本,需要把里面的内容替换如下:
# For every machineTHIS_NAME=${NAME_2}THIS_IP=${HOST_2}THIS_PORT_API=${PORT_API_2}THIS_PORT_PEER=${PORT_PEER_2}# 用于杀死进程lsof -i:2479 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9
第三个脚本,需要把里面的内容替换如下:
# For every machineTHIS_NAME=${NAME_3}THIS_IP=${HOST_3}THIS_PORT_API=${PORT_API_3}THIS_PORT_PEER=${PORT_PEER_3}# 用于杀死进程lsof -i:2579 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9
有了这3个脚本,分别开3个窗口,分别执行,服务启动截图如下:
当这3个脚本全部启动后,集群部署完毕,我们检查一下3个节点的健康状态:
curl http://127.0.0.1:2379/healthcurl http://127.0.0.1:2479/healthcurl http://127.0.0.1:2579/health
返回结果如下,其中peerURLs是节点互相通信访问的url,clientURLs是对外访问的url:
{"members":[ {"id":"264ae6bc59e99892","name":"etcd-01","peerURLs":["http://127.0.0.1:2380" ],"clientURLs":["http://127.0.0.1:2379" ] }, {"id":"dbafe5ad6b652eda","name":"etcd-02","peerURLs":["http://127.0.0.1:2480" ],"clientURLs":["http://127.0.0.1:2479" ] }, {"id":"f570ae41f524bdcb","name":"etcd-03","peerURLs":["http://127.0.0.1:2580" ],"clientURLs":["http://127.0.0.1:2579" ] } ]}
3.2.2 遇到问题
问题1:服务启动后,不能使用v2接口,比如执行
“curl http://127.0.0.1:2379/v2/members”,提示“404 page not found”
问题原因:因为V3.4版本默认是V3接口,不支持V2
解决方案:需要在启动etcd时,加上“--enable-v2=true”,强制使用V2接口
问题2:服务启动失败,提示“conflicting environment variable "ETCD_ENABLE_V2" is shadowed by corresponding command-line flag (either unset environment variable or disable flag)”
问题原因:因为启动etcd时,参数“--enable-v2=true”导致,因为V3.4版本会读取该配置,所以提示配置重复。
解决方案:不能删除该参数,否则会引入其它问题,我是关闭所有窗口,然后重新启动etcd即可。
问题3:启动某个节点时,提示member已经存在
问题原因:因为之前启动过该节点,该member已经存在,不能初始化,只能加入已经存在的member
解决方案:需要将启动脚本中的“CLUSTER_STATE=new”改为“CLUSTER_STATE=existing”
3.3 常规操作
3.3.1 集群管理
我们在部署集群时,用到一些方法,这里我简单汇总一下:
// 版本检查,输出{"etcdserver":"3.4.14","etcdcluster":"3.4.0"}curl http://127.0.0.1:2379/version// 健康检查,输出{"health":"true"}curl http://127.0.0.1:2379/health// 查看集群节点curl http://127.0.0.1:2379/v2/members
3.3.2 键值操作
设置键的值:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world"
返回结果:
{"action":"set","node":{"key":"/message","value":"hello world","modifiedIndex":43,"createdIndex":43 }}
读取键的值:
curl http://127.0.0.1:2379/v2/keys/message
返回结果:
{"action":"get","node":{"key":"/message","value":"hello world","modifiedIndex":43,"createdIndex":43 }}
给键设置10s的超时时间:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world" -d ttl=10
返回结果(prevNode是旧值):
{"action":"set","node":{"key":"/message","value":"hello world","expiration":"2021-01-21T00:16:13.777434Z","ttl":10,"modifiedIndex":44,"createdIndex":44 },"prevNode":{"key":"/message","value":"hello world","modifiedIndex":43,"createdIndex":43 }}
获取该键值,超时后,就提示“key not found”:
3.3.3 watch通知
可以对key设置监听,当key的值有变化时,会通知监听的客户端,我们先在客户端A监听key:
curl http://127.0.0.1:2379/v2/keys/message?wait=true
然后在客户端B,修改该key的值:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world2"
客户端A返回并退出,返回结果:
{"action":"set","node":{"key":"/message","value":"hello world2","modifiedIndex":48,"createdIndex":48 }}
如果希望客户端A能持续监听,不退出,可以通过增加stream=true参数:
curl "http://127.0.0.1:2379/v2/keys/message?wait=true&stream=true"
当在客户端B执行如下时:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world" -d ttl=1
客户端A会实时监听返回,比如当给key设置值,或者当key过期时,客户端A都会监听到:
3.3.4 目录操作
这个可用于配置管理,因为etcd数据结构是颗目录树,所以我们也可以PUT一个目录,目录里存放我们的服务的配置数据:
curl http://127.0.0.1:2379/v2/keys/animal -XPUT -d dir=true
返回结果:
{"action":"set","node":{"key":"/animal","dir":true,"modifiedIndex":80,"createdIndex":80 }}
可以获取目录中的数据,如果增加参数recursive=true,可以递归罗列:
curl http://127.0.0.1:2379/v2/keys?recursive=true
返回结果:
{"action":"get","node":{"dir":true,"nodes":[ {"key":"/animal","dir":true,"modifiedIndex":80,"createdIndex":80 } ] }}
下面我们举一个稍微复杂的示例,创建2个目录:
curl http://127.0.0.1:2379/v2/keys/animal -XPUT -d dir=truecurl http://127.0.0.1:2379/v2/keys/animal/cat -XPUT -d value="a little cat"curl http://127.0.0.1:2379/v2/keys/animal/dog -XPUT -d value="a big dog"curl http://127.0.0.1:2379/v2/keys/tool -XPUT -d dir=truecurl http://127.0.0.1:2379/v2/keys/tool/car -XPUT -d value="a small car"curl http://127.0.0.1:2379/v2/keys/tool/ship -XPUT -d value="a big ship"
然后递归获取里面的数据:
curl http://127.0.0.1:2379/v2/keys?recursive=true
返回结果:
{"action":"get","node":{"dir":true,"nodes":[ {"key":"/tool","dir":true,"nodes":[ {"key":"/tool/car","value":"a small car","modifiedIndex":84,"createdIndex":84 }, {"key":"/tool/ship","value":"a big ship","modifiedIndex":85,"createdIndex":85 } ],"modifiedIndex":83,"createdIndex":83 }, {"key":"/animal","dir":true,"nodes":[ {"key":"/animal/cat","value":"a little cat","modifiedIndex":81,"createdIndex":81 }, {"key":"/animal/dog","value":"a big dog","modifiedIndex":82,"createdIndex":82 } ],"modifiedIndex":80,"createdIndex":80 } ] }}
我们也可以通过下面方式删除animal目录:
curl http://127.0.0.1:2379/v2/keys/animal?recursive=true -XDELETE
返回结果:
{"action":"delete","node":{"key":"/animal","dir":true,"modifiedIndex":86,"createdIndex":80 },"prevNode":{"key":"/animal","dir":true,"modifiedIndex":80,"createdIndex":80 }}
3.3.5 自动创建有序key
通过对一个目录发起POST请求,我们能够让创建的key的名字是有序的。自动创建有序key的这个功能在许多场景下都很有用,例如,用于实现一个对处理顺序有严格要求的队列等。
curl http://127.0.0.1:2379/v2/keys/queue -XPOST -d value=Jobl
返回结果:
{"action":"create","node":{"key":"/queue/00000000000000000088","value":"Jobl","modifiedIndex":88,"createdIndex":88 }}
如果我们多执行几次,会发现key中的值是递增关系,但如果在并发情况下,就不一定每次都是递增1,看一下执行结果:
我们可以列举这个目录下所有的key,并排序输出:
curl -s "http://127.0.0.1:2379/v2/keys/queue?recursive=true&sort=true"
输出结果:
{"action":"get","node":{"key":"/queue","dir":true,"nodes":[ {"key":"/queue/00000000000000000088","value":"Jobl","modifiedIndex":88,"createdIndex":88 }, {"key":"/queue/00000000000000000089","value":"Jobl","modifiedIndex":89,"createdIndex":89 }, {"key":"/queue/00000000000000000090","value":"Jobl","modifiedIndex":90,"createdIndex":90 }, {"key":"/queue/00000000000000000091","value":"Jobl","modifiedIndex":91,"createdIndex":91 }, {"key":"/queue/00000000000000000092","value":"Jobl","modifiedIndex":92,"createdIndex":92 }, {"key":"/queue/00000000000000000093","value":"Jobl","modifiedIndex":93,"createdIndex":93 } ],"modifiedIndex":88,"createdIndex":88 }}