OKD/OpenShift 上跑 Redis:手动部署和 Operator 哪个更靠谱?

发布时间:2026/10/1 17:58:40
OKD/OpenShift 上跑 Redis:手动部署和 Operator 哪个更靠谱? 如果你打算在 OKD/OpenShift 上跑 Redis八成已经在两种路径之间犹豫到底是手动写一堆 Deployment、Service、PVC、ConfigMap把 Redis 像普通应用一样“塞”进集群里还是装一个 Operator让自定义资源CR声明式地管理 Redis 的整个生命周期。这两种姿势我在不同项目里都完整跑过踩过的坑也是各不相同所以这篇文章想把两边都摊开讲清楚——手动部署的每一份 YAML 我要拆开解释Operator 管理的核心概念我也会讲透最后用一张实例对比表直接把差异摆在桌面上。这篇文章适合正在做平台工程、SRE 的人也适合后端同学想快速在 OKD/OpenShift 上获得一个带密码、带持久化、能在故障后自动重建的 Redis。我会把实际部署的命令、遇到的报错、恢复步骤都写出来你可以把里面的 YAML 和思路当成一份可复用的模板。1. 为什么把 Redis 放进 OKD先搞清两种方式的底层逻辑1.1 手动部署的本质人在 K8s API 面前当“操作员”手动部署 Redis本质上就是把一个系统管理员对 Redis 的运维经验翻译成 K8s 的 API 对象。你要自己规划 StatefulSet 还是 Deployment自己决定挂载哪种 Volume自己设置探针自己管理配置和密码连 OpenShift 的 SCCSecurityContextConstraints权限都要亲手动一动。听起来繁琐但优点也很直接每一步都是你能看见、能控制的。资源文件放在 Git 仓库里review 时可以精确到每个字段出了问题也知道去哪排查。对于开发环境、临时 demo、或者只是验证某个 Redis 参数的场景手动部署并不吃亏反而更直观。但它最大的问题在于Redis 是一个有状态的、需要持续关注的应用。节点挂了谁来拉起主从切换怎么做AOF 文件写不进磁盘怎么办密码轮换时要不要重启这些在手动模式下全部要由“人”来判断和执行。也就是说K8s 在替你干活的时候你自己成了那个围绕实例团团转的“操作员”而这个操作员的经验、手速、专注程度决定了 Redis 的可用性。1.2 Operator 的本质把运维经验变成控制器Operator 的出现就是想把上面说的“人肉操作员”变成一段常驻集群里的程序。它由两部分组成一组自定义资源定义CRD让 Redis 变成一种可声明的对象一个控制器不断对比“你想要的状态”和“当前实际状态”然后自动采取措施让两者对齐。你只要写一个 Redis 的自定义资源CR说清楚“我要 3 个副本、用 10Gi 存储、开持久化”Operator 就会自动去创建 StatefulSet、Service、PVC、ConfigMap还会帮你配置探针。如果某个副本挂掉了它补一个新的上去如果节点上发生了主从故障它会主动感知并重新选主如果要扩大副本数改一行字段即可。这个设计的本质是把 Redis 相关的运维知识下沉到了代码里。对平台工程师来说相当于身边多了一个随时待命、不会疲倦的“Redis 专家”而且这个专家不会记错命令也不会漏掉步骤。1.3 什么时候选哪种几个实用的判断标准我自己的选择逻辑很简单按这几点来判断环境重要性但凡要放业务流量我优先 Operator。开发环境、功能预览环境手动部署就够用。实例数量只有一两个独立 Redis 实例团队又对 K8s 很熟手动没问题。一旦有十几个实例或者一个集群里有主从、哨兵、多分片没有 Operator 很容易失控。运维人力团队里有专门的 DBA 或 SRE愿意持续维护 Redis 监控和备份手动没问题。如果团队只有一两个后端顺手管 RedisOperator 能大幅降低操作门槛。控制粒度有些特殊场景比如会用 Redis 模块、需要非常特定的启动参数某些 Operator 可能限制较多手动部署反而能给你更大的自由度。另外提一点Operator 并不是万能银弹。它引入了 CRD、控制器、Webhook 等额外组件一旦这层控制面出了问题Redis 反而可能陷入“没人管”的状态。所以比较理想的做法是用 Operator 干日常运维但手动部署那一套 YAML 也留一份在仓库里当作逃生通道。这个我后面会再讲。2. 手动部署 Redis 的完整细节一份可直接抄的 YAML 说明2.1 基础架构StatefulSet 而不是 Deployment先说明一个我自己的选择单实例 Redis 我也用 StatefulSet不用 Deployment。原因很简单Redis 是一个有状态应用它依赖稳定的网络标识和独立的存储。Deployment 创建出来的 Pod 后缀是随机字符串重建之后 IP 和主机名全变不利于客户端稳定连接StatefulSet 的 Pod 编排顺序固定、标识稳定而且可以直接用volumeClaimTemplates给每个副本挂独立存储。下面是我常用的一份单实例 Redis StatefulSet其中 ConfigMap 和 Secret 我后面单独展开apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-node namespace: redis-manual spec: serviceName: redis-headless replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: serviceAccountName: redis-sa terminationGracePeriodSeconds: 30 securityContext: fsGroup: 1001 runAsUser: 1001 containers: - name: redis image: redis:7.0-alpine command: - sh - -c - | redis-server /usr/local/etc/redis/redis.conf \ --requirepass $(cat /etc/redis-secret/requirepass) ports: - name: redis containerPort: 6379 volumeMounts: - name: data mountPath: /data - name: config mountPath: /usr/local/etc/redis/redis.conf subPath: redis.conf - name: secret mountPath: /etc/redis-secret readOnly: true livenessProbe: exec: command: - sh - -c - redis-cli -a $(cat /etc/redis-secret/requirepass) ping | grep -q PONG initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: - sh - -c - redis-cli -a $(cat /etc/redis-secret/requirepass) ping | grep -q PONG initialDelaySeconds: 5 periodSeconds: 5 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi volumes: - name: config configMap: name: redis-config - name: secret secret: name: redis-secret volumeClaimTemplates: - metadata: name: data spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi注意这里我用了serviceName: redis-headlessStatefulSet 要求存在一个 headless Service 来提供稳定的网络标识。即便只有 1 个副本也必须配。还有terminationGracePeriodSecondsRedis 关闭前需要时间把内存里的数据落盘太短会导致 AOF/RDB 不完整。2.2 配置与密码ConfigMap Secret 的拆分Redis 本身支持通过命令行参数传配置也可以加载配置文件。我用的是 ConfigMap 挂配置文件、Secret 挂密码。分开的好处是配置可以放在 Git 里被 review密码则不能明文出现轮换密码时只改 Secret 再滚动更新 Pod不用动配置模板。ConfigMap 内容大致长这样apiVersion: v1 kind: ConfigMap metadata: name: redis-config namespace: redis-manual data: redis.conf: | bind 0.0.0.0 protected-mode yes port 6379 daemonize no dir /data appendonly yes appendfsync everysec save 3600 1 300 100 60 10000 maxmemory 256mb maxmemory-policy allkeys-lru loglevel notice再说几个关键配置的含义。appendonly yes是开启 AOF 持久化appendfsync everysec每秒刷盘一次这是性能和安全的折中方案。save控制 RDB 快照的触发频率生产里我会根据数据变化频率调整。maxmemory-policy allkeys-lru是淘汰策略如果这个 Redis 是纯缓存场景可以这么配如果是业务数据存储那就得用noeviction宁可报错也不要静默丢数据。Secret 的创建我建议直接用命令行一步到位oc create secret generic redis-secret \ --namespace redis-manual \ --from-literalrequirepass你的强密码这样密码不会落到 shell 历史之外的任何明文文件。允许在 YAML 里用stringData演示但生产环境请用命令行或 sealed-secret 这类加密方式。2.3 存储与权限PVC 和 OpenShift SCC 的坑有状态应用在 OpenShift 上第一个绕不开的问题就是 SCC。OpenShift 默认的restricted-v2SCC 不允许容器以 root 身份运行也不允许任意挂载宿主机路径。而官方 redis 镜像近几年的版本为了兼容性启动进程的用户在不同基础镜像上会不一样一旦它以 root 启动就有大概率被 SCC 拦下来Pod 会卡在CreateContainerConfigError或直接报container has runAsUser错误。我的做法是在 Pod 上显式声明非 root 用户和组securityContext: fsGroup: 1001 runAsUser: 1001同时给 Redis 使用的 ServiceAccount 授予合适的 SCC。这里要强调不要图省事直接给anyuid那等于关掉了一个重要的安全层。如果 redis 镜像本身支持非 root 运行nonrootSCC 就够了oc adm policy add-scc-to-user nonroot \ -z redis-sa \ -n redis-manual注意-z redis-sa里的redis-sa必须和 StatefulSet 里serviceAccountName一致创建完 SA 后再绑定。如果你在自己公司内部有统一的 Redis 基础镜像Dockerfile 里显式USER 1001那 OpenShift 这边的处理会更干净。存储的坑也常在这里。PV 的属主和 Pod 的fsGroup不一致时Redis 写 AOF 文件会报 permission denied。把fsGroup固定到 1001让 K8s 在挂载时把卷属主改掉基本能解决大部分问题。如果底层存储类是 NFS可能还要看 NFS 的 squash 配置这个我放到后面排查部分细讲。2.4 探针与服务liveness / readiness / 访问 Service探针对 Redis 尤其重要。因为 Redis 是单线程进程如果某个命令阻塞过久不代表进程死了但业务已经访问不了。我用的是redis-cli ping而不是 TCP 端口探测。原因很简单端口在不代表 Redis 能正常响应。redis-cli ping返回 PONG说明事件循环还在正常运转。为了带密码验证我会从 Secret 文件里把密码读进来拼接命令这样不会把密码明文写死在探针配置里。Service 方面对内访问用一个普通的 ClusterIP 就够apiVersion: v1 kind: Service metadata: name: redis namespace: redis-manual spec: selector: app: redis ports: - name: redis port: 6379 targetPort: 6379但注意对 StatefulSet 来说必须还有一个 headless Service不带 ClusterIP给 Pod 提供 DNS 名称。headless Service 的 selector 和上面保持一致类型clusterIP: None。3. Operator 管理的核心概念CRD 和自定义资源到底发生了什么3.1 安装 Operator 的两种途径OperatorHub vs CLI在 OpenShift 里安装 Operator最省事的是通过控制台的 OperatorHub。搜索 Redis选中你要的 Operator确认命名空间点击安装即可。整个安装过程其实是在做三件事创建 Subscription、生成 ClusterServiceVersionCSV、把 CRD 注册到集群里。命令行方式我也会用尤其在做自动化或批量环境初始化时。先创建一个Subscription对象apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: redis-operator namespace: operators spec: channel: stable name: redis-operator source: community-operators sourceNamespace: openshift-marketplace然后等待 CSV 变成Succeeded状态oc get csv -n operators | grep redisCSV 是 Operator 部署的灵魂文件它描述了 Operator 需要哪些权限、创建哪些 CRD、Pod 里跑哪些容器。看到 CSV 成功Operator 的 Pod 才会真正启动。3.2 用 CR 描述期望状态一句话让 Operator 接管Operator 安装好之后真正日常打交道的是 CRD。拿社区常见的 Redis Operator 来说你只要写一个Redis类型的 CROperator 就会帮你把整套实例拉起来。比如下面的例子apiVersion: redis.ot-operator.io/v1 kind: Redis metadata: name: redis-demo namespace: redis-operator spec: size: 3 persistence: size: 10Gi storageClass: standard exporter: enabled: true这里我没写密码、没写版本、没写探针因为 Operator 有默认值。它会在自己控制的命名空间里创建 StatefulSet、Service、Secret、PVC并自动配置主从或哨兵模式需要的网络身份。等 Pod 起来后你只需要关注 CR 的状态字段比如status.phase: Running、status.readyReplicas: 3。这也带来一个新的心里门槛写 CR 的时候你要信任 Operator 的默认策略。如果你不确定某个字段的含义最好先oc explain redis.spec看看注释而不是凭感觉乱填。3.3 Operator 做了什么从创建到生命周期Operator 的真正价值体现在创建之后。举几个我实际观察到的行为自动注入配置有些 Operator 会在创建时生成随机的requirepass保存在 Secret 里然后把密码注入到 Redis 配置文件。这样即使底层的 K8s 清单被误导出也不会直接泄露出明文密码。监听状态并自动修复删掉一个 Redis PodOperator 会在几秒内依照期望副本数补回一个新 Pod。Redis 节点进程异常退出、主从切换失败控制器会按照内在的协调逻辑介入。版本升级联动修改 CR 里的版本号或者升级 Operator 自身控制器会滚动更新节点。这一步在手动模式下需要手动改镜像 tag 再触发 rollout一不小心就会让主从节点同时重启影响数据一致性。监控与备份的互补能力不少 Operator 会附带 exporter直接输出 Redis metrics 给 Prometheus有的还提供备份接口能定时执行SAVE或BGSAVE并把 RDB 文件存到对象存储。也就是说从创建实例那天开始你不再关心“Pod 怎么排布”“PVC 怎么定义”“配置怎么下放”而是从更高维度想“我的 Redis 集群需要几个副本、多大存储、什么网络策略”。这个概念转变需要点时间适应但适应之后真的回不去。4. 实操过程实录两种方式走一遍差异立刻现形4.1 手动部署踩坑实录我在测试环境完整走了一遍手动部署。新建项目oc new-project redis-manual oc create sa redis-sa oc adm policy add-scc-to-user nonroot -z redis-sa -n redis-manual然后是 ConfigMap、Secret、StatefulSet、Service 逐个 apply。顺序上有个讲究先配置和密钥再应用工作负载因为 Pod 创建时必须能挂载到 Secret 和 ConfigMap顺序反了会报找不到卷。我特意把initialDelaySeconds拉长到 30 秒因为 Redis 第一次启动要先初始化/data目录太短的探针会让 Pod 反复被重启。有一个小技巧创建完后马上执行oc get events -n redis-manual --sort-by.lastTimestamp | tail -20就能看到容器启动阶段是否触发了 SCC 拒绝、镜像拉取失败等问题。我遇到的第一个坑就是官方 redis 镜像启动时以 root 运行被 restricted-v2 SCC 直接拦下报错里明确写着container has runAsUser 0 which is not allowed。当时我的处理是把基础镜像换成了支持任意 user 的运行方式同时在 StatefulSet 里固定runAsUser: 1001再绑定nonrootSCC问题解决。验证探针和服务也不难oc get pods -n redis-manual -o wide oc exec -it redis-node-0 -n redis-manual -- redis-cli -a $(cat /etc/redis-secret/requirepass) info replication从另一个命名空间访问 Redis 时要确认网络策略允许跨命名空间流量。4.2 Operator 管理实操Operator 这边我的操作过程是先在 OperatorHub 里搜 Redis选一个采用率较高的社区 Operator 安装到operators命名空间。这里要注意Operator 安装的命名空间不等于 Redis 实例所在的命名空间。Operator 可以是集群级别的它能管理任意命名空间里的 CR。安装完成后我在业务命名空间里直接创建一个 CRoc apply -f - EOF apiVersion: redis.ot-operator.io/v1 kind: Redis metadata: name: redis-app namespace: demo spec: size: 3 persistence: size: 5Gi exporter: enabled: true EOF几秒后看状态oc get redis -n demo oc get pods -n demo -l app.kubernetes.io/nameredis能看到 Operator 自动生成的 Pod 命名规则和手动 StatefulSet 不太一样更像是一个应用下的多个节点。我故意删了一个 Podoc delete pod redis-app-1 -n demo大概十几秒后新 Pod 就补齐了。整个过程我没有手动创建任何 Service、PVC、ConfigMap连密码都是 Operator 自动生成并存在 Secret 里的。这个差异在交付给业务团队的时特别有价值——他们只需提交一个 CR不需要自己管理一堆底层对象。5. 客观对比手动部署 vs Operator 管理 Redis下面是一张我在复盘时整理的对照表覆盖了我认为最重要的一些维度对比维度手动部署Operator 管理首次部署耗时1 小时以上需要理解配置、探针、SCC 等细节15-30 分钟安装 Operator 加创建 CR配置变更改 ConfigMap / Secret 并手动触发滚动更新改 CR 字段Operator 自动协调扩缩容手工改副本数或新建实例还要处理主从关系改size字段自动完成故障恢复人工判断节点状态并手动重建或切主控制器自动检测、重启、切换备份与恢复自建 CronJob 配合 redis-cli / redis-dump部分 Operator 内置备份接口定时备份监控指标自行集成 exporter 和 ServiceMonitor多数自带 exporter开箱即用资源占用低只有 Redis 本身Redis 外多一个 Operator Pod 和控制面组件学习成本需要熟练 K8s 原生对象编排需要理解 CRD、CR、Operator 的生命周期概念精细控制所有参数都能精确控制受 Operator 支持的字段范围限制团队协作依赖 Git 里的 YAML 和运维人员的规范业务团队可以自助申请平台团队统一管下面再展开说说其中几个关键差异。5.1 配置变更改 YAML vs 改 CR手动模式下改一段 Redis 配置要经历编辑 ConfigMap、apply、再滚动重启相关 Pod。这本身不难难的是判断哪些节点需要重启、哪些配置能热加载。比如maxmemory可以CONFIG SET动态生效但appendonly通常要重启。Operator 模式下你只需要改 CR 里的对应字段控制器会计算当前状态和目标状态的差异再决定怎么滚动。但它也不是完美的如果 Operator 不支持某个细节参数你反而没法绕过它去“硬改”。遇到这种情况只能看文档是否支持extraConfig之类的透传字段。5.2 故障恢复人的响应时间 vs 控制器的检测周期手动模式最考验人的时候出现在凌晨。节点挂了K8s 会重新调度 Pod但 Redis 主从关系、哨兵投票、脑裂防护这些逻辑不会自动发生。你需要登录去redis-cli info replication看谁是新主然后决定是否调整客户端连接。Operator 的控制器会定期 reconcile它天然知道“期望 3 个副本现在只有 2 个 ready”于是自动补副本也知道主从切换后拓扑状态不对会触发新的选举流程。不过要注意不是所有 Redis Operator 都实现了完整哨兵逻辑安装前要确认它支持架构是单点、哨兵还是集群模式。5.3 备份恢复谁在兜底有状态服务的备份不像无状态服务那样“删了重建就好”。手动模式下我通常用 CronJob 定期执行redis-cli BGSAVE然后把 dump.rdb 放到对象存储。恢复时要把文件拷回 Pod 的/data再启动 Redis。Operator 如果带备份能力恢复会简单一点但依然要实际演练否则真到故障时会发现备份文件损坏、对象存储桶权限不对等问题。我的建议是不管选哪种部署方式都把备份恢复演练当成上线 checklist 的一部分。6. 常见问题与排查技巧实录6.1 OpenShift SCC 权限问题导致 Pod 无法启动典型报错Error: container has runAsUser 0 which is not allowed by this SCC或者 Pod 一直处于CreateContainerConfigError。这个基本就是镜像默认以 root 运行而 OpenShift 的 restricted SCC 不允许 root。排查步骤oc describe pod pod-name -n namespace oc get sa -n namespace oc get scc | grep nonroot解决办法不是直接把 runAsUser 改成 0而是让镜像以非 root 运行并绑定nonrootSCC。如果业务镜像确实无法改我会单独建一个 ServiceAccount 并绑定受限的 nonroot而不是盲开 anyuid。6.2 AOF 文件写入失败报 permission denied现象是 Redis 正常启动但写 AOF 时疯狂报错Redis 日志里出现类似Cant open the append-only file: Permission denied。原因在 PV 的属主与容器运行的 UID 不一致。在 OpenShift 上给 SecurityContext 设置fsGroup: 1001可以让底层卷在挂载时被 chown 到 1001。但注意有些存储插件不支持动态 fsGroupNFS 尤其麻烦。如果 NFS 服务端配置了 root_squash即使 K8s 做了 chown也可能因为 NFS 自身规则拒绝。这类问题最终方案往往要回到存储层去调导出参数或者换一个支持原生卷管理的存储类。6.3 密码修改后主从复制失败这是我自己踩过比较隐蔽的问题。手动部署环境下改了 Secret 里的requirepass然后滚动重启了主节点但没注意从节点的复制参数还没有同步更新。结果从节点一直报MASTER - REPLICA sync: Master is down or slave is in error复制链路中断。正确处理方式是先同时更新 Secrets 和 ConfigMap再让所有节点滚动重启并且顺序上先重启从节点再切换主节点升级。用 Operator 时一般不用考虑这个细节但如果你在手动模式就必须有一套严谨的发布程序而不是想到哪改到哪。6.4 Operator 安装后 CR 无法创建CRD 不是最新版有些时候 Operator 一直在 Running但创建 CR 会报 schema 错误比如no matches for kind Redis in version ...。这通常发生在 Operator 升级过程中旧的 CRD 和新的控制器逻辑不兼容。排查建议oc get crd | grep redis oc get csv -n operators | grep redis看看是否同时存在多个版本的 CRD。如果 CRD 被新版本替换后旧 CR 的字段被标记为废弃控制器可能无法 reconcile。这时候要谨慎先把 CR 备份再决定是回滚 Operator 版本还是按新 schema 改写 CR。6.5 客户端连接不上Service 选择器与标签对不上这个坑很幼稚但确实常见。手动创建 Service 时selector 写的是app: redis但 StatefulSet 的 Pod 标签却因为复制粘贴成了name: redis。Pod 起来了endpoint 却是空的。oc get endpoints -n redis-manual如果 endpoints 为空赶紧检查oc get pod --show-labels。像这种问题用 Operator 基本不会遇到因为控制器生成的 Service 选择器一定和 Pod 标签保持一致。这其实也说明了 Operator 的一个好处它减少了“人写 YAML 时手滑”的概率。最后的实践经验与一个小技巧如果让我重新选一次生产环境里我会优先用 Operator 来管理 Redis这样团队可以把精力放在业务配置和数据安全上而不是整天盯节点状态。但我仍然会把一份手动部署的 YAML 放进 Git 仓库里当作逃生通道。曾经有一次 Operator 升级后 CRD 迁移出了问题控制器反复 crashRedis 实例处于无人协调的状态这时我果断手动创建了一个临时 StatefulSet 顶住流量才争取到时间去排查新版本 Operator 的配置问题。这里也顺带分享一个很实用的小习惯不管用哪种方式我都会在 Redis 的 Service 前面加一层负责读写路由的中间层或者至少让业务方通过 ConfigMap 统一维护连接地址。这样切换部署方式时只需要在 DNS 或 ConfigMap 层面改一个地址业务代码完全不用动。运维手段可以激进接入方式必须保守这是我跑 Redis 这类有状态中间件几年下来最重要的经验。