安装与快速上手
准备环境
本篇在 Ubuntu 22.04 上用一个单节点把 etcd 跑起来,重点是把「启动参数、健康检查、etcdctl 读写、watch」这条链路走通。单节点只适合开发和验证,生产必须用集群,部署方式见后面的篇章。
先确认几件事:
- 一台能联网的 Ubuntu 22.04,建议 2 核 4G 起步,磁盘用 SSD。
- 端口
2379(客户端)和2380(集群内部通信)没有被占用。 - 用普通用户操作即可,但数据目录要提前建好并给足权限。
# 检查端口占用,有输出说明被占了
ss -lntp | grep -E '2379|2380'
# 规划目录
sudo mkdir -p /var/lib/etcd /etc/etcd
sudo chown -R "$USER":"$USER" /var/lib/etcd /etc/etcd
etcd 3.5 是过去几年最广泛部署的版本,K8s 1.2x 到 1.3x 的 kubeadm 集群大多跑在 3.5 上。3.6 是较新的版本,正在逐步铺开,启动参数和 etcdctl 用法基本兼容,差异主要集中在日志参数和 etcdutl 工具上。本篇命令以 3.5 为准。
二进制下载
官方发布页在 GitHub Releases:
- Releases 列表:etcd-io/etcd/releases
- 官方安装文档:etcd.io/docs/v3.5/install
下载、解压、放到 PATH:
# 版本号按 Releases 页的最新 3.5.x 替换
ETCD_VER=v3.5.16
ARCH=amd64
curl -LO https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-${ARCH}.tar.gz
tar xf etcd-${ETCD_VER}-linux-${ARCH}.tar.gz
sudo mv etcd-${ETCD_VER}-linux-${ARCH}/etcd /usr/local/bin/
sudo mv etcd-${ETCD_VER}-linux-${ARCH}/etcdctl /usr/local/bin/
etcd --version
etcdctl version
压缩包里通常还有 etcdutl(快照和备份工具)和 etcd-dump-* 系列调试工具,生产机器建议把 etcdutl 也装上。ARM 机器把 ARCH 换成 arm64,macOS 本地玩把平台换成 darwin-arm64 或 darwin-amd64。
Releases 页同时提供 sha256sum 文件。内网环境手动拷贝二进制时,至少对一下 checksum,避免拿到损坏或被替换的文件。
单节点启动
先用前台方式启动,方便看日志。下面这份命令是最小可用配置:
etcd \
--name etcd-01 \
--data-dir /var/lib/etcd \
--listen-client-urls http://127.0.0.1:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://127.0.0.1:2380 \
--initial-advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster etcd-01=http://127.0.0.1:2380 \
--initial-cluster-state new \
--initial-cluster-token etcd-cluster-1 \
--logger zap \
--log-level info
参数逐个解释:
| 参数 | 作用 | 注意点 |
|---|---|---|
--name | 成员名 | 集群内唯一,和 --initial-cluster 里的名字一致 |
--data-dir | 数据目录 | 存 WAL 和 db,丢了数据就没了,别放临时目录 |
--listen-client-urls | 客户端监听地址 | 本机验证用 127.0.0.1,对外服务改 0.0.0.0 |
--advertise-client-urls | 告诉客户端和其他成员的地址 | 必须是对端可达的真实地址,不能写 0.0.0.0 |
--listen-peer-urls | 集群通信监听地址 | 单节点也要有,集群时必须是内网地址 |
--initial-advertise-peer-urls | 告诉其他成员的 peer 地址 | 集群初始化时用于互相发现 |
--initial-cluster | 初始集群成员列表 | 所有成员必须完全一致,写错会各起各的集群 |
--initial-cluster-state | 初始化状态 | 首次启动用 new,加入已有集群用 existing |
--initial-cluster-token | 集群令牌 | 防止误把不同集群的成员混在一起 |
启动成功的日志里会出现类似这样的关键行:
{"level":"info","msg":"ready to serve client requests"}
{"level":"info","msg":"serving client traffic insecurely","address":"127.0.0.1:2379"}
{"level":"info","msg":"published local member to cluster","member-id":"8e9e05c52164694d"}
看到 ready to serve client requests 就说明 Raft 已经选出 leader(单节点自己就是 leader),可以接客户端请求了。
验证服务
用内置的健康检查和状态接口确认:
# HTTP 健康检查,返回 healthy 即正常
curl -s http://127.0.0.1:2379/health
# 3.5 还提供 livez / readyz
curl -s http://127.0.0.1:2379/livez
curl -s http://127.0.0.1:2379/readyz
# etcdctl 视角
export ETCDCTL_ENDPOINTS=http://127.0.0.1:2379
etcdctl endpoint health
etcdctl endpoint status -w table
etcdctl member list -w table
endpoint status 的表格里重点看几列:
DB SIZE:当前 db 文件大小,单节点空库通常几 MB。IS LEADER:单节点应该是true。RAFT TERM/RAFT INDEX:term 频繁增长说明在反复选主,单节点场景几乎不会变。ERRORS:有内容就要停下来查。
--advertise-client-urls 和 --initial-advertise-peer-urls 是写给别的成员和客户端用的,写 0.0.0.0 会让它们连到一个不可达地址。监听地址可以是 0.0.0.0,但 advertise 必须是具体 IP 或域名。
systemd 托管
前台跑只适合验证。生产上用 systemd 拉起,先把启动参数写进配置文件。etcd 3.5 支持 YAML 配置文件:
# /etc/etcd/etcd.conf.yml
name: etcd-01
data-dir: /var/lib/etcd
listen-client-urls: http://127.0.0.1:2379
advertise-client-urls: http://127.0.0.1:2379
listen-peer-urls: http://127.0.0.1:2380
initial-advertise-peer-urls: http://127.0.0.1:2380
initial-cluster: etcd-01=http://127.0.0.1:2380
initial-cluster-state: new
initial-cluster-token: etcd-cluster-1
logger: zap
log-level: info
对应的 systemd unit:
[Unit]
Description=etcd key-value store
Documentation=https://etcd.io/docs/v3.5/
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=etcd
Group=etcd
ExecStart=/usr/local/bin/etcd --config-file=/etc/etcd/etcd.conf.yml
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
启用并启动:
sudo useradd -r -s /sbin/nologin etcd 2>/dev/null || true
sudo chown -R etcd:etcd /var/lib/etcd /etc/etcd
sudo systemctl daemon-reload
sudo systemctl enable --now etcd
sudo systemctl status etcd --no-pager
journalctl -u etcd -f
Type=notify 依赖 etcd 的 sd_notify 支持,启动完成后 systemd 才会认为服务就绪;如果换成 simple 也能跑,但端口没起来时 systemd 也会显示 active,不利于排障。
Docker 方式
本地学习、临时验证用 Docker 最省事:
docker run -d \
--name etcd \
-p 2379:2379 \
-p 2380:2380 \
-v etcd-data:/etcd-data \
quay.io/coreos/etcd:v3.5.16 \
--name etcd-docker \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster etcd-docker=http://127.0.0.1:2380 \
--initial-cluster-state new \
--logger zap
查看日志和健康状态:
docker logs -f etcd
curl -s http://127.0.0.1:2379/health
docker exec etcd etcdctl --endpoints=http://127.0.0.1:2379 endpoint health
说明几点:
- 数据卷
etcd-data挂到/etcd-data,容器删了数据还在;想彻底重来,先docker rm -f etcd再docker volume rm etcd-data。 - 容器内监听地址用
0.0.0.0,advertise 仍写127.0.0.1,因为客户端是从宿主机通过映射端口访问的。 quay.io/coreos/etcd拉不动时,可以换gcr.io/etcd-development/etcd或公司内网镜像仓库。
容器化 etcd 本身没问题,但要把数据目录落到宿主机卷、固定网络地址、限制 CPU 和内存,并确保容器重启不会带着空数据目录起来。K8s 里用 StatefulSet 跑 etcd 的坑更多,生产控制面一般还是 kubeadm 的静态 Pod 或独立主机。
etcdctl 基本操作
etcdctl 是官方命令行客户端。3.4 之后默认就是 v3 API,老脚本里的 ETCDCTL_API=3 现在可加可不加,留着也不影响。
环境变量
每次敲 --endpoints 太啰嗦,用环境变量固定下来:
export ETCDCTL_ENDPOINTS=http://127.0.0.1:2379
# 开了 TLS 再加这三个
# export ETCDCTL_CACERT=/etc/etcd/certs/ca.crt
# export ETCDCTL_CERT=/etc/etcd/certs/client.crt
# export ETCDCTL_KEY=/etc/etcd/certs/client.key
etcdctl version
etcdctl endpoint health
增删改查
# 写
etcdctl put /demo/hello world
etcdctl put /demo/count 1
# 读单个 key
etcdctl get /demo/hello
etcdctl get /demo/hello --print-value-only
# 读带元数据的 JSON,能看到 revision、version、lease
etcdctl get /demo/hello -w json
# 前缀查询
etcdctl get /demo/ --prefix
etcdctl get /demo/ --prefix --keys-only
etcdctl get /demo/ --prefix -w json
# 删除单个 key
etcdctl del /demo/hello
# 按前缀批量删除
etcdctl del /demo/ --prefix
# 只看 key 数量
etcdctl get /demo/ --prefix --count-only -w fields
几个常用输出选项:
| 选项 | 作用 |
|---|---|
-w json | 输出完整 JSON,脚本解析用 |
-w fields | 输出字段格式,适合 grep |
--print-value-only | 只打印 value,做管道时方便 |
--keys-only | 只返回 key,统计和巡检时省流量 |
--count-only | 只返回数量,必须配合 -w fields 使用 |
--rev=N | 读指定 revision 的历史快照 |
get 默认是线性读,会跟 leader 确认。做批量导出、对一致性要求不高的巡检时,可以加 --consistency=s 走串行读,减轻 leader 压力。
watch
开一个终端挂 watch:
# 监听单个 key
etcdctl watch /demo/hello
# 监听前缀,最常用
etcdctl watch --prefix /demo/
# 从指定 revision 开始补事件
etcdctl watch --prefix /demo/ --rev=10
# JSON 格式,能看到 revision 和 prev_kv
etcdctl watch --prefix /demo/ -w json
另开一个终端写数据:
etcdctl put /demo/hello etcd
etcdctl put /demo/world 42
etcdctl del /demo/world
watch 终端会实时打印 PUT / DELETE 事件。按 Ctrl+C 结束。watch 是阻塞的,脚本里用的时候记得放到后台或配超时。
只 watch 增量会漏掉「启动前已经存在的数据」。标准做法是先 get --prefix 拿全量并记下 header 里的 revision,再从这个 revision 的下一个值开始 watch;revision 是包含语义,从 R+1 起才只拿增量。K8s 的 List-Watch 也是这个思路,自己写控制器时别偷懒。
常见启动问题
端口被占用
bind: address already in use
先 ss -lntp | grep 2379 找到占用进程。常见原因是上一个 etcd 没退干净,或者 Docker 映射了同样的端口。
数据目录权限不对
failed to create WAL: mkdir ... permission denied
systemd 用 etcd 用户跑,但数据目录还是 root 的。chown -R etcd:etcd /var/lib/etcd 即可。SELinux 或 AppArmor 环境下还要看安全上下文。
重启后起不来,提示集群 ID 不匹配
cluster ID mismatch
通常是换了数据目录、或者拿旧数据目录去连一个新初始化的集群。不要直接删数据目录,先确认这份数据是不是还要;确认不要了再清空,并保持 --initial-cluster 与首次启动一致。
重启时不要重复初始化
--initial-cluster-state new 只在「数据目录为空」的引导阶段生效;数据目录里有 WAL 之后重启,etcd 会直接读原来的成员信息,不再用初始集群参数重新组集群。真正危险的是把数据目录清空再启动——那会变成一个新集群,原数据全没了。重启前先确认 --data-dir 指的还是原目录,不要顺手删 WAL 和 db。
健康检查返回 unhealthy
curl /health 会带出原因,常见的是 RAFT NO LEADER(选主中)、NOSPACE(db 超配额)、CORRUPT(数据损坏)。前两个看后面的运维和排障篇章,最后一个基本只能从备份恢复。
小结
- 二进制安装就三步:下载解压、放 PATH、准备数据目录;生产用 systemd 托管,别前台裸跑。
- 单节点启动的关键参数是
--name、--data-dir、四个 URL、--initial-cluster和--initial-cluster-state;advertise 地址不能写0.0.0.0。 - 验证顺序:
curl /health→endpoint health→endpoint status→member list,一层层确认。 - etcdctl 日常四板斧:put / get / del / watch,配合
--prefix、-w json、--rev覆盖大部分场景。 - 启动失败优先查端口、目录权限、
initial-cluster是否一致;不要一上来就删数据目录。
单机跑通之后,下一步是把 etcdctl 用熟:前缀查询、租约、事务、watch 事件结构,以及 K8s 集群绕不开的 compact 和 defrag。