# Redis Sentinel 部署问题诊断与修复报告

**问题日期：** 2026年2月22日  
**命名空间：** juwan  
**涉及资源：** user-rpc deployment, RedisSentinel  

---

## 📋 目录

1. [问题背景](#问题背景)
2. [问题现象](#问题现象)
3. [诊断过程](#诊断过程)
4. [根因分析](#根因分析)
5. [解决方案](#解决方案)
6. [修复步骤](#修复步骤)
7. [验证结果](#验证结果)
8. [后续建议](#后续建议)

---

## 🎯 问题背景

### 部署目标
部署一个简单的三节点 Redis Sentinel 哨兵集群作为缓存服务，供 user-rpc 服务使用。后续如有需要再扩展为分片集群。

### 初始配置
在 `deploy/k8s/service/user/user-rpc.yaml` 中配置了：
- user-rpc Deployment（3副本）
- user-rpc Service
- HPA（CPU和内存）
- **RedisSentinel 资源**
- PostgreSQL Cluster

---

## 🔴 问题现象

### 执行的操作
```bash
kubectl apply -f .\deploy\k8s\service\user\user-rpc.yaml
```

### 输出结果
```
deployment.apps/user-rpc configured
service/user-rpc-svc unchanged
horizontalpodautoscaler.autoscaling/user-rpc-hpa-c unchanged
horizontalpodautoscaler.autoscaling/user-rpc-hpa-m unchanged
redissentinel.redis.redis.opstreelabs.in/user-redis unchanged
cluster.postgresql.cnpg.io/user-db unchanged
```

### 观察到的异常
查看命名空间资源：
```bash
kubectl get all -n juwan
```

**发现：**
- ✅ user-api pods 正常运行
- ✅ user-rpc pods 正常运行
- ✅ PostgreSQL clusters 正常运行
- ❌ **没有任何 Redis 相关的 Pod**
- ❌ **没有 Redis Service**

---

## 🔍 诊断过程

### 步骤 1：检查 RedisSentinel 资源状态

**目的：** 确认 RedisSentinel 资源是否被成功创建

**命令：**
```bash
kubectl get redissentinel user-redis -n juwan
```

**输出：**
```
NAME         AGE
user-redis   9m56s
```

**分析：**
- ✅ RedisSentinel 资源已创建
- ❌ 但没有创建任何 Pod
- **结论：** Operator 没有按照 RedisSentinel 规格创建实际资源

---

### 步骤 2：查看 RedisSentinel 详细信息

**目的：** 检查资源的详细配置和事件

**命令：**
```bash
kubectl describe redissentinel user-redis -n juwan
```

**关键输出：**
```yaml
API Version:  redis.redis.opstreelabs.in/v1beta2
Kind:         RedisSentinel
Metadata:
  Creation Timestamp:  2026-02-22T12:41:47Z
  Finalizers:
    redisSentinelFinalizer
  Generation:        2
Spec:
  Cluster Size:  3
  Redis Sentinel Config:
    Redis Replication Name:   user-redis  # ⚠️ 关键配置
    Master Group Name:        myMaster
    Redis Port:               6379
    Quorum:                   2
Events:  <none>  # ⚠️ 没有任何事件
```

**分析：**
- 配置中引用了 `redisReplicationName: user-redis`
- 没有任何 Events，说明 Operator 可能在等待某些前置条件
- **需要进一步检查依赖资源**

---

### 步骤 3：检查 Secret 资源

**目的：** 确认 RedisSentinel 所需的密码 Secret 是否存在

**命令：**
```bash
kubectl get secret user-redis -n juwan
```

**输出：**
```
NAME         TYPE     DATA   AGE
user-redis   Opaque   1      5h48m
```

**分析：**
- ✅ Secret 存在，排除认证配置问题

---

### 步骤 4：查找 Redis Pods

**目的：** 确认是否有 Redis Pod 使用不同的命名规则

**命令：**
```bash
kubectl get pods -n juwan | Select-String redis
```

**输出：**
```
(空输出 - 没有找到任何 Redis Pod)
```

**分析：**
- ❌ 确认没有任何 Redis Pod 被创建

---

### 步骤 5：检查 StatefulSet

**目的：** Redis 通常使用 StatefulSet 部署，检查是否有相关资源

**命令：**
```bash
kubectl get statefulset -n juwan
```

**输出：**
```
No resources found in juwan namespace.
```

**分析：**
- ❌ 没有 StatefulSet 被创建
- **结论：** Operator 完全没有执行创建动作

---

### 步骤 6：检查 Redis Operator 状态

**目的：** 确认 Redis Operator 本身是否正常运行

**命令：**
```bash
kubectl get pods -A | Select-String redis-operator
```

**输出：**
```
default        redis-operator-7dcf4468c9-gfbcm     1/1     Running        0        4h50m
ot-operators   redis-operator-69bb645f5b-6kxkv     0/1     ErrImageNeverPull   0   4h48m
```

**分析：**
- ✅ default namespace 中的 Operator 正常运行
- ⚠️ ot-operators namespace 中的 Operator 镜像拉取失败（但不影响我们的场景）

---

### 步骤 7：查看 Operator 日志

**目的：** 从 Operator 日志中寻找线索

**命令：**
```bash
kubectl logs redis-operator-7dcf4468c9-gfbcm -n default --tail=50
```

**关键输出：**
```json
{"level":"info","ts":"2026-02-22T08:01:56Z","msg":"Starting Controller","controller":"redissentinel"}
{"level":"info","ts":"2026-02-22T08:01:56Z","msg":"Starting workers","controller":"redissentinel","worker count":1}
```

**分析：**
- ✅ RedisSentinel Controller 已启动
- ✅ 没有错误日志
- ❌ 但也没有处理 user-redis 资源的日志
- **推测：** Operator 在等待某个依赖资源

---

### 步骤 8：检查 RedisReplication 资源（关键发现）

**目的：** 根据 RedisSentinel 配置中的 `redisReplicationName: user-redis`，检查对应的 RedisReplication 是否存在

**命令：**
```bash
kubectl get redisreplication -n juwan
```

**输出：**
```
No resources found in juwan namespace.
```

**分析：**
- ❌ **RedisReplication 资源不存在！**
- 🔎 **这就是问题的根本原因**

---

## 💡 根因分析

### 问题根源

**RedisSentinel 依赖 RedisReplication，但配置中只创建了 RedisSentinel，没有创建 RedisReplication。**

### Redis Operator 架构理解

在 OpsTree Redis Operator 中，资源之间的关系如下：

```
┌─────────────────────────────────────────┐
│        RedisSentinel (哨兵层)            │
│  - 3个 Sentinel 节点                     │
│  - 负责监控和自动故障转移                 │
│  - 引用: redisReplicationName           │
└──────────────┬──────────────────────────┘
               │ 监控
               ↓
┌─────────────────────────────────────────┐
│     RedisReplication (数据层)            │
│  - 1个 Master + N个 Replica              │
│  - 提供实际的缓存服务                     │
│  - 主从复制                              │
└─────────────────────────────────────────┘
```

### 错误配置的问题

原始配置直接创建了 RedisSentinel，但：

1. **缺少被监控对象：** Sentinel 需要监控一个 RedisReplication 集群
2. **引用不存在的资源：** `redisReplicationName: user-redis` 指向一个不存在的 RedisReplication
3. **Operator 行为：** Operator 发现依赖的 RedisReplication 不存在，因此不会创建 Sentinel Pod

### 为什么没有错误提示？

- CRD 验证只检查语法和字段类型
- 资源引用关系由 Operator 运行时检查
- Operator 采用了"等待依赖"策略，而不是报错

---

## ✅ 解决方案

### 正确的部署顺序

1. **先创建 RedisReplication**（建立 Redis 主从复制集群）
2. **再创建 RedisSentinel**（监控上述复制集群）

### 配置结构

```yaml
# 第一步：创建 Redis 主从复制（数据层）
apiVersion: redis.redis.opstreelabs.in/v1beta2
kind: RedisReplication
metadata:
  name: user-redis  # Sentinel 将引用这个名称
  namespace: juwan
spec:
  clusterSize: 3  # 1 Master + 2 Replicas
  kubernetesConfig:
    image: quay.io/opstree/redis:v7.0.12
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    redisSecret:
      name: user-redis
      key: password
  storage:
    volumeClaimTemplate:
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi  # 每个 Redis 节点 1GB 存储

---
# 第二步：创建 Sentinel 监控（监控层）
apiVersion: redis.redis.opstreelabs.in/v1beta2
kind: RedisSentinel
metadata:
  name: user-redis-sentinel  # 使用不同的名称避免混淆
  namespace: juwan
spec:
  clusterSize: 3  # 3个 Sentinel 节点（推荐奇数）
  kubernetesConfig:
    image: quay.io/opstree/redis-sentinel:v7.0.12  # 使用 Sentinel 专用镜像
  redisSentinelConfig:
    redisReplicationName: user-redis  # 引用上面的 RedisReplication
    masterGroupName: mymaster
    quorum: "2"  # 需要 2 个 Sentinel 同意才能进行故障转移
```

---

## 🔧 修复步骤

### 步骤 1：删除错误的 RedisSentinel 资源

**命令：**
```bash
kubectl delete redissentinel user-redis -n juwan
```

**输出：**
```
redissentinel.redis.redis.opstreelabs.in "user-redis" deleted
```

**说明：** 删除仅创建了 CRD 实例但未创建实际 Pod 的资源

---

### 步骤 2：更新配置文件

修改 `deploy/k8s/service/user/user-rpc.yaml`，将单独的 RedisSentinel 替换为：
1. RedisReplication（数据层）
2. RedisSentinel（监控层）

**变更内容：**
- 添加 `RedisReplication` 资源定义
- 添加 `storage.volumeClaimTemplate` 配置
- 修改 RedisSentinel 的 `metadata.name` 为 `user-redis-sentinel`
- 使用正确的 Sentinel 镜像：`quay.io/opstree/redis-sentinel:v7.0.12`
- 完善 Sentinel 配置参数

---

### 步骤 3：应用更新后的配置

**命令：**
```bash
kubectl apply -f .\deploy\k8s\service\user\user-rpc.yaml
```

**输出：**
```
deployment.apps/user-rpc configured
service/user-rpc-svc unchanged
horizontalpodautoscaler.autoscaling/user-rpc-hpa-c unchanged
horizontalpodautoscaler.autoscaling/user-rpc-hpa-m unchanged
redisreplication.redis.redis.opstreelabs.in/user-redis created  ✅
redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created  ✅
cluster.postgresql.cnpg.io/user-db unchanged
```

**分析：**
- ✅ RedisReplication 成功创建
- ✅ RedisSentinel 成功创建
- 🎯 两个资源都是新创建（created），符合预期

---

## ✅ 验证结果

### 验证 1：检查 Pod 创建情况（等待 30 秒）

**命令：**
```bash
kubectl get statefulset,pods -n juwan | Select-String -Pattern "user-redis|NAME"
```

**输出：**
```
NAME                                            READY   AGE
statefulset.apps/user-redis                     3/3     81s   ✅
statefulset.apps/user-redis-sentinel-sentinel   3/3     24s   ✅

NAME                                 READY   STATUS    RESTARTS   AGE
pod/user-redis-0                     2/2     Running   0          80s   ✅
pod/user-redis-1                     2/2     Running   0          52s   ✅
pod/user-redis-2                     2/2     Running   0          47s   ✅
pod/user-redis-sentinel-sentinel-0   1/1     Running   0          24s   ✅
pod/user-redis-sentinel-sentinel-1   1/1     Running   0          8s    ✅
pod/user-redis-sentinel-sentinel-2   1/1     Running   0          5s    ✅
```

**分析：**
- ✅ **RedisReplication** 创建了 3 个 Pod（user-redis-0/1/2）
  - 每个 Pod 有 2 个容器（2/2）：Redis + Exporter
  - 所有 Pod 处于 Running 状态
- ✅ **RedisSentinel** 创建了 3 个 Pod（user-redis-sentinel-sentinel-0/1/2）
  - 每个 Pod 有 1 个容器（1/1）：Sentinel
  - 所有 Pod 处于 Running 状态
- ✅ 创建了 2 个 StatefulSet，READY 状态为 3/3

---

### 验证 2：检查 Service 资源

**命令：**
```bash
kubectl get svc -n juwan | Select-String -Pattern "redis|NAME"
```

**输出：**
```
NAME                                      TYPE        CLUSTER-IP       PORT(S)             AGE
user-redis                                ClusterIP   10.103.91.84     6379/TCP,9121/TCP   95s   ✅
user-redis-additional                     ClusterIP   10.107.228.48    6379/TCP            95s
user-redis-headless                       ClusterIP   None             6379/TCP            95s   ✅
user-redis-master                         ClusterIP   10.97.120.76     6379/TCP            95s   ✅
user-redis-replica                        ClusterIP   10.100.213.103   6379/TCP            95s   ✅
user-redis-sentinel-sentinel              ClusterIP   10.105.28.231    26379/TCP           40s   ✅
user-redis-sentinel-sentinel-additional   ClusterIP   10.97.111.42     26379/TCP           39s
user-redis-sentinel-sentinel-headless     ClusterIP   None             26379/TCP           41s
```

**Service 功能说明：**

#### Redis 数据层 Service（端口 6379）
- **user-redis-master**: 主节点服务，用于写操作
- **user-redis-replica**: 从节点服务，用于读操作
- **user-redis**: 通用访问入口（负载均衡到所有节点）
- **user-redis-headless**: 无头服务，用于 StatefulSet Pod 间通信
- **user-redis-additional**: 额外的访问入口

#### Sentinel 监控层 Service（端口 26379）
- **user-redis-sentinel-sentinel**: Sentinel 访问入口
- **user-redis-sentinel-sentinel-headless**: Sentinel 节点间通信
- **user-redis-sentinel-sentinel-additional**: 额外的 Sentinel 访问入口

---

### 验证 3：检查完整的集群状态

**命令：**
```bash
kubectl get all -n juwan
```

**最终状态统计：**

| 资源类型 | 名称 | 数量 | 状态 |
|---------|------|------|------|
| **Deployment** | user-api | 3/3 | ✅ Running |
| **Deployment** | user-rpc | 3/3 | ✅ Running |
| **StatefulSet** | cluster-example (PostgreSQL) | 3/3 | ✅ Running |
| **StatefulSet** | user-db (PostgreSQL) | 3/3 | ✅ Running |
| **StatefulSet** | user-redis (Redis 数据) | 3/3 | ✅ Running |
| **StatefulSet** | user-redis-sentinel-sentinel | 3/3 | ✅ Running |

**Pod 总计：** 18 个（全部 Running）
**Service 总计：** 13 个
**HPA 总计：** 6 个

---

## 📊 架构图

### 部署后的 Redis 架构

```
┌────────────────────────────────────────────────────────────┐
│                    应用层 (user-rpc)                        │
│                                                            │
│              [需要添加 Redis 连接配置]                       │
└──────────┬─────────────────────────────┬───────────────────┘
           │                             │
           │ 写操作                       │ 读操作
           ↓                             ↓
    ┌─────────────┐              ┌─────────────┐
    │ user-redis- │              │ user-redis- │
    │   master    │              │   replica   │
    │ Service     │              │  Service    │
    └─────────────┘              └─────────────┘
           │                             │
           └──────────┬──────────────────┘
                      ↓
    ┌──────────────────────────────────────────┐
    │      RedisReplication (数据层)            │
    │                                          │
    │   ┌──────────┐  ┌──────────┐  ┌───────┐ │
    │   │ Master   │→ │ Replica  │→ │Replica│ │
    │   │ redis-0  │  │ redis-1  │  │redis-2│ │
    │   └──────────┘  └──────────┘  └───────┘ │
    └──────────────────────────────────────────┘
                      ↑
                      │ 监控 & 故障转移
                      │
    ┌──────────────────────────────────────────┐
    │     RedisSentinel (监控层)               │
    │                                          │
    │   ┌──────────┐  ┌──────────┐  ┌───────┐ │
    │   │Sentinel-0│  │Sentinel-1│  │Sentinel-2│
    │   └──────────┘  └──────────┘  └───────┘ │
    │                                          │
    │   Quorum: 2/3 (多数派决策)                │
    └──────────────────────────────────────────┘
```

---

## 📝 后续建议

### 1. 应用集成 Redis

user-rpc 服务目前还没有配置 Redis 连接，需要：

#### 修改配置文件 `app/users/rpc/etc/pb.yaml`
```yaml
Name: pb.rpc
ListenOn: 0.0.0.0:8080

# 添加 Redis 配置（使用 Sentinel 模式）
Redis:
  - Host: user-redis-sentinel-sentinel:26379
    Type: sentinel
    MasterName: mymaster
    Pass: ${REDIS_PASSWORD}

# 或使用主从模式
# Redis:
#   - Host: user-redis-master:6379  # 写
#     Type: node
#     Pass: ${REDIS_PASSWORD}
#   - Host: user-redis-replica:6379  # 读
#     Type: node
#     Pass: ${REDIS_PASSWORD}

Etcd:
  Hosts:
  - etcd-service:2379  # 需要配置实际的 Etcd 地址
  Key: pb.rpc
```

#### 修改 Config 结构 `app/users/rpc/internal/config/config.go`
```go
package config

import (
    "github.com/zeromicro/go-zero/core/stores/redis"
    "github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
    zrpc.RpcServerConf
    Redis redis.RedisConf  // 添加 Redis 配置
}
```

#### 初始化 Redis 客户端 `app/users/rpc/internal/svc/serviceContext.go`
```go
package svc

import (
    "github.com/zeromicro/go-zero/core/stores/redis"
    "juwan-backend/app/users/rpc/internal/config"
)

type ServiceContext struct {
    Config config.Config
    Redis  *redis.Redis  // 添加 Redis 客户端
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config: c,
        Redis:  redis.MustNewRedis(c.Redis),  // 初始化 Redis
    }
}
```

#### 更新 Deployment 环境变量
```yaml
# deploy/k8s/service/user/user-rpc.yaml
env:
  - name: DB_URI
    valueFrom:
      secretKeyRef:
        name: user-db-app
        key: uri
  - name: REDIS_PASSWORD  # 添加 Redis 密码
    valueFrom:
      secretKeyRef:
        name: user-redis
        key: password
```

---

### 2. Redis 性能监控

已启用 Redis Exporter（端口 9121），可以配置 Prometheus 监控：

```yaml
apiVersion: v1
kind: ServiceMonitor
metadata:
  name: user-redis-metrics
  namespace: juwan
spec:
  selector:
    matchLabels:
      app: user-redis
  endpoints:
    - port: redis-exporter
      interval: 30s
```

**监控指标：**
- redis_up: 实例状态
- redis_connected_clients: 连接数
- redis_memory_used_bytes: 内存使用
- redis_commands_processed_total: 命令处理数
- redis_master_repl_offset: 复制偏移量

---

### 3. 高可用性测试

#### 测试主节点故障转移
```bash
# 1. 查找当前主节点
kubectl exec -it user-redis-sentinel-sentinel-0 -n juwan -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

# 2. 模拟主节点故障
kubectl delete pod user-redis-0 -n juwan

# 3. 观察 Sentinel 的故障转移过程
kubectl logs -f user-redis-sentinel-sentinel-0 -n juwan

# 4. 确认新主节点
kubectl exec -it user-redis-sentinel-sentinel-0 -n juwan -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
```

#### 预期结果
- Sentinel 检测到主节点下线（5 秒）
- 2/3 Sentinel 节点达成共识（quorum=2）
- 自动提升一个从节点为主节点
- 客户端自动重连到新主节点

---

### 4. 扩展为分片集群（未来）

当缓存数据量增长需要横向扩展时，可以迁移到 RedisCluster：

```yaml
apiVersion: redis.redis.opstreelabs.in/v1beta2
kind: RedisCluster
metadata:
  name: user-redis-cluster
  namespace: juwan
spec:
  clusterSize: 6  # 3 主 + 3 从
  kubernetesConfig:
    image: quay.io/opstree/redis:v7.0.12
  redisLeader:
    replicas: 3
  redisFollower:
    replicas: 3
  storage:
    volumeClaimTemplate:
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 5Gi
```

**迁移步骤：**
1. 部署新的 RedisCluster
2. 使用 redis-cli --cluster import 迁移数据
3. 更新应用配置指向新集群
4. 下线旧的 Sentinel 集群

---

### 5. 备份策略

Redis Operator 不提供自动备份，建议配置定时任务：

```bash
# 创建 CronJob 定期执行 BGSAVE
apiVersion: batch/v1
kind: CronJob
metadata:
  name: redis-backup
  namespace: juwan
spec:
  schedule: "0 2 * * *"  # 每天凌晨 2 点
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: redis:7.0.12
            command:
            - /bin/sh
            - -c
            - |
              redis-cli -h user-redis-master -a $REDIS_PASSWORD BGSAVE
              # 将 /data/dump.rdb 上传到对象存储
          restartPolicy: OnFailure
```

---

## 📚 总结

### 关键经验

1. **理解资源依赖关系：** RedisSentinel 依赖 RedisReplication，部署顺序很重要
2. **资源命名规范：** 使用清晰的名称区分不同层次的资源（如 user-redis 和 user-redis-sentinel）
3. **诊断思路：**
   - 从现象（Pod 缺失）→ 资源状态（CRD 存在）→ Operator 日志 → 依赖检查
   - 逐层排查，最终定位到 RedisReplication 缺失
4. **验证完整性：** 不仅要检查 Pod，还要验证 Service、StatefulSet 等所有相关资源

### 文档价值

本文档可用于：
- ✅ 团队知识传承
- ✅ 类似问题的快速排查手册
- ✅ 新成员的 Redis Operator 学习资料
- ✅ 事后复盘和经验总结

---

**最后更新时间：** 2026年2月22日  
**文档状态：** ✅ 问题已解决，Redis 集群运行正常  
**下一步行动：** 配置应用连接 Redis
