最近在帮团队把一套基于 Redis 的业务系统迁移到 OKD/OpenShift 4.x 环境,顺带把 Redis 的部署方式从手工 YAML 改成了 Operator 管理。整个过程不算复杂,但中间踩了不少坑,尤其是 Security Context 权限、持久化绑定、故障转移这些环节,光排查问题就花了一个下午。这篇梳理一下手动部署和 Operator 管理两种方式的完整对比,给正在评估技术选型的朋友做个参考,也聊聊我实测下来的真实体会。
如果你还没在 Kubernetes 系平台里跑过 Redis,可以先把这篇文章当成一份“从零到生产”的笔记看。文中涉及的资源清单、Operator 安装流程和故障排查方式,都可以直接在 OKD 或 OpenShift 环境里复现。我会尽量把选择背后的原因讲清楚,而不是只丢一堆 YAML 让你抄。
1. 现实场景:OKD/OpenShift 上跑 Redis 的价值与挑战
1.1 为什么要把 Redis 放进 OKD/OpenShift
OKD 是 OpenShift 的社区版,两者在核心架构上基本一致,都内置了完整的 Kubernetes 能力,还额外带了 Operator Lifecycle Manager、内置监控、安全上下文控制和多租户隔离等企业级特性。把 Redis 跑在这类平台上,直接带来的好处是资源管理、弹性伸缩和运维流程可以跟其他应用统一,不用再单独维护一套虚拟机或物理机。
Redis 在业务里的角色通常是缓存、会话存储、队列或分布式锁。这些场景对数据可靠性和响应速度要求很高,一旦没部署好,小则缓存雪崩,大则会话数据丢失。传统做法是直接在服务器上安装 Redis,然后靠脚本、cron 和人工盯监控来维持运行;放到容器平台之后,Pod 重建、存储挂载、网络策略这些底层操作全都交给平台处理,运维同学可以把精力放在 Redis 本身的配置、性能和数据结构设计上。
1.2 部署 Redis 的两种路线概览
在 OKD/OpenShift 上部署 Redis,目前主流的有两条路线。
一是手动部署,也就是自己写 StatefulSet、Deployment、ConfigMap、Service、PVC 这些资源,把 Redis 单实例或主从架构通过原生 Kubernetes 对象定义出来。这种方式灵活度高,想怎么调就怎么调,但所有高可用逻辑、故障转移、升级策略都要自己写,Redis 本身没做到的事,平台也不会替你补。
二是 Operator 管理,也就是通过 OLM 安装一个 Redis Operator,然后用自定义资源来描述“我想要一个什么样的 Redis 集群”。Operator 会负责把底层的 StatefulSet、Service、PVC、监控、备份、哨兵或集群配置自动生成并持续维护。跟手动方式相比,它把大量运维知识固化成了代码,实现的效果更接近“声明式运维”。
两条路线各有利弊。下面我分别拆开讲,然后再做全方位对比。
2. 手动部署 Redis 的完整实践
2.1 基础设施清单设计与核心参数
手动部署的第一步是设计一组基础清单。日常我至少会准备这几个对象:一个命名空间、一个 ConfigMap 放 Redis 配置、一个 StatefulSet 管理 Pod 和存储、一个 Headless Service 提供稳定的网络标识。如果要做外部访问,再加一个 NodePort 或 Route。
其中 ConfigMap 通常长这样:
apiVersion: v1 kind: ConfigMap metadata: name: redis-config data: redis.conf: | appendonly yes appendfsync everysec maxmemory 256mb maxmemory-policy allkeys-lruappendonly yes开启 AOF 持久化,appendfsync everysec表示每秒刷盘一次,这是数据安全与性能之间比较稳妥的折中。maxmemory和maxmemory-policy是防止 Redis 无脑占用容器内存的关键参数。容器平台通常会设内存 limit,一旦 Redis 超过 limit 可能会被 OOM Kill,所以提前设好内存上限,配合 LRU 淘汰策略,能避免很多线上问题。
2.2 为什么优先选择 StatefulSet 而不是 Deployment
单实例 Redis 我建议用 StatefulSet,而不是 Deployment。很多人图省事用 Deployment,几行代码就搞定,但 Redis 是有状态服务,它的数据必须存到持久卷上,而且 Pod 重建之后主机名和网络标识最好保持稳定。Deployment 创建的 Pod 名字是随机后缀,换一台节点重建之后,之前的粘性就没了;StatefulSet 则有稳定的 Pod 命名和稳定的存储绑定。
下面是一个常见的单实例 Redis StatefulSet:
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: redis replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7.0 ports: - containerPort: 6379 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: redis-data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi注意这里我同时写了volumes和volumeClaimTemplates,刚开始容易混淆。两个选一个用就行。如果希望系统自动为每个副本生成独立的 PVC,就用volumeClaimTemplates;如果你先手动创建好具体的 PVC,再用volumes绑定也是可以的。我自己的习惯是生产环境使用 StorageClass 动态供给时倾向volumeClaimTemplates,维护成本低。
2.3 服务暴露与配置挂载的细节
Redis 需要给应用访问时,通常配一个 Headless Service,方便集群内部通过主机名解析:
apiVersion: v1 kind: Service metadata: name: redis spec: clusterIP: None selector: app: redis ports: - name: redis port: 6379 targetPort: 6379ClusterIP 设为 None 后,StatefulSet 的每个 Pod 都能获得稳定的 DNS 名字,比如redis-0.redis.namespace.svc。这在做主从复制或哨兵配置时非常关键。你可以在 ConfigMap 里直接用类似replicaof redis-0.redis.namespace.svc 6379的写法,让从节点自动找到主节点。
挂载配置时要注意:直接把整个/data目录挂成 volume 是常见的,因为 Redis 默认把 RDB 和 AOF 文件放在/data。如果挂载点不对,数据落不到 PVC 上,Pod 一重启数据就没了。检查方式很简单,进 Pod 执行redis-cli CONFIG GET dir,看看当前工作目录是不是卷挂载路径。
2.4 OpenShift 特有权限坑:Security Context
在 OKD/OpenShift 上跑 Redis,最容易遇到的是 Security Context 问题。OpenShift 默认的安全策略会拒绝以 root 用户运行的容器,而官方 Redis 镜像在某些版本里恰恰会以 root 启动,或者监听了低端口导致权限报错。你可能会看到类似container has runAsNonRoot and image will run as root的拒绝信息。
解决办法有两种。一种是创建一个允许指定用户运行的 ServiceAccount,并绑定到合适的 SCC;另一种是直接用官方提供的 UBI 基础镜像构建 Redis 镜像,设置好任意用户 ID 都可运行。这个环节说大不大,但每次新建项目都容易踩一遍,建议提前把 ServiceAccount 和 SCC 梳理成项目模板,省得后面反复配。
2.5 手动部署的优缺点
手动部署最大的优点是透明。你能精确控制每一个参数,出了问题也容易排查,因为你清楚每条配置是怎么来的。它最大的缺点也很明显:如果要做高可用,光靠一个 StatefulSet 是不够的,你还需要 Redis Sentinel 或 Redis Cluster,这意味着要再维护一组哨兵 Pod、再写故障转移脚本、再处理主从切换后客户端重连的问题。
我见过不少团队用手动方式搭了一套 Redis 主从加哨兵,刚开始跑得挺好,后来出现一次节点维护,哨兵选主后新主节点上的数据是旧的,因为持久化策略没有同步调整,整个业务缓存全部错乱。这种问题不是不能解决,而是解决成本非常高,需要你把 Redis 的故障转移原理吃透,还要和平台调度策略磨合。这时候,Operator 的价值就体现出来了。
3. Operator 管理 Redis 的实践
3.1 Operator 到底是什么,为什么适合 Redis
Operator 本质上是一个“应用领域的控制器”。它把人类运维 Redis 的经验,比如健康检查、故障转移、备份恢复、配置变更、版本升级,编码成一段持续运行的逻辑。这个逻辑会一直盯着自定义资源的状态,一旦发现实际状态和期望状态有偏差,就自动执行操作把它拉回期望状态。
用生活化的类比来说:手动部署就像你雇了一个新手运维,所有操作都要你把步骤写在文档里,他照着做;Operator 则像一个经验丰富的老运维,你只要告诉他“我要一个 3 节点的 Redis 集群,数据要持久化”,他会自己搞定后面的所有事,还会定期检查集群健康,出问题自动修复。
Redis 这类有状态服务特别适合 Operator,因为它拥有复制、哨兵、集群管理、持久化、重分片等一套复杂生命周期。这些逻辑如果全部靠人写在脚本里,维护成本高得吓人;交给 Operator 之后,反而稳定可靠得多。
3.2 在 OKD/OpenShift 上安装 Redis Operator
在 OKD/OpenShift 里安装 Operator,通常不用手动 kubectl apply CRD。打开 OpenShift 控制台的 OperatorHub,搜索 Redis,会看到几个候选 Operator,比如 Redis Enterprise Operator 或社区维护的 Redis Operator。选择信任来源的 Operator 后,一般会要求选择一个安装模式。
安装模式通常有两种:单命名空间模式,Operator 只管理某个指定项目里的 Redis 实例;全集群模式,Operator 可以管理整个集群多个项目下的 Redis。单实例体验最低成本选单命名空间,以后想扩,也可以再重新安装成集群模式。
安装完成后,OLM 会自动创建 Operator 的 Deployment、CRD、RBAC 和 Webhook。这个过程会在项目的openshift-operators或者其他命名空间里跑起来一个或多个控制器 Pod。可以通过oc get csv查看 ClusterServiceVersion 状态,当状态变成 Succeeded,说明 Operator 已经被成功注册。
3.3 声明式创建 Redis 实例
Operator 装好后,就可以用自定义资源声明要的 Redis 实例。以 Redis Enterprise Operator 为例,资源大致长这样:
apiVersion: app.redislabs.com/v1 kind: RedisEnterpriseCluster metadata: name: redis spec: size: 3 persistenceEnabled: true storageClassName: managed-csi podAntiAffinity: true我只填了几个关键字段,Operator 就会在后台创建整套基础设施:StatefulSet、Service、Secret、PV/PVC、哨兵配置、监控 exporter,以及集群初始化任务。相比手动写几百行 YAML,这种方式明显更接近“告诉平台你要什么,而不是告诉平台怎么做”。
不同 Operator 的 CRD 会略有差异,但核心思路一致。社区里有不少 Redis Operator 用RedisCluster或Redis作为 Kind。创建前建议先oc explain rediscluster.spec看一下字段说明,或者直接查 Operator 的文档,避免因为字段名不对导致资源无法创建。
3.4 Operator 自动维护哪些能力
一个成熟 Redis Operator 通常会自动处理这几件事:
- 自动生成 StatefulSet 和固定网络标识
- 自动配置持久化存储并管理 PVC
- 自动启停并维护 Redis 主从或集群拓扑
- 自动执行故障转移,在节点不可用时重新选举主节点
- 自动处理配置变更,并把新配置滚动应用到全部节点
- 定期做备份,支持按时间点恢复
- 集成 Prometheus metrics,提供默认告警规则
这些能力不是一次性任务,而是持续运行的控制循环。比如某个 Redis 节点对应的工作节点宕机,Kubernetes 会把 Pod 调度到别的可用节点,但 Redis 集群的主从关系、IP 变化后的客户端发现,都需要额外处理。Operator 会在检测到异常后,自动把故障节点摘除、触发主从切换、更新服务路由,整个过程可以不用人盯着。
不过要清醒地认识到,Operator 不是万能的。Redis 本身的性能瓶颈、慢查询、大 Key、热 Key、内存碎片等问题,Operator 并不能替你优化,该做的数据建模和性能调优还是要做。
4. 全面对比:手动部署 vs Operator 管理
4.1 部署效率与可维护性对比
手动部署第一次搭建可能只要半小时,因为一个单实例 Redis 并不复杂。但随着需求增加,加主从、加哨兵、配持久化、配监控,每一层都要手动扩展,工作量成倍增长。而且每个人的写法不一样,今天这个环境用 Deployment,明天那个环境用 StatefulSet,风格很难统一。
Operator 部署首次需要安装 Operator,投入的时间比手动写 YAML 更长,可能一个多小时。但之后就方便了,创建一个 Redis 实例就是提交一个 CR 的事,多套环境之间的配置也容易保持一致性。后续加节点、改配置,也是改 CR 就能完成。我个人更看重长期的可维护性,所以生产环境偏好 Operator。
4.2 高可用与故障自愈能力
手动部署 Redis 单实例,Pod 如果挂了,平台会自动拉起新 Pod,但数据是否完整取决于持久化配置。想要主从自动切换,就必须额外搭 Sentinel。手动搭 Sentinel 本身不难,难在如何和 Kubernetes 的网络模型适配,以及如何避免哨兵误判。比如网络抖动触发哨兵投票,主从切换期间缓存不可用,这种故障在手动方式下几乎无法避免。
Operator 方案里,故障转移逻辑被内置到了 Reconciler 中。节点失联后,Operator 会根据优先级重新选举主节点,并自动在 Service 层面更新端点,客户端的连接不会长时间断掉。当然,真正的故障转移耗时还取决于很多因素,但只要 Operator 本身健康,整体流程是自动化的。
为了更直观理解,我列一下两者的运维工作量对比:
| 运维环节 | 手动部署 | Operator 管理 |
|---|---|---|
| 搭建单实例 | 简单,半小时 | 需要先装 Operator |
| 搭建主从 | 手工配置复制关系 | 声明式指定副本数 |
| 实现高可用 | 手动部署 Sentinel | Operator 内置故障转移 |
| 集群扩容 | 手动增加节点并调整槽位 | CR 改 size 即可 |
| 版本升级 | 手动改镜像并滚动验证 | Operator 支持滚动升级 |
| 备份恢复 | 自己写 CronJob | Operator 管理备份与恢复 |
| 监控集成 | 手动部署 Prometheus exporter | 通常自带 metrics 和告警规则 |
4.3 持久化和数据可靠性
持久化几乎是所有有状态应用的命门。手动部署时,很容易犯两个错误:一个是不挂 PVC,只把数据写在容器可写层,Pod 一重建数据全丢;另一个是 PVC 绑定的存储类型不对,本地存储导致 Pod 调度到其他节点后数据无法访问。我在 OKD 上建议优先使用平台提供的 CSI 存储类,并提前验证扩容能力。
Operator 管理持久化通常做得更规范。比如 Redis Enterprise Operator 在创建集群时就会创建持久卷声明模板,并把数据目录、备份目录分开管理。这样即使某个节点损坏,新 Pod 调度后也能挂载上同一份数据,最大限度减少数据丢失风险。
4.4 升级与配置变更
Redis 版本升级是大家容易忽视的环节。手动部署时,升级 Redis 需要改镜像 tag,还要手动执行滚动更新,并且要关注主从顺序,先升从再升主,避免数据不一致。更麻烦的是,如果跨大版本升级,比如从 6.x 升到 7.x,很多配置项会有变化,手动升级很难全部覆盖检查。
Operator 把升级也做成了自动化。声明式改一个镜像版本,Operator 会按拓扑顺序逐个升级节点,并在升级时保留持久化数据,还会验证节点状态,不符合条件就不会继续。整个过程比手动滚动要稳得多。我实际用过一次 Operator 从 Redis 6.2 升到 7.0,全程没有什么人工参与,比手动的压力小很多。
4.5 资源占用与控制权
Operator 不是零成本的,它本身也要占用控制平面资源。一个 Operator Pod 大约需要几十到几百 MB 内存,如果你的集群里 Node 数量很少,这个开销相对明显。另外,Operator 会带来额外的抽象层,底层细节被封装后,排查问题时需要理解 Operator 的行为逻辑,对排障能力要求反而更高。
手动部署则控制权最高,你可以为某个极端的业务场景做定制化配置,比如特殊的集群拓扑、自定义的网络策略、精确到毫秒的主从同步参数等。Operator 再灵活,最终也只能支持它设计好的那些场景。所以如果业务非常特殊,Operator 不一定是最优选,手动部署的灵活性无可替代。
4.6 学习成本与团队协作
手动部署的学习曲线集中在 Kubernetes 基础,学习成本相对平缓,团队成员只要懂 StatefulSet、PVC 和 Service,就能参与维护。Operator 则要求理解自定义资源、控制器和 OLM 机制,入门门槛高一些。但是团队一旦熟悉了 Operator,后续维护不同应用的思路可以复用,因为其他中间件比如 Kafka、PostgreSQL 也都有 Operator。
这里有个现实问题:很多团队招人时要求“懂 Redis”,但懂 Redis 的人不一定懂 Operator 和 Kubernetes。如果团队成员对控制平面调度逻辑不熟悉,遇到奇怪问题可能无从下手。建议在引入 Operator 之前,先确保团队有 1 到 2 个人能把 Pod 启动、调度、健康检查、服务发现这些基本链路讲清楚。
5. 常见问题与排查技巧实录
5.1 Pod 一直处于 CreateContainerConfigError 或 CrashLoopBackOff
这个在我手动部署时遇到频率最高。CreateContainerConfigError通常是 ConfigMap 或 Secret 找不到,检查一下资源是否创建在同一个命名空间,名字是否一致。CrashLoopBackOff则要进日志看具体原因,OpenShift 里可以用oc logs redis-0 --previous查上次启动日志。
日志如果是权限类报错,就去查服务账户和 SCC;如果是数据目录无法写入,检查挂载目录的权限,特别是镜像用非 root 用户启动时,PVC 目录属主可能还是 root,需要把 fsGroup 或 SecurityContext 调整好。
5.2 在 OpenShift 上遇到 imagePullBackOff
OpenShift 集群通常默认要求镜像仓库配置可信证书,如果你的 Redis 镜像是从外部私有仓库拉的,经常遇到imagePullBackOff。解决方法要么把仓库证书加入集群可信列表,要么直接使用平台内置的镜像流,或者把镜像上传到平台自带的 registry。
另外,国内网络环境访问 Docker Hub 经常不稳定,这也是imagePullBackOff的一个真实原因。稳妥的做法是提前把 Redis 镜像上传到可信 registry,再在 Deployment 里指定完整镜像地址,不要依赖默认拉取。
5.3 数据不持久化或 PVC 无法动态供给
数据丢失是 Redis 部署里最致命的问题。排查思路是三步:先确认 PVC 状态是否是 Bound;再进入 Pod 执行redis-cli CONFIG GET dir确认目录在挂载卷下;最后测试模拟删除 Pod,看看新 Pod 是否恢复原数据。
如果 PVC 一直停留在 Pending,大概率是 StorageClass 没配置或者默认存储类不存在。OpenShift 安装时如果没安装 CSI 驱动,PVC 动态供给就会失败。你可以在 YAML 里显式指定一个已存在的 storageClassName,比如managed-csi、gp2、standard,具体以平台实际为准。
5.4 端口冲突和 Service 不稳定
Redis 主从之间使用 6379 通信,Sentinel 使用 26379,集群模式还会用到 16379、16380 等端口。在 OpenShift 里需要注意 Service 的 targetPort 不能配错,尤其是同一命名空间下多个 Redis 实例时,端口很容易混乱。
我排查过的一个案例是应用连接 Redis 偶发超时,后来发现 Service 的 selector 匹配到了两个不同的 Redis Pod,流量被负载分发到了错误节点。这类问题在手动部署里很常见,因为手动方式下 selector 标签全靠人写,写错一个字符就能造成诡异现象。Operator 管理通常不会出这种问题,因为标签、selector 和服务都是系统自动生成的。
5.5 Operator 安装后创建 CR 一直不 Ready
如果是 Operator 方式,遇到创建 Redis 实例后状态长时间不 Ready,优先看 Operator 日志。日志里有很明确的信息,比如存储类不支持、PVC 数量不足、节点资源不够、镜像仓库认证失败等。
还有一个隐蔽问题:OpenShift 的默认项目可能限制了 Pod 的安全上下文,而 Operator 生成的 Pod 需要更宽松的 SCC。这时你需要为 Operator 使用的 ServiceAccount 绑定一个合适的 SCC。不要一上来就放开 all 权限,先确定是哪个安全属性不满足,再针对性地调整。
5.6 监控告警接入的常见遗漏
OpenShift 自带监控栈,但默认不会自动采集所有用户项目的 metrics。手动部署 Redis 时,就算通过 exporter 暴露了 metrics,也要记得创建 ServiceMonitor 并给命名空间打上openshift.io/cluster-monitoring="true"这类标签,否则 Prometheus 根本不会来抓数据。
Operator 管理通常会把 ServiceMonitor 作为附带资源自动创建,但并不是每个 Operator 都会自动创建,有些仅创建 Service,ServiceMonitor 还需要你自己补。所以无论哪种方式,我都建议最后检查一下 Prometheus 的 Targets 里是否能看到 Redis 的 exporter,看不到就把监控链路重新顺一遍。
写在最后的个人建议
如果今天让我重新选一次,测试环境或开发环境我会继续用手动部署,因为快,方便调试;生产环境我倾向直接用 Operator,尤其是需要多副本和高可用的 Redis 集群。理由很简单,人的精力是有限的,与其天天盯故障转移和升级流程,不如把这类重复性极高的操作交给 Operator,把时间留给业务和性能优化。
还有一点,不少朋友以为有了 Operator 就不需要懂内部原理,这个想法很危险。我实操之后最大的感受是:Operator 只是把运维动作自动化了,判断和调优还是得靠人。你越理解 StatefulSet、PVC、SCC 和 Redis 复制原理,越能把 Operator 用得顺手。反正我现在排查问题,依然是先看 Operator 生成出来的底层资源,再决定下一步怎么改。希望这篇内容能让你少走一些弯路。