ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Rainbond 内置 etcd 实战指南:集群搭建、etcdctl 全量操作与源码级原理

Rainbond 内置 etcd 实战指南:集群搭建、etcdctl 全量操作与源码级原理
  • 云原生
  • 后端
  • 微服务
  • DevOps
  • AI 应用

【免费下载链接】rainbond

无需学习 Kubernetes 的容器平台,在 Kubernetes 上构建、部署、组装和管理应用,无需 K8s 专业知识,全流程图形化管理

项目地址:https://gitcode.com/goodrain/rainbond
点击查看免费下载

本文以 Rainbond 仓库内置的 etcd(位于 third_party/coreos-etcd)官方 Demo 文档为核心骨架,系统讲解从零搭建 etcd 多节点集群、用 etcdctl 完成读写、前缀查询、事务、监听、租约、分布式锁、选举、快照、迁移、成员管理与认证授权的完整流程,并结合 util/etcd 下的客户端封装源码,说明这些操作在 Rainbond 平台中的真实落地方式。读完本文,你将具备独立搭建并运维一套生产级 etcd 集群、熟练使用 etcdctl 完成日常键值管理与故障排查的实战能力。

Rainbond 是一款"无需学习 Kubernetes"的应用管理平台,而 etcd 作为其底层分布式协调组件,承担着服务发现、配置存储、任务分发与主节点选举等关键职责。仓库在 third_party/coreos-etcd 中完整保留了 etcd 官方文档与源码,其中 Documentation/demo.md 是一份面向实操的端到端演示指南——从集群搭建到 API 的每个核心操作都有可复制的命令行示例。本文将该文档的每一步命令展开讲解,并对照 Rainbond 源码中真实的 etcd 客户端封装,让你"既会敲命令,也懂底层原理"。

一、etcd 在 Rainbond 中的角色与源码封装概览

在进入 Demo 操作之前,先建立整体认知。从源码结构看,Rainbond 对 etcd 的封装集中在 util/etcd 目录:

文件职责
client.go封装 etcd v3 客户端创建,支持多 Endpoint、TLS 证书、自动同步与重试
etcd.go基于 v3 事务与租约实现键值抽象(RemoteKV / EphemeralKV)
error.go统一 etcd 错误处理与日志输出
etcdlock/master.go基于concurrency.Election实现主节点选举(Master 锁)

其中 client.go 的NewClient只支持 v3 协议(注释明确写着 "do not support v2"),并对连接参数做了生产级默认值处理:

  • DialTimeout小于等于 5 秒时使用默认 5 秒;
  • AutoSyncInterval小于等于 30 秒时使用默认 10 秒;
  • 额外设置DialKeepAliveTime: 2s与DialKeepAliveTimeout: 6s保持长连接存活;
  • 当CaFile、CertFile、KeyFile三者同时非空时,自动构建 TLS 客户端配置;
  • 连接失败时每 10 秒重试一次,直到成功,保证平台核心组件对 etcd 的强依赖。

而在业务层面,api/handler/etcd_handler.go 展示了 Rainbond 实际使用的三类 etcd 键空间:/servicecheck/(源码检测结果)、/rainbond/shareresult/(应用分享结果)、/rainbond/backup_restore/(备份恢复任务)。这些前缀键的清理逻辑即依赖下文将要讲到的"前缀删除"能力。理解这些落地场景后,再来看 Demo 中的每一条命令,含义会更加清晰。

二、搭建 etcd 集群:静态配置方式

Demo 文档给出的第一种集群搭建方式是静态指定集群成员。在每台 etcd 节点上先定义统一的集群变量:

TOKEN=token-01 CLUSTER_STATE=new NAME_1=machine-1 NAME_2=machine-2 NAME_3=machine-3 HOST_1=10.240.0.17 HOST_2=10.240.0.18 HOST_3=10.240.0.19 CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380

关键变量说明:

  • TOKEN:集群初始化令牌,同一集群的所有节点必须一致,用于区分不同集群;
  • CLUSTER_STATE:初始状态为new,表示这是一个全新集群;扩容时改为existing(见"成员管理"章节);
  • CLUSTER:节点名=节点peer地址的逗号分隔列表,是全集群共享的成员拓扑。

然后在每台机器上分别执行(三台机器仅THIS_NAME与THIS_IP不同):

# For machine 1 THIS_NAME=${NAME_1} THIS_IP=${HOST_1} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} # For machine 2 THIS_NAME=${NAME_2} THIS_IP=${HOST_2} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} # For machine 3 THIS_NAME=${NAME_3} THIS_IP=${HOST_3} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

端口约定(全平台通用):

端口用途
2379客户端通信端口(--listen-client-urls/--advertise-client-urls)
2380节点间 Peer 通信端口(--listen-peer-urls/--initial-advertise-peer-urls)

参数含义补充:

  • --data-dir:数据目录,Raft 日志、WAL 与键值快照均存储于此;
  • --name:当前节点在集群内的唯一名字,必须与CLUSTER中对应的名字一致;
  • --initial-advertise-peer-urls:向其他节点广播的 Peer 地址;
  • --listen-peer-urls:本节点监听 Peer 请求的地址;
  • --advertise-client-urls:向客户端广播的可访问地址(客户端通过它连接);
  • --listen-client-urls:本节点监听客户端请求的地址;
  • --initial-cluster:初始成员拓扑;
  • --initial-cluster-state:new或existing;
  • --initial-cluster-token:集群唯一性令牌,防止不同集群的数据混淆。

三、搭建 etcd 集群:使用公共 Discovery 服务

当节点 IP 事先不确定、不便手写成员拓扑时,可以使用 etcd 官方提供的公共发现服务discovery.etcd.io:

curl https://discovery.etcd.io/new?size=3 https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92 # grab this token TOKEN=token-01 CLUSTER_STATE=new NAME_1=machine-1 NAME_2=machine-2 NAME_3=machine-3 HOST_1=10.240.0.17 HOST_2=10.240.0.18 HOST_3=10.240.0.19 DISCOVERY=https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92

工作流程是:curl /new?size=3向发现服务申请一个容量为 3 的集群令牌,获得一个唯一的发现 URL;然后每台节点启动时都携带--discovery指向该 URL,由发现服务负责把各节点互相"介绍"给对方,从而自动完成成员汇聚。

三台节点分别执行(同样仅THIS_NAME与THIS_IP不同):

THIS_NAME=${NAME_1} THIS_IP=${HOST_1} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --discovery ${DISCOVERY} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} THIS_NAME=${NAME_2} THIS_IP=${HOST_2} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --discovery ${DISCOVERY} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} THIS_NAME=${NAME_3} THIS_IP=${HOST_3} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --discovery ${DISCOVERY} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

与静态配置的唯一区别是:用--discovery ${DISCOVERY}取代了--initial-cluster ${CLUSTER}。注意 Discovery 模式同样需要--initial-cluster-token标识集群,且发现服务注册的节点数量达到size=3后即开始组集群。该方式适合私有云、跨网段等不便预知全部地址的场景。

四、连接集群:etcdctl 与 Endpoints 约定

集群就绪后,用 etcdctl 连接。Demo 文档约定使用 v3 API 与多端点列表:

export ETCDCTL_API=3 HOST_1=10.240.0.17 HOST_2=10.240.0.18 HOST_3=10.240.0.19 ENDPOINTS=$HOST_1:2379,$HOST_2:2379,$HOST_3:2379 etcdctl --endpoints=$ENDPOINTS member list

说明:

  • ETCDCTL_API=3:显式声明使用 v3 API(Rainbond 的 util/etcd/client.go 也只支持 v3,与之一致);
  • --endpoints传入逗号分隔的多个节点,客户端会轮流尝试、自动故障转移;
  • member list输出集群成员 ID 与地址,可用来验证集群是否搭建成功。

五、键值读写:put 与 get

5.1 写入(put)

etcdctl --endpoints=$ENDPOINTS put foo "Hello World!"

put写入一个键值对。在 etcd v3 中,每次写入都会产生一个新的版本号(revision),历史版本会被保留直至压缩。

5.2 读取(get)

etcdctl --endpoints=$ENDPOINTS get foo etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo
  • 默认输出纯文本形式;
  • --write-out="json"输出完整的 JSON 结构,包含header(集群 ID、成员 ID、revision)与kvs(键值及版本信息),是排查与脚本化处理的常用方式。

Rainbond 侧的对应用法可参考 util/etcd/etcd.go:其newSequentialKV通过kv.Get(prefix, WithLastKey())先读取前缀下最后一个键,再通过事务写入新键,这正是"前缀查询 + 事务"组合实现队列分配的典型模式。

六、前缀查询:get --prefix

etcd 的键是扁平的字节串,前缀查询是最常用的"目录式"组织手段:

etcdctl --endpoints=$ENDPOINTS put web1 value1 etcdctl --endpoints=$ENDPOINTS put web2 value2 etcdctl --endpoints=$ENDPOINTS put web3 value3 etcdctl --endpoints=$ENDPOINTS get web --prefix

get web --prefix会返回所有以web为前缀的键(web1、web2、web3)。Raft 的线性读保证该查询返回一致视图。

Rainbond 正是利用前缀键来组织业务数据:如 api/handler/etcd_handler.go 中定义的/servicecheck/%s、/rainbond/shareresult/%s、/rainbond/backup_restore/%s三类前缀,并通过DeleteWithPrefix一次清理某应用的全部相关键。前缀设计是 etcd 键空间组织的第一原则。

七、删除操作:del

单键删除与前缀批量删除:

etcdctl --endpoints=$ENDPOINTS put key myvalue etcdctl --endpoints=$ENDPOINTS del key etcdctl --endpoints=$ENDPOINTS put k1 value1 etcdctl --endpoints=$ENDPOINTS put k2 value2 etcdctl --endpoints=$ENDPOINTS del k --prefix
  • del key:删除单个键;
  • del k --prefix:删除所有以k开头的前缀键(此处会删除 k1、k2)。

删除操作同样会产生 revision 变更,可通过get验证键已不存在。

八、事务写入:txn(比较-交换)

txn将多个请求包装进一个原子事务,按"比较条件成立与否"分别执行成功/失败分支:

etcdctl --endpoints=$ENDPOINTS put user1 bad etcdctl --endpoints=$ENDPOINTS txn --interactive compares: value("user1") = "bad" success requests (get, put, delete): del user1 failure requests (get, put, delete): put user1 good

执行逻辑:事务首先比较user1当前值是否等于bad;若成立则执行成功分支(del user1),否则执行失败分支(put user1 good)。整个比较与执行在服务端原子完成,不会被打断。--interactive表示交互式输入 compares / success / failure 三段内容,以空行结束一段。

事务是构建分布式协调逻辑的基石。Rainbond 源码中大量使用事务保证原子性,例如 util/etcd/etcd.go 的putNewKV:

cmp := v3.Compare(v3.Version(key), "=", 0) // 仅当键尚不存在(Version == 0) req := v3.OpPut(key, val, v3.WithLease(leaseID)) txnresp, err := kv.Txn(ctx).If(cmp).Then(req).Commit()

它通过"比较 Version 为 0"实现键不存在才创建的原子语义;etcd.go 的deleteRevKey则比较ModRevision(key) == rev后再删除,实现仅删除指定版本的安全回收。这正是txn在真实系统中的典型用法。

九、监听变更:watch

watch用于订阅键的未来变化,是实现事件驱动、配置热更新的核心机制:

etcdctl --endpoints=$ENDPOINTS watch stock1 etcdctl --endpoints=$ENDPOINTS put stock1 1000

第一个终端执行watch stock1进入阻塞监听;第二个终端执行put stock1 1000后,监听终端立即收到变更事件(包含键、值、版本号)。

前缀监听可同时关注多个键:

etcdctl --endpoints=$ENDPOINTS watch stock --prefix etcdctl --endpoints=$ENDPOINTS put stock1 10 etcdctl --endpoints=$ENDPOINTS put stock2 20

watch stock --prefix会捕获 stock1、stock2 的所有写入。在分布式系统中,watch 通常配合租约与锁使用,作为状态变更的"通知总线"。

十、租约:lease(带 TTL 的键)

租约(Lease)为键附加存活时间(TTL),到期后键自动失效:

etcdctl --endpoints=$ENDPOINTS lease grant 300 # lease 2be7547fbc6a5afa granted with TTL(300s) etcdctl --endpoints=$ENDPOINTS put sample value --lease=2be7547fbc6a5afa etcdctl --endpoints=$ENDPOINTS get sample etcdctl --endpoints=$ENDPOINTS lease keep-alive 2be7547fbc6a5afa etcdctl --endpoints=$ENDPOINTS lease revoke 2be7547fbc6a5afa # or after 300 seconds etcdctl --endpoints=$ENDPOINTS get sample

流程拆解:

  1. lease grant 300:申请一个 TTL 为 300 秒的租约,返回租约 ID(如2be7547fbc6a5afa);
  2. put ... --lease=...:将键绑定到该租约,键的存活周期即租约剩余时间;
  3. lease keep-alive:周期性续约,防止键到期(守护进程/选举持锁者必备);
  4. lease revoke:主动撤销租约,绑定的键立即全部删除;
  5. 既不续约也不撤销时,300 秒后get sample将为空——这是"临时/注册类数据自动过期"的经典实现。

Rainbond 中EphemeralKV(util/etcd/etcd.go)正是"键 + 会话租约"的封装:通过concurrency.Session获取租约,再创建绑定该租约的键值,配合心跳自动续约,实现节点注册与自动摘除。etcdlock/master.go 中Lease.Grant后以租约 ID 创建 Session,同样是这一机制的运用。

十一、分布式锁:lock

lock基于租约 + 前缀键实现分布式互斥锁:

etcdctl --endpoints=$ENDPOINTS lock mutex1 # another client with the same name blocks etcdctl --endpoints=$ENDPOINTS lock mutex1

第一个客户端持有mutex1锁后命令阻塞(持续持有);第二个客户端请求同名锁时同样阻塞,直到第一个客户端释放(中断命令或租约过期)。锁实现的核心是"同一前缀下创建唯一键 + 按 revision 排序取最小者持锁",util/etcd/etcd.go 的newUniqueKV用前缀/UnixNano时间戳生成唯一键的写法正是此类锁的键构造方式。

十二、选举:elect(主节点竞选)

elect提供基于 etcd 的领导者选举原语:

etcdctl --endpoints=$ENDPOINTS elect one p1 # another client with the same name blocks etcdctl --endpoints=$ENDPOINTS elect one p2

elect one p1让当前进程以候选人p1身份参与名为one的选举;竞选成功则命令持续持有领导权并阻塞。第二个客户端elect one p2竞选同一名称时进入等待,直到当前领导者退出才接任。

Rainbond 将这一机制封装在 util/etcd/etcdlock/master.go 中:

  • CreateMasterLock(endpoints, election, prop, ttl)创建选举锁(master.go):默认 Endpoints 为http://127.0.0.1:2379,先Lease.Grant申请租约,再用concurrency.NewElection(session, election)建立选举对象;
  • campaign()(master.go)调用election.Campaign参与竞选,并通过election.Observe监听结果:当选后发出MasterAdded事件,否则持续观察当前主节点;会话过期时发出MasterError并自动退出;
  • 事件类型MasterAdded / MasterDeleted / MasterModified / MasterError通过EventsChan()通知调用方(master.go),上层据此判断"本节点是否成为主节点"。

这就是"多个 worker 实例同时启动,只有一个能成为主节点执行任务"的标准实现,与 Demo 中elect命令的语义完全一致。

十三、集群状态检查:endpoint status / health

13.1 查看各节点状态

etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status +------------------+------------------+---------+---------+-----------+-----------+------------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | +------------------+------------------+---------+---------+-----------+-----------+------------+ | 10.240.0.17:2379 | 4917a7ab173fabe7 | 3.0.0 | 45 kB | true | 4 | 16726 | | 10.240.0.18:2379 | 59796ba9cd1bcd72 | 3.0.0 | 45 kB | false | 4 | 16726 | | 10.240.0.19:2379 | 94df724b66343e6c | 3.0.0 | 45 kB | false | 4 | 16726 | +------------------+------------------+---------+---------+-----------+-----------+------------+

表格列含义:

  • ENDPOINT:被检查的客户端地址;
  • ID:该成员在 Raft 集群内的唯一 ID;
  • VERSION:etcd 版本号(Demo 环境为 3.0.0);
  • DB SIZE:当前键值数据库大小;
  • IS LEADER:是否为 Raft 领导者(示例中 10.240.0.17 为 Leader);
  • RAFT TERM:当前任期号,每次领导者变更递增;
  • RAFT INDEX:已提交日志索引,三个节点一致说明数据同步正常。

13.2 健康检查

etcdctl --endpoints=$ENDPOINTS endpoint health 10.240.0.17:2379 is healthy: successfully committed proposal: took = 3.345431ms 10.240.0.19:2379 is healthy: successfully committed proposal: took = 3.767967ms 10.240.0.18:2379 is healthy: successfully committed proposal: took = 4.025451ms

endpoint health会向每个端点发起提案提交测试,能成功提交并返回耗时即判定为 healthy。这是日常巡检与故障定位(如节点掉线、网络分区)最常用的命令。

十四、快照:snapshot save / status

snapshot用于保存 etcd 数据库的时间点一致性快照,是备份与恢复的基础:

etcdctl --endpoints=$ENDPOINTS snapshot save my.db Snapshot saved at my.db

查看快照文件信息:

etcdctl --write-out=table --endpoints=$ENDPOINTS snapshot status my.db +---------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +---------+----------+------------+------------+ | c55e8b8 | 9 | 13 | 25 kB | +---------+----------+------------+------------+

字段说明:HASH为快照校验哈希(用于完整性校验),REVISION为快照时的最新修订号,TOTAL KEYS为键总数,TOTAL SIZE为文件大小。注意官方建议从非 Leader 节点执行snapshot save,以减轻 Leader 负载;恢复时需用同一哈希校验快照完整性。快照 + WAL 共同构成 etcd 的持久化层,对应--data-dir下的member/snap与member/wal目录。

十五、数据迁移:migrate(v2 → v3)

migrate用于将 etcd v2 存储中的数据转换到 v3 存储(注意:v2 与 v3 使用不同的存储引擎与 API,数据不互通):

# write key in etcd version 2 store export ETCDCTL_API=2 etcdctl --endpoints=http://$ENDPOINT set foo bar # read key in etcd v2 etcdctl --endpoints=$ENDPOINTS --output="json" get foo # stop etcd node to migrate, one by one # migrate v2 data export ETCDCTL_API=3 etcdctl --endpoints=$ENDPOINT migrate --data-dir="default.etcd" --wal-dir="default.etcd/member/wal" # restart etcd node after migrate, one by one # confirm that the key got migrated etcdctl --endpoints=$ENDPOINTS get /foo

操作要点:

  1. 用ETCDCTL_API=2配合 v2 命令set foo bar写入 v2 存储;
  2. --data-dir指向原 v2 数据目录,--wal-dir指向 WAL 子目录;
  3. 逐节点执行:先停止节点 → 迁移 → 重启,再处理下一个节点,保证集群始终有可用成员(Raft 多数派存活);
  4. 迁移后键会带上/前缀(示例中foo变为/foo),这是 v2 键在 v3 存储中的命名规则。

Rainbond 仅使用 v3 API(见 util/etcd/client.go 的注释 "do not support v2"),因此该迁移命令主要适用于从旧版 etcd 升级的场景。

十六、成员管理:member add / remove

成员管理用于集群扩容、故障节点替换。先按前文静态配置方式启动一个三节点集群(此例地址为 10.240.0.13~15):

# For each machine TOKEN=my-etcd-token-1 CLUSTER_STATE=new NAME_1=etcd-node-1 NAME_2=etcd-node-2 NAME_3=etcd-node-3 HOST_1=10.240.0.13 HOST_2=10.240.0.14 HOST_3=10.240.0.15 CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380 # For node 1 THIS_NAME=${NAME_1} THIS_IP=${HOST_1} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 \ --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 \ --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} \ --initial-cluster-token ${TOKEN} # For node 2 THIS_NAME=${NAME_2} THIS_IP=${HOST_2} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 \ --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 \ --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} \ --initial-cluster-token ${TOKEN} # For node 3 THIS_NAME=${NAME_3} THIS_IP=${HOST_3} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 \ --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 \ --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} \ --initial-cluster-token ${TOKEN}

16.1 移除成员

先列出成员拿到目标成员的 ID,再执行移除:

# get member ID export ETCDCTL_API=3 HOST_1=10.240.0.13 HOST_2=10.240.0.14 HOST_3=10.240.0.15 etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 member list # remove the member MEMBER_ID=278c654c9a6dfd3b etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 \ member remove ${MEMBER_ID}

16.2 添加新成员

# add a new member (node 4) export ETCDCTL_API=3 NAME_1=etcd-node-1 NAME_2=etcd-node-2 NAME_4=etcd-node-4 HOST_1=10.240.0.13 HOST_2=10.240.0.14 HOST_4=10.240.0.16 # new member etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379 \ member add ${NAME_4} \ --peer-urls=http://${HOST_4}:2380

member add只需连接任意一个现存成员即可把新节点登记进集群元数据,并返回该新节点启动所需的--initial-cluster参数。

16.3 以 existing 状态启动新成员

新节点加入既有集群时,CLUSTER_STATE必须改为existing:

# [WARNING] If the new member starts from the same disk space, # make sure to remove the data directory of the old member # # restart with 'existing' flag TOKEN=my-etcd-token-1 CLUSTER_STATE=existing NAME_1=etcd-node-1 NAME_2=etcd-node-2 NAME_4=etcd-node-4 HOST_1=10.240.0.13 HOST_2=10.240.0.14 HOST_4=10.240.0.16 # new member CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_4}=http://${HOST_4}:2380 THIS_NAME=${NAME_4} THIS_IP=${HOST_4} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 \ --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 \ --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} \ --initial-cluster-token ${TOKEN}

两条运维红线:

  1. --initial-cluster必须填写包含新成员在内的完整成员列表(node-1、node-2、node-4);
  2. 若新成员复用了旧成员的磁盘/数据目录,必须先删除旧数据目录(data.etcd),否则可能因残留数据导致成员加入失败或数据不一致。

十七、认证与权限:auth / role / user

etcd 提供基于角色的访问控制(RBAC),保护集群数据不被未授权客户端读写:

export ETCDCTL_API=3 ENDPOINTS=localhost:2379 etcdctl --endpoints=${ENDPOINTS} role add root etcdctl --endpoints=${ENDPOINTS} role grant-permission root readwrite foo etcdctl --endpoints=${ENDPOINTS} role get root etcdctl --endpoints=${ENDPOINTS} user add root etcdctl --endpoints=${ENDPOINTS} user grant-role root root etcdctl --endpoints=${ENDPOINTS} user get root etcdctl --endpoints=${ENDPOINTS} auth enable # now all client requests go through auth etcdctl --endpoints=${ENDPOINTS} --user=root:123 put foo bar etcdctl --endpoints=${ENDPOINTS} get foo etcdctl --endpoints=${ENDPOINTS} --user=root:123 get foo etcdctl --endpoints=${ENDPOINTS} --user=root:123 get foo1

逐步说明:

  1. role add root:创建root角色;
  2. role grant-permission root readwrite foo:授予该角色对foo键的读写权限(注意此权限只覆盖foo本身,不含前缀范围,因此后续get foo1会因无权限被拒绝);
  3. role get root:查看角色权限;
  4. user add root:创建用户(交互式设置密码,示例密码为123);
  5. user grant-role root root:把root角色授予root用户;
  6. auth enable:开启认证,此后所有客户端请求都必须携带凭据;
  7. 未带--user的get foo会被拒绝;带--user=root:123的读写正常;get foo1因超出foo键的授权范围同样被拒——体现了"认证 + 键级授权"的双重约束。

在生产环境中,开启认证后所有应用客户端(包括 Rainbond 各组件)都需要在连接时配置用户名密码或 TLS 证书。Rainbond 的 util/etcd/client.go 已支持通过CaFile / CertFile / KeyFile构建 TLS 客户端,即为此类安全连接场景预留的能力。

十八、从 Demo 到生产:关键要点小结

综合上述全部操作,将 etcd 集群从搭建到运维的关键要点归纳如下:

  1. 搭建:静态--initial-cluster或 Discovery 服务二选一;三节点起步,TOKEN与CLUSTER_STATE全集群一致;
  2. 端口:2379 面向客户端,2380 面向节点间通信,切勿混淆;
  3. 读多写少:查询善用--prefix与--write-out=json;写操作可组合txn保证原子性;
  4. 高可用:用lock/elect实现互斥与选举,用lease管理临时数据生命周期,用watch驱动配置与状态同步;
  5. 运维:endpoint status/health做日常巡检;snapshot save定期备份;成员变更(member add/remove)配合existing状态扩容替换;
  6. 安全:auth enable前先完成角色与用户规划,生产环境建议配合 TLS 双向认证;
  7. 升级:跨大版本(v2 → v3)按节点逐个migrate,始终保证 Raft 多数派存活。

若希望进一步深入,可继续阅读仓库内的相关材料:

  • etcd 完整文档与源码:third_party/coreos-etcd(含 Documentation/demo.md、Documentation/op-guide/运维指南、Documentation/dev-guide/开发指南等);
  • Rainbond etcd 客户端封装:util/etcd/client.go、util/etcd/etcd.go、util/etcd/error.go;
  • 基于 etcd 的主节点选举实现:util/etcd/etcdlock/master.go;
  • etcd 键空间在平台中的实际划分:api/handler/etcd_handler.go。
  • 云原生
  • 后端
  • 微服务
  • DevOps
  • AI 应用

【免费下载链接】rainbond

无需学习 Kubernetes 的容器平台,在 Kubernetes 上构建、部署、组装和管理应用,无需 K8s 专业知识,全流程图形化管理

项目地址:https://gitcode.com/goodrain/rainbond
点击查看免费下载

相关推荐

上一篇:为什么选择admin3?探索轻量级后台框架的5大核心优势
下一篇:5步掌握PT-Plugin-Plus:浏览器插件实现PT种子下载效率革命

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进