- 零信任
- 网络
- 后端
- 认证鉴权
【免费下载链接】ziti
The parent project for OpenZiti. Here you will find the executables for a fully zero-trust, programmable network @OpenZiti
导读:本文基于 OpenZiti 仓库 doc/ha/quickstart.md 编写,完整讲解如何在单台机器上用三个独立 TCP 端口跑起一套自包含的 OpenZiti 高可用(HA)集群,涵盖首个成员创建集群、后续成员加入、Agent 工具查看集群状态、模拟成员故障与恢复的全过程。读完本文,你将能独立复现这套交互式演练,并理解
ziti edge quickstart命令背后的集群初始化、Raft 选主与自动加入机制,同时掌握如何用ziti agent cluster list定位与验证集群领导者。
一、背景:为什么需要快速启动一套 HA 集群
OpenZiti 的控制器(Controller)是一个零信任网络的“大脑”,负责身份认证、服务授权与网络策略编排。在生产环境中,控制器以多副本方式构成一个 Raft 集群,实现状态机复制与领导者选举——当某个成员宕机时,其余成员仍能继续提供服务,这正是 HA(High Availability)的核心价值。
仓库中的 doc/ha/dev-setup.md 明确标注“HA 目前处于 beta 阶段”,因此在开发、测试与学习场景下,我们希望用最小成本验证 HA 行为。ziti edge quickstart提供的 HA 模式正好满足这一诉求:它不需要预先准备 PKI、不需要手写配置文件,一个命令即可拉起“控制器 + 内嵌路由器”,再通过join子命令把多个实例加入同一集群。
从源码结构看,quickstart 的 HA 能力集中在 ziti/run/quickstart.go(单实例快速启动与集群加入)与 ziti/run/quickstart_cluster.go(一键拉起整簇),后文会结合这两处实现逐一展开。
二、环境准备:干净的工作目录
整个演练是**自包含(self-contained)**的:所有数据、PKI、配置都产生在当前工作目录内,前后无需任何额外步骤即可得到一个可用的本地集群。
cd $(mktemp -d)mktemp -d会创建一个临时目录并把工作目录切换进去。之后的所有命令都默认在该目录下执行。演练结束后,可以随时删除这个目录完成清理。
三、启动第一个成员:创建集群
第一个成员(ctrl1)负责创建集群,因此使用不带join子命令的 quickstart:
nohup ziti edge quickstart \ --instance-id="ctrl1" \ --ctrl-port="1281" \ --router-port="3021" \ --home="${PWD}" \ --ctrl-address="127.0.0.1" \ --router-address="127.0.0.1" \ --trust-domain="ha-quickstart" \ &> ctrl1.log &各参数的含义如下(参数解析实现见 ziti/run/quickstart.go 中的addCommonQuickstartFlags与addQuickstartHaFlags):
| 参数 | 含义 | 本演练取值 | 源码中的处理 |
|---|---|---|---|
--instance-id | HA 模式下每个实例的唯一标识,会写入 SPIFFE ID 与实例目录名 | ctrl1 | 缺省时使用instance-1(源码NewQuickStartCmd默认值);join模式强制要求 |
--ctrl-port | 控制面与 Edge API 使用的端口 | 1281 | 默认值取自constants包中的DefaultCtrlEdgeAdvertisedPort |
--router-port | 内嵌路由器监听端口 | 3021 | 默认值取自constants.DefaultZitiEdgeRouterPort |
--home | 数据目录(数据库、PKI、配置文件的根目录) | ${PWD} | 缺省时创建临时目录并在退出时删除;显式指定则永久保留 |
--ctrl-address | 控制面/API 对外通告地址 | 127.0.0.1 | 启动时写入ZITI_CTRL_ADVERTISED_ADDRESS、ZITI_CTRL_EDGE_ADVERTISED_ADDRESS等环境变量 |
--router-address | 内嵌路由器通告地址 | 127.0.0.1 | 写入ZITI_EDGE_ROUTER_ADVERTISED_ADDRESS |
--trust-domain | SPIFFE ID 中使用的信任域 | ha-quickstart | 缺省值为quickstart;加入集群时强制要求 |
其他常用参数还包括:--username/-u与--password/-p(管理员账号,默认均为admin)、--no-router(不启动内嵌路由器)、--zac(下载并挂载 Ziti Admin Console 管理界面)、--configure-and-exit(完成配置后即退出)等。
启动后立即检查进程与日志:
tail ctrl1.log; echo; jobs预期输出大致如下(日志被省略):
. . . ... logs ... . . . [1] + running nohup ziti edge quickstart --instance-id="ctrl1" --ctrl-port="1281" &从源码看,第一个成员启动时会依次完成:生成最小 PKI(CreateMinimalPki,创建根 CA、中间 CA、服务器/客户端证书,SPIFFE ID 形如spiffe://ha-quickstart/controller/ctrl1)→ 生成控制器配置文件ctrl.yaml→ 启动控制器并轮询等待其 HTTP 端点可用(waitForController)→ 通过本进程的 ops-agent 发起agent cluster init初始化集群(源码中对初始化失败最多重试 5 次)→ 创建并注册内嵌路由器。这也是为什么第一个成员必须由它自己“初始化”集群,而后续成员只需“加入”。
四、启动第二、三个成员:加入集群
第二个与第三个成员使用join子命令加入同一个集群,其余参数与第一个成员保持一致,仅更换端口与实例 ID:
nohup ziti edge quickstart join \ --instance-id="ctrl2" \ --ctrl-port="1282" \ --router-port="3022" \ --home="${PWD}" \ --ctrl-address="127.0.0.1" \ --router-address="127.0.0.1" \ --trust-domain="ha-quickstart" \ --cluster-member="tls:127.0.0.1:1281" \ &> ctrl2.log &nohup ziti edge quickstart join \ --instance-id="ctrl3" \ --ctrl-port="1283" \ --router-port="3023" \ --home="${PWD}" \ --ctrl-address="127.0.0.1" \ --router-address="127.0.0.1" \ --trust-domain="ha-quickstart" \ --cluster-member="tls:127.0.0.1:1281" \ &> ctrl3.log &新增参数--cluster-member指定已有集群成员的控制面地址(本例为tls:127.0.0.1:1281,即 ctrl1),新成员将通过它发起加入请求。在 ziti/run/quickstart.go 的NewQuickStartJoinClusterCmd中,join模式还额外支持:
--cluster-member/-m:必填,集群成员的tls:host:port地址;--non-voting:将新成员以非投票成员(non-voter)身份加入集群,不参与 Raft 投票。
分别确认两个后台任务运行正常:
tail ctrl2.log; echo; jobs tail ctrl3.log; echo; jobs预期输出(日志省略):
[1] - running nohup ziti edge quickstart --instance-id="ctrl1" --ctrl-port="1281" & [2] + running nohup ziti edge quickstart join --instance-id="ctrl2" --ctrl-port="1282" [1] running nohup ziti edge quickstart --instance-id="ctrl1" --ctrl-port="1281" & [2] - running nohup ziti edge quickstart join --instance-id="ctrl2" --ctrl-port="1282" [3] + running nohup ziti edge quickstart join --instance-id="ctrl3" --ctrl-port="1283"加入过程的源码细节:join模式下 quickstart 不会重新生成根 CA(源码注释明确指出“don't emit a root-ca, expect it'll be there or error”,即复用第一个成员生成的根 CA 来创建本实例的服务器 PKI),随后通过本进程 ops-agent 反复执行agent cluster add,直到成功或 90 秒超时。源码中有一段针对性的重试逻辑:加入请求提交后,集群成员关系已通过 AddPeer Raft 命令落盘,但管理员认证数据可能尚未复制到本地 FSM,导致一次性登录返回 401,因此loginWithRetry会以 1 秒间隔重试最多 60 秒。
可选:如果想在另一个终端同时观察三个实例的日志,可以合并追踪所有*.log:
tail -F -n +1 *.log五、观察集群:Agent 工具与领导者定位
OpenZiti 为控制器提供了基于 gops agent 的运维通道。先列出本机所有 agent 应用:
ziti agent list预期输出:
╭────────┬────────────┬────────┬─────────────────────────────┬────────────┬─────────────┬───────────╮ │ PID │ EXECUTABLE │ APP ID │ UNIX SOCKET │ APP TYPE │ APP VERSION │ APP ALIAS │ ├────────┼────────────┼────────┼─────────────────────────────┼────────────┼─────────────┼───────────┤ │ 276912 │ ziti │ ctrl1 │ /tmp/gops-agent.276912.sock │ controller │ v0.0.0 │ │ │ 277714 │ ziti │ ctrl2 │ /tmp/gops-agent.277714.sock │ controller │ v0.0.0 │ │ │ 281490 │ ziti │ ctrl3 │ /tmp/gops-agent.281490.sock │ controller │ v0.0.0 │ │ ╰────────┴────────────┴────────┴─────────────────────────────┴────────────┴─────────────┴───────────╯其中APP ID对应--instance-id,APP TYPE均为controller,每个进程在/tmp下暴露独立的gops-agent.<pid>.sock(源码中 quickstart 正是通过unix:/tmp/gops-agent.<pid>.sock这个 socket 直接发起集群操作,避免按 PID 枚举全部 agent)。
查看 ctrl1 视角下的集群成员:
ziti agent cluster list --app-id ctrl1预期输出:
╭───────┬────────────────────┬───────┬────────┬─────────┬───────────╮ │ ID │ ADDRESS │ VOTER │ LEADER │ VERSION │ CONNECTED │ ├───────┼────────────────────┼───────┼────────┼─────────┼───────────┤ │ ctrl1 │ tls:127.0.0.1:1281 │ true │ true │ v0.0.0 │ true │ │ ctrl2 │ tls:127.0.0.1:1282 │ true │ false │ v0.0.0 │ true │ │ ctrl3 │ tls:127.0.0.1:1283 │ true │ false │ v0.0.0 │ true │ ╰───────┴────────────────────┴───────┴────────┴─────────┴───────────╯此时ctrl1 是集群领导者(LEADER 列为 true),三个成员均为投票成员(VOTER 为 true)且全部连接正常。
该命令的底层实现见 ziti/cmd/agentcli/agent_cluster_list.go:它向目标 controller agent 发送RaftListMembersRequestType管理消息,接收RaftMemberListResponse,再以圆角表格渲染Id / Address / Voter / Leader / Version / Connected / Preferred七列。Preferred列表示是否为优先领导者——从源码推断,集群在故障转移时会优先尝试将领导权交给 preferred 成员。
六、故障演练:终止领导者并观察集群自愈
HA 的核心价值在于成员故障时的自动选主。现在模拟一次可用性事故:把当前领导者 ctrl1 杀掉。
先确认各后台任务的编号——领导者不一定对应%1,务必以jobs实际输出为准:
jobs示例输出(任务编号顺序可能与启动顺序不同):
[1] running nohup ziti edge quickstart --instance-id="ctrl1" --ctrl-port="1281" [2] - running nohup ziti edge quickstart join --instance-id="ctrl3" --ctrl-port="1283" [3] + running nohup ziti edge quickstart join --instance-id="ctrl2" --ctrl-port="1282"本例中任务 1 属于 ctrl1(当前领导者),将其终止:
kill %1然后换一个幸存的成员查看集群状态:
ziti agent cluster list --app-id ctrl2预期输出:
╭───────┬────────────────────┬───────┬────────┬─────────────────┬───────────╮ │ ID │ ADDRESS │ VOTER │ LEADER │ VERSION │ CONNECTED │ ├───────┼────────────────────┼───────┼────────┼─────────────────┼───────────┤ │ ctrl1 │ tls:127.0.0.1:1281 │ true │ false │ <not connected> │ false │ │ ctrl2 │ tls:127.0.0.1:1282 │ true │ true │ v0.0.0 │ true │ │ ctrl3 │ tls:127.0.0.1:1283 │ true │ false │ v0.0.0 │ true │ ╰───────┴────────────────────┴───────┴────────┴─────────────────┴───────────┘两个关键变化值得注意:
- ctrl1 显示
<not connected>/false:被终止的成员失去了控制面连接; - 领导者已从 ctrl1 转移到 ctrl2:剩余的两个投票成员(ctrl2、ctrl3)构成多数派(3 节点集群的法定人数为 2),Raft 完成重新选举,ctrl2 接管领导权。
七、成员恢复:重启断连成员
任意断连成员都可以用 quickstart 命令重启。此时只需要提供启动参数,无需再指定集群成员地址——数据目录仍在,它会以既有 Raft 成员身份自动回归集群:
nohup ziti edge quickstart \ --instance-id="ctrl1" \ --home="${PWD}" \ &>> ctrl1.log &注意两点:
- 这里只传了
--instance-id与--home,其余端口/地址参数会从已持久化的配置(<home>/ctrl1/ctrl.yaml)中读取。源码中applyConfiguredEndpoints正是负责解析已生成的配置文件并恢复通告地址与端口;而noteIgnoredSetupFlags会提示哪些仅在首次创建时生效的参数(--ctrl-address、--ctrl-port、--router-address、--router-port、--trust-domain)在本次重启中被忽略; - 日志使用
&>>追加写入,保留历史输出。
重启完成后再次查看集群:
ziti agent cluster list --app-id ctrl2预期输出:
╭───────┬────────────────────┬───────┬────────┬─────────────────┬───────────╮ │ ID │ ADDRESS │ VOTER │ LEADER │ VERSION │ CONNECTED │ ├───────┼────────────────────┼───────┼────────┼─────────────────┼───────────┤ │ ctrl1 │ tls:127.0.0.1:1281 │ true │ false │ v0.0.0 │ true │ │ ctrl2 │ tls:127.0.0.1:1282 │ true │ false │ v0.0.0 │ true │ │ ctrl3 │ tls:127.0.0.1:1283 │ true │ true │ v0.0.0 │ true │ ╰───────┴────────────────────┴───────┴────────┴─────────────────┴───────────╯ctrl1 已恢复连接(true),但并未重新成为领导者——新的领导者是 ctrl3。这正是 Raft 选主机制的正常表现:领导者是当前任期内的选举结果,成员重启后不会自动“抢回”领导权。这一事实告诉我们:不要对“哪个节点是领导者”做任何静态假设,客户端与运维脚本都应动态查询。
八、清理:停止全部后台进程
演练结束后,停止所有后台任务。
BASH 环境:
kill $(jobs -p)ZSH 环境:
kill ${${(v)jobstates##*:*:}%=*}预期输出:
[1] + done nohup ziti edge quickstart --instance-id="ctrl1" --ctrl-port="1281" [2] done nohup ziti edge quickstart join --instance-id="ctrl2" --ctrl-port="1282" [3] + done nohup ziti edge quickstart join --instance-id="ctrl3" --ctrl-port="1283"随后可以删除演练目录(即第一步mktemp -d创建的目录),彻底完成清理。
九、进阶:ziti run quickstart cluster一键拉起整簇
除了逐个nohup启动,仓库还提供了更高层的封装:ziti run quickstart cluster(实现见 ziti/run/quickstart_cluster.go)。它以一个父进程启动 N 个子进程,每个子进程即一个 quickstart 节点,自动完成“节点 1 初始化、其余节点依次 join”的编排,按 Ctrl-C 即可优雅停止全部节点。
该命令的核心参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
--size | 集群节点数量 | 3(合法范围 3~9,更多成员会拖慢写操作延迟) |
--ctrl-port | 基础控制面端口,节点 i 监听 base+i | 默认取自constants.DefaultCtrlEdgeAdvertisedPort |
--router-port | 基础路由器端口,节点 i 监听 base+i | 默认取自constants.DefaultZitiEdgeRouterPort |
--home | 数据目录;缺省使用临时目录并在退出时删除 | 无 |
--ctrl-address/--router-address | 所有节点统一使用的通告地址 | 环境默认值 |
--trust-domain | SPIFFE 信任域 | quickstart |
--shutdown-grace | 停止节点时的最长等待时间 | 30s |
--zac | 下载 ZAC 并为每个节点挂载控制台 | false |
从源码实现看,这个命令还做了几件值得注意的事情:
- 端口范围预校验:
--ctrl-port/--router-port加上--size后不得超过 65535,且两条端口段不能重叠,否则在拉起前直接报错(quickstart_cluster.go中run开头的一段校验逻辑); - 重启语义:若
--home下所有实例的db目录都已存在(isFullRestart判定),说明是既有集群重启,必须同时启动全部节点以重新形成法定人数,否则单节点无法选出领导者; - 按实例隔离 CLI 配置目录:每个子进程通过
ZITI_CONFIG_DIR指向独立的 CLI 配置目录,避免多个节点并发登录时争用同一份会话文件; - 就绪哨兵:父进程监听子进程输出中“Quickly add another member”字样,以此判定节点已完全就绪(领导者选举/加入完成、路由器运行),再启动下一个节点。
对于希望快速验证 HA、又不想手工管理多个终端窗口的场景,这一命令是最省事的入口。
十、对照:手写配置的 HA 开发环境(dev-setup)
快速启动(quickstart)自动生成了所有配置;而 doc/ha/dev-setup.md 给出了另一条更贴近生产形态的路径——手工 PKI + 手写控制器配置,适合需要定制参数的开发环境。注意该文档同样标注 HA 处于 beta 阶段。
10.1 生成三节点 PKI
执行 doc/ha/create-pki.sh(需先安装好zitiCLI):
./create-pki.sh脚本内容展示了 HA 场景下的信任链设计:一个共享的根 CA(--trust-domain ha.test)作为集群信任根,每个控制器拥有独立的中级签名证书,并用--spiffe-id 'controller/ctrlN'绑定各自的 SPIFFE 身份:
ziti pki create ca --trust-domain ha.test --pki-root ./pki --ca-file ca --ca-name 'HA Example Trust Root' ziti pki create intermediate --pki-root ./pki --ca-name ca --intermediate-file ctrl1 --intermediate-name 'Controller One Signing Cert' ziti pki create server --pki-root ./pki --ca-name ctrl1 --dns localhost --ip 127.0.0.1 --server-name ctrl1 --spiffe-id 'controller/ctrl1' ziti pki create client --pki-root ./pki --ca-name ctrl1 --client-name ctrl1 --spiffe-id 'controller/ctrl1' # ... 其余两个控制器以此类推10.2 启动三个控制器
配置文件 doc/ha/ctrl1.yml(以及同目录的ctrl2.yml、ctrl3.yml)中包含了相对路径,因此必须在doc/ha目录下运行:
ziti controller run ctrl1.yml ziti controller run ctrl2.yml ziti controller run ctrl3.yml以 ctrl1 的配置为例,HA 相关的关键结构包括:
cluster.dataDir:本节点 Raft 数据目录(./data/ctrl1);identity:节点自身的服务端证书、私钥与 CA 链(对应 10.1 生成的 PKI);ctrl.listener与options.advertiseAddress:控制面监听与通告地址(tls:127.0.0.1:6262/tls:localhost:6262);events.jsonLogger:订阅connect、cluster事件并写入json格式日志文件,便于观察集群事件;edge.api/edge.enrollment:Edge API 监听地址与注册签名证书、enrollment 时长;web:绑定多个 API(health-checks、fabric、edge-management、edge-client、edge-oidc)并限定 TLS 版本。
10.3 初始化集群并加入成员
三个控制器启动后,利用 Agent 通道完成集群初始化与成员加入:
# 初始化第一个控制器(用户名/密码均为 admin,显示名 'Default Admin') ziti agent cluster init -i ctrl1 admin admin 'Default Admin' # 将另外两个节点加入集群(地址取各自 ctrl 通告地址) ziti agent cluster add -i ctrl1 tls:localhost:6363 ziti agent cluster add -i ctrl1 tls:localhost:6464至此三节点集群已就绪,可分别登录各控制器验证:
ziti edge login localhost:1280 ziti edge -i ctrl2 login localhost:1380 ziti edge -i ctrl3 login localhost:1480随后可在任意控制器上创建模型数据并跨节点查询:
ziti demo setup echo client ziti demo setup echo single-sdk-hostedziti edge login localhost:1280 ziti edge ls services ziti edge login -i ctrl2 localhost:1380 ziti edge -i ctrl2 ls services ziti edge login -i ctrl3 localhost:1480 ziti edge -i ctrl3 ls services因为 Raft 会在各成员间复制状态,三个控制器的查询结果应保持一致。
10.4 HA 场景下的 SDK 兼容性
dev-setup 文档同时说明:自 Golang SDK v1.2.3 起,SDK 无需特殊改动即可接入 HA 系统。这意味着客户端只需使用常规的 Edge API 登录与拨号流程,控制器集群的领导者转移对 SDK 是透明的。
十一、测试佐证:HA 集群在仓库中的验证方式
快速启动脚本并非孤证,仓库的端到端测试基建同样覆盖了多控制器集群场景。tests/ha_cluster.go 中:
peerController表示一个次要集群成员控制器,主控制器保留在TestContext字段上,使既有测试辅助函数无需改动即可继续工作;StartHaCluster会先清空 Raft 数据目录,启动主控制器与所有 peer,把它们加入同一个 Raft 集群,并等待每个成员都成为投票者且注册进 Controller store——与本文演练的“初始化 + join”流程一一对应;NewEdgeClientApiForHost支持将 Edge API 客户端指向集群中任意成员,用来验证跨节点读写一致性。
此外 tests/cli_tests/cluster_test.go 与 quickstart/test/ha-test.sh 也提供了 CLI 层与脚本层的 HA 验证入口,读者可结合这些文件深入了解测试细节。
十二、小结:HA 快速启动的关键事实清单
- 三节点是 HA 的最小形态:3 个投票成员的法定人数为 2,任一成员宕机后集群仍可选出新领导者;节点数越多,写操作(Raft 提交)延迟越高,这也是
quickstart cluster把--size限制在 3~9 的原因。 - 领导者不固定:成员故障会触发重新选举,重启后的原领导者不会自动夺回领导权;所有客户端与脚本都应通过
ziti agent cluster list动态定位领导者。 - quickstart 自动完成全部编排:PKI 生成、配置生成、集群初始化(
agent cluster init)、成员加入(agent cluster add)、内嵌路由器注册与启动,全部内置于 ziti/run/quickstart.go,这也是它能“一个命令拉起一个节点”的原因。 - 重启成员只需最小参数:数据目录持久化后,
--home与--instance-id即足以让节点以既有 Raft 成员身份回归,其余端点参数从已生成配置读取。 - 两条上手路径:追求效率用本文的
ziti edge quickstart+join(或ziti run quickstart cluster);需要定制配置则参考 doc/ha/dev-setup.md 的手工 PKI + 配置文件方案,配置模板可对照 doc/ha/ctrl1.yml 学习。
- 零信任
- 网络
- 后端
- 认证鉴权
【免费下载链接】ziti
The parent project for OpenZiti. Here you will find the executables for a fully zero-trust, programmable network @OpenZiti
相关推荐
Redis集群节点故障模拟终极指南:CacheCloud测试环境快速搭建与实战演练
Redis集群节点故障模拟终极指南:CacheCloud测试环境快速搭建与实战演练 CacheCloud是搜狐视频开发的Redis私有云平台,支持Standal
后端运维MAS 激活脚本完整教程:4 条路线 10 分钟搞定 Windows 与 Office 永久激活
MAS 激活脚本完整教程:4 条路线 10 分钟搞定 Windows 与 Office 永久激活 多数激活问题,缺的不是密钥,而是选错了通道。开源项目 Micr
操作系统minikube 本地 Kubernetes 集群实战指南:从快速启动到多集群、Addons 与源码级解析
minikube 本地 Kubernetes 集群实战指南:从快速启动到多集群、Addons 与源码级解析 minikube 是 Kubernetes 官方社区
云原生容器编排CLI开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考