etcd
etcd 是 CoreOS 基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。
在分布式系统中,如何管理节点间的状态一直是一个难题,etcd 像是专门为集群环境的服务发现和注册而设计,它提供了数据 TTL 失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便地跟踪并管理集群节点的状态。
- 键值对存储:将数据存储在分层组织的目录中,如同在标准文件系统中
- 监测变更:监测特定的键或目录以进行更改,并对值的更改做出反应
- 简单:curl 可访问的用户的 API (HTTP+JSON)
- 安全:可选的 SSL 客户端证书认证
- 快速:单实例每秒 1000 次写操作,2000+ 次读操作
- 可靠:使用 Raft 算法保证一致性
主要功能
- 基本的 key-value 存储
- 监听机制
- key 的过期及续约机制,用于监控和服务发现
- 原子 Compare And Swap 和 Compare And Delete,用于分布式锁和 leader 选举
使用场景
- 也可以用于键值对存储,应用程序可以读取和写入 etcd 中的数据
- etcd 比较多的应用场景是用于服务注册与发现
- 基于监听机制的分布式异步系统
键值对存储
etcd 是一个键值存储的组件,其他的应用都是基于其键值存储的功能展开。
- 采用 kv 型数据存储,一般情况下比关系型数据库快。
- 支持动态存储(内存)以及静态存储(磁盘)。
- 分布式存储,可集成为多节点集群。
- 存储方式,采用类似目录结构。(B+tree)
- 只有叶子节点才能真正存储数据,相当于文件。
- 叶子节点的父节点一定是目录,目录不能存储数据。
服务注册与发现
- 强一致性、高可用的服务存储目录。
- 基于 Raft 算法的 etcd 天生就是这样一个强一致性、高可用的服务存储目录。
- 一种注册服务和服务健康状况的机制。
- 用户可以在 etcd 中注册服务,并且对注册的服务配置 key TTL,定时保持服务的心跳以达到监控健康状态的效果。

消息发布与订阅
- 在分布式系统中,最适用的一种组件间通信方式就是消息发布与订阅。
- 即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦主题有消息发布,就会实时通知订阅者。
- 通过这种方式可以做到分布式系统配置的集中式管理与动态更新。
- 应用中用到的一些配置信息放到 etcd 上进行集中管理。
- 应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 节点上注册一个 Watcher 并等待,以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。

etcd 的安装
下载安装包,参考 https://github.com/etcd-io/etcd/releases
安装
ETCD_VER=v3.4.17
DOWNLOAD_URL=https://github.com/etcd-io/etcd/releases/download
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
运行
cd /tmp/etcd-download-test/
./etcd
清除
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test
更多信息:https://github.com/cncamp/101/blob/master/module5/1.etcd-member-list.MD
第三方库和客户端工具
目前有很多支持etcd的库和客户端工具
- 命令行客户端工具 etcdctl
- 安装: sudo apt install etcd-client(默认已安装)
- Go 客户端 go-etcd
- Java 客户端 jetcd
- Python 客户端 python-etcd
etcd 练习
cd /tmp/etcd-download-test/
查看集群成员状态
./etcdctl member list
8e9e05c52164694d, started, default, http://localhost:2380, http://localhost:2379, false
./etcdctl --endpoints=127.0.0.1:2379 member list --write-out=table
+------------------+---------+---------+-----------------------+-----------------------+------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER |
+------------------+---------+---------+-----------------------+-----------------------+------------+
| 8e9e05c52164694d | started | default | http://localhost:2380 | http://localhost:2379 | false |
+------------------+---------+---------+-----------------------+-----------------------+------------+
基本的数据读写操作
写入数据
./etcdctl --endpoints=localhost:2379 put /version v1
OK
读取数据
./etcdctl --endpoints=localhost:2379 get /version
/version
v1
./etcdctl --endpoints=localhost:2379 get /version -wjson
{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":2,"raft_term":3},"kvs":[{"key":"L3ZlcnNpb24=","create_revision":2,"mod_revision":2,"version":1,"value":"djE="}],"count":1}
key 和 value 都是 base64 加密了的
echo L3ZlcnNpb24=|base64 -d
/version
echo djE=|base64 -d
v1
按 key 的前缀查询数据
./etcdctl --endpoints=localhost:2379 put /version/java 1.8
./etcdctl --endpoints=localhost:2379 put /version/hadoop 2.7.0
./etcdctl --endpoints=localhost:2379 put /version/spark 3.2
./etcdctl get --prefix /version
/version
v2
/version/hadoop
2.7.0
/version/java
1.8
/version/spark
3.2
只显示键值
./etcdctl get --prefix / --keys-only
/version
/version/hadoop
/version/java
/version/spark
查看 debug 信息
类似于 k8s 中的 -v=9
./etcdctl get --prefix / --keys-only --debug
ETCDCTL_CACERT=
ETCDCTL_CERT=
ETCDCTL_COMMAND_TIMEOUT=5s
ETCDCTL_DEBUG=true
ETCDCTL_DIAL_TIMEOUT=2s
ETCDCTL_DISCOVERY_SRV=
ETCDCTL_DISCOVERY_SRV_NAME=
ETCDCTL_ENDPOINTS=[127.0.0.1:2379]
ETCDCTL_HEX=false
ETCDCTL_INSECURE_DISCOVERY=true
ETCDCTL_INSECURE_SKIP_TLS_VERIFY=false
ETCDCTL_INSECURE_TRANSPORT=true
ETCDCTL_KEEPALIVE_TIME=2s
ETCDCTL_KEEPALIVE_TIMEOUT=6s
ETCDCTL_KEY=
ETCDCTL_PASSWORD=
ETCDCTL_USER=
ETCDCTL_WRITE_OUT=simple
WARNING: 2022/11/14 21:45:14 Adjusting keepalive ping interval to minimum period of 10s
WARNING: 2022/11/14 21:45:14 Adjusting keepalive ping interval to minimum period of 10s
INFO: 2022/11/14 21:45:14 parsed scheme: "endpoint"
INFO: 2022/11/14 21:45:14 ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 <nil> 0 <nil>}]
/version
/version/hadoop
/version/java
/version/spark
监听数据
./etcdctl --endpoints=localhost:2379 watch /version
./etcdctl --endpoints=localhost:2379 put /version v2
PUT
/version
v2
- 监听前缀
./etcdctl --endpoints=localhost:2379 watch --prefix /version
核心:TTL & CAS
TTL (time to live)指的是给一个 key 设置一个有效期,到期后这个 key 就会被自动删掉,这在很多分布式锁的实现上都会用到,可以保证锁的实时有效性。
Atomic Compare-and-Swap (CAS) 指的是在对 key 进行赋值的时候,客户端需要提供一些条 件,当这些条件满足后,才能赋值成功。这些条件包括:
- prevExist: key 当前赋值前是否存在
- prevValue: key 当前赋值前的值
- prevlndex: key 当前赋值前的 Index
这样的话,key 的设置是有前提的,需要知道这个 key 当前的具体情况才可以对其设置。
查看 k8s 在 etcd 中存储的数据
进入 etcd 容器
ks get pod|grep etcd
ks exec -it etcd-cjx sh
查看启动参数
ps -ef|grep etcd
root 2431 2372 2 14:23 ? 00:00:08 etcd --advertise-client-urls=https://192.168.1.168:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --experimental-initial-corrupt-check=true --initial-advertise-peer-urls=https://192.168.1.168:2380 --initial-cluster=cjx=https://192.168.1.168:2380 --key-file=/etc/kubernetes/pki/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://192.168.1.168:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://192.168.1.168:2380 --name=cjx --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/etc/kubernetes/pki/etcd/peer.key --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt --snapshot-count=10000 --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- endpoints: --advertise-client-urls=https://192.168.1.168:2379
- cert: --cert-file=/etc/kubernetes/pki/etcd/server.crt
- key: --key-file=/etc/kubernetes/pki/etcd/server.key
- cacert: --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
操作
member list
etcdctl --endpoints https://127.0.0.1:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt member list --write-out=table
output
+------------------+---------+------+----------------------------+----------------------------+------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER |
+------------------+---------+------+----------------------------+----------------------------+------------+
| 37646e37976003c2 | started | cjx | https://192.168.1.168:2380 | https://192.168.1.168:2379 | false |
+------------------+---------+------+----------------------------+----------------------------+------------+
get
etcdctl --endpoints https://127.0.0.1:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get --prefix / --keys-only
output
......
/registry/pods/default/nginx
......
/registry/pods/kube-system/kube-proxy-kwnx7
......
/registry/services/specs/calico-system/calico-typha
/registry/services/specs/default/kubernetes
/registry/services/specs/kube-system/kube-dns
获取细节
etcdctl --endpoints https://127.0.0.1:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get /registry/pods/default/nginx
etcdctl --endpoints https://127.0.0.1:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get /registry/pods/kube-system/kube-proxy-kwnx7
结果以 protobuf 格式存储