Skip to content

Commit e7eb44a

Browse files
Copilotjaehanbyun
andcommitted
Add custom redis.conf support to RedisCluster CRD
Co-authored-by: jaehanbyun <80397512+jaehanbyun@users.noreply.github.com>
1 parent 1fda9ed commit e7eb44a

File tree

9 files changed

+232
-10
lines changed

9 files changed

+232
-10
lines changed

api/v1beta1/rediscluster_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type RedisClusterSpec struct {
5656
Affinity *corev1.Affinity `json:"affinity,omitempty"`
5757
Persistence *PersistenceSpec `json:"persistence,omitempty"`
5858
Exporter *ExporterSpec `json:"exporter,omitempty"`
59+
RedisConfig map[string]string `json:"redisConfig,omitempty"`
5960
}
6061

6162
// RedisClusterStatus defines the observed state of RedisCluster

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/redis-operator/crds/redis.redis_redisclusters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,10 @@ spec:
988988
required:
989989
- enabled
990990
type: object
991+
redisConfig:
992+
additionalProperties:
993+
type: string
994+
type: object
991995
replicas:
992996
format: int32
993997
type: integer

charts/redis-operator/templates/rediscluster.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ spec:
4848
affinity:
4949
{{- toYaml .Values.redisCluster.affinity | nindent 4 }}
5050
{{- end }}
51+
{{- if .Values.redisCluster.redisConfig }}
52+
redisConfig:
53+
{{- toYaml .Values.redisCluster.redisConfig | nindent 4 }}
54+
{{- end }}
5155
exporter:
5256
enabled: {{ .Values.redisCluster.exporter.enabled }}
5357
image: {{ .Values.redisCluster.exporter.image }}:{{ .Values.redisCluster.exporter.tag }}

charts/redis-operator/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ redisCluster:
8383
# cpu: 100m
8484
# memory: 128Mi
8585

86+
# Custom redis.conf configuration
87+
# Each key-value pair will be added to redis.conf
88+
# Example:
89+
# redisConfig:
90+
# maxmemory-policy: "allkeys-lru"
91+
# timeout: "300"
92+
# tcp-keepalive: "60"
93+
redisConfig: {}
94+
8695
# persistence:
8796
# enabled: true
8897
# storageClass: ""

config/crd/bases/redis.redis_redisclusters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,10 @@ spec:
988988
required:
989989
- enabled
990990
type: object
991+
redisConfig:
992+
additionalProperties:
993+
type: string
994+
type: object
991995
replicas:
992996
format: int32
993997
type: integer

config/samples/redis_v1beta1_rediscluster.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,17 @@ metadata:
99
app.kubernetes.io/created-by: redis-operator
1010
name: rediscluster-sample
1111
spec:
12-
# TODO(user): Add fields here
12+
image: redis
13+
tag: "7.0"
14+
masters: 3
15+
replicas: 1
16+
basePort: 10000
17+
maxMemory: "512mb"
18+
# Custom redis.conf configuration (optional)
19+
# redisConfig:
20+
# maxmemory-policy: "allkeys-lru"
21+
# timeout: "300"
22+
# tcp-keepalive: "60"
23+
# save: "900 1 300 10"
24+
exporter:
25+
enabled: true
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apiVersion: redis.redis/v1beta1
2+
kind: RedisCluster
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: rediscluster
6+
app.kubernetes.io/instance: rediscluster-custom-config
7+
app.kubernetes.io/part-of: redis-operator
8+
app.kubernetes.io/managed-by: kustomize
9+
app.kubernetes.io/created-by: redis-operator
10+
name: rediscluster-custom-config
11+
spec:
12+
image: redis
13+
tag: "7.0"
14+
masters: 3
15+
replicas: 1
16+
basePort: 10000
17+
maxMemory: "512mb"
18+
# Custom redis.conf configuration
19+
# These key-value pairs will be written to redis.conf
20+
# Essential cluster settings (port, cluster-enabled, etc.) are automatically added if not specified
21+
redisConfig:
22+
maxmemory-policy: "allkeys-lru"
23+
timeout: "300"
24+
tcp-keepalive: "60"
25+
save: "900 1 300 10"
26+
appendonly: "yes"
27+
appendfsync: "everysec"
28+
exporter:
29+
enabled: true

k8sutils/utils.go

Lines changed: 160 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,51 @@ func ExtractPortFromPodName(podName string) int32 {
6565
return int32(port)
6666
}
6767

68+
// CreateOrUpdateRedisConfigMap creates or updates the ConfigMap for custom redis configuration
69+
func CreateOrUpdateRedisConfigMap(ctx context.Context, k8scl kubernetes.Interface, redisCluster *redisv1beta1.RedisCluster, logger logr.Logger, port int32) error {
70+
if len(redisCluster.Spec.RedisConfig) == 0 {
71+
return nil // No custom config, skip ConfigMap creation
72+
}
73+
74+
configMap := GenerateRedisConfigMap(redisCluster, port)
75+
configMapName := configMap.Name
76+
77+
// Try to get existing ConfigMap
78+
existingConfigMap, err := k8scl.CoreV1().ConfigMaps(redisCluster.Namespace).Get(ctx, configMapName, metav1.GetOptions{})
79+
if err != nil {
80+
if errors.IsNotFound(err) {
81+
// Create new ConfigMap
82+
_, err = k8scl.CoreV1().ConfigMaps(redisCluster.Namespace).Create(ctx, configMap, metav1.CreateOptions{})
83+
if err != nil {
84+
logger.Error(err, "Failed to create ConfigMap", "ConfigMap", configMapName)
85+
return err
86+
}
87+
logger.Info("ConfigMap created successfully", "ConfigMap", configMapName)
88+
return nil
89+
}
90+
logger.Error(err, "Failed to get ConfigMap", "ConfigMap", configMapName)
91+
return err
92+
}
93+
94+
// Update existing ConfigMap
95+
existingConfigMap.Data = configMap.Data
96+
_, err = k8scl.CoreV1().ConfigMaps(redisCluster.Namespace).Update(ctx, existingConfigMap, metav1.UpdateOptions{})
97+
if err != nil {
98+
logger.Error(err, "Failed to update ConfigMap", "ConfigMap", configMapName)
99+
return err
100+
}
101+
logger.Info("ConfigMap updated successfully", "ConfigMap", configMapName)
102+
return nil
103+
}
104+
68105
// CreateMasterPod creates a Redis master Pod with the given port
69106
func CreateMasterPod(ctx context.Context, k8scl kubernetes.Interface, redisCluster *redisv1beta1.RedisCluster, logger logr.Logger, port int32) error {
107+
// Create or update ConfigMap if custom redis config is provided
108+
if err := CreateOrUpdateRedisConfigMap(ctx, k8scl, redisCluster, logger, port); err != nil {
109+
logger.Error(err, "Failed to create or update ConfigMap")
110+
return err
111+
}
112+
70113
podName := fmt.Sprintf("rediscluster-%s-%d", redisCluster.Name, port)
71114
masterPod := GenerateRedisPodDef(redisCluster, port, "")
72115
_, err := k8scl.CoreV1().Pods(redisCluster.Namespace).Create(ctx, masterPod, metav1.CreateOptions{})
@@ -100,6 +143,12 @@ func CreateMasterPod(ctx context.Context, k8scl kubernetes.Interface, redisClust
100143

101144
// CreateReplicaPod creates a Redis replica Pod attached to the specified master node
102145
func CreateReplicaPod(ctx context.Context, k8scl kubernetes.Interface, redisCluster *redisv1beta1.RedisCluster, logger logr.Logger, port int32, masterNodeID string) error {
146+
// Create or update ConfigMap if custom redis config is provided
147+
if err := CreateOrUpdateRedisConfigMap(ctx, k8scl, redisCluster, logger, port); err != nil {
148+
logger.Error(err, "Failed to create or update ConfigMap")
149+
return err
150+
}
151+
103152
podName := fmt.Sprintf("rediscluster-%s-%d", redisCluster.Name, port)
104153
replicaPod := GenerateRedisPodDef(redisCluster, port, masterNodeID)
105154
_, err := k8scl.CoreV1().Pods(redisCluster.Namespace).Create(ctx, replicaPod, metav1.CreateOptions{})
@@ -187,6 +236,79 @@ func UpdatePodLabelWithRedisID(ctx context.Context, k8scl kubernetes.Interface,
187236
return nil
188237
}
189238

239+
// GenerateRedisConfigMap creates a ConfigMap for custom redis configuration
240+
func GenerateRedisConfigMap(redisCluster *redisv1beta1.RedisCluster, port int32) *corev1.ConfigMap {
241+
configMapName := fmt.Sprintf("rediscluster-%s-config", redisCluster.Name)
242+
243+
// Build redis.conf content from redisConfig map
244+
var redisConf strings.Builder
245+
246+
// Add essential cluster configuration if not already present
247+
hasPort := false
248+
hasClusterEnabled := false
249+
hasClusterPort := false
250+
hasClusterNodeTimeout := false
251+
hasMaxMemory := false
252+
hasProtectedMode := false
253+
254+
for key, value := range redisCluster.Spec.RedisConfig {
255+
if key == "port" {
256+
hasPort = true
257+
}
258+
if key == "cluster-enabled" {
259+
hasClusterEnabled = true
260+
}
261+
if key == "cluster-port" {
262+
hasClusterPort = true
263+
}
264+
if key == "cluster-node-timeout" {
265+
hasClusterNodeTimeout = true
266+
}
267+
if key == "maxmemory" {
268+
hasMaxMemory = true
269+
}
270+
if key == "protected-mode" {
271+
hasProtectedMode = true
272+
}
273+
redisConf.WriteString(fmt.Sprintf("%s %s\n", key, value))
274+
}
275+
276+
// Add required cluster settings if not provided by user
277+
if !hasPort {
278+
redisConf.WriteString(fmt.Sprintf("port %d\n", port))
279+
}
280+
if !hasClusterEnabled {
281+
redisConf.WriteString("cluster-enabled yes\n")
282+
}
283+
if !hasClusterPort {
284+
redisConf.WriteString(fmt.Sprintf("cluster-port %d\n", port+10000))
285+
}
286+
if !hasClusterNodeTimeout {
287+
redisConf.WriteString("cluster-node-timeout 5000\n")
288+
}
289+
if !hasMaxMemory {
290+
redisConf.WriteString(fmt.Sprintf("maxmemory %s\n", redisCluster.Spec.Maxmemory))
291+
}
292+
if !hasProtectedMode {
293+
redisConf.WriteString("protected-mode no\n")
294+
}
295+
296+
configMap := &corev1.ConfigMap{
297+
ObjectMeta: metav1.ObjectMeta{
298+
Name: configMapName,
299+
Namespace: redisCluster.Namespace,
300+
OwnerReferences: []metav1.OwnerReference{
301+
*metav1.NewControllerRef(redisCluster, redisv1beta1.GroupVersion.WithKind("RedisCluster")),
302+
},
303+
},
304+
Data: map[string]string{
305+
"redis.conf": redisConf.String(),
306+
},
307+
}
308+
309+
return configMap
310+
}
311+
190312
// GenerateRedisPodDef generates a Pod definition for a Redis instance
191313
func GenerateRedisPodDef(redisCluster *redisv1beta1.RedisCluster, port int32, matchMasterNodeID string) *corev1.Pod {
192314
podName := fmt.Sprintf("rediscluster-%s-%d", redisCluster.Name, port)
@@ -196,6 +318,24 @@ func GenerateRedisPodDef(redisCluster *redisv1beta1.RedisCluster, port int32, ma
196318
image = fmt.Sprintf("%s:%s", redisCluster.Spec.Image, redisCluster.Spec.Tag)
197319
}
198320

321+
// Build redis-server command
322+
redisCommand := []string{"redis-server"}
323+
324+
// If custom redis config is provided, use ConfigMap mount
325+
if len(redisCluster.Spec.RedisConfig) > 0 {
326+
redisCommand = append(redisCommand, "/redis-config/redis.conf")
327+
} else {
328+
// Use default command-line arguments
329+
redisCommand = append(redisCommand,
330+
"--port", fmt.Sprintf("%d", port),
331+
"--cluster-enabled", "yes",
332+
"--cluster-port", fmt.Sprintf("%d", port+10000),
333+
"--cluster-node-timeout", "5000",
334+
"--maxmemory", redisCluster.Spec.Maxmemory,
335+
"--protected-mode", "no",
336+
)
337+
}
338+
199339
pod := &corev1.Pod{
200340
ObjectMeta: metav1.ObjectMeta{
201341
Name: podName,
@@ -223,15 +363,7 @@ func GenerateRedisPodDef(redisCluster *redisv1beta1.RedisCluster, port int32, ma
223363
Name: "redis-bus",
224364
},
225365
},
226-
Command: []string{
227-
"redis-server",
228-
"--port", fmt.Sprintf("%d", port),
229-
"--cluster-enabled", "yes",
230-
"--cluster-port", fmt.Sprintf("%d", port+10000),
231-
"--cluster-node-timeout", "5000",
232-
"--maxmemory", redisCluster.Spec.Maxmemory,
233-
"--protected-mode", "no",
234-
},
366+
Command: redisCommand,
235367
ReadinessProbe: GenerateRedisProbe(port),
236368
LivenessProbe: GenerateRedisProbe(port),
237369
},
@@ -277,6 +409,25 @@ func GenerateRedisPodDef(redisCluster *redisv1beta1.RedisCluster, port int32, ma
277409
pod.Spec.Containers[0].Resources = *redisCluster.Spec.Resources
278410
}
279411

412+
// Add ConfigMap volume and mount if custom redis config is provided
413+
if len(redisCluster.Spec.RedisConfig) > 0 {
414+
configMapName := fmt.Sprintf("rediscluster-%s-config", redisCluster.Name)
415+
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
416+
Name: "redis-config",
417+
VolumeSource: corev1.VolumeSource{
418+
ConfigMap: &corev1.ConfigMapVolumeSource{
419+
LocalObjectReference: corev1.LocalObjectReference{
420+
Name: configMapName,
421+
},
422+
},
423+
},
424+
})
425+
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
426+
Name: "redis-config",
427+
MountPath: "/redis-config",
428+
})
429+
}
430+
280431
// Add imagePullSecrets if specified
281432
if len(redisCluster.Spec.ImagePullSecrets) > 0 {
282433
pod.Spec.ImagePullSecrets = redisCluster.Spec.ImagePullSecrets

0 commit comments

Comments
 (0)