拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenZiti HA 快速启动实战:三节点本地集群搭建、故障演练与源码级原理解析

OpenZiti HA 快速启动实战:三节点本地集群搭建、故障演练与源码级原理解析
  • 零信任
  • 网络
  • 后端
  • 认证鉴权

【免费下载链接】ziti

The parent project for OpenZiti. Here you will find the executables for a fully zero-trust, programmable network @OpenZiti

项目地址:https://gitcode.com/gh_mirrors/zi/ziti
点击查看免费下载

导读:本文基于 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-idHA 模式下每个实例的唯一标识,会写入 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-domainSPIFFE 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 │ ╰───────┴────────────────────┴───────┴────────┴─────────────────┴───────────┘

两个关键变化值得注意:

  1. ctrl1 显示<not connected>/false:被终止的成员失去了控制面连接;
  2. 领导者已从 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-domainSPIFFE 信任域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-hosted
ziti 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 快速启动的关键事实清单

  1. 三节点是 HA 的最小形态:3 个投票成员的法定人数为 2,任一成员宕机后集群仍可选出新领导者;节点数越多,写操作(Raft 提交)延迟越高,这也是quickstart cluster把--size限制在 3~9 的原因。
  2. 领导者不固定:成员故障会触发重新选举,重启后的原领导者不会自动夺回领导权;所有客户端与脚本都应通过ziti agent cluster list动态定位领导者。
  3. quickstart 自动完成全部编排:PKI 生成、配置生成、集群初始化(agent cluster init)、成员加入(agent cluster add)、内嵌路由器注册与启动,全部内置于 ziti/run/quickstart.go,这也是它能“一个命令拉起一个节点”的原因。
  4. 重启成员只需最小参数:数据目录持久化后,--home与--instance-id即足以让节点以既有 Raft 成员身份回归,其余端点参数从已生成配置读取。
  5. 两条上手路径:追求效率用本文的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

项目地址:https://gitcode.com/gh_mirrors/zi/ziti
点击查看免费下载

相关推荐

上一篇:5个场景让你彻底告别PDF处理难题:在线PDF工具全攻略
下一篇:Dependencies:Windows DLL依赖分析终极指南 - 快速解决应用程序启动问题

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

返回列表