因为我们有多分片,而且 Server 是有状态的,所以每一个分片用 StatefulSet 进行托管。在新建集群时,我们默认分片内的 0 号 Pod 为 Master Pod,其余所有的 Pod 是 Slave。这是一个初始状态,后续可能会跟随 Failover 或其他异常发生变更,但是 Configserver 里会实时记录最新的状态信息。
Redis Server 启动的时候需要一些配置文件,里面涉及到一些用户名和密码,我们是用 Secret 来存储的。在 Server Pod 运行的时候通过 volume 机制挂载到 Server Pod 内部。对于 Proxy,通过 HPA,基于 Proxy 的 CPU 利用率,支持 Proxy 服务的动态扩缩容。放置策略对于一个 Redis 集群涉及到的 Server 和 Proxy 组件,我们有一些放置策略的要求,比如:
同一个 Server 分片下的节点不能在同一台机器上,即,一个分片内的主从节点不能在同一台机器上。转换成 K8s 里面的模型,即我们希望一个 StatefulSet 下所有的 Pod 部署在不同的机器上。我们会利用 Pod-AntiAffinity 下面的 required 语义,来保证 StatefulSet 下所有的 Pod 都部署在不同的机器上。
一个集群下的 Proxy Pod 需要尽可能分布在不同的机器上,可通过 Pod-AntiAffinity 下的 preferred 语义加上拓扑分布约束来满足。preferred 语义只能保证 Pod 尽可能分布在不同的机器上,为了避免极端情况下所有 Pod 都在同一台机器上的情况,我们会使用拓扑分布约束。
Server 组件的升级请求发送给 ApiServer,ApiServer 接收到这个请求之后会把它推送给 Operator。
Operator 首先会按照分片内 Pod 编号从大到小的顺序选择要升级的 Pod。
选定 Pod 之后,会把它从 Configserver 的读拓扑里摘掉。(如果要摘除的这个 Pod 在集群拓扑里是 Master,我们会先调用 Configserver 的 API,执行 Failover,把它变成 Slave,然后再把它从读的拓扑里边给摘除掉。)
之后,Operator 等待 30 秒。这个机制的出发点是:
首先,Proxy 去 Configserver 拉取配置是异步过程,可能需要经过至少一轮的数据同步才能正常拿到数据。等待 30 秒主要是为了保证所有的 Proxy 都已经拿到了最新的读拓扑,新的读请求不会再发送到要升级的 Server 节点上。
另外,我们要保证等待 30 秒的时间,让已经被要升级的 Server Pod 接收的这些请求都成功地被处理,并且返回之后,才能把要升级的 Server Pod kill 掉。
30 秒之后请求 ApiServer 执行实际的 Pod 删除操作。删除之后 K8s 会重新调度一个新的 Pod 起来,这时新创建的 Server Pod 也是一个独立的 Server 的状态,没有跟任何节点建立主从关系。Operator 感知到新的 Server Pod 已经处于 ready 的状态,会把它注册到 Configserver。
Configserver 连到新的 Server Pod 上,根据它所处的分片跟所在分片的 Master 节点建立主从关系,同时进行数据同步。
Operator 会定期检查新的 Server 分片在 Configserver 是否已经完成数据同步。如果是,Operator 才会认为一个 Server Pod 完成了升级。该分片内其他所有 Server pod 的升级流程都是类似的,直到该分片内所有 Server Pod 都升级完,才认为这个分片升级完成。最后 Operator 会更新自己 Redis Cluster 的 CRD 里对应的 Status 状态信息,反映当前组件升级的流程和变更操作已经结束了。