etcd 是一个分布式、高可用、强一致性的键值存储系统,主要用于存储分布式系统中的关键元数据,是 Kubernetes 集群的核心组件之一(被称为 Kubernetes 的 "大脑")。
官方网站: etcd
核心特性:
- 强一致性:基于 Raft 共识算法,确保分布式环境中数据的一致性(所有节点看到的数据完全相同)。
- 高可用:支持集群部署(通常 3、5 或 7 个节点),单个节点故障不影响整体服务,自动选举新 leader 节点。
- 键值存储 :以键值对(key-value)形式存储数据,键支持目录结构(类似文件系统,如
/kubernetes/pods/default/my-pod
)。 - Watch 机制:支持监听键或前缀的变化,实时感知数据更新(Kubernetes 组件间通信依赖此特性)。
- API 接口:提供 HTTP/JSON 和 gRPC 接口,方便操作和集成。
在 Kubernetes 中的作用:
etcd 是 Kubernetes 集群的唯一数据存储中心,所有集群状态都保存在 etcd 中,包括但不限于:
- 集群节点信息(节点健康状态、资源等)。
- Pod、Deployment、Service 等资源的配置和状态。
- 网络规则(如 NetworkPolicy)、存储配置(如 PV/PVC)。
- 权限认证数据(如 RBAC 规则、ServiceAccount)。
Kubernetes 各组件(如 kube-apiserver、kube-scheduler、kube-controller-manager)通过 kube-apiserver 与 etcd 交互,实现集群状态的读取和更新。
基本操作工具:etcdctl
etcdctl
是 etcd 的命令行工具,用于管理 etcd 集群和数据,常用操作如下:
-
检查集群健康状态:
bashetcdctl endpoint health --endpoints=<etcd节点地址> # 如 --endpoints=https://127.0.0.1:2379
-
查看集群成员:
bashetcdctl member list --endpoints=<etcd节点地址>
-
写入数据:
bashetcdctl put /demo/key1 "value1" # 存储键 /demo/key1,值为 value1
-
读取数据:
bashetcdctl get /demo/key1 # 读取单个键 etcdctl get /demo/ --prefix # 读取前缀为 /demo/ 的所有键 etcdctl get / --prefix --keys-only # 只显示key
-
删除数据:
bashetcdctl del /demo/key1 # 删除单个键 etcdctl del /demo/ --prefix # 删除前缀为 /demo/ 的所有键
重要注意事项:
-
数据备份 :etcd 数据是 Kubernetes 集群的核心,必须定期备份(通过
etcdctl snapshot save
命令),否则数据丢失可能导致集群崩溃且无法恢复。单点数据备份
bashetcdctl snapshot save backup.db --endpoints=<etcd地址> # 创建快照
bashroot@etcd1:/data/etcdbackup# etcdctl snapshot save /data/etcdbackup/etcd-backup-$(date +\%Y\%m\%d\%H\%M)
bash# 单点恢复数据 etcdctl snapshot restore /data/etcdbackup/etcd-backup-202509231530 --data-dir=/data/etcd_backup/ vim /etc/systemd/system/etcd.service # 修改WorkingDirectory=/var/lib/etcd 路径为恢复路径/data/etcd_backup WorkingDirectory=/data/etcd_backup --data-dir=/data/etcd_backup systemctl daemon-reload systemctl restart etcd
集群数据备份
bashroot@master1:/etc/kubeasz# cd /etc/kubeasz/ # 语法格式 ./ezctl backup 集群名称 root@master1:/etc/kubeasz# ./ezctl backup k8s-01 ansible-playbook -i clusters/k8s-01/hosts -e @clusters/k8s-01/config.yml playbooks/94.backup.yml 2025-09-23 17:55:42 INFO cluster:k8s-01 backup begins in 5s, press any key to abort: PLAY [localhost] ********************************************************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************************************************** ok: [localhost] TASK [set NODE_IPS of the etcd cluster] ********************************************************************************************************************************************************************************************************************************************** ok: [localhost] TASK [get etcd cluster status] ******************************************************************************************************************************************************************************************************************************************************* changed: [localhost] TASK [debug] ************************************************************************************************************************************************************************************************************************************************************************* ok: [localhost] => { "ETCD_CLUSTER_STATUS": { "changed": true, "cmd": "for ip in 192.168.121.106 192.168.121.107 192.168.121.108 ;do ETCDCTL_API=3 /etc/kubeasz/bin/etcdctl --endpoints=https://\"$ip\":2379 --cacert=/etc/kubeasz/clusters/k8s-01/ssl/ca.pem --cert=/etc/kubeasz/clusters/k8s-01/ssl/etcd.pem --key=/etc/kubeasz/clusters/k8s-01/ssl/etcd-key.pem endpoint health; done", "delta": "0:00:00.175756", "end": "2025-09-23 17:55:50.572736", "failed": false, "rc": 0, "start": "2025-09-23 17:55:50.396980", "stderr": "", "stderr_lines": [], "stdout": "https://192.168.121.106:2379 is healthy: successfully committed proposal: took = 30.916864ms\nhttps://192.168.121.107:2379 is healthy: successfully committed proposal: took = 11.701547ms\nhttps://192.168.121.108:2379 is healthy: successfully committed proposal: took = 11.665092ms", "stdout_lines": [ "https://192.168.121.106:2379 is healthy: successfully committed proposal: took = 30.916864ms", "https://192.168.121.107:2379 is healthy: successfully committed proposal: took = 11.701547ms", "https://192.168.121.108:2379 is healthy: successfully committed proposal: took = 11.665092ms" ] } } TASK [get a running ectd node] ******************************************************************************************************************************************************************************************************************************************************* changed: [localhost] TASK [debug] ************************************************************************************************************************************************************************************************************************************************************************* ok: [localhost] => { "RUNNING_NODE.stdout": "192.168.121.106" } TASK [get current time] ************************************************************************************************************************************************************************************************************************************************************** changed: [localhost] TASK [make a backup on the etcd node] ************************************************************************************************************************************************************************************************************************************************ changed: [localhost -> 192.168.121.106] TASK [fetch the backup data] ********************************************************************************************************************************************************************************************************************************************************* changed: [localhost -> 192.168.121.106] TASK [update the latest backup] ****************************************************************************************************************************************************************************************************************************************************** changed: [localhost] PLAY RECAP *************************************************************************************************************************************************************************************************************************************************************************** localhost : ok=10 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 备份所在位置 root@master1:/etc/kubeasz# ls clusters/k8s-01/backup/ snapshot.db snapshot_202509231755.db # 校验 etcd 快照文件的完整性 root@master1:/etc/kubeasz# md5sum clusters/k8s-01/backup/snapshot.db 02654ea19e4132469d461114d145d977 clusters/k8s-01/backup/snapshot.db root@master1:/etc/kubeasz# md5sum clusters/k8s-01/backup/snapshot_202509231755.db 02654ea19e4132469d461114d145d977 clusters/k8s-01/backup/snapshot_202509231755.db # 误删除一个pod,测试恢复 root@master1:/etc/kubeasz# kubectl get pods NAME READY STATUS RESTARTS AGE net-test2 1/1 Running 4 (6h41m ago) 46h net-test3 1/1 Running 2 (18h ago) 46h net-test4 1/1 Running 0 76m root@master1:/etc/kubeasz# kubectl delete pod net-test2 pod "net-test2" deleted root@master1:/etc/kubeasz# kubectl get pods NAME READY STATUS RESTARTS AGE net-test3 1/1 Running 2 (18h ago) 46h net-test4 1/1 Running 0 83m # 恢复备份 root@master1:/etc/kubeasz# ./ezctl restore k8s-01 ansible-playbook -i clusters/k8s-01/hosts -e @clusters/k8s-01/config.yml playbooks/95.restore.yml 2025-09-23 18:09:25 INFO cluster:k8s-01 restore begins in 5s, press any key to abort: PLAY [kube_master] ******************************************************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************************************************** ok: [192.168.121.101] ok: [192.168.121.102] TASK [stopping kube_master services] ************************************************************************************************************************************************************************************************************************************************* changed: [192.168.121.102] => (item=kube-apiserver) changed: [192.168.121.101] => (item=kube-apiserver) changed: [192.168.121.101] => (item=kube-controller-manager) changed: [192.168.121.102] => (item=kube-controller-manager) changed: [192.168.121.101] => (item=kube-scheduler) changed: [192.168.121.102] => (item=kube-scheduler) PLAY [kube_master,kube_node] ********************************************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************************************************** ok: [192.168.121.112] ok: [192.168.121.111] TASK [stopping kube_node services] *************************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.102] => (item=kubelet) changed: [192.168.121.111] => (item=kubelet) changed: [192.168.121.101] => (item=kubelet) changed: [192.168.121.112] => (item=kubelet) changed: [192.168.121.102] => (item=kube-proxy) changed: [192.168.121.111] => (item=kube-proxy) changed: [192.168.121.112] => (item=kube-proxy) changed: [192.168.121.101] => (item=kube-proxy) PLAY [etcd] ************************************************************************************************************************************************************************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************************************************** ok: [192.168.121.106] ok: [192.168.121.107] ok: [192.168.121.108] TASK [cluster-restore : 停止ectd 服务] *************************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.108] changed: [192.168.121.107] changed: [192.168.121.106] TASK [cluster-restore : 清除etcd 数据目录] ************************************************************************************************************************************************************************************************************************************************* changed: [192.168.121.107] changed: [192.168.121.108] changed: [192.168.121.106] TASK [cluster-restore : 生成备份目录] ****************************************************************************************************************************************************************************************************************************************************** ok: [192.168.121.106] changed: [192.168.121.107] changed: [192.168.121.108] TASK [cluster-restore : 准备指定的备份etcd 数据] ********************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.108] changed: [192.168.121.106] changed: [192.168.121.107] TASK [cluster-restore : 清理上次备份恢复数据] ************************************************************************************************************************************************************************************************************************************************** ok: [192.168.121.106] ok: [192.168.121.107] ok: [192.168.121.108] TASK [cluster-restore : etcd 数据恢复] *************************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.107] changed: [192.168.121.106] changed: [192.168.121.108] TASK [cluster-restore : 恢复数据至etcd 数据目录] ********************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.106] changed: [192.168.121.108] changed: [192.168.121.107] TASK [cluster-restore : 重启etcd 服务] *************************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.106] changed: [192.168.121.107] changed: [192.168.121.108] TASK [cluster-restore : 以轮询的方式等待服务同步完成] ********************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.107] changed: [192.168.121.106] changed: [192.168.121.108] PLAY [kube_master] ******************************************************************************************************************************************************************************************************************************************************************* TASK [starting kube_master services] ************************************************************************************************************************************************************************************************************************************************* changed: [192.168.121.101] => (item=kube-apiserver) changed: [192.168.121.102] => (item=kube-apiserver) changed: [192.168.121.101] => (item=kube-controller-manager) changed: [192.168.121.102] => (item=kube-controller-manager) changed: [192.168.121.102] => (item=kube-scheduler) changed: [192.168.121.101] => (item=kube-scheduler) PLAY [kube_master,kube_node] ********************************************************************************************************************************************************************************************************************************************************* TASK [starting kube_node services] *************************************************************************************************************************************************************************************************************************************************** changed: [192.168.121.111] => (item=kubelet) changed: [192.168.121.112] => (item=kubelet) changed: [192.168.121.102] => (item=kubelet) changed: [192.168.121.101] => (item=kubelet) changed: [192.168.121.111] => (item=kube-proxy) changed: [192.168.121.112] => (item=kube-proxy) changed: [192.168.121.102] => (item=kube-proxy) changed: [192.168.121.101] => (item=kube-proxy) PLAY RECAP *************************************************************************************************************************************************************************************************************************************************************************** 192.168.121.101 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.102 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.106 : ok=10 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.107 : ok=10 changed=8 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.108 : ok=10 changed=8 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.111 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.121.112 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 查看pod是否恢复 root@master1:/etc/kubeasz# kubectl get pods NAME READY STATUS RESTARTS AGE net-test2 1/1 Running 0 46h net-test3 1/1 Running 2 (18h ago) 46h net-test4 1/1 Running 0 86m
-
集群规模 :etcd 集群节点数需为奇数(3、5、7 等),以确保 Raft 算法能正常选举 leader(避免脑裂)。
-
性能优化:etcd 适合存储小容量元数据(而非大量业务数据),需合理规划资源(CPU、内存、磁盘 IO),避免成为集群瓶颈。
etcd.service 配置示例
bash
root@etcd1:/var/lib/etcd# cat /etc/systemd/system/etcd.service
ini
[Unit]
# 服务描述:标识此服务为 Etcd 服务器
Description=Etcd Server
# 启动顺序:在网络服务(network.target)之后启动
After=network.target
# 启动顺序:在网络完全就绪(network-online.target)之后启动(确保网络可用)
After=network-online.target
# 依赖关系:希望 network-online.target 启动(增强网络就绪的可靠性)
Wants=network-online.target
# 文档地址:指向 Etcd 官方文档(CoreOS 是 Etcd 的最初开发方)
Documentation=https://github.com/coreos
[Service]
# 服务类型:notify 表示服务启动完成后会主动通知 systemd(确保 systemd 准确感知状态)
Type=notify
# 工作目录:Etcd 运行时的工作目录(与数据目录可分开,此处与数据目录一致)
WorkingDirectory=/var/lib/etcd
# 启动命令:Etcd 主程序及参数配置
ExecStart=/usr/local/bin/etcd \
# 节点名称:当前 Etcd 节点的唯一标识(此处用 IP 作为名称,便于识别)
--name=etcd-192.168.121.106 \
# 服务器证书:Etcd 服务端用于加密客户端通信的证书(公钥)
--cert-file=/etc/kubernetes/ssl/etcd.pem \
# 服务器私钥:与上述证书配对的私钥(用于解密客户端请求)
--key-file=/etc/kubernetes/ssl/etcd-key.pem \
# Peer 证书:Etcd 集群内节点间通信的证书(公钥)
--peer-cert-file=/etc/kubernetes/ssl/etcd.pem \
# Peer 私钥:与上述 Peer 证书配对的私钥(用于集群内节点间解密通信)
--peer-key-file=/etc/kubernetes/ssl/etcd-key.pem \
# 可信 CA 证书:客户端通信时验证服务器证书的根 CA 证书
--trusted-ca-file=/etc/kubernetes/ssl/ca.pem \
# Peer 可信 CA 证书:集群内节点间通信时验证对方证书的根 CA 证书
--peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem \
# 初始公告 Peer 地址:向集群内其他节点公告的当前节点的 Peer 通信地址(用于集群发现)
--initial-advertise-peer-urls=https://192.168.121.106:2380 \
# 监听 Peer 地址:当前节点监听集群内其他节点通信的地址(2380 是 Etcd 集群通信默认端口)
--listen-peer-urls=https://192.168.121.106:2380 \
# 监听客户端地址:当前节点监听外部客户端(如 kube-apiserver)请求的地址(2379 是 Etcd 客户端默认端口)
# 同时监听了本地回环地址(127.0.0.1)和节点 IP,支持本地和远程客户端访问
--listen-client-urls=https://192.168.121.106:2379,http://127.0.0.1:2379 \
# 公告客户端地址:向外部客户端公告的访问地址(供客户端连接)
--advertise-client-urls=https://192.168.121.106:2379 \
# 初始集群令牌:标识集群的唯一令牌(同一集群内所有节点必须一致,用于区分不同集群)
--initial-cluster-token=etcd-cluster-0 \
# 初始集群成员:集群启动时的所有节点及对应的 Peer 地址(3 节点集群:106、107、108)
--initial-cluster=etcd-192.168.121.106=https://192.168.121.106:2380,etcd-192.168.121.107=https://192.168.121.107:2380,etcd-192.168.121.108=https://192.168.121.108:2380 \
# 初始集群状态:new 表示新建集群(首次启动时使用;加入已有集群时需改为 existing)
--initial-cluster-state=new \
# 数据目录:Etcd 持久化存储数据的目录(关键数据,需确保磁盘可靠)
--data-dir=/var/lib/etcd \
# WAL 日志目录:预写日志(Write-Ahead Log)存储路径(为空时默认使用 data-dir 下的 wal 子目录)
--wal-dir= \
# 快照触发次数:每处理 50000 次事务后生成一次数据快照(减少日志体积,加速恢复)
--snapshot-count=50000 \
# 自动压缩保留时间:开启数据自动压缩,保留最近 1 小时的历史数据(节省磁盘空间)
--auto-compaction-retention=1 \
# 自动压缩模式:periodic 表示按时间周期压缩(另一种模式是 revision,按版本号压缩)
--auto-compaction-mode=periodic \
# 最大请求字节数:单个客户端请求的最大大小限制(10MB,防止超大请求压垮服务)
--max-request-bytes=10485760 \
# 后端存储配额:Etcd 总存储容量上限(8GB,防止数据无限增长导致磁盘占满)
--quota-backend-bytes=8589934592
# 重启策略:always 表示无论服务以何种方式退出(正常或异常),都自动重启(保证高可用)
Restart=always
# 重启间隔:服务退出后,15 秒后再尝试重启(避免频繁重启)
RestartSec=15
# 文件描述符限制:允许 Etcd 打开的最大文件数(65536,避免高并发时文件句柄耗尽)
LimitNOFILE=65536
# OOM 评分调整:-999 表示降低被内核 OOM 杀死的优先级(避免 Etcd 因内存紧张被优先终止)
OOMScoreAdjust=-999
[Install]
# 启动级别:设置服务在多用户模式(multi-user.target)下自动启动(系统正常运行时的默认模式)
WantedBy=multi-user.target
批量检查 etcd 集群中所有节点的健康状态
bash
root@etcd1:/var/lib/etcd# export NODE_IPS="192.168.121.106 192.168.121.107 192.168.121.108"
root@etcd1:/var/lib/etcd# for ip in ${NODE_IPS}; do ETCDCTL_API=3 /usr/local/bin/etcdctl --write-out=table endpoint status --endpoints=https://${ip}:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/kubernetes/ssl/etcd.pem --key=/etc/kubernetes/ssl/etcd-key.pem endpoint health; done
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| https://192.168.121.106:2379 | 456deda1ec40b66c | 3.5.1 | 3.6 MB | true | false | 9 | 175420 | 175420 | |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| https://192.168.121.107:2379 | bf985c54c12faade | 3.5.1 | 3.6 MB | false | false | 9 | 175420 | 175420 | |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| https://192.168.121.108:2379 | 9bcce2ff5fe48bea | 3.5.1 | 3.6 MB | false | false | 9 | 175420 | 175420 | |
+------------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
表格字段含义及当前状态分析
每个节点的输出表格包含以下核心信息,逐一说明:
字段 | 含义说明 | 当前集群状态 |
---|---|---|
ENDPOINT |
etcd 节点的客户端访问地址 | 三个节点分别为 192.168.121.106:2379 、107:2379 、108:2379 ,与配置一致。 |
ID |
节点的唯一标识符(集群内唯一) | 三个节点 ID 不同(456deda... 、bf985c5... 、9bcce2f... ),正常。 |
VERSION |
etcd 版本号 | 所有节点均为 3.5.1 ,版本统一(版本不一致可能导致兼容性问题,此处无风险)。 |
DB SIZE |
数据存储大小(etcd 持久化数据的总容量) | 三个节点均为 3.6 MB ,数据量完全一致(说明数据同步正常)。 |
IS LEADER |
是否为 Raft 共识算法中的 leader 节点(负责协调数据同步) | 仅 192.168.121.106 为 true (单 leader 机制,符合 Raft 规范,正常)。 |
IS LEARNER |
是否为 learner 节点(新加入集群、正在同步数据的临时节点) | 所有节点均为 false (均为成熟节点,无新节点同步中,正常)。 |
RAFT TERM |
Raft 任期号(类似 "选举轮次",leader 变更时递增) | 所有节点均为 9 (任期一致,说明集群未分裂,共识正常)。 |
RAFT INDEX |
Raft 日志的最新索引(记录集群所有数据变更的序号) | 所有节点均为 175420 (索引一致,说明数据日志完全同步)。 |
RAFT APPLIED INDEX |
已应用到状态机的日志索引(表示节点已实际执行到该序号的变更) | 所有节点均为 175420 (与 RAFT INDEX 一致,说明所有变更均已生效,无延迟)。 |
ERRORS |
错误信息(为空表示无异常) | 所有节点均为空(无任何错误,通信和操作正常)。 |