写在最前
在 Kubernetes 中部署 Redis 6 的 3 主 3 从的分片集群,真正的难点是 Redis Cluster 强依赖节点的固定 IP。Redis 在初始化时会把每个节点的 IP 写进集群元数据,一旦 IP 变化,主从关系就会失效,集群直接崩坏。而 Kubernetes 的 Pod IP 天然是不稳定的,即便使用 StatefulSet 与稳定名称,只要发生调度漂移,Redis 就会出现“IP 属于新节点,数据却来自旧节点 PVC”的矛盾,导致集群结构损坏。因此,StatefulSet + PVC 并不能解决问题,反而会制造混乱。
我们采用 Calico 的固定 IP 注解 cni.projectcalico.org/ipAddrs 为每个 Redis 节点分配稳定且可控的 Pod IP。
这种方式能够确保 Pod 在重启、漂移、节点重建等场景下仍能获得不变的 IP 地址,从而满足 Redis 集群对节点 IP 强一致性的要求。
在实现上,我们为 Redis 的每个节点分别创建 6 个独立的 Deployment,并为每个 Deployment 绑定一个对应的 PVC。随后,通过为每个 Deployment 添加cni.projectcalico.org/ipAddrs注解并设置唯一的固定 IP ,使其在 Calico 网络下始终分配相同的 IP。
由于需要为 Pod 分配固定 IP,因此 Deployment 必须使用 Recreate 更新策略。在更新过程中必须先删除旧 Pod,才能让 Calico 释放原有 IP 地址,从而确保新 Pod 可以顺利获取并占用指定的固定 IP。否则,若采用滚动更新方式,IP 地址将无法及时释放,导致新的 Pod 无法创建并出现卡住现象。
strategy:
type: Recreate1. docker 部署
2. kubernetes 部署
2.1 configmap
kind: ConfigMap
apiVersion: v1
metadata:
name: redis-config
namespace: default
annotations:
kubesphere.io/creator: admin
data:
redis.conf: |-
port 6379
bind 0.0.0.0
# 允许作为集群节点
cluster-enabled yes
# 节点的 cluster 配置文件(会自动创建)
cluster-config-file nodes.conf
# 超时时间
cluster-node-timeout 5000
# 开启 AOF
appendonly yes
# 自行变更,不可使用弱密码
requirepass 123456
masterauth 1234562.2 deployment
我们需要创建6个redis-cluster的Deployment与6个redis-cluster的pvc分别与之对应绑定,其中cni.projectcalico.org/ipAddrs: '["172.244.1.11"]' 为固定的容器ip,每个Deployment分别+1 例如 172.244.1.12,172.244.1.13直到172.244.1.16
redis-cluster-1
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-1
namespace: default
labels:
app: redis-cluster-1
annotations:
deployment.kubernetes.io/revision: '15'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-1
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-1
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.11"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-9xhdq0
persistentVolumeClaim:
claimName: redis-cluster-1
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-9xhdq0
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
redis-cluster-2
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-2
namespace: default
labels:
app: redis-cluster-2
annotations:
deployment.kubernetes.io/revision: '3'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-2
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-2
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.12"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-on3pb0
persistentVolumeClaim:
claimName: redis-cluster-2
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-on3pb0
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
redis-cluster-3
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-3
namespace: default
labels:
app: redis-cluster-3
annotations:
deployment.kubernetes.io/revision: '2'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-3
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-3
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.13"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-9zcket
persistentVolumeClaim:
claimName: redis-cluster-3
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-9zcket
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
redis-cluster-4
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-4
namespace: default
labels:
app: redis-cluster-4
annotations:
deployment.kubernetes.io/revision: '2'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-4
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-4
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.14"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-6vcmgq
persistentVolumeClaim:
claimName: redis-cluster-4
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-6vcmgq
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
redis-cluster-5
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-5
namespace: default
labels:
app: redis-cluster-5
annotations:
deployment.kubernetes.io/revision: '2'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-5
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-5
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.15"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-1z78qc
persistentVolumeClaim:
claimName: redis-cluster-5
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-1z78qc
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
redis-cluster-6
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis-cluster-6
namespace: default
labels:
app: redis-cluster-6
annotations:
deployment.kubernetes.io/revision: '2'
kubesphere.io/creator: admin
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-6
template:
metadata:
creationTimestamp: null
labels:
app: redis-cluster-6
annotations:
cni.projectcalico.org/ipAddrs: '["172.244.1.16"]'
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: '{}'
kubesphere.io/restartedAt: '2025-11-28T10:17:31.386Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
- name: redis-config
configMap:
name: redis-config
defaultMode: 420
- name: volume-px25j8
persistentVolumeClaim:
claimName: redis-cluster-6
containers:
- name: redis
image: 'redis:6.2.19'
command:
- redis-server
args:
- /etc/redis/redis.conf
ports:
- name: http-0
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf
- name: volume-px25j8
mountPath: /data
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirstWithHostNet
securityContext: {}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cluster
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
strategy:
type: Recreate
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
2.3 初始化集群
自行填写主机ip与redis密码
redis-cli -a xxxxxxxxxx \
--cluster create \
--cluster-replicas 1 \
172.244.1.11:6379 \
172.244.1.12:6379 \
172.244.1.13:6379 \
172.244.1.14:6379 \
172.244.1.15:6379 \
172.244.1.16:63792.4 service
创建6个service,其中我启用了nodeport自行决定自身是否开启。
app-redis1
kind: Service
apiVersion: v1
metadata:
name: app-redis1
namespace: default
labels:
app: app-redis1
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30031
selector:
app: redis-cluster-1
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
app-redis2
kind: Service
apiVersion: v1
metadata:
name: app-redis2
namespace: default
labels:
app: app-redis2
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30032
selector:
app: redis-cluster-2
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
app-redis3
kind: Service
apiVersion: v1
metadata:
name: app-redis3
namespace: default
labels:
app: app-redis3
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30033
selector:
app: redis-cluster-3
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
app-redis4
kind: Service
apiVersion: v1
metadata:
name: app-redis4
namespace: default
labels:
app: app-redis4
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30034
selector:
app: redis-cluster-4
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
app-redis5
kind: Service
apiVersion: v1
metadata:
name: app-redis5
namespace: default
labels:
app: app-redis5
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30035
selector:
app: redis-cluster-5
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
app-redis6
kind: Service
apiVersion: v1
metadata:
name: app-redis6
namespace: default
labels:
app: app-redis6
annotations:
kubesphere.io/creator: admin
spec:
ports:
- name: http-6379
protocol: TCP
port: 6379
targetPort: 6379
nodePort: 30036
selector:
app: redis-cluster-6
type: NodePort
sessionAffinity: None
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
internalTrafficPolicy: Cluster
评论