设置 Active-Active Replication
Active-Active 模式处于 alpha,且仅适用于 Redis 7.2。Redis 6.0 不支持该模式 — admission 会拒绝它。
设置 Active-Active replication 包含两个部分:在每个 Redis 实例上启用 Active-Active 模式,以及为每个成员创建一个 ActiveRedisMesh,以便成员能够彼此发现。有关该模式的工作方式 — 已发现的成员关系、两个端口、冲突解决和时钟要求 — 请先阅读 架构。有关它与 Disaster Recovery 的比较,请参阅选择模式。
目录
前提条件步骤 1:在每个数据中心配置对等认证凭证专用帐户(推荐)实例的默认帐户(备用方案)步骤 2:在每个实例上启用 Active-Active 模式步骤 3:在每个集群中创建 ActiveRedisMeshSeed 语法可选字段第 4 步:验证收敛Mesh 阶段成员状态版本字段后续步骤前提条件
- 每个成员运行 Redis
7.2。 - 每个成员都有一个位于
[0-15]范围内的唯一serviceID。 - 每个成员都必须显式设置可从外部路由的
announceAddress— 请参阅步骤 2。不会默认将其设置为跨数据中心地址,而 admission 只会发出警告,因此未设置该地址的成员会公布一个其对等成员无法访问的地址。 - 每个成员的代理端点都必须可从其他每个成员访问,并且访问其代理 Service 发布的每个端口,每个端口均使用自身的端口号 — 而不仅仅是公布的端口。
- 所有成员的系统时钟必须保持同步,即在组内每个集群的每个节点上运行时间同步守护进程(
ntpd、chronyd或平台对应的组件)。这是硬性前提条件,而不是建议 — 请参阅时钟同步。
步骤 1:在每个数据中心配置对等认证凭证
Active-Active 组中的每条对等连接都需要进行身份验证,并且每个成员都必须提供相同的凭证:只有当入站对等成员提供的用户名等于该成员自身的对等认证用户名,且密码通过 Redis ACL 验证时,成员才会接受该入站对等成员。每个实例通过 spec.activeRedis.redisUserRef 绑定该凭证,该字段指定实例自身命名空间中的一个 RedisUser 对象。
无论绑定哪个帐户,该帐户都必须:
- 引用本地实例 — 指向其他实例的
RedisUser会被拒绝; - 是
custom或default帐户;system帐户始终会被拒绝; - 拥有一个包含
password键的密码 Secret; - 能够达到
status.phase: Success— 在此之前绑定会被拒绝; - 在每个数据中心携带相同的用户名和相同的密码值。
专用帐户(推荐)
在每个数据中心创建一个用于此目的的 custom RedisUser。这样可以缩小凭证授予的权限范围 — 否则,任何持有默认密码的对象都可以充当 mesh 对等成员 — 并且可以独立于实例密码轮换复制凭证。
密码 Secret 与 RedisUser 对象是一对一关系,因此每个数据中心都需要自己的 Secret 对象;跨数据中心必须匹配的是密码值,而不是 Secret 名称。
密码会在 admission 时进行策略检查:长度为 8 到 32 个字符,并且必须同时包含字母、数字和特殊字符。不符合要求的值会在创建 RedisUser 时被拒绝,并返回 password should consists of letters, number and special characters。
等待帐户就绪后再绑定:
实例的默认帐户(备用方案)
也可以绑定实例自身的默认帐户 — 这是 Disaster Recovery 使用的同一凭证,因此如果这些实例已经存在绑定,可以直接跳到步骤 2。
每个实例都有一个默认帐户 RedisUser,但只有在创建实例时使用了 spec.passwordSecret,该帐户才会持有密码 Secret。在未使用该选项创建的实例上,该帐户仍然存在,并报告 Success,但不携带凭证;绑定该帐户会使实例的 ActiveRedis 保持为 Failed:
请先设置实例密码,或按上述方式绑定专用帐户。
默认帐户 RedisUser 以实例命名:
此后每个成员都必须携带相同的 default 密码,而默认密码是按实例生成的 — 请在启用该模式前将它们设置为相同的值。任何持有该密码的对象都可以充当 mesh 对等成员,并且轮换实例密码也会随之轮换复制凭证。专用帐户可以避免这两种情况。
步骤 2:在每个实例上启用 Active-Active 模式
这些操作步骤假设实例从一开始就要加入 Active-Active 组。对于已经在进行复制的实例,准入控制不会阻止将 spec.activeRedis.mode 从 peerof 更改为 mesh,但将已建立的 Disaster Recovery 组转换为 Active-Active 不属于本操作步骤的范围,且未经验证。请使用尚未接入 Disaster Recovery 组的实例构建 Active-Active 组。
启用 Active-Active 会将该模块的 init container 添加到实例,因此 operator 会滚动重启 Redis Pod,并且实例会在再次处于 Ready 状态前重新经过 Initializing。请先完成步骤 1:当实例不处于 Ready 状态时,无法针对该实例创建 custom RedisUser — 准入控制会拒绝请求并返回 redis failover <instance> is not ready — 因此在帐户创建之前启用该模式,会让您只能等待重启完成后才能创建帐户。
成员必须公布一个可从外部路由的地址,因为对等成员每次拨号时都会重新解析该地址。这就是设置在代理 Service 配置中的 announceAddress。
ActiveRedis 暴露相同的 spec.proxy.service 字段,但该资源是派生资源:operator 会在每次 reconcile 时,根据 Redis 实例重写其 spec.proxy — 在 mesh 模式下,会将已解析的公布地址写入其中 — 因此在该资源上进行的编辑会被覆盖。请始终修改 Redis 对象。
- 建议使用 DNS 名称:该模块会在每次拨号时重新解析该名称,因此可以适应 IP 变化;包含多个 A 记录的 DNS 名称还可以覆盖多个 NodePort 地址。当公布地址为 DNS 名称时,operator 会通过 ExternalDNS 将其绑定到代理 Service。
- 允许使用裸 IP,但会发出警告 — IP 发生变化后不会重新解析,因此仅适用于稳定的 VIP。
- 留空会产生警告,之后的行为取决于 operator:如果 operator 配置了受管 DNS 区域,则会派生出
<instance>.<namespace>.<zone>,并通过 ExternalDNS 将其公布在代理 Service 上;如果未配置区域,则完全不发送公布地址,模块会公布Pod IP,而该地址无法跨数据中心路由。不会有其他机制填充此字段。 - 包含 scheme(
tcp://…)、端口(host:6379)或路径的地址会被拒绝。端口应填写在announcePort中。
announcePort 是一个身份值:它用于向对等成员命名此成员的端点,本身不承载任何流量。gossip 和复制都不会由它引导,它也不会覆盖模块寻址对等成员的方式。
代理仅运行单个监听器 — 应用流量、gossip 和复制流量都会到达该监听器,并按会话进行区分 — 但代理 Service 会将多个端口发布到该监听器上。当外部负载均衡器或防火墙位于代理前端时,请转发该 Service 发布的每个端口,并分别使用各自的端口号。将其中任何端口重新映射到其他端口号都会破坏 mesh。
步骤 3:在每个集群中创建 ActiveRedisMesh
每个 Kubernetes 集群为其本地实例管理一个 ActiveRedisMesh。spec.seeds 用于指定其他数据中心成员公布的端点 — 即这些成员在步骤 2 中获得的 announceAddress 和 announcePort。
步骤 2 会滚动 Pod,模块也会随 Pod 一起到位。如果在滚动更新到达分片的主节点之前创建 ActiveRedisMesh,mesh 会进入 Failed,并报告模块自身的命令不存在:
此状态会自行清除 — 主节点加载模块后,mesh 会转为 Pending,随后转为 Healthy — 但在此期间,它与实际故障无法区分。请等待 kubectl get redis 报告 Ready。
Seed 语法
Seed 是 host:port — 远程成员的 announceAddress:announcePort,必须完全按照该成员公布自身信息的方式填写。它是一个可从外部路由的端点,因此当负载均衡器或 VIP 位于该成员代理的前端时,seed 应填写负载均衡器的地址和端口,而不是集群内代理 Service。请原样填写这一对值:成员会将 seed 的地址和端口与自身公布的地址和端口进行比较,以识别自己的条目;如果两者不匹配,则自我跳过机制会失效。
请将 seed 写成普通的 host:port,由 operator 完成其余处理。operator 会先将列表规范化为模块内部的 seed 语法,然后注入模块,因此 CONFIG GET activeredis.mesh-seeds 报告的每个条目会比你填写的形式更长。这种补全是 seed 能够使用的必要条件,其中没有任何需要你设置的内容。
每个远程数据中心只需一个可访问的 seed 即可 — gossip 会发现其他所有成员。可以包含本地成员自身的条目,因此每个数据中心都可以使用相同的统一 seed 列表;模块会跳过自身记录。
operator 会将规范化后的集合注入每个节点,包括副本节点,因此 mesh 会在每个 Pod 上自动启动。因此,重启和故障转移后的重新加入都无需手动操作。
可选字段
第 4 步:验证收敛
上面的第一条记录是此实例自身的记录,其中没有 address、clockOffsetMs 或 moduleVersion。这是预期行为,并不表示成员缺失:gossip 会报告其他成员,因此 operator 会根据实例对自身的了解补全本地记录,而这三个字段没有本地来源。每个远程成员都会携带这些字段。值为零的计数 — suspectCount、deadCount — 会被省略,而不是打印出来。
Mesh 阶段
成员状态
成员的状态为 alive、suspect 或 dead — 请参阅成员关系生命周期。
clockOffsetMs 是每个成员相对于此实例的时钟偏移量,单位为毫秒。请参阅时钟同步。
版本字段
status.fleetFloorVersion 报告已知版本的非失效成员中的最低 Redis 版本 — 这是整个 mesh 的功能上限。所有非失效成员运行相同的 Redis 和模块版本后,status.versionConverged 为 true。
这是预期行为,由模块负责处理;它不会导致 mesh 阶段失败。整个成员集收敛后,它会再次变为 true。
后续步骤
Day-2 操作 — 监控时钟偏移、调整 gossip 计时器,以及添加或移除成员 — 详见运维。