跳到主要内容

安装与快速上手

准备环境​

本篇在 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:

下载、解压、放到 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:有内容就要停下来查。
不要用 0.0.0.0 做 advertise 地址

--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 是阻塞的,脚本里用的时候记得放到后台或配超时。

先 list 再 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。