From 76d40b09e95dc7b3d718182e84f763c252608229 Mon Sep 17 00:00:00 2001 From: simonnorra-sap Date: Wed, 4 Feb 2026 08:36:10 +0100 Subject: [PATCH 01/20] fix: RedisCluster - make scale-out resilient by waiting for node convergence and blocking rebalance/add-node while slots are open (#1629) * Increased timeouts on data assert execution to eliminate flakes. Also increased resource quota of data assert as it sometimes ran into OOM issues. Signed-off-by: Simon Norra * RedisCluster: wait for node convergence and block rebalance/add-node on open slots to prevent CLUSTERDOWN during scale-out Signed-off-by: Simon Norra * fixed linting Signed-off-by: Simon Norra * Added artefacts from 'make codegen' Signed-off-by: Simon Norra --------- Signed-off-by: Simon Norra --- .../rediscluster/rediscluster_controller.go | 20 +- internal/k8sutils/cluster-scaling.go | 239 +++++++++ internal/k8sutils/cluster-scaling_test.go | 487 ++++++++++++++++++ .../redis-cluster-restart/chainsaw-test.yaml | 4 +- .../setup/redis-cluster/chainsaw-test.yaml | 2 +- .../v1beta2/setup/redis-ha/chainsaw-test.yaml | 6 +- .../redis-replication/chainsaw-test.yaml | 2 +- 7 files changed, 751 insertions(+), 9 deletions(-) diff --git a/internal/controller/rediscluster/rediscluster_controller.go b/internal/controller/rediscluster/rediscluster_controller.go index 48b3b9a37c..25b8777755 100644 --- a/internal/controller/rediscluster/rediscluster_controller.go +++ b/internal/controller/rediscluster/rediscluster_controller.go @@ -241,11 +241,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // Step 2 : Add Redis Node k8sutils.AddRedisNodeToCluster(ctx, r.K8sClient, instance) monitoring.RedisClusterAddingNodeAttempt.WithLabelValues(instance.Namespace, instance.Name).Inc() - // Step 3 Rebalance the cluster using the empty masters - k8sutils.RebalanceRedisClusterEmptyMasters(ctx, r.K8sClient, instance) + + return intctrlutil.RequeueAfter(ctx, 10*time.Second, "added node, waiting for cluster convergence before rebalancing") } } } else { + stable, err := k8sutils.ClusterStableNoOpenSlots(ctx, r.K8sClient, instance) + if err != nil { + return ctrl.Result{}, err + } + if !stable { + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + empty, err := k8sutils.ClusterHasEmptyMasters(ctx, r.K8sClient, instance) + if err != nil { + return ctrl.Result{}, err + } + if empty { + k8sutils.RebalanceRedisClusterEmptyMasters(ctx, r.K8sClient, instance) + } + if followerReplicas > 0 { logger.Info("All leader are part of the cluster, adding follower/replicas", "Leaders.Count", leaderCount, "Instance.Size", leaderReplicas, "Follower.Replicas", followerReplicas) k8sutils.ExecuteRedisReplicationCommand(ctx, r.K8sClient, instance) diff --git a/internal/k8sutils/cluster-scaling.go b/internal/k8sutils/cluster-scaling.go index c7fc8ca75d..ca78f5ecd8 100644 --- a/internal/k8sutils/cluster-scaling.go +++ b/internal/k8sutils/cluster-scaling.go @@ -6,6 +6,7 @@ import ( "net" "strconv" "strings" + "time" rcvb2 "github.com/OT-CONTAINER-KIT/redis-operator/api/rediscluster/v1beta2" redis "github.com/redis/go-redis/v9" @@ -13,6 +14,220 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) +// ClusterHasEmptyMasters returns true if the cluster contains at least one master +// with no slot ranges assigned. +// This is useful to decide if `redis-cli --cluster rebalance --cluster-use-empty-masters` +// should be attempted. +func ClusterHasEmptyMasters(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) (bool, error) { + seedPod := fmt.Sprintf("%s-leader-0", cr.Name) + redisClient := configureRedisClient(ctx, client, cr, seedPod) + defer redisClient.Close() + + nodes, err := clusterNodes(ctx, redisClient) + if err != nil { + return false, err + } + + return clusterHasEmptyMasters(nodes), nil +} + +func clusterHasEmptyMasters(nodes []clusterNodesResponse) bool { + for _, fields := range nodes { + // CLUSTER NODES format: + // 0:id 1:addr 2:flags 3:master 4:ping 5:pong 6:epoch 7:link [8+:slots...] + if len(fields) < 8 { + // malformed line; ignore rather than failing hard + continue + } + + flags := fields[2] + linkState := fields[7] + + if !hasFlag(flags, "master") { + continue + } + + // Ignore masters that are clearly not healthy participants + if hasAnyFlag(flags, "fail", "handshake", "noaddr") || linkState != "connected" { + continue + } + + // Slots are everything after index 7. + // If none exist, this master is "empty". + if len(fields) == 8 { + return true + } + + hasSlotToken := false + for _, tok := range fields[8:] { + if looksLikeSlotToken(tok) { + hasSlotToken = true + break + } + } + if !hasSlotToken { + return true + } + } + + return false +} + +func hasAnyFlag(flags string, wanted ...string) bool { + for _, w := range wanted { + if hasFlag(flags, w) { + return true + } + } + return false +} + +func hasFlag(flags string, flag string) bool { + // flags are comma-separated: "master", "myself,master", "slave", ... + for _, f := range strings.Split(flags, ",") { + if f == flag { + return true + } + } + return false +} + +func looksLikeSlotToken(tok string) bool { + // Very permissive on purpose. + // We only need to distinguish "some slot-related token exists" from "none". + if tok == "" { + return false + } + // migration markers are also slot-related + if strings.Contains(tok, "->-") || strings.Contains(tok, "-<-") { + return true + } + // common cases: "0-5460" or "5461" + if tok[0] >= '0' && tok[0] <= '9' { + return true + } + // bracketed forms: "[0-5460]" or "[5461->-...]" + if strings.HasPrefix(tok, "[") && len(tok) > 1 && tok[1] >= '0' && tok[1] <= '9' { + return true + } + return false +} + +// ClusterStableNoOpenSlots returns true if the cluster appears safe for +// disruptive operations like rebalance/reshard: +// - cluster_state == ok +// - no migrating/importing slot markers in CLUSTER NODES ("->-" / "-<-") +// - no nodes in handshake/fail/noaddr +// - (optional) cluster_slot_migration_active_tasks == 0 if present in CLUSTER INFO +func ClusterStableNoOpenSlots(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) (bool, error) { + seedPod := fmt.Sprintf("%s-leader-0", cr.Name) + redisClient := configureRedisClient(ctx, client, cr, seedPod) + defer redisClient.Close() + + info, err := redisClient.ClusterInfo(ctx).Result() + if err != nil { + return false, err + } + + nodes, err := clusterNodes(ctx, redisClient) + if err != nil { + return false, err + } + + return clusterStableNoOpenSlots(info, nodes), nil +} + +func clusterStableNoOpenSlots(clusterInfo string, nodes []clusterNodesResponse) bool { + // 1) cluster_state gate + kv := parseClusterInfo(clusterInfo) + if kv["cluster_state"] != "ok" { + return false + } + + // 2) migration-task gate (Redis 7 exposes these fields in your output) + if v, ok := kv["cluster_slot_migration_active_tasks"]; ok { + n, convErr := strconv.Atoi(strings.TrimSpace(v)) + if convErr == nil && n > 0 { + return false + } + } + + // 3) open slot + node health gate from CLUSTER NODES + for _, fields := range nodes { + if len(fields) < 8 { + // malformed line -> treat as not stable (conservative) + return false + } + + flags := fields[2] + linkState := fields[7] + + // If nodes are not fully established, don't rebalance. + if hasAnyFlag(flags, "handshake", "fail", "noaddr") || linkState != "connected" { + return false + } + } + + return !clusterHasOpenSlots(nodes) +} + +func parseClusterInfo(info string) map[string]string { + out := make(map[string]string) + for _, line := range strings.Split(info, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + out[parts[0]] = strings.TrimSpace(parts[1]) + } + return out +} + +func clusterHasOpenSlots(nodes []clusterNodesResponse) bool { + for _, fields := range nodes { + // Open slot markers appear in the slot tokens: + // "[5491->-...]" migrating / "[5491-<-...]" importing + for _, tok := range fields[8:] { + if strings.Contains(tok, "->-") || strings.Contains(tok, "-<-") { + return true + } + } + } + return false +} + +func waitForClusterNoOpenSlots(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster, timeout time.Duration) error { + redisClient := configureRedisClient(ctx, client, cr, cr.Name+"-leader-0") + defer redisClient.Close() + + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + info, err := redisClient.ClusterInfo(ctx).Result() + if err != nil || !strings.Contains(info, "cluster_state:ok") { + time.Sleep(2 * time.Second) + continue + } + + nodes, err := clusterNodes(ctx, redisClient) + if err != nil { + time.Sleep(2 * time.Second) + continue + } + + if clusterHasOpenSlots(nodes) { + time.Sleep(2 * time.Second) + continue + } + + return nil + } + return fmt.Errorf("cluster still has open slots or is not converged after %s", timeout) +} + // ReshardRedisCluster transfer the slots from the last node to the provided transfer node. // // NOTE: when all slot been transferred, the node become slave of the transfer node. @@ -129,6 +344,11 @@ func getRedisNodeID(ctx context.Context, client kubernetes.Interface, cr *rcvb2. // Rebalance the Redis CLuster using the Empty Master Nodes func RebalanceRedisClusterEmptyMasters(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) { + if err := waitForClusterNoOpenSlots(ctx, client, cr, 2*time.Minute); err != nil { + log.FromContext(ctx).Info("Skipping rebalance: cluster not ready", "reason", err.Error()) + return + } + // cmd = redis-cli --cluster rebalance : --cluster-use-empty-masters -a var cmd []string pod := RedisDetails{ @@ -197,6 +417,21 @@ func RebalanceRedisCluster(ctx context.Context, client kubernetes.Interface, cr executeCommand(ctx, client, cr, cmd, cr.Name+"-leader-1") } +func waitForNodePresence(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster, nodeIP string, timeout time.Duration) error { + redisClient := configureRedisClient(ctx, client, cr, cr.Name+"-leader-0") + defer redisClient.Close() + + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + nodes, err := clusterNodes(ctx, redisClient) + if err == nil && checkRedisNodePresence(ctx, nodes, nodeIP) { + return nil + } + time.Sleep(2 * time.Second) + } + return fmt.Errorf("node %s did not appear in cluster nodes after %s", nodeIP, timeout) +} + // Add redis cluster node would add a node to the existing redis cluster using redis-cli func AddRedisNodeToCluster(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) { cmd := []string{"redis-cli", "--cluster", "add-node"} @@ -223,6 +458,10 @@ func AddRedisNodeToCluster(ctx context.Context, client kubernetes.Interface, cr cmd = append(cmd, getRedisTLSArgs(cr.Spec.TLS, cr.Name+"-leader-0")...) executeCommand(ctx, client, cr, cmd, cr.Name+"-leader-0") + + if err := waitForNodePresence(ctx, client, cr, getRedisServerIP(ctx, client, newPod), 90*time.Second); err != nil { + log.FromContext(ctx).Error(err, "node added but not converged yet: %w") + } } // getAttachedFollowerNodeIDs would return a slice of redis followers attached to a redis leader diff --git a/internal/k8sutils/cluster-scaling_test.go b/internal/k8sutils/cluster-scaling_test.go index 69fa1fba00..c55fcad561 100644 --- a/internal/k8sutils/cluster-scaling_test.go +++ b/internal/k8sutils/cluster-scaling_test.go @@ -10,6 +10,493 @@ import ( "github.com/stretchr/testify/assert" ) +func Test_clusterHasEmptyMasters_WhenConnectedMasterHasNoSlots_ReturnsTrue(t *testing.T) { + nodes := []clusterNodesResponse{ + // master with slots assigned + { + "id-master-with-slots", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + // empty master (no slots) + { + "id-empty-master", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + "connected", + // NOTE: no slot tokens => len(fields) == 8 + }, + // a replica (should be ignored) + { + "id-replica", + "10.0.0.12:6379@16379", + "slave", + "id-master-with-slots", + "0", + "0", + "3", + "connected", + }, + } + + // Act: + got := clusterHasEmptyMasters(nodes) + + // Assert: + if !got { + t.Fatalf("expected true, got false") + } +} + +func Test_clusterHasEmptyMasters_skipMalformedNode(t *testing.T) { + nodes := []clusterNodesResponse{ + // master with slot token + { + "id-empty-master", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + // NOTE: missing linkState => len(fields) < 8 + }, + } + + // Act: + got := clusterHasEmptyMasters(nodes) + + // Assert: + if got { + t.Fatalf("expected false, got true") + } +} + +func Test_clusterHasEmptyMasters_skipFlags_ReturnsFalse(t *testing.T) { + tests := []struct { + name string + flags string + linkState string + }{ + { + name: "with non master flag", + flags: "bla", + linkState: "connected", + }, + { + name: "with master,fail flag", + flags: "master,fail", + linkState: "connected", + }, + { + name: "with master,handshake flag", + flags: "master,handshake", + linkState: "connected", + }, + { + name: "with master,noaddr flag", + flags: "master,noaddr", + linkState: "connected", + }, + { + name: "with non connected linkState", + flags: "master", + linkState: "disconnected", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodes := []clusterNodesResponse{ + // master with slot token + { + "id-empty-master", + "10.0.0.11:6379@16379", + tt.flags, + "-", + "0", + "0", + "2", + tt.linkState, + }, + } + + // Act: + got := clusterHasEmptyMasters(nodes) + + // Assert: + if got { + t.Fatalf("expected false, got true") + } + }) + } +} + +func Test_clusterHasEmptyMasters_ReturnsTrue(t *testing.T) { + tests := []struct { + name string + secondMasterSlots string + }{ + { + name: "with empty string", + secondMasterSlots: "", + }, + { + name: "with non slot value", + secondMasterSlots: "abc", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodes := []clusterNodesResponse{ + // master with slots assigned + { + "id-master-with-slots", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + // second master with slot token + { + "id-empty-master", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + "connected", + tt.secondMasterSlots, + }, + // a replica (should be ignored) + { + "id-replica", + "10.0.0.12:6379@16379", + "slave", + "id-master-with-slots", + "0", + "0", + "3", + "connected", + }, + } + + // Act: + got := clusterHasEmptyMasters(nodes) + + // Assert: + if !got { + t.Fatalf("expected true, got false") + } + }) + } +} + +func Test_clusterHasEmptyMasters_ReturnsFalse(t *testing.T) { + tests := []struct { + name string + expectedBool bool + secondMasterSlots string + }{ + { + name: "with slot (without brackets)", + secondMasterSlots: "5461", + }, { + name: "with slot (with brackets)", + secondMasterSlots: "[5461]", + }, { + name: "with migration markers", + secondMasterSlots: "[5461->-nodeid]", + }, { + name: "with migration markers", + secondMasterSlots: "[5461->-nodeid]", + }, { + name: "with import markers", + secondMasterSlots: "[5461-<-nodeid]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodes := []clusterNodesResponse{ + // master with slots assigned + { + "id-master-with-slots", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + // second master with slot token + { + "id-empty-master", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + "connected", + tt.secondMasterSlots, + }, + // a replica (should be ignored) + { + "id-replica", + "10.0.0.12:6379@16379", + "slave", + "id-master-with-slots", + "0", + "0", + "3", + "connected", + }, + } + + // Act: + got := clusterHasEmptyMasters(nodes) + + // Assert: + if got { + t.Fatalf("expected false, got true") + } + }) + } +} + +func Test_clusterStableNoOpenSlots_ReturnsTrue(t *testing.T) { + info := "cluster_state:ok\ncluster_slot_migration_active_tasks:0\n" + nodes := []clusterNodesResponse{ + { + "id-1", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + { + "id-2", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + "connected", + "5461-10922", + }, + { + "id-3", + "10.0.0.12:6379@16379", + "slave", + "id-1", + "0", + "0", + "3", + "connected", + }, + } + + if got := clusterStableNoOpenSlots(info, nodes); !got { + t.Fatalf("expected true, got false") + } +} + +// For redis 6 compatibility +func Test_clusterStableNoOpenSlots_missingMigrationActiveTask_ReturnsTrue(t *testing.T) { + info := "cluster_state:ok\n" + nodes := []clusterNodesResponse{ + { + "id-1", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + { + "id-2", + "10.0.0.11:6379@16379", + "master", + "-", + "0", + "0", + "2", + "connected", + "5461-10922", + }, + { + "id-3", + "10.0.0.12:6379@16379", + "slave", + "id-1", + "0", + "0", + "3", + "connected", + }, + } + + if got := clusterStableNoOpenSlots(info, nodes); !got { + t.Fatalf("expected true, got false") + } +} + +func Test_clusterStableNoOpenSlots_malformedNode_ReturnsFalse(t *testing.T) { + info := "cluster_state:ok\ncluster_slot_migration_active_tasks:0\n" + nodes := []clusterNodesResponse{ + { + "id-1", + "10.0.0.10:6379@16379", + "myself,master", + "-", + "0", + "0", + "1", + }, + } + + if got := clusterStableNoOpenSlots(info, nodes); got { + t.Fatalf("expected false, got true") + } +} + +func Test_clusterStableNoOpenSlots_ReturnsFalse(t *testing.T) { + tests := []struct { + name string + clusterState string + clusterSlotMigrationActiveTasks string + nodeFields string + linkState string + slots string + }{ + { + name: "cluster state no ok", + clusterState: "bad", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master", + linkState: "connected", + slots: "5461", + }, + { + name: "active migration tasks", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "2", + nodeFields: "master", + linkState: "connected", + slots: "5461", + }, + { + name: "handshake flag", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master,handshake", + linkState: "connected", + slots: "5461", + }, + { + name: "fail flag", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master,fail", + linkState: "connected", + slots: "5461", + }, + { + name: "noaddr flag", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master,noaddr", + linkState: "connected", + slots: "5461", + }, + { + name: "linkState not connected", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master", + linkState: "disconnected", + slots: "5461", + }, + { + name: "migrating slots", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master", + linkState: "connected", + slots: "[5491->-6000]", + }, + { + name: "importing slots", + clusterState: "ok", + clusterSlotMigrationActiveTasks: "0", + nodeFields: "master", + linkState: "connected", + slots: "[5491-<-6000]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + info := "cluster_state:" + tt.clusterState + "\ncluster_slot_migration_active_tasks:" + tt.clusterSlotMigrationActiveTasks + "\n" + + nodes := []clusterNodesResponse{ + { + "id-1", + "10.0.0.10:6379@16379", + "master", + "-", + "0", + "0", + "1", + "connected", + "0-5460", + }, + { + "id-2", + "10.0.0.11:6379@16379", + tt.nodeFields, + "-", + "0", + "0", + "2", + tt.linkState, + tt.slots, + }, + } + if got := clusterStableNoOpenSlots(info, nodes); got { + t.Fatalf("expected false, got true") + } + }) + } +} + func Test_verifyLeaderPodInfo(t *testing.T) { tests := []struct { name string diff --git a/tests/e2e-chainsaw/v1beta2/redis-cluster-restart/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/redis-cluster-restart/chainsaw-test.yaml index 7725612c90..b844d23786 100644 --- a/tests/e2e-chainsaw/v1beta2/redis-cluster-restart/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/redis-cluster-restart/chainsaw-test.yaml @@ -17,7 +17,7 @@ spec: - name: Put data try: - script: - timeout: 30s + timeout: 90s content: > kubectl exec --namespace ${NAMESPACE} --container data-assert data-assert -- bash -c "cd /go/src/data-assert && go run main.go gen-redis-data --host redis-cluster-v1beta2-leader.${NAMESPACE}.svc.cluster.local:6379 --mode cluster" @@ -44,7 +44,7 @@ spec: - name: Assert data try: - script: - timeout: 30s + timeout: 60s content: > kubectl exec --namespace ${NAMESPACE} --container data-assert data-assert -- bash -c "cd /go/src/data-assert && go run main.go chk-redis-data --host redis-cluster-v1beta2-leader.${NAMESPACE}.svc.cluster.local:6379 --mode cluster" diff --git a/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/chainsaw-test.yaml index 86154f8aee..2c82fcf967 100644 --- a/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/chainsaw-test.yaml @@ -121,7 +121,7 @@ spec: - name: Put data try: - script: - timeout: 30s + timeout: 90s content: > kubectl exec --namespace ${NAMESPACE} --container data-assert data-assert -- bash -c "cd /go/src/data-assert && go run main.go gen-redis-data --host redis-cluster-v1beta2-leader.${NAMESPACE}.svc.cluster.local:6379 --mode cluster --password Opstree1234 --tls" diff --git a/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml index beb8c7c934..c444ad28b7 100644 --- a/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml @@ -50,7 +50,7 @@ spec: - name: Test Master IP consistency try: - sleep: - duration: 60s + duration: 90s - script: timeout: 10s content: | @@ -64,7 +64,7 @@ spec: - name: Put data try: - script: - timeout: 30s + timeout: 90s content: > kubectl exec --namespace ${NAMESPACE} --container data-assert data-assert -- bash -c "cd /go/src/data-assert && go run main.go gen-redis-data \ @@ -86,7 +86,7 @@ spec: - name: Test Master IP consistency try: - sleep: - duration: 60s + duration: 90s - script: timeout: 10s content: | diff --git a/tests/e2e-chainsaw/v1beta2/setup/redis-replication/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/setup/redis-replication/chainsaw-test.yaml index 0801ec5e09..20bec1fc4b 100644 --- a/tests/e2e-chainsaw/v1beta2/setup/redis-replication/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/setup/redis-replication/chainsaw-test.yaml @@ -56,7 +56,7 @@ spec: - name: Put data try: - script: - timeout: 30s + timeout: 90s content: > kubectl exec --namespace ${NAMESPACE} --container data-assert data-assert -- bash -c "cd /go/src/data-assert && go run main.go gen-redis-data \ From 9299c997b3ff0d562584ed7ba458e7853eab9c5b Mon Sep 17 00:00:00 2001 From: naimadswdn <39454533+naimadswdn@users.noreply.github.com> Date: Thu, 5 Feb 2026 06:50:12 +0100 Subject: [PATCH 02/20] fix: Fix ACL SAVE when using ACL from PVC (#1645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redis always rewrites ACLs (and redis.conf, RDB, AOF) via the same pattern: write a temp file alongside the target, fsync, then rename(2) over the original. Because user.acl is mounted via subPath, Kubernetes turns that single file into its own bind mount. Bind mounts behave like a mini mount point, and Linux forbids rename(2) on a mount target—exactly what Redis tries to do during ACL SAVE (it writes tempfile + rename). The kernel therefore returns EBUSY, which surfaces as “Resource busy”. The PR is fixing this behavior, by mounting the PVC as directory under the /data/redis. Signed-off-by: Damian Seredyn Co-authored-by: Damian Seredyn --- api/common/v1beta2/common_types.go | 3 +- charts/redis-operator/crds/crds.yaml | 9 ++- .../redis.redis.opstreelabs.in_redis.yaml | 3 +- ...is.redis.opstreelabs.in_redisclusters.yaml | 3 +- ...edis.opstreelabs.in_redisreplications.yaml | 3 +- .../CRD Reference/API Reference/_index.md | 2 +- example/v1beta2/acl-pvc/cluster.yaml | 2 +- example/v1beta2/acl-pvc/replication.yaml | 2 +- example/v1beta2/acl-pvc/standalone.yaml | 2 +- internal/agent/bootstrap/redis/config.go | 7 +- internal/agent/bootstrap/sentinel/config.go | 7 +- internal/k8sutils/statefulset.go | 25 +++++-- internal/k8sutils/statefulset_test.go | 74 +++++++++++++++++-- .../acl-pvc/redis-cluster/chainsaw-test.yaml | 35 +++++++-- .../redis-replication/chainsaw-test.yaml | 4 +- .../redis-standalone/chainsaw-test.yaml | 2 +- 16 files changed, 147 insertions(+), 36 deletions(-) diff --git a/api/common/v1beta2/common_types.go b/api/common/v1beta2/common_types.go index ac9ef29b25..e1dfc519a5 100644 --- a/api/common/v1beta2/common_types.go +++ b/api/common/v1beta2/common_types.go @@ -288,7 +288,8 @@ type ACLConfig struct { Secret *corev1.SecretVolumeSource `json:"secret,omitempty"` // PersistentVolumeClaim-based ACL configuration // Specify the PVC name to mount ACL file from persistent storage - // The operator will automatically mount /etc/redis/user.acl from the PVC + // The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + // This feature requires the GenerateConfigInInitContainer feature gate to be enabled. PersistentVolumeClaim *string `json:"persistentVolumeClaim,omitempty"` } diff --git a/charts/redis-operator/crds/crds.yaml b/charts/redis-operator/crds/crds.yaml index d01f1b010a..50dea28562 100644 --- a/charts/redis-operator/crds/crds.yaml +++ b/charts/redis-operator/crds/crds.yaml @@ -117,7 +117,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- @@ -5544,7 +5545,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- @@ -13393,7 +13395,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml index a797eef580..51c8fe5540 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml @@ -118,7 +118,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml index 0ec9292dc4..d6e0bfd4f2 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml @@ -146,7 +146,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml index 38e0bdee7a..e7aff74439 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml @@ -124,7 +124,8 @@ spec: description: |- PersistentVolumeClaim-based ACL configuration Specify the PVC name to mount ACL file from persistent storage - The operator will automatically mount /etc/redis/user.acl from the PVC + The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl + This feature requires the GenerateConfigInInitContainer feature gate to be enabled. type: string secret: description: |- diff --git a/docs/content/en/docs/CRD Reference/API Reference/_index.md b/docs/content/en/docs/CRD Reference/API Reference/_index.md index da02d86642..a58b047657 100644 --- a/docs/content/en/docs/CRD Reference/API Reference/_index.md +++ b/docs/content/en/docs/CRD Reference/API Reference/_index.md @@ -43,7 +43,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `secret` _[SecretVolumeSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#secretvolumesource-v1-core)_ | Secret-based ACL configuration.
Adapts a Secret into a volume containing ACL rules.
The contents of the target Secret's Data field will be presented in a volume
as files using the keys in the Data field as the file names.
Secret volumes support ownership management and SELinux relabeling. | | | -| `persistentVolumeClaim` _string_ | PersistentVolumeClaim-based ACL configuration
Specify the PVC name to mount ACL file from persistent storage
The operator will automatically mount /etc/redis/user.acl from the PVC | | | +| `persistentVolumeClaim` _string_ | PersistentVolumeClaim-based ACL configuration
Specify the PVC name to mount ACL file from persistent storage
The operator mounts the PVC at /data/redis so Redis can read and update /data/redis/user.acl
This feature requires the GenerateConfigInInitContainer feature gate to be enabled. | | | #### AdditionalVolume diff --git a/example/v1beta2/acl-pvc/cluster.yaml b/example/v1beta2/acl-pvc/cluster.yaml index f85a5bf6b7..f417aeebb5 100644 --- a/example/v1beta2/acl-pvc/cluster.yaml +++ b/example/v1beta2/acl-pvc/cluster.yaml @@ -10,7 +10,7 @@ spec: image: quay.io/opstree/redis:latest imagePullPolicy: IfNotPresent # ACL configuration from PVC - # The operator will mount /etc/redis/user.acl from the PVC + # The operator mounts the PVC at /data/redis, so Redis reads /data/redis/user.acl # Make sure the PVC contains a file named "user.acl" with Redis ACL rules acl: persistentVolumeClaim: "redis-acl-pvc" diff --git a/example/v1beta2/acl-pvc/replication.yaml b/example/v1beta2/acl-pvc/replication.yaml index a61b624e06..230d01100b 100644 --- a/example/v1beta2/acl-pvc/replication.yaml +++ b/example/v1beta2/acl-pvc/replication.yaml @@ -10,7 +10,7 @@ spec: image: quay.io/opstree/redis:latest imagePullPolicy: IfNotPresent # ACL configuration from PVC - # The operator will mount /etc/redis/user.acl from the PVC + # The operator mounts the PVC at /data/redis, so Redis reads /data/redis/user.acl # Make sure the PVC contains a file named "user.acl" with Redis ACL rules acl: persistentVolumeClaim: "redis-acl-pvc" diff --git a/example/v1beta2/acl-pvc/standalone.yaml b/example/v1beta2/acl-pvc/standalone.yaml index fab429e18a..b6fe224388 100644 --- a/example/v1beta2/acl-pvc/standalone.yaml +++ b/example/v1beta2/acl-pvc/standalone.yaml @@ -9,7 +9,7 @@ spec: image: quay.io/opstree/redis:latest imagePullPolicy: IfNotPresent # ACL configuration from PVC - # The operator will mount /etc/redis/user.acl from the PVC + # The operator mounts the PVC at /data/redis, so Redis reads /data/redis/user.acl # Make sure the PVC contains a file named "user.acl" with Redis ACL rules acl: persistentVolumeClaim: "redis-acl-pvc" diff --git a/internal/agent/bootstrap/redis/config.go b/internal/agent/bootstrap/redis/config.go index 54a582728b..964e5dd51a 100644 --- a/internal/agent/bootstrap/redis/config.go +++ b/internal/agent/bootstrap/redis/config.go @@ -41,6 +41,8 @@ func GenerateConfig() error { nodeport = util.CoalesceEnv1("NODEPORT", "false") tlsMode = util.CoalesceEnv1("TLS_MODE", "false") clusterMode = util.CoalesceEnv1("SETUP_MODE", "standalone") + aclMode = util.CoalesceEnv1("ACL_MODE", "") + aclFilePath = util.CoalesceEnv1("ACL_FILE_PATH", "/etc/redis/user.acl") ) if val, ok := util.CoalesceEnv("REDIS_PASSWORD", ""); ok && val != "" { @@ -112,8 +114,9 @@ func GenerateConfig() error { fmt.Println("Running without TLS mode") } - if aclMode := util.CoalesceEnv1("ACL_MODE", ""); aclMode == "true" { - cfg.Append("aclfile", "/etc/redis/user.acl") + if aclMode == "true" { + fmt.Println("ACL_MODE is true, modifying ACL file path to", aclFilePath) + cfg.Append("aclfile", aclFilePath) } else { fmt.Println("ACL_MODE is not true, skipping ACL file modification") } diff --git a/internal/agent/bootstrap/sentinel/config.go b/internal/agent/bootstrap/sentinel/config.go index 52f3f8da6d..c95ecc2cb2 100644 --- a/internal/agent/bootstrap/sentinel/config.go +++ b/internal/agent/bootstrap/sentinel/config.go @@ -88,8 +88,11 @@ func GenerateConfig() error { // acl_setup { - if aclMode, ok := util.CoalesceEnv("ACL_MODE", ""); ok && aclMode == "true" { - cfg.Append("aclfile", "/etc/redis/user.acl") + aclMode, _ := util.CoalesceEnv("ACL_MODE", "") + aclFilePath, _ := util.CoalesceEnv("ACL_FILE_PATH", "/etc/redis/user.acl") + if aclMode == "true" { + fmt.Println("ACL_MODE is true, modifying ACL file path to", aclFilePath) + cfg.Append("aclfile", aclFilePath) } else { fmt.Println("ACL_MODE is not true, skipping ACL file modification") } diff --git a/internal/k8sutils/statefulset.go b/internal/k8sutils/statefulset.go index ef07df1b1f..1339d5c25d 100644 --- a/internal/k8sutils/statefulset.go +++ b/internal/k8sutils/statefulset.go @@ -800,15 +800,18 @@ func getVolumeMount(name string, persistenceEnabled *bool, clusterMode bool, nod } if aclConfig != nil { - volumeName := "acl-secret" if aclConfig.PersistentVolumeClaim != nil { - volumeName = "acl-pvc" + VolumeMounts = append(VolumeMounts, corev1.VolumeMount{ + Name: "acl-pvc", + MountPath: "/data/redis", + }) + } else { + VolumeMounts = append(VolumeMounts, corev1.VolumeMount{ + Name: "acl-secret", + MountPath: "/etc/redis/user.acl", + SubPath: "user.acl", + }) } - VolumeMounts = append(VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: "/etc/redis/user.acl", - SubPath: "user.acl", - }) } if externalConfig != nil { @@ -917,6 +920,14 @@ func getEnvironmentVariables(role string, enabledPassword *bool, secretName *str Name: "ACL_MODE", Value: "true", }) + aclFilePath := "/etc/redis/user.acl" + if aclConfig.PersistentVolumeClaim != nil { + aclFilePath = "/data/redis/user.acl" + } + envVars = append(envVars, corev1.EnvVar{ + Name: "ACL_FILE_PATH", + Value: aclFilePath, + }) } envVars = append(envVars, corev1.EnvVar{ diff --git a/internal/k8sutils/statefulset_test.go b/internal/k8sutils/statefulset_test.go index 35d2b7fc5e..e146952e98 100644 --- a/internal/k8sutils/statefulset_test.go +++ b/internal/k8sutils/statefulset_test.go @@ -191,15 +191,34 @@ func TestGetVolumeMount(t *testing.T) { expectedMounts: []corev1.VolumeMount{{Name: "tls-certs", MountPath: "/tls", ReadOnly: true}}, }, { - name: "6. Only acl enabled", + name: "6. Only acl enabled (secret)", persistenceEnabled: nil, clusterMode: false, nodeConfVolume: false, externalConfig: nil, mountpath: []corev1.VolumeMount{}, tlsConfig: nil, - aclConfig: &common.ACLConfig{}, - expectedMounts: []corev1.VolumeMount{{Name: "acl-secret", MountPath: "/etc/redis/user.acl", SubPath: "user.acl"}}, + aclConfig: &common.ACLConfig{ + Secret: &corev1.SecretVolumeSource{SecretName: "acl-secret"}, + }, + expectedMounts: []corev1.VolumeMount{ + {Name: "acl-secret", MountPath: "/etc/redis/user.acl", SubPath: "user.acl"}, + }, + }, + { + name: "6b. Only acl enabled (PVC)", + persistenceEnabled: nil, + clusterMode: false, + nodeConfVolume: false, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: &common.ACLConfig{ + PersistentVolumeClaim: ptr.To("acl-pvc"), + }, + expectedMounts: []corev1.VolumeMount{ + {Name: "acl-pvc", MountPath: "/data/redis"}, + }, }, { name: "7. Everything enabled except externalConfig", @@ -214,7 +233,9 @@ func TestGetVolumeMount(t *testing.T) { }, }, tlsConfig: &common.TLSConfig{}, - aclConfig: &common.ACLConfig{}, + aclConfig: &common.ACLConfig{ + Secret: &corev1.SecretVolumeSource{SecretName: "acl-secret"}, + }, expectedMounts: []corev1.VolumeMount{ {Name: "persistent-volume", MountPath: "/data"}, {Name: "node-conf", MountPath: "/node-conf"}, @@ -242,7 +263,9 @@ func TestGetVolumeMount(t *testing.T) { externalConfig: nil, mountpath: []corev1.VolumeMount{}, tlsConfig: nil, - aclConfig: &common.ACLConfig{}, + aclConfig: &common.ACLConfig{ + Secret: &corev1.SecretVolumeSource{SecretName: "acl-secret"}, + }, expectedMounts: []corev1.VolumeMount{ {Name: "persistent-volume", MountPath: "/data"}, {Name: "node-conf", MountPath: "/node-conf"}, @@ -1499,6 +1522,7 @@ func TestGetEnvironmentVariables(t *testing.T) { }, clusterVersion: ptr.To("v6"), expectedEnvironment: []corev1.EnvVar{ + {Name: "ACL_FILE_PATH", Value: "/etc/redis/user.acl"}, {Name: "ACL_MODE", Value: "true"}, {Name: "PERSISTENCE_ENABLED", Value: "true"}, {Name: "REDIS_ADDR", Value: "redis://localhost:26379"}, @@ -1562,12 +1586,15 @@ func TestGetEnvironmentVariables(t *testing.T) { secretKey: ptr.To("test-key"), persistenceEnabled: ptr.To(true), tlsConfig: nil, - aclConfig: &common.ACLConfig{}, + aclConfig: &common.ACLConfig{ + Secret: &corev1.SecretVolumeSource{SecretName: "acl-secret"}, + }, envVar: &[]corev1.EnvVar{ {Name: "TEST_ENV", Value: "test-value"}, }, port: ptr.To(6380), expectedEnvironment: []corev1.EnvVar{ + {Name: "ACL_FILE_PATH", Value: "/etc/redis/user.acl"}, {Name: "ACL_MODE", Value: "true"}, {Name: "PERSISTENCE_ENABLED", Value: "true"}, {Name: "REDIS_ADDR", Value: "redis://localhost:6379"}, @@ -1585,6 +1612,37 @@ func TestGetEnvironmentVariables(t *testing.T) { {Name: "REDIS_PORT", Value: "6380"}, }, }, + { + name: "Test with cluster role and acl pvc", + role: "cluster", + enabledPassword: ptr.To(true), + secretName: ptr.To("test-secret"), + secretKey: ptr.To("test-key"), + persistenceEnabled: ptr.To(true), + tlsConfig: nil, + aclConfig: &common.ACLConfig{ + PersistentVolumeClaim: ptr.To("acl-pvc"), + }, + envVar: nil, + port: ptr.To(6381), + expectedEnvironment: []corev1.EnvVar{ + {Name: "ACL_FILE_PATH", Value: "/data/redis/user.acl"}, + {Name: "ACL_MODE", Value: "true"}, + {Name: "PERSISTENCE_ENABLED", Value: "true"}, + {Name: "REDIS_ADDR", Value: "redis://localhost:6379"}, + {Name: "REDIS_PASSWORD", ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-secret", + }, + Key: "test-key", + }, + }}, + {Name: "REDIS_PORT", Value: "6381"}, + {Name: "SERVER_MODE", Value: "cluster"}, + {Name: "SETUP_MODE", Value: "cluster"}, + }, + }, { name: "Test with cluster role and only metrics enabled", role: "cluster", @@ -1756,6 +1814,10 @@ func TestGenerateStatefulSetsDef(t *testing.T) { Name: "test-sts", Image: "redis:latest", Env: []corev1.EnvVar{ + { + Name: "ACL_FILE_PATH", + Value: "/etc/redis/user.acl", + }, { Name: "ACL_MODE", Value: "true", diff --git a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-cluster/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-cluster/chainsaw-test.yaml index f20d7ef783..c0200fd168 100644 --- a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-cluster/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-cluster/chainsaw-test.yaml @@ -96,7 +96,7 @@ spec: timeout: 30s content: > kubectl exec --namespace ${NAMESPACE} --container redis-cluster-acl-pvc-leader redis-cluster-acl-pvc-leader-0 -- - cat /etc/redis/user.acl 2>&1 + cat /data/redis/user.acl 2>&1 check: (contains($stdout, 'user pvcuser')): true (contains($stdout, 'user readonly')): true @@ -113,7 +113,32 @@ spec: (contains($stdout, 'pvcuser')): true (contains($stdout, 'readonly')): true - # Step 11: Test cluster operations with ACL + # Step 11: Test ACL SAVE persists to PVC + - name: Verify ACL SAVE writes to PVC + try: + - script: + timeout: 30s + content: > + kubectl exec --namespace ${NAMESPACE} --container redis-cluster-acl-pvc-leader redis-cluster-acl-pvc-leader-0 -- + redis-cli -c -p 6379 --user admin --pass admin@secure456 ACL SETUSER saveduser on ~* &* +@all >saved@secure789 2>&1 + check: + (contains($stdout, 'OK')): true + - script: + timeout: 30s + content: > + kubectl exec --namespace ${NAMESPACE} --container redis-cluster-acl-pvc-leader redis-cluster-acl-pvc-leader-0 -- + redis-cli -c -p 6379 --user admin --pass admin@secure456 ACL SAVE 2>&1 + check: + (contains($stdout, 'OK')): true + - script: + timeout: 30s + content: > + kubectl exec --namespace ${NAMESPACE} --container redis-cluster-acl-pvc-leader redis-cluster-acl-pvc-leader-0 -- + cat /data/redis/user.acl 2>&1 + check: + (contains($stdout, 'user saveduser')): true + + # Step 12: Test cluster operations with ACL - name: Test cluster info with ACL user try: - script: @@ -124,13 +149,13 @@ spec: check: (contains($stdout, 'cluster_state:ok')): true - # Step 12: Verify PVC data persistence + # Step 13: Verify PVC data persistence - name: Verify ACL PVC volume mount try: - script: timeout: 30s content: > kubectl exec --namespace ${NAMESPACE} --container redis-cluster-acl-pvc-leader redis-cluster-acl-pvc-leader-0 -- - cat /proc/mounts | grep '/etc/redis/user.acl' 2>&1 + cat /proc/mounts | grep '/data/redis' 2>&1 check: - (contains($stdout, '/etc/redis/user.acl')): true + (contains($stdout, '/data/redis')): true diff --git a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-replication/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-replication/chainsaw-test.yaml index 427fe84988..1329db16c0 100644 --- a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-replication/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-replication/chainsaw-test.yaml @@ -76,7 +76,7 @@ spec: timeout: 30s content: > kubectl exec --namespace ${NAMESPACE} redis-replication-acl-pvc-0 -- - cat /etc/redis/user.acl 2>&1 + cat /data/redis/user.acl 2>&1 check: (contains($stdout, 'user repluser')): true @@ -87,7 +87,7 @@ spec: timeout: 30s content: > kubectl exec --namespace ${NAMESPACE} redis-replication-acl-pvc-1 -- - cat /etc/redis/user.acl 2>&1 + cat /data/redis/user.acl 2>&1 check: (contains($stdout, 'user repluser')): true diff --git a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-standalone/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-standalone/chainsaw-test.yaml index b2f959869a..1b490f10aa 100644 --- a/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-standalone/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/acl-pvc/redis-standalone/chainsaw-test.yaml @@ -78,7 +78,7 @@ spec: timeout: 30s content: > kubectl exec --namespace ${NAMESPACE} redis-standalone-acl-pvc-0 -- - cat /etc/redis/user.acl 2>&1 + cat /data/redis/user.acl 2>&1 check: (contains($stdout, 'user standaloneuser')): true From 2eb7d566ce3084b9ea46d6b4d52070ac382d7a64 Mon Sep 17 00:00:00 2001 From: Mohamed Dewidar Date: Tue, 24 Feb 2026 01:09:50 +0200 Subject: [PATCH 03/20] docs: update architecture diagram with Meshery design (#1686) - Recreated architecture diagram using Meshery Kanvas - Expanded coverage to 4 topologies: Standalone, Cluster, Replication, and Sentinel - Integrated operator flow, K8s resources, monitoring, and security layers - Improved visualization of CRD -> Operator -> Resource reconciliation Fixes: meshery/meshery#17519 Signed-off-by: Mohamed-Magdy-Dewidar --- README.md | 2 +- ...dis-operator-architecture-using-meshery.jpg | Bin 0 -> 278305 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 static/updated-redis-operator-architecture-using-meshery.jpg diff --git a/README.md b/README.md index d0a6dc69ca..c05fe45f98 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This operator only supports versions of Redis `>=6`. ## Architecture
- +
## Purpose diff --git a/static/updated-redis-operator-architecture-using-meshery.jpg b/static/updated-redis-operator-architecture-using-meshery.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6f4c6d7ffb4fdb731e1a950400a728e13be5cd68 GIT binary patch literal 278305 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∈ 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∋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 T&WIn@NP%64!%-ykyXpS{!PgIA literal 0 HcmV?d00001 From 6d1208b07f048067d07ea13f5ac67879d710d6b1 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:09:14 +0000 Subject: [PATCH 04/20] docs: fix minor typos (#1674) Signed-off-by: Your Name Co-authored-by: Your Name --- CONTRIBUTING.md | 2 +- docs/content/en/docs/CRD Reference/API Reference/_index.md | 3 +-- docs/content/en/docs/Contribute/_index.md | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47ff0e2512..8ddcd46f9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ For development and testing of operator on local system, we need to set up a [Minikube](https://minikube.sigs.k8s.io/docs/start/) or local Kubernetes cluster. -Minikube is a single node Kubernetes cluster that generally gets used for the development and testing on Kubernetes. For creating a Minkube cluster we need to simply run: +Minikube is a single node Kubernetes cluster that generally gets used for the development and testing on Kubernetes. For creating a Minikube cluster we need to simply run: ```shell $ minikube start --vm-driver virtualbox diff --git a/docs/content/en/docs/CRD Reference/API Reference/_index.md b/docs/content/en/docs/CRD Reference/API Reference/_index.md index a58b047657..c43e911fe4 100644 --- a/docs/content/en/docs/CRD Reference/API Reference/_index.md +++ b/docs/content/en/docs/CRD Reference/API Reference/_index.md @@ -661,7 +661,7 @@ _Appears in:_ -Storage is the inteface to add pvc and pv support in redis +Storage is the interface to add pvc and pv support in redis @@ -698,4 +698,3 @@ _Appears in:_ | `key` _string_ | | | | | `secret` _[SecretVolumeSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#secretvolumesource-v1-core)_ | Reference to secret which contains the certificates | | | - diff --git a/docs/content/en/docs/Contribute/_index.md b/docs/content/en/docs/Contribute/_index.md index 85a5e87029..d9d39966c1 100644 --- a/docs/content/en/docs/Contribute/_index.md +++ b/docs/content/en/docs/Contribute/_index.md @@ -21,7 +21,7 @@ description: > For development and testing of operator on local system, we need to set up a [Minikube](https://minikube.sigs.k8s.io/docs/start/) or local Kubernetes cluster. -Minikube is a single node Kubernetes cluster that generally gets used for the development and testing on Kubernetes. For creating a Minkube cluster we need to simply run: +Minikube is a single node Kubernetes cluster that generally gets used for the development and testing on Kubernetes. For creating a Minikube cluster we need to simply run: ```shell $ minikube start --vm-driver virtualbox From 7ae2c1826e557f05bfe58e734e36087ad3a3ffda Mon Sep 17 00:00:00 2001 From: Vadim Yushmanov <111231219+vvyushmanov@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:30:53 +0400 Subject: [PATCH 05/20] feat: Add sentinel support to redis-replication chart (#1684) * feat(helm): add sentinel support to redis-replication chart The RedisReplication CRD supports an embedded spec.sentinel field, but the redis-replication Helm chart does not template it. This forces users to use kubectl patch after Helm install, which creates a race condition with the operator reconciler. Add a sentinel section to values.yaml (disabled by default) and template all supported sentinel fields in redis-replication.yaml, including container config, auth, and sentinel-specific parameters. Fixes #1683 Signed-off-by: Vadim Iushmanov * fix(helm): remove unsupported replication sentinel values * docs(helm): regenerate redis-replication chart docs * fix --------- Signed-off-by: Vadim Iushmanov Co-authored-by: Vadim Iushmanov Co-authored-by: Yang Wu --- api/common/v1beta2/common_types.go | 2 +- charts/redis-operator/crds/crds.yaml | 4 +- charts/redis-replication/README.md | 6 +++ .../templates/redis-replication.yaml | 37 +++++++++++++++++- charts/redis-replication/values.yaml | 38 +++++++++++++++++++ .../redis.redis.opstreelabs.in_redis.yaml | 2 +- ...edis.opstreelabs.in_redisreplications.yaml | 2 +- .../CRD Reference/API Reference/_index.md | 1 + 8 files changed, 86 insertions(+), 6 deletions(-) diff --git a/api/common/v1beta2/common_types.go b/api/common/v1beta2/common_types.go index e1dfc519a5..67ce009cb6 100644 --- a/api/common/v1beta2/common_types.go +++ b/api/common/v1beta2/common_types.go @@ -160,7 +160,7 @@ type RedisConfig struct { AdditionalRedisConfig *string `json:"additionalRedisConfig,omitempty"` } -// Storage is the inteface to add pvc and pv support in redis +// Storage is the interface to add pvc and pv support in redis // +k8s:deepcopy-gen=true type Storage struct { KeepAfterDelete bool `json:"keepAfterDelete,omitempty"` diff --git a/charts/redis-operator/crds/crds.yaml b/charts/redis-operator/crds/crds.yaml index 50dea28562..eeb1e287b5 100644 --- a/charts/redis-operator/crds/crds.yaml +++ b/charts/redis-operator/crds/crds.yaml @@ -3220,7 +3220,7 @@ spec: type: object type: array storage: - description: Storage is the inteface to add pvc and pv support in + description: Storage is the interface to add pvc and pv support in redis properties: keepAfterDelete: @@ -16760,7 +16760,7 @@ spec: type: object type: array storage: - description: Storage is the inteface to add pvc and pv support in + description: Storage is the interface to add pvc and pv support in redis properties: keepAfterDelete: diff --git a/charts/redis-replication/README.md b/charts/redis-replication/README.md index 8b82119857..fb23ef803d 100644 --- a/charts/redis-replication/README.md +++ b/charts/redis-replication/README.md @@ -96,6 +96,12 @@ helm delete --namespace | redisReplication.serviceType | string | `"ClusterIP"` | | | redisReplication.tag | string | `"v7.0.15"` | | | securityContext | object | `{}` | | +| sentinel | object | `{"announceHostnames":"no","downAfterMilliseconds":"5000","enabled":false,"failoverTimeout":"10000","ignoreAnnotations":[],"image":"quay.io/opstree/redis-sentinel","imagePullPolicy":"IfNotPresent","minReadySeconds":0,"parallelSyncs":"1","persistentVolumeClaimRetentionPolicy":{},"resolveHostnames":"no","resources":{},"size":3,"tag":"v7.0.15"}` | Sentinel configuration for automatic failover. When enabled, the operator creates a Sentinel StatefulSet alongside the replication pods. The operator queries Sentinel for the current master instead of forcing master-by-ordinal. | +| sentinel.announceHostnames | string | `"no"` | Whether Sentinel announces hostnames instead of IPs to clients | +| sentinel.downAfterMilliseconds | string | `"5000"` | Time in milliseconds before master is considered down | +| sentinel.failoverTimeout | string | `"10000"` | Failover timeout in milliseconds | +| sentinel.parallelSyncs | string | `"1"` | Number of replicas to reconfigure in parallel during failover | +| sentinel.resolveHostnames | string | `"no"` | Use hostnames instead of IPs for Sentinel monitoring. WARNING: the operator does not pass RESOLVE_HOSTNAMES env var to sentinel pods, so setting this to "yes" will cause SENTINEL MONITOR to fail. Keep as "no". | | serviceAccountName | string | `""` | | | serviceMonitor.enabled | bool | `false` | | | serviceMonitor.extraLabels | object | `{}` | extraLabels are added to the servicemonitor when enabled set to true | diff --git a/charts/redis-replication/templates/redis-replication.yaml b/charts/redis-replication/templates/redis-replication.yaml index 21c4a62d9c..812bc3f77c 100644 --- a/charts/redis-replication/templates/redis-replication.yaml +++ b/charts/redis-replication/templates/redis-replication.yaml @@ -123,4 +123,39 @@ spec: minAvailable: {{ .Values.pdb.minAvailable }} maxUnavailable: {{ .Values.pdb.maxUnavailable }} {{- end }} - + {{- if .Values.sentinel.enabled }} + sentinel: + image: "{{ .Values.sentinel.image }}:{{ .Values.sentinel.tag }}" + imagePullPolicy: "{{ .Values.sentinel.imagePullPolicy }}" + size: {{ .Values.sentinel.size }} + {{- if .Values.sentinel.resources }} + resources: {{ toYaml .Values.sentinel.resources | nindent 6 }} + {{- end }} + {{- if .Values.sentinel.ignoreAnnotations }} + ignoreAnnotations: {{ toYaml .Values.sentinel.ignoreAnnotations | nindent 6 }} + {{- end }} + {{- if .Values.sentinel.minReadySeconds }} + minReadySeconds: {{ .Values.sentinel.minReadySeconds }} + {{- end }} + {{- if .Values.sentinel.persistentVolumeClaimRetentionPolicy }} + persistentVolumeClaimRetentionPolicy: {{ toYaml .Values.sentinel.persistentVolumeClaimRetentionPolicy | nindent 6 }} + {{- end }} + {{- if .Values.sentinel.parallelSyncs }} + parallelSyncs: {{ .Values.sentinel.parallelSyncs | quote }} + {{- end }} + {{- if .Values.sentinel.failoverTimeout }} + failoverTimeout: {{ .Values.sentinel.failoverTimeout | quote }} + {{- end }} + {{- if .Values.sentinel.downAfterMilliseconds }} + downAfterMilliseconds: {{ .Values.sentinel.downAfterMilliseconds | quote }} + {{- end }} + {{- if .Values.sentinel.resolveHostnames }} + resolveHostnames: {{ .Values.sentinel.resolveHostnames | quote }} + {{- end }} + {{- if .Values.sentinel.announceHostnames }} + announceHostnames: {{ .Values.sentinel.announceHostnames | quote }} + {{- end }} + {{- if .Values.sentinel.additionalSentinelConfig }} + additionalSentinelConfig: {{ .Values.sentinel.additionalSentinelConfig | quote }} + {{- end }} + {{- end }} diff --git a/charts/redis-replication/values.yaml b/charts/redis-replication/values.yaml index f14f086df8..a7bee5b70e 100644 --- a/charts/redis-replication/values.yaml +++ b/charts/redis-replication/values.yaml @@ -199,3 +199,41 @@ pdb: enabled: false minAvailable: 1 maxUnavailable: null + +# -- Sentinel configuration for automatic failover. +# When enabled, the operator creates a Sentinel StatefulSet alongside the replication pods. +# The operator queries Sentinel for the current master instead of forcing master-by-ordinal. +sentinel: + enabled: false + image: quay.io/opstree/redis-sentinel + tag: v7.0.15 + imagePullPolicy: IfNotPresent + size: 3 + resources: {} + # requests: + # cpu: 100m + # memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + ignoreAnnotations: [] + # - "redis.opstreelabs.in/ignore" + minReadySeconds: 0 + persistentVolumeClaimRetentionPolicy: {} + # whenDeleted: Delete + # whenScaled: Retain + # -- Number of replicas to reconfigure in parallel during failover + parallelSyncs: "1" + # -- Failover timeout in milliseconds + failoverTimeout: "10000" + # -- Time in milliseconds before master is considered down + downAfterMilliseconds: "5000" + # -- Use hostnames instead of IPs for Sentinel monitoring. + # WARNING: the operator does not pass RESOLVE_HOSTNAMES env var to sentinel pods, + # so setting this to "yes" will cause SENTINEL MONITOR to fail. Keep as "no". + resolveHostnames: "no" + # -- Whether Sentinel announces hostnames instead of IPs to clients + announceHostnames: "no" + # -- Additional raw sentinel.conf lines + # additionalSentinelConfig: | + # sentinel auth-pass mymaster password123 diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml index 51c8fe5540..5d131f3d07 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml @@ -3221,7 +3221,7 @@ spec: type: object type: array storage: - description: Storage is the inteface to add pvc and pv support in + description: Storage is the interface to add pvc and pv support in redis properties: keepAfterDelete: diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml index e7aff74439..9020a029f5 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redisreplications.yaml @@ -3489,7 +3489,7 @@ spec: type: object type: array storage: - description: Storage is the inteface to add pvc and pv support in + description: Storage is the interface to add pvc and pv support in redis properties: keepAfterDelete: diff --git a/docs/content/en/docs/CRD Reference/API Reference/_index.md b/docs/content/en/docs/CRD Reference/API Reference/_index.md index c43e911fe4..58dace4f43 100644 --- a/docs/content/en/docs/CRD Reference/API Reference/_index.md +++ b/docs/content/en/docs/CRD Reference/API Reference/_index.md @@ -698,3 +698,4 @@ _Appears in:_ | `key` _string_ | | | | | `secret` _[SecretVolumeSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#secretvolumesource-v1-core)_ | Reference to secret which contains the certificates | | | + From bafe989a76d4a03092f190565c43f06891e8b5ee Mon Sep 17 00:00:00 2001 From: Shubham Gupta <69793468+shubham-cmyk@users.noreply.github.com> Date: Tue, 3 Mar 2026 01:51:07 +0530 Subject: [PATCH 06/20] fix: Make RedisCluster scaling resilient to failover and open slots (#1647) * chore: demo PR not to merge Signed-off-by: Shubham Gupta * feat: Enhance Redis cluster scaling with failover and fix open slots functionality Signed-off-by: Shubham Gupta * fix: Improve Redis cluster failover handling and error logging during scaling Signed-off-by: Shubham Gupta --------- Signed-off-by: Shubham Gupta --- Makefile | 1 + .../rediscluster/rediscluster_controller.go | 21 ++++++++++++++++ internal/k8sutils/cluster-scaling.go | 25 +++++++++++++++++++ internal/service/redis/client.go | 21 ++++++++++------ .../v1beta2/setup/redis-ha/chainsaw-test.yaml | 18 +++++++++++++ 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c029edbde5..86725aed3b 100644 --- a/Makefile +++ b/Makefile @@ -291,3 +291,4 @@ GOBIN=$(LOCALBIN) go install $${package} ;\ mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ } endef + diff --git a/internal/controller/rediscluster/rediscluster_controller.go b/internal/controller/rediscluster/rediscluster_controller.go index 25b8777755..d8d0fbd9a0 100644 --- a/internal/controller/rediscluster/rediscluster_controller.go +++ b/internal/controller/rediscluster/rediscluster_controller.go @@ -90,6 +90,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if masterCount := k8sutils.CheckRedisNodeCount(ctx, r.K8sClient, instance, "leader"); masterCount == leaderCount { r.Recorder.Event(instance, corev1.EventTypeNormal, events.EventReasonRedisClusterDownscale, "Redis cluster is downscaling...") logger.Info("Redis cluster is downscaling...", "Current.LeaderReplicas", leaderCount, "Desired.LeaderReplicas", leaderReplicas) + + // Before resharding, ensure all remaining leader pods (the transfer targets) are masters. + // After scale-out, a failover may have converted some leader pods to slaves, which causes + // reshard to fail with "The specified node is not known or not a master". + // We handle one failover per reconcile cycle and requeue — the loop will converge + // over successive reconciliations until all target pods are masters. + for i := int32(0); i < leaderReplicas; i++ { + if !(k8sutils.VerifyLeaderPod(ctx, r.K8sClient, instance, i)) { + logger.Info("Transfer target leader pod is not a master, initiating failover before scale-down", "Pod.Index", i) + if err = k8sutils.ClusterFailover(ctx, r.K8sClient, instance, i); err != nil { + logger.Error(err, "Failed to initiate cluster failover for transfer target") + return intctrlutil.RequeueE(ctx, err, "") + } + return intctrlutil.RequeueAfter(ctx, time.Second*10, "Waiting for failover to complete before scale-down") + } + } + for shardIdx := leaderCount - 1; shardIdx >= leaderReplicas; shardIdx-- { logger.Info("Remove the shard", "Shard.Index", shardIdx) // Imp if the last index of leader sts is not leader make it then @@ -238,6 +255,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } else { if leaderCount < leaderReplicas { // Scale up the cluster + // Step 1 : Fix any open slots from previous interrupted operations + if err := k8sutils.FixRedisCluster(ctx, r.K8sClient, instance); err != nil { + logger.Error(err, "Failed to fix redis cluster slots, proceeding with scale-up") + } // Step 2 : Add Redis Node k8sutils.AddRedisNodeToCluster(ctx, r.K8sClient, instance) monitoring.RedisClusterAddingNodeAttempt.WithLabelValues(instance.Namespace, instance.Name).Inc() diff --git a/internal/k8sutils/cluster-scaling.go b/internal/k8sutils/cluster-scaling.go index ca78f5ecd8..d1d5afe18f 100644 --- a/internal/k8sutils/cluster-scaling.go +++ b/internal/k8sutils/cluster-scaling.go @@ -342,6 +342,31 @@ func getRedisNodeID(ctx context.Context, client kubernetes.Interface, cr *rcvb2. return output } +// FixRedisCluster runs `redis-cli --cluster fix` to resolve any open/stuck slots +// (e.g., slots left in migrating/importing state from a previous interrupted rebalance). +// This must be called before add-node or rebalance when the cluster may have open slots. +func FixRedisCluster(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) error { + pod := RedisDetails{ + PodName: cr.Name + "-leader-0", + Namespace: cr.Namespace, + } + cmd := []string{"redis-cli", "--cluster", "fix"} + cmd = append(cmd, getEndpoint(ctx, client, cr, pod)) + if cr.Spec.KubernetesConfig.ExistingPasswordSecret != nil { + pass, err := getRedisPassword(ctx, client, cr.Namespace, *cr.Spec.KubernetesConfig.ExistingPasswordSecret.Name, *cr.Spec.KubernetesConfig.ExistingPasswordSecret.Key) + if err != nil { + log.FromContext(ctx).Error(err, "Error in getting redis password") + } + cmd = append(cmd, "-a") + cmd = append(cmd, pass) + } + cmd = append(cmd, "--cluster-yes") + cmd = append(cmd, getRedisTLSArgs(cr.Spec.TLS, cr.Name+"-leader-0")...) + + _, err := executeCommand1(ctx, client, cr, cmd, cr.Name+"-leader-0") + return err +} + // Rebalance the Redis CLuster using the Empty Master Nodes func RebalanceRedisClusterEmptyMasters(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) { if err := waitForClusterNoOpenSlots(ctx, client, cr, 2*time.Minute); err != nil { diff --git a/internal/service/redis/client.go b/internal/service/redis/client.go index f3b72aa975..558a00e893 100644 --- a/internal/service/redis/client.go +++ b/internal/service/redis/client.go @@ -200,9 +200,11 @@ func (c *service) SentinelMonitor(ctx context.Context, master *ConnectionInfo, m } defer client.Close() + masterExists := false masterCheckCmd := rediscli.NewSliceCmd(ctx, "SENTINEL", "MASTER", masterGroupName) if err = client.Process(ctx, masterCheckCmd); err == nil { if err = masterCheckCmd.Err(); err == nil { + masterExists = true result, _ := masterCheckCmd.Result() var monitoredHost, monitoredPort string for i := 0; i+1 < len(result); i += 2 { @@ -232,13 +234,18 @@ func (c *service) SentinelMonitor(ctx context.Context, master *ConnectionInfo, m } } - cmd = rediscli.NewBoolCmd(ctx, "SENTINEL", "REMOVE", masterGroupName) - err = client.Process(ctx, cmd) - if err != nil { - return err - } - if err = cmd.Err(); err != nil { - return err + // Only remove if master was already being monitored (with wrong host/port). + // On first-time setup, SENTINEL REMOVE would fail with "ERR No such master + // with that name" and prevent SENTINEL MONITOR from ever being called. + if masterExists { + cmd = rediscli.NewBoolCmd(ctx, "SENTINEL", "REMOVE", masterGroupName) + err = client.Process(ctx, cmd) + if err != nil { + return err + } + if err = cmd.Err(); err != nil { + return err + } } cmd = rediscli.NewBoolCmd(ctx, "SENTINEL", "MONITOR", masterGroupName, master.Host, master.Port, quorum) diff --git a/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml index c444ad28b7..df5d0c8214 100644 --- a/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml +++ b/tests/e2e-chainsaw/v1beta2/setup/redis-ha/chainsaw-test.yaml @@ -46,6 +46,24 @@ spec: kind: Issuer secretName: redis-tls-cert EOF + # Wait for replication StatefulSet to be ready + - assert: + resource: + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: redis-replication + status: + readyReplicas: 3 + # Wait for sentinel StatefulSet to be ready + - assert: + resource: + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: redis-sentinel-sentinel + status: + readyReplicas: 1 - name: Test Master IP consistency try: From 7f56593827b2f5be5b0bae413c3bbb5f476c4f54 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:42:16 +0000 Subject: [PATCH 07/20] fix: correct namespaces RBAC resource (#1673) Signed-off-by: Your Name Co-authored-by: Your Name --- charts/redis-operator/templates/role.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/redis-operator/templates/role.yaml b/charts/redis-operator/templates/role.yaml index 61b89710b2..6b4aade0c6 100644 --- a/charts/redis-operator/templates/role.yaml +++ b/charts/redis-operator/templates/role.yaml @@ -80,7 +80,7 @@ rules: - configmaps - events - persistentvolumeclaims - - namespace + - namespaces verbs: - create - delete From 587ed19c7b58e85c9ad7eaf62964c11d8159d412 Mon Sep 17 00:00:00 2001 From: Julian Paulus <6230679+JulianPaulus@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:28:22 +0100 Subject: [PATCH 08/20] fix: race condition with global serviceType variable (#1696) --- internal/k8sutils/services.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/k8sutils/services.go b/internal/k8sutils/services.go index d64b467e8b..fddd8e7b36 100644 --- a/internal/k8sutils/services.go +++ b/internal/k8sutils/services.go @@ -14,8 +14,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -var serviceType corev1.ServiceType - // exporterPortProvider return the exporter port if bool is true type exporterPortProvider func() (port int, enable bool) @@ -75,6 +73,7 @@ func enableMetricsPort(port int) *corev1.ServicePort { // generateServiceType generates service type func generateServiceType(k8sServiceType string) corev1.ServiceType { + var serviceType corev1.ServiceType switch k8sServiceType { case "LoadBalancer": serviceType = corev1.ServiceTypeLoadBalancer From cd4c9b3c296b2550dd0d298995564e39e11a4bc0 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Thu, 5 Mar 2026 07:40:55 +0000 Subject: [PATCH 09/20] fix: respect persistenceEnabled for initContainer (#1671) Signed-off-by: Your Name Co-authored-by: Your Name --- internal/k8sutils/redis-cluster.go | 2 +- internal/k8sutils/redis-cluster_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/internal/k8sutils/redis-cluster.go b/internal/k8sutils/redis-cluster.go index 3a79ee6ae5..15a033b8fb 100644 --- a/internal/k8sutils/redis-cluster.go +++ b/internal/k8sutils/redis-cluster.go @@ -103,7 +103,7 @@ func generateRedisClusterInitContainerParams(cr *rcvb2.RedisCluster) initContain initcontainerProp.AdditionalVolume = cr.Spec.Storage.VolumeMount.Volume initcontainerProp.AdditionalMountPath = cr.Spec.Storage.VolumeMount.MountPath } - if cr.Spec.Storage != nil { + if cr.Spec.Storage != nil && cr.Spec.PersistenceEnabled != nil && *cr.Spec.PersistenceEnabled { initcontainerProp.PersistenceEnabled = &trueProperty } } diff --git a/internal/k8sutils/redis-cluster_test.go b/internal/k8sutils/redis-cluster_test.go index 3c14acfebd..39a593f55d 100644 --- a/internal/k8sutils/redis-cluster_test.go +++ b/internal/k8sutils/redis-cluster_test.go @@ -524,3 +524,24 @@ func Test_generateRedisClusterInitContainerParams(t *testing.T) { actual := generateRedisClusterInitContainerParams(input) assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) } + +func Test_generateRedisClusterInitContainerParams_PersistenceDisabled(t *testing.T) { + enabled := true + persistenceEnabled := false + + input := &rcvb2.RedisCluster{ + Spec: rcvb2.RedisClusterSpec{ + PersistenceEnabled: &persistenceEnabled, + Storage: &rcvb2.ClusterStorage{}, + InitContainer: &common.InitContainer{ + Enabled: &enabled, + Image: "busybox:latest", + }, + }, + } + + actual := generateRedisClusterInitContainerParams(input) + if actual.PersistenceEnabled != nil { + t.Fatalf("Expected PersistenceEnabled to be nil when persistence is disabled, got %v", *actual.PersistenceEnabled) + } +} From b3598b25f0198dfc90f8eeeef2cb42dced84a1a7 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Thu, 5 Mar 2026 08:56:01 +0000 Subject: [PATCH 10/20] docs: add nodeConfVolume to cluster example (#1670) Signed-off-by: Your Name Co-authored-by: Your Name --- docs/content/en/docs/Getting Started/Cluster/_index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/content/en/docs/Getting Started/Cluster/_index.md b/docs/content/en/docs/Getting Started/Cluster/_index.md index fa92bde5eb..2b8c1184a7 100644 --- a/docs/content/en/docs/Getting Started/Cluster/_index.md +++ b/docs/content/en/docs/Getting Started/Cluster/_index.md @@ -105,6 +105,13 @@ spec: resources: requests: storage: 1Gi + nodeConfVolume: true + nodeConfVolumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi ``` The yaml manifest can easily get applied by using `kubectl`. From 64b573c81cfcb92a91118bc8ae67bc0a1426e8e5 Mon Sep 17 00:00:00 2001 From: naimadswdn <39454533+naimadswdn@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:19:44 +0100 Subject: [PATCH 11/20] feat(metrics): Add Redis Exporter metrics service for cluster, replication, sentinel, and standalone setup (#1658) * feat(metrics): Add Redis Exporter metrics service for cluster, replication, sentinel, and standalone setups - Updated ServiceMonitor template to conditionally enable metrics based on Redis Exporter configuration. - Implemented CreateOrUpdateMetricsService function to create dedicated metrics services for Redis Cluster, Replication, Sentinel, and Standalone setups. - Added tests to validate the creation and functionality of metrics services, ensuring they only expose the exporter port (9121) and do not expose Redis data ports (6379). - Created YAML files for testing metrics services in various Redis configurations, including Cluster, Replication, Sentinel, and Standalone. Signed-off-by: Shubham Gupta * fix: add metrics scope to semantic PR validation Signed-off-by: Shubham Gupta --------- Signed-off-by: Shubham Gupta Co-authored-by: Shubham Gupta --- .github/workflows/pr-semantics.yaml | 1 + .../redis-cluster/templates/follower-sm.yaml | 3 +- charts/redis-cluster/templates/leader-sm.yaml | 3 +- .../templates/servicemonitor.yaml | 3 +- .../templates/servicemonitor.yaml | 5 +- charts/redis/templates/servicemonitor.yaml | 3 +- internal/k8sutils/redis-cluster.go | 11 ++ internal/k8sutils/redis-replication.go | 8 ++ internal/k8sutils/redis-sentinel.go | 9 ++ internal/k8sutils/redis-standalone.go | 9 ++ internal/k8sutils/services.go | 39 +++++++ .../redis-cluster/chainsaw-test.yaml | 108 ++++++++++++++++++ .../redis-cluster/cluster.yaml | 50 ++++++++ .../redis-cluster/ready-cluster.yaml | 10 ++ .../redis-cluster/ready-metrics-svc.yaml | 58 ++++++++++ .../metrics-service/redis-cluster/secret.yaml | 8 ++ .../redis-replication/chainsaw-test.yaml | 75 ++++++++++++ .../redis-replication/ready-metrics-svc.yaml | 29 +++++ .../redis-replication/ready-sts.yaml | 8 ++ .../redis-replication/replication.yaml | 41 +++++++ .../redis-replication/secret.yaml | 8 ++ .../redis-sentinel/chainsaw-test.yaml | 88 ++++++++++++++ .../redis-sentinel/ready-metrics-svc.yaml | 29 +++++ .../redis-sentinel/ready-replication-sts.yaml | 8 ++ .../redis-sentinel/ready-sentinel-sts.yaml | 8 ++ .../redis-sentinel/replication.yaml | 30 +++++ .../redis-sentinel/secret.yaml | 8 ++ .../redis-sentinel/sentinel.yaml | 36 ++++++ .../redis-standalone/chainsaw-test.yaml | 76 ++++++++++++ .../redis-standalone/ready-metrics-svc.yaml | 29 +++++ .../redis-standalone/ready-sts.yaml | 8 ++ .../redis-standalone/standalone.yaml | 37 ++++++ .../setup/redis-cluster/ready-svc.yaml | 58 ++++++++++ 33 files changed, 898 insertions(+), 6 deletions(-) create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/chainsaw-test.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/cluster.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-cluster.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-metrics-svc.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/secret.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/chainsaw-test.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-metrics-svc.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-sts.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/replication.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/secret.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/chainsaw-test.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-metrics-svc.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-replication-sts.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-sentinel-sts.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/replication.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/secret.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/sentinel.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/chainsaw-test.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-metrics-svc.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-sts.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/standalone.yaml diff --git a/.github/workflows/pr-semantics.yaml b/.github/workflows/pr-semantics.yaml index fd83483796..ddb118d0bb 100644 --- a/.github/workflows/pr-semantics.yaml +++ b/.github/workflows/pr-semantics.yaml @@ -68,6 +68,7 @@ jobs: replication sentinel cluster + metrics ignoreLabels: | bot ignore-semantic-pull-request diff --git a/charts/redis-cluster/templates/follower-sm.yaml b/charts/redis-cluster/templates/follower-sm.yaml index c7bc21bc8d..1c1498f126 100644 --- a/charts/redis-cluster/templates/follower-sm.yaml +++ b/charts/redis-cluster/templates/follower-sm.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.serviceMonitor.enabled true) (gt (int .Values.redisCluster.follower.replicas) 0) }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int .Values.redisCluster.follower.replicas) 0) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor @@ -19,6 +19,7 @@ spec: selector: matchLabels: app: {{ .Values.redisCluster.name | default .Release.Name }}-follower + app.kubernetes.io/component: metrics redis_setup_type: cluster role: follower endpoints: diff --git a/charts/redis-cluster/templates/leader-sm.yaml b/charts/redis-cluster/templates/leader-sm.yaml index d8af1d6df2..53216838c3 100644 --- a/charts/redis-cluster/templates/leader-sm.yaml +++ b/charts/redis-cluster/templates/leader-sm.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.serviceMonitor.enabled true) (gt (int .Values.redisCluster.leader.replicas) 0) }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int .Values.redisCluster.leader.replicas) 0) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor @@ -19,6 +19,7 @@ spec: selector: matchLabels: app: {{ .Values.redisCluster.name | default .Release.Name }}-leader + app.kubernetes.io/component: metrics redis_setup_type: cluster role: leader endpoints: diff --git a/charts/redis-replication/templates/servicemonitor.yaml b/charts/redis-replication/templates/servicemonitor.yaml index e4cdc589e6..7768b16425 100644 --- a/charts/redis-replication/templates/servicemonitor.yaml +++ b/charts/redis-replication/templates/servicemonitor.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.serviceMonitor.enabled true }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor @@ -19,6 +19,7 @@ spec: selector: matchLabels: app: {{ .Values.redisReplication.name | default .Release.Name }} + app.kubernetes.io/component: metrics redis_setup_type: replication role: replication endpoints: diff --git a/charts/redis-sentinel/templates/servicemonitor.yaml b/charts/redis-sentinel/templates/servicemonitor.yaml index 2351fd5236..7d968b4e66 100644 --- a/charts/redis-sentinel/templates/servicemonitor.yaml +++ b/charts/redis-sentinel/templates/servicemonitor.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.serviceMonitor.enabled true }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor @@ -18,7 +18,8 @@ metadata: spec: selector: matchLabels: - app: {{ .Values.redisSentinel.name | default .Release.Name }} + app: {{ .Values.redisSentinel.name | default .Release.Name }}-sentinel + app.kubernetes.io/component: metrics redis_setup_type: sentinel role: sentinel endpoints: diff --git a/charts/redis/templates/servicemonitor.yaml b/charts/redis/templates/servicemonitor.yaml index 0bccc1f8b2..7bf42d0f5f 100644 --- a/charts/redis/templates/servicemonitor.yaml +++ b/charts/redis/templates/servicemonitor.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.serviceMonitor.enabled true }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor @@ -19,6 +19,7 @@ spec: selector: matchLabels: app: {{ .Values.redisStandalone.name | default .Release.Name }} + app.kubernetes.io/component: metrics redis_setup_type: standalone role: standalone endpoints: diff --git a/internal/k8sutils/redis-cluster.go b/internal/k8sutils/redis-cluster.go index 15a033b8fb..68260c39f5 100644 --- a/internal/k8sutils/redis-cluster.go +++ b/internal/k8sutils/redis-cluster.go @@ -402,6 +402,17 @@ func (service RedisClusterService) CreateRedisClusterService(ctx context.Context log.FromContext(ctx).Error(err, "Cannot create master service for Redis", "Setup.Type", service.RedisServiceRole) return err } + + if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { + defaultP := ptr.To(common.RedisExporterPort) + exporterPort := *util.Coalesce(cr.Spec.RedisExporter.Port, defaultP) + selectorLabels := getRedisStableLabels(serviceName, string(cluster), service.RedisServiceRole) + err = CreateOrUpdateMetricsService(ctx, cr.Namespace, serviceName+"-metrics", selectorLabels, redisClusterAsOwner(cr), exporterPort, cl) + if err != nil { + log.FromContext(ctx).Error(err, "Cannot create metrics service for Redis", "Setup.Type", service.RedisServiceRole) + return err + } + } return nil } diff --git a/internal/k8sutils/redis-replication.go b/internal/k8sutils/redis-replication.go index fbe1815f0a..319c094d15 100644 --- a/internal/k8sutils/redis-replication.go +++ b/internal/k8sutils/redis-replication.go @@ -59,6 +59,14 @@ func CreateReplicationService(ctx context.Context, cr *rrvb2.RedisReplication, c log.FromContext(ctx).Error(err, "Cannot create replica service for Redis") return err } + if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { + exporterPort := *util.Coalesce(cr.Spec.RedisExporter.Port, ptr.To(common.RedisExporterPort)) + selectorLabels := getRedisStableLabels(cr.Name, string(replication), "replication") + if err := CreateOrUpdateMetricsService(ctx, cr.Namespace, cr.Name+"-metrics", selectorLabels, redisReplicationAsOwner(cr), exporterPort, cl); err != nil { + log.FromContext(ctx).Error(err, "Cannot create metrics service for Redis Replication") + return err + } + } return nil } diff --git a/internal/k8sutils/redis-sentinel.go b/internal/k8sutils/redis-sentinel.go index 336de4106c..24bb6db488 100644 --- a/internal/k8sutils/redis-sentinel.go +++ b/internal/k8sutils/redis-sentinel.go @@ -254,6 +254,15 @@ func (service RedisSentinelService) CreateRedisSentinelService(ctx context.Conte log.FromContext(ctx).Error(err, "Cannot create additional service for Redis", "Setup.Type", service.RedisServiceRole) return err } + if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { + exporterPort := *util.Coalesce(cr.Spec.RedisExporter.Port, ptr.To(common.RedisExporterPort)) + selectorLabels := getRedisStableLabels(serviceName, string(sentinel), service.RedisServiceRole) + err = CreateOrUpdateMetricsService(ctx, cr.Namespace, serviceName+"-metrics", selectorLabels, redisSentinelAsOwner(cr), exporterPort, cl) + if err != nil { + log.FromContext(ctx).Error(err, "Cannot create metrics service for Redis", "Setup.Type", service.RedisServiceRole) + return err + } + } return nil } diff --git a/internal/k8sutils/redis-standalone.go b/internal/k8sutils/redis-standalone.go index b4d1c5857d..69338ec90d 100644 --- a/internal/k8sutils/redis-standalone.go +++ b/internal/k8sutils/redis-standalone.go @@ -68,6 +68,15 @@ func CreateStandaloneService(ctx context.Context, cr *rvb2.Redis, cl kubernetes. return err } } + if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { + exporterPort := *util.Coalesce(cr.Spec.RedisExporter.Port, ptr.To(common.RedisExporterPort)) + selectorLabels := getRedisStableLabels(cr.Name, string(standalone), "standalone") + err = CreateOrUpdateMetricsService(ctx, cr.Namespace, cr.Name+"-metrics", selectorLabels, redisAsOwner(cr), exporterPort, cl) + if err != nil { + log.FromContext(ctx).Error(err, "Cannot create metrics service for Redis") + return err + } + } return nil } diff --git a/internal/k8sutils/services.go b/internal/k8sutils/services.go index fddd8e7b36..42e4860d57 100644 --- a/internal/k8sutils/services.go +++ b/internal/k8sutils/services.go @@ -2,6 +2,7 @@ package k8sutils import ( "context" + "strconv" "github.com/OT-CONTAINER-KIT/redis-operator/internal/controller/common" "github.com/OT-CONTAINER-KIT/redis-operator/internal/util/maps" @@ -175,3 +176,41 @@ func patchService(ctx context.Context, storedService *corev1.Service, newService log.FromContext(ctx).V(1).Info("Redis service is already in-sync") return nil } + +// CreateOrUpdateMetricsService creates a dedicated ClusterIP service exposing only the +// redis-exporter port. This prevents monitoring tools from accidentally scraping the +// Redis data port and triggering false "security attack" logs. +func CreateOrUpdateMetricsService(ctx context.Context, namespace string, serviceName string, selectorLabels map[string]string, ownerDef metav1.OwnerReference, exporterPort int, cl kubernetes.Interface) error { + serviceLabels := maps.Copy(selectorLabels) + serviceLabels["app.kubernetes.io/component"] = "metrics" + + annotations := map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": strconv.Itoa(exporterPort), + } + objectMeta := generateObjectMetaInformation(serviceName, namespace, serviceLabels, annotations) + + metricsPort := enableMetricsPort(exporterPort) + service := &corev1.Service{ + TypeMeta: generateMetaInformation("Service", "v1"), + ObjectMeta: objectMeta, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: selectorLabels, + Ports: []corev1.ServicePort{*metricsPort}, + }, + } + AddOwnerRefToObject(service, ownerDef) + + storedService, err := getService(ctx, cl, namespace, serviceName) + if err != nil { + if errors.IsNotFound(err) { + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(service); err != nil { //nolint:gocritic + log.FromContext(ctx).Error(err, "Unable to patch metrics service with compare annotations") + } + return createService(ctx, cl, namespace, service) + } + return err + } + return patchService(ctx, storedService, service, namespace, cl) +} diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/chainsaw-test.yaml new file mode 100644 index 0000000000..be6b4bd5de --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/chainsaw-test.yaml @@ -0,0 +1,108 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metrics-service-redis-cluster +spec: + steps: + - name: Deploy Redis Cluster with exporter + try: + - apply: + file: secret.yaml + - apply: + file: cluster.yaml + - assert: + file: ready-cluster.yaml + + - name: Assert metrics services created for leader and follower + try: + - assert: + file: ready-metrics-svc.yaml + + - name: Verify leader metrics service only exposes exporter port + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + # Verify leader metrics service has exactly one port (9121) + PORTS=$(kubectl get svc -n ${NAMESPACE} redis-cluster-metrics-leader-metrics -o jsonpath='{.spec.ports[*].port}') + if [ "$PORTS" != "9121" ]; then + echo "Expected leader metrics service to only expose port 9121, got: $PORTS" + exit 1 + fi + # Verify follower metrics service has exactly one port (9121) + PORTS=$(kubectl get svc -n ${NAMESPACE} redis-cluster-metrics-follower-metrics -o jsonpath='{.spec.ports[*].port}') + if [ "$PORTS" != "9121" ]; then + echo "Expected follower metrics service to only expose port 9121, got: $PORTS" + exit 1 + fi + # Verify neither metrics service exposes Redis data port 6379 + for ROLE in leader follower; do + HAS_REDIS_PORT=$(kubectl get svc -n ${NAMESPACE} redis-cluster-metrics-${ROLE}-metrics -o jsonpath='{.spec.ports[?(@.port==6379)].port}') + if [ -n "$HAS_REDIS_PORT" ]; then + echo "${ROLE} metrics service should not expose Redis port 6379" + exit 1 + fi + HAS_BUS_PORT=$(kubectl get svc -n ${NAMESPACE} redis-cluster-metrics-${ROLE}-metrics -o jsonpath='{.spec.ports[?(@.port==16379)].port}') + if [ -n "$HAS_BUS_PORT" ]; then + echo "${ROLE} metrics service should not expose Redis bus port 16379" + exit 1 + fi + COMPONENT=$(kubectl get svc -n ${NAMESPACE} redis-cluster-metrics-${ROLE}-metrics -o jsonpath='{.metadata.labels.app\.kubernetes\.io/component}') + if [ "$COMPONENT" != "metrics" ]; then + echo "Expected app.kubernetes.io/component=metrics label on ${ROLE}, got: $COMPONENT" + exit 1 + fi + done + echo "Cluster metrics services validation passed" + check: + (contains($stdout, 'Cluster metrics services validation passed')): true + + - name: Verify metrics are fetchable via leader metrics service + try: + - script: + timeout: 60s + content: | + #!/bin/bash + set -e + OUTPUT=$(kubectl run -n ${NAMESPACE} metrics-check-leader --rm -i --restart=Never --image=busybox:1.36 -- \ + wget -qO- --timeout=10 http://redis-cluster-metrics-leader-metrics.${NAMESPACE}.svc.cluster.local:9121/metrics 2>&1) + echo "$OUTPUT" + if ! echo "$OUTPUT" | grep -q "redis_"; then + echo "ERROR: No redis_ metrics found in leader output" + exit 1 + fi + echo "Leader metrics fetch validation passed" + check: + (contains($stdout, 'redis_')): true + + - name: Verify metrics are fetchable via follower metrics service + try: + - script: + timeout: 60s + content: | + #!/bin/bash + set -e + OUTPUT=$(kubectl run -n ${NAMESPACE} metrics-check-follower --rm -i --restart=Never --image=busybox:1.36 -- \ + wget -qO- --timeout=10 http://redis-cluster-metrics-follower-metrics.${NAMESPACE}.svc.cluster.local:9121/metrics 2>&1) + echo "$OUTPUT" + if ! echo "$OUTPUT" | grep -q "redis_"; then + echo "ERROR: No redis_ metrics found in follower output" + exit 1 + fi + echo "Follower metrics fetch validation passed" + check: + (contains($stdout, 'redis_')): true + + - name: Cleanup + try: + - delete: + ref: + name: redis-cluster-metrics + kind: RedisCluster + apiVersion: redis.redis.opstreelabs.in/v1beta2 + - error: + file: ready-metrics-svc.yaml diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/cluster.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/cluster.yaml new file mode 100644 index 0000000000..28d4ff6e3a --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/cluster.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisCluster +metadata: + name: redis-cluster-metrics +spec: + clusterSize: 3 + clusterVersion: v7 + persistenceEnabled: true + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:latest + imagePullPolicy: Always + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisSecret: + name: redis-secret + key: password + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi + nodeConfVolume: true + nodeConfVolumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-cluster.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-cluster.yaml new file mode 100644 index 0000000000..5acd6c5c50 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-cluster.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisCluster +metadata: + name: redis-cluster-metrics +status: + readyFollowerReplicas: 3 + readyLeaderReplicas: 3 + state: Ready + reason: RedisCluster is ready diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-metrics-svc.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-metrics-svc.yaml new file mode 100644 index 0000000000..eb4fce3696 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/ready-metrics-svc.yaml @@ -0,0 +1,58 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cluster-metrics-leader-metrics + labels: + app: redis-cluster-metrics-leader + redis_setup_type: cluster + role: leader + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisCluster + name: redis-cluster-metrics +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-cluster-metrics-leader + redis_setup_type: cluster + role: leader +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cluster-metrics-follower-metrics + labels: + app: redis-cluster-metrics-follower + redis_setup_type: cluster + role: follower + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisCluster + name: redis-cluster-metrics +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-cluster-metrics-follower + redis_setup_type: cluster + role: follower diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/secret.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/secret.yaml new file mode 100644 index 0000000000..e9788c3f95 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-cluster/secret.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-secret +type: Opaque +data: + password: T3BzdHJlZTEyMzQ= diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/chainsaw-test.yaml new file mode 100644 index 0000000000..af8a1932d1 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/chainsaw-test.yaml @@ -0,0 +1,75 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metrics-service-redis-replication +spec: + steps: + - name: Deploy Redis Replication with exporter + try: + - apply: + file: secret.yaml + - apply: + file: replication.yaml + - assert: + file: ready-sts.yaml + + - name: Assert metrics service created + try: + - assert: + file: ready-metrics-svc.yaml + + - name: Verify metrics service only exposes exporter port + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + PORTS=$(kubectl get svc -n ${NAMESPACE} redis-replication-metrics-metrics -o jsonpath='{.spec.ports[*].port}') + if [ "$PORTS" != "9121" ]; then + echo "Expected metrics service to only expose port 9121, got: $PORTS" + exit 1 + fi + HAS_REDIS_PORT=$(kubectl get svc -n ${NAMESPACE} redis-replication-metrics-metrics -o jsonpath='{.spec.ports[?(@.port==6379)].port}') + if [ -n "$HAS_REDIS_PORT" ]; then + echo "Metrics service should not expose Redis port 6379" + exit 1 + fi + COMPONENT=$(kubectl get svc -n ${NAMESPACE} redis-replication-metrics-metrics -o jsonpath='{.metadata.labels.app\.kubernetes\.io/component}') + if [ "$COMPONENT" != "metrics" ]; then + echo "Expected app.kubernetes.io/component=metrics label, got: $COMPONENT" + exit 1 + fi + echo "Replication metrics service validation passed" + check: + (contains($stdout, 'Replication metrics service validation passed')): true + + - name: Verify metrics are fetchable via metrics service + try: + - script: + timeout: 60s + content: | + #!/bin/bash + set -e + OUTPUT=$(kubectl run -n ${NAMESPACE} metrics-check --rm -i --restart=Never --image=busybox:1.36 -- \ + wget -qO- --timeout=10 http://redis-replication-metrics-metrics.${NAMESPACE}.svc.cluster.local:9121/metrics 2>&1) + echo "$OUTPUT" + if ! echo "$OUTPUT" | grep -q "redis_"; then + echo "ERROR: No redis_ metrics found in output" + exit 1 + fi + echo "Metrics fetch validation passed" + check: + (contains($stdout, 'redis_')): true + + - name: Cleanup + try: + - delete: + ref: + name: redis-replication-metrics + kind: RedisReplication + apiVersion: redis.redis.opstreelabs.in/v1beta2 + - error: + file: ready-metrics-svc.yaml diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-metrics-svc.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-metrics-svc.yaml new file mode 100644 index 0000000000..675269cd41 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-metrics-svc.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-replication-metrics-metrics + labels: + app: redis-replication-metrics + redis_setup_type: replication + role: replication + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisReplication + name: redis-replication-metrics +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-replication-metrics + redis_setup_type: replication + role: replication diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-sts.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-sts.yaml new file mode 100644 index 0000000000..64f9fd2d7d --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/ready-sts.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-replication-metrics +status: + replicas: 3 + readyReplicas: 3 diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/replication.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/replication.yaml new file mode 100644 index 0000000000..0030686a6c --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/replication.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisReplication +metadata: + name: redis-replication-metrics +spec: + clusterSize: 3 + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:latest + imagePullPolicy: Always + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisSecret: + name: redis-secret + key: password + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/secret.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/secret.yaml new file mode 100644 index 0000000000..e9788c3f95 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-replication/secret.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-secret +type: Opaque +data: + password: T3BzdHJlZTEyMzQ= diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/chainsaw-test.yaml new file mode 100644 index 0000000000..06210a041b --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/chainsaw-test.yaml @@ -0,0 +1,88 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metrics-service-redis-sentinel +spec: + steps: + - name: Deploy Redis Replication (prerequisite for Sentinel) + try: + - apply: + file: secret.yaml + - apply: + file: replication.yaml + - assert: + file: ready-replication-sts.yaml + + - name: Deploy Redis Sentinel with exporter + try: + - apply: + file: sentinel.yaml + - assert: + file: ready-sentinel-sts.yaml + + - name: Assert sentinel metrics service created + try: + - assert: + file: ready-metrics-svc.yaml + + - name: Verify sentinel metrics service only exposes exporter port + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + PORTS=$(kubectl get svc -n ${NAMESPACE} redis-sentinel-metrics-sentinel-metrics -o jsonpath='{.spec.ports[*].port}') + if [ "$PORTS" != "9121" ]; then + echo "Expected metrics service to only expose port 9121, got: $PORTS" + exit 1 + fi + # Verify it does NOT expose sentinel port 26379 + HAS_SENTINEL_PORT=$(kubectl get svc -n ${NAMESPACE} redis-sentinel-metrics-sentinel-metrics -o jsonpath='{.spec.ports[?(@.port==26379)].port}') + if [ -n "$HAS_SENTINEL_PORT" ]; then + echo "Metrics service should not expose Sentinel port 26379" + exit 1 + fi + COMPONENT=$(kubectl get svc -n ${NAMESPACE} redis-sentinel-metrics-sentinel-metrics -o jsonpath='{.metadata.labels.app\.kubernetes\.io/component}') + if [ "$COMPONENT" != "metrics" ]; then + echo "Expected app.kubernetes.io/component=metrics label, got: $COMPONENT" + exit 1 + fi + echo "Sentinel metrics service validation passed" + check: + (contains($stdout, 'Sentinel metrics service validation passed')): true + + - name: Verify metrics are fetchable via sentinel metrics service + try: + - script: + timeout: 60s + content: | + #!/bin/bash + set -e + OUTPUT=$(kubectl run -n ${NAMESPACE} metrics-check --rm -i --restart=Never --image=busybox:1.36 -- \ + wget -qO- --timeout=10 http://redis-sentinel-metrics-sentinel-metrics.${NAMESPACE}.svc.cluster.local:9121/metrics 2>&1) + echo "$OUTPUT" + if ! echo "$OUTPUT" | grep -q "redis_"; then + echo "ERROR: No redis_ metrics found in output" + exit 1 + fi + echo "Sentinel metrics fetch validation passed" + check: + (contains($stdout, 'redis_')): true + + - name: Cleanup + try: + - delete: + ref: + name: redis-sentinel-metrics + kind: RedisSentinel + apiVersion: redis.redis.opstreelabs.in/v1beta2 + - delete: + ref: + name: redis-replication-sentinel-metrics + kind: RedisReplication + apiVersion: redis.redis.opstreelabs.in/v1beta2 + - error: + file: ready-metrics-svc.yaml diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-metrics-svc.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-metrics-svc.yaml new file mode 100644 index 0000000000..4aed85c711 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-metrics-svc.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-sentinel-metrics-sentinel-metrics + labels: + app: redis-sentinel-metrics-sentinel + redis_setup_type: sentinel + role: sentinel + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisSentinel + name: redis-sentinel-metrics +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-sentinel-metrics-sentinel + redis_setup_type: sentinel + role: sentinel diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-replication-sts.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-replication-sts.yaml new file mode 100644 index 0000000000..f6576fe553 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-replication-sts.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-replication-sentinel-metrics +status: + replicas: 3 + readyReplicas: 3 diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-sentinel-sts.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-sentinel-sts.yaml new file mode 100644 index 0000000000..9133ec1f23 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/ready-sentinel-sts.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-sentinel-metrics-sentinel +status: + replicas: 1 + readyReplicas: 1 diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/replication.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/replication.yaml new file mode 100644 index 0000000000..5d556b6821 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/replication.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisReplication +metadata: + name: redis-replication-sentinel-metrics +spec: + clusterSize: 3 + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:latest + imagePullPolicy: Always + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisSecret: + name: redis-secret + key: password + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/secret.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/secret.yaml new file mode 100644 index 0000000000..e9788c3f95 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/secret.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-secret +type: Opaque +data: + password: T3BzdHJlZTEyMzQ= diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/sentinel.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/sentinel.yaml new file mode 100644 index 0000000000..0cc7df7e9a --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-sentinel/sentinel.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisSentinel +metadata: + name: redis-sentinel-metrics +spec: + clusterSize: 1 + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + redisSentinelConfig: + redisReplicationName: redis-replication-sentinel-metrics + kubernetesConfig: + image: quay.io/opstree/redis-sentinel:latest + imagePullPolicy: Always + redisSecret: + name: redis-secret + key: password + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/chainsaw-test.yaml new file mode 100644 index 0000000000..83c6208a5b --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/chainsaw-test.yaml @@ -0,0 +1,76 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metrics-service-redis-standalone +spec: + steps: + - name: Deploy Redis Standalone with exporter + try: + - apply: + file: standalone.yaml + - assert: + file: ready-sts.yaml + + - name: Assert metrics service created + try: + - assert: + file: ready-metrics-svc.yaml + + - name: Verify metrics service only exposes exporter port + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + # Verify the metrics service exists and has exactly one port (9121) + PORTS=$(kubectl get svc -n ${NAMESPACE} redis-standalone-metrics-metrics -o jsonpath='{.spec.ports[*].port}') + if [ "$PORTS" != "9121" ]; then + echo "Expected metrics service to only expose port 9121, got: $PORTS" + exit 1 + fi + # Verify it does NOT expose Redis data port 6379 + HAS_REDIS_PORT=$(kubectl get svc -n ${NAMESPACE} redis-standalone-metrics-metrics -o jsonpath='{.spec.ports[?(@.port==6379)].port}') + if [ -n "$HAS_REDIS_PORT" ]; then + echo "Metrics service should not expose Redis port 6379" + exit 1 + fi + # Verify the component label + COMPONENT=$(kubectl get svc -n ${NAMESPACE} redis-standalone-metrics-metrics -o jsonpath='{.metadata.labels.app\.kubernetes\.io/component}') + if [ "$COMPONENT" != "metrics" ]; then + echo "Expected app.kubernetes.io/component=metrics label, got: $COMPONENT" + exit 1 + fi + echo "Metrics service validation passed" + check: + (contains($stdout, 'Metrics service validation passed')): true + + - name: Verify metrics are fetchable via metrics service + try: + - script: + timeout: 60s + content: | + #!/bin/bash + set -e + OUTPUT=$(kubectl run -n ${NAMESPACE} metrics-check --rm -i --restart=Never --image=busybox:1.36 -- \ + wget -qO- --timeout=10 http://redis-standalone-metrics-metrics.${NAMESPACE}.svc.cluster.local:9121/metrics 2>&1) + echo "$OUTPUT" + if ! echo "$OUTPUT" | grep -q "redis_"; then + echo "ERROR: No redis_ metrics found in output" + exit 1 + fi + echo "Metrics fetch validation passed" + check: + (contains($stdout, 'redis_')): true + + - name: Cleanup + try: + - delete: + ref: + name: redis-standalone-metrics + kind: Redis + apiVersion: redis.redis.opstreelabs.in/v1beta2 + - error: + file: ready-metrics-svc.yaml diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-metrics-svc.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-metrics-svc.yaml new file mode 100644 index 0000000000..eed4525694 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-metrics-svc.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-standalone-metrics-metrics + labels: + app: redis-standalone-metrics + redis_setup_type: standalone + role: standalone + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: Redis + name: redis-standalone-metrics +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-standalone-metrics + redis_setup_type: standalone + role: standalone diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-sts.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-sts.yaml new file mode 100644 index 0000000000..6a16880209 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/ready-sts.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-standalone-metrics +status: + replicas: 1 + readyReplicas: 1 diff --git a/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/standalone.yaml b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/standalone.yaml new file mode 100644 index 0000000000..b98eb88eea --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/metrics-service/redis-standalone/standalone.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone-metrics +spec: + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:latest + imagePullPolicy: Always + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi diff --git a/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/ready-svc.yaml b/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/ready-svc.yaml index 6f6bc90bc1..d7e727a913 100644 --- a/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/ready-svc.yaml +++ b/tests/e2e-chainsaw/v1beta2/setup/redis-cluster/ready-svc.yaml @@ -230,3 +230,61 @@ spec: cluster: redis-cluster-v1beta2 redis-role: master redis_setup_type: cluster +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cluster-v1beta2-leader-metrics + labels: + app: redis-cluster-v1beta2-leader + redis_setup_type: cluster + role: leader + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisCluster + name: redis-cluster-v1beta2 +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-cluster-v1beta2-leader + redis_setup_type: cluster + role: leader +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cluster-v1beta2-follower-metrics + labels: + app: redis-cluster-v1beta2-follower + redis_setup_type: cluster + role: follower + app.kubernetes.io/component: metrics + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + ownerReferences: + - apiVersion: redis.redis.opstreelabs.in/v1beta2 + controller: true + kind: RedisCluster + name: redis-cluster-v1beta2 +spec: + type: ClusterIP + ports: + - name: redis-exporter + port: 9121 + protocol: TCP + targetPort: 9121 + selector: + app: redis-cluster-v1beta2-follower + redis_setup_type: cluster + role: follower From 5af3ac995863d9dfbd6b6589fb9660d06eca0eab Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:24:18 +0000 Subject: [PATCH 12/20] fix: Skip PVC shrink attempts in HandlePVCResizing (#1669) * Skip PVC shrink attempts in HandlePVCResizing Signed-off-by: Your Name * fix: log PVC resize only on success Signed-off-by: Your Name * fix: prevent PVC shrinking and add tests for storage capacity handling Signed-off-by: Shubham Gupta --------- Signed-off-by: Your Name Signed-off-by: Shubham Gupta Co-authored-by: Arpan Rajani Co-authored-by: Shubham Gupta --- internal/k8sutils/pvc.go | 12 ++- internal/k8sutils/pvc_test.go | 96 +++++++++++++++++++ .../redis-standalone/chainsaw-test.yaml | 83 ++++++++++++++++ .../redis-standalone/expand-patch.yaml | 13 +++ .../pvc-capacity-expanded.yaml | 9 ++ .../redis-standalone/pvc-expanded.yaml | 11 +++ .../redis-standalone/pvc-unchanged.yaml | 9 ++ .../redis-standalone/ready-pvc.yaml | 14 +++ .../redis-standalone/ready-sts.yaml | 12 +++ .../redis-standalone/shrink-patch.yaml | 13 +++ .../redis-standalone/standalone.yaml | 26 +++++ 11 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/chainsaw-test.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/expand-patch.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-capacity-expanded.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-expanded.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-unchanged.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-pvc.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-sts.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/shrink-patch.yaml create mode 100644 tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/standalone.yaml diff --git a/internal/k8sutils/pvc.go b/internal/k8sutils/pvc.go index 39358061dd..28c4102da6 100644 --- a/internal/k8sutils/pvc.go +++ b/internal/k8sutils/pvc.go @@ -65,6 +65,15 @@ func HandlePVCResizing(ctx context.Context, storedStateful, newStateful *appsv1. return nil } + // If the desired capacity is less than the stored capacity, skip the resize entirely. + // Kubernetes does not support PVC shrinking. + if desiredCapacity < storedCapacity { + log.FromContext(ctx).Info(fmt.Sprintf( + "sts:%s skipping PVC resize: desired capacity %d is less than current %d (Kubernetes does not support PVC shrinking)", + storedStateful.Name, desiredCapacity, storedCapacity)) + return nil + } + // Create a label selector to list all related PVCs. labelSelector := labels.FormatLabels(map[string]string{ "app": storedStateful.Name, @@ -96,8 +105,9 @@ func HandlePVCResizing(ctx context.Context, storedStateful, newStateful *appsv1. if _, err := cl.CoreV1().PersistentVolumeClaims(storedStateful.Namespace).Update(context.Background(), pvc, metav1.UpdateOptions{}); err != nil { updateFailed = true log.FromContext(ctx).Error(fmt.Errorf("sts:%s resize pvc [%s] failed: %s", storedStateful.Name, pvc.Name, err.Error()), "") + } else { + log.FromContext(ctx).Info(fmt.Sprintf("sts:%s resized pvc [%s] from %d to %d", storedStateful.Name, pvc.Name, currentCapacity, desiredCapacity)) } - log.FromContext(ctx).Info(fmt.Sprintf("sts:%s resized pvc [%s] from %d to %d", storedStateful.Name, pvc.Name, currentCapacity, desiredCapacity)) } } diff --git a/internal/k8sutils/pvc_test.go b/internal/k8sutils/pvc_test.go index 0c385b4395..e1f5138dac 100644 --- a/internal/k8sutils/pvc_test.go +++ b/internal/k8sutils/pvc_test.go @@ -178,6 +178,102 @@ func TestHandlePVCResizing_UpdatePVC(t *testing.T) { } } +// TestHandlePVCResizing_ShrinkSkipped verifies that when the desired capacity is smaller than +// the current PVC size, the resize is skipped and no update is attempted. +func TestHandlePVCResizing_ShrinkSkipped(t *testing.T) { + ctx := context.Background() + + // Stored PVC spec with 10Gi and new spec with 4Gi for the target (redis-data). + storedQuantity := resource.MustParse("10Gi") + desiredQuantity := resource.MustParse("4Gi") + storedPVCSpec := corev1.PersistentVolumeClaimSpec{ + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: storedQuantity, + }, + }, + } + newPVCSpec := corev1.PersistentVolumeClaimSpec{ + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: desiredQuantity, + }, + }, + } + + storedTemplates := []corev1.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "node-conf"}, + Spec: storedPVCSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "redis-data"}, + Spec: storedPVCSpec, + }, + } + + annotations := map[string]string{ + "storageCapacity": strconv.FormatInt(storedQuantity.Value(), 10), + } + + storedStateful := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "redis", + Namespace: "default", + Annotations: annotations, + }, + Spec: appsv1.StatefulSetSpec{ + VolumeClaimTemplates: storedTemplates, + }, + } + + newTemplates := []corev1.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "node-conf"}, + Spec: storedPVCSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "redis-data"}, + Spec: newPVCSpec, + }, + } + newStateful := storedStateful.DeepCopy() + newStateful.Spec.VolumeClaimTemplates = newTemplates + + existingPVC := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "redis-data-0", + Namespace: "default", + Labels: map[string]string{ + "app": "redis", + "app.kubernetes.io/component": "middleware", + }, + }, + Spec: storedPVCSpec, + } + + cl := fake.NewSimpleClientset(existingPVC) + + err := HandlePVCResizing(ctx, storedStateful, newStateful, cl) + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + // Ensure no PVC list or update actions occurred (early return before PVC listing). + actions := cl.Actions() + for _, action := range actions { + if action.GetVerb() == "list" || action.GetVerb() == "update" { + t.Errorf("Unexpected action %q on shrink skip: %#v", action.GetVerb(), action) + } + } + + // Annotation should remain at the original stored capacity (not updated on shrink). + expectedAnnotation := strconv.FormatInt(storedQuantity.Value(), 10) + if storedStateful.Annotations["storageCapacity"] != expectedAnnotation { + t.Errorf("Expected annotation storageCapacity to remain %s, got %s", expectedAnnotation, storedStateful.Annotations["storageCapacity"]) + } +} + // TestHandlePVCResizing_UpdateFailure simulates a failure during PVC update and verifies that an error is returned. func TestHandlePVCResizing_UpdateFailure(t *testing.T) { ctx := context.Background() diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/chainsaw-test.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/chainsaw-test.yaml new file mode 100644 index 0000000000..8c1b2e0d30 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/chainsaw-test.yaml @@ -0,0 +1,83 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: pvc-shrink-guard-redis-standalone +spec: + steps: + - name: Deploy Redis standalone with 1Gi storage + try: + - apply: + file: standalone.yaml + - assert: + file: ready-sts.yaml + - assert: + file: ready-pvc.yaml + + - name: Attempt to shrink storage to 512Mi (should be blocked) + try: + - apply: + file: shrink-patch.yaml + # Wait for the operator to reconcile the change + - sleep: + duration: 30s + # PVC must remain at 1Gi - shrink should have been skipped + - assert: + file: pvc-unchanged.yaml + # StatefulSet should still be healthy + - assert: + file: ready-sts.yaml + + - name: Verify annotation not updated on shrink + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + ANNOTATION=$(kubectl get statefulset -n ${NAMESPACE} redis-standalone-shrink-test -o jsonpath='{.metadata.annotations.storageCapacity}') + CURRENT_PVC_SIZE=$(kubectl get pvc -n ${NAMESPACE} redis-standalone-shrink-test-redis-standalone-shrink-test-0 -o jsonpath='{.status.capacity.storage}') + echo "storageCapacity annotation: ${ANNOTATION}" + echo "Current PVC size: ${CURRENT_PVC_SIZE}" + if [ "${CURRENT_PVC_SIZE}" != "1Gi" ]; then + echo "FAIL: PVC was resized to ${CURRENT_PVC_SIZE}, expected 1Gi" + exit 1 + fi + # Annotation should either be empty/unset or reflect actual capacity, not the shrink target + if [ "${ANNOTATION}" == "536870912" ]; then + echo "FAIL: annotation was incorrectly updated to shrink target (512Mi = 536870912)" + exit 1 + fi + echo "PASS: PVC shrink was correctly blocked" + + - name: Expand storage to 2Gi (should succeed after prior shrink attempt) + try: + - apply: + file: expand-patch.yaml + - assert: + timeout: 5m + file: pvc-expanded.yaml + - assert: + file: ready-sts.yaml + + - name: Wait for operator to reconcile annotation + try: + - sleep: + duration: 30s + + - name: Verify annotation updated after successful expand + try: + - script: + timeout: 30s + content: | + #!/bin/bash + set -e + ANNOTATION=$(kubectl get statefulset -n ${NAMESPACE} redis-standalone-shrink-test -o jsonpath='{.metadata.annotations.storageCapacity}') + echo "storageCapacity annotation: ${ANNOTATION}" + # 2Gi = 2147483648 bytes + if [ "${ANNOTATION}" != "2147483648" ]; then + echo "FAIL: annotation should be 2147483648 (2Gi), got ${ANNOTATION}" + exit 1 + fi + echo "PASS: annotation correctly updated to 2Gi after expansion" diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/expand-patch.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/expand-patch.yaml new file mode 100644 index 0000000000..c113bfea7b --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/expand-patch.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone-shrink-test +spec: + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 2Gi diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-capacity-expanded.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-capacity-expanded.yaml new file mode 100644 index 0000000000..e7cb52fc3e --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-capacity-expanded.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-standalone-shrink-test-redis-standalone-shrink-test-0 +status: + capacity: + storage: 2Gi + phase: Bound diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-expanded.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-expanded.yaml new file mode 100644 index 0000000000..ebee53a5a5 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-expanded.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-standalone-shrink-test-redis-standalone-shrink-test-0 +spec: + resources: + requests: + storage: 2Gi +status: + phase: Bound diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-unchanged.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-unchanged.yaml new file mode 100644 index 0000000000..778ee9c6c7 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/pvc-unchanged.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-standalone-shrink-test-redis-standalone-shrink-test-0 +status: + capacity: + storage: 1Gi + phase: Bound diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-pvc.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-pvc.yaml new file mode 100644 index 0000000000..484f4decbd --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-pvc.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-standalone-shrink-test-redis-standalone-shrink-test-0 + labels: + app: redis-standalone-shrink-test + redis_setup_type: standalone + role: standalone +status: + accessModes: [ReadWriteOnce] + capacity: + storage: 1Gi + phase: Bound diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-sts.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-sts.yaml new file mode 100644 index 0000000000..12c30989e7 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/ready-sts.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-standalone-shrink-test + labels: + app: redis-standalone-shrink-test + redis_setup_type: standalone + role: standalone +status: + replicas: 1 + readyReplicas: 1 diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/shrink-patch.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/shrink-patch.yaml new file mode 100644 index 0000000000..cbead0d6f0 --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/shrink-patch.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone-shrink-test +spec: + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 512Mi diff --git a/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/standalone.yaml b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/standalone.yaml new file mode 100644 index 0000000000..cf51e22c8b --- /dev/null +++ b/tests/e2e-chainsaw/v1beta2/pvc-shrink-guard/redis-standalone/standalone.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone-shrink-test +spec: + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:latest + imagePullPolicy: Always + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi From 9ddef465684e5a0db3975cb4bdb29f6f5ba432a9 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:39:37 +0000 Subject: [PATCH 13/20] docs: fix example redisSecret indentation (#1675) Signed-off-by: Your Name Co-authored-by: Your Name --- example/v1beta2/acl_config/cluster.yaml | 10 +++++----- example/v1beta2/acl_config/replication.yaml | 10 +++++----- example/v1beta2/acl_config/standalone.yaml | 10 +++++----- .../v1beta2/backup_restore/restore/redis-cluster.yaml | 10 +++++----- example/v1beta2/env_vars/redis-cluster.yaml | 10 +++++----- example/v1beta2/env_vars/redis-replication.yaml | 4 ++-- example/v1beta2/env_vars/redis-standalone.yaml | 10 +++++----- example/v1beta2/redis-cluster.yaml | 10 +++++----- example/v1beta2/redis-replication.yaml | 10 +++++----- example/v1beta2/redis-standalone.yaml | 10 +++++----- example/v1beta2/sidecar_features/sidecar.yaml | 10 +++++----- .../topology_spread_constraints/redis-cluster.yaml | 6 +++--- example/v1beta2/volume_mount/redis-cluster.yaml | 4 ++-- example/v1beta2/volume_mount/redis-replication.yaml | 10 +++++----- example/v1beta2/volume_mount/redis-standalone.yaml | 10 +++++----- 15 files changed, 67 insertions(+), 67 deletions(-) diff --git a/example/v1beta2/acl_config/cluster.yaml b/example/v1beta2/acl_config/cluster.yaml index fc058b36fa..5b38c2bad9 100644 --- a/example/v1beta2/acl_config/cluster.yaml +++ b/example/v1beta2/acl_config/cluster.yaml @@ -20,11 +20,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred acl: secret: secretName: acl-secret diff --git a/example/v1beta2/acl_config/replication.yaml b/example/v1beta2/acl_config/replication.yaml index a1bed6997c..7679a6ea5d 100644 --- a/example/v1beta2/acl_config/replication.yaml +++ b/example/v1beta2/acl_config/replication.yaml @@ -20,11 +20,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred acl: secret: secretName: acl-secret diff --git a/example/v1beta2/acl_config/standalone.yaml b/example/v1beta2/acl_config/standalone.yaml index 8b910f3eee..2a1f595a9d 100644 --- a/example/v1beta2/acl_config/standalone.yaml +++ b/example/v1beta2/acl_config/standalone.yaml @@ -16,11 +16,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred acl: secret: secretName: acl-secret diff --git a/example/v1beta2/backup_restore/restore/redis-cluster.yaml b/example/v1beta2/backup_restore/restore/redis-cluster.yaml index a6c95ad78d..357d78156c 100644 --- a/example/v1beta2/backup_restore/restore/redis-cluster.yaml +++ b/example/v1beta2/backup_restore/restore/redis-cluster.yaml @@ -20,11 +20,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred initContainer: enabled: true image: quay.io/opstree/redis-operator-restore:latest diff --git a/example/v1beta2/env_vars/redis-cluster.yaml b/example/v1beta2/env_vars/redis-cluster.yaml index 1b1c30297f..13a7a8f56b 100644 --- a/example/v1beta2/env_vars/redis-cluster.yaml +++ b/example/v1beta2/env_vars/redis-cluster.yaml @@ -25,11 +25,11 @@ spec: value: "custom_value_1" - name: CUSTOM_ENV_VAR_2 value: "custom_value_2" - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred storage: volumeClaimTemplate: spec: diff --git a/example/v1beta2/env_vars/redis-replication.yaml b/example/v1beta2/env_vars/redis-replication.yaml index 1ffdfb22aa..c7827d653c 100644 --- a/example/v1beta2/env_vars/redis-replication.yaml +++ b/example/v1beta2/env_vars/redis-replication.yaml @@ -23,8 +23,8 @@ spec: redisSecret: name: redis-secret key: password - # imagePullSecrets: - # - name: regcred + # imagePullSecrets: + # - name: regcred env: - name: CUSTOM_ENV_VAR_1 value: "custom_value_1" diff --git a/example/v1beta2/env_vars/redis-standalone.yaml b/example/v1beta2/env_vars/redis-standalone.yaml index 2979c78ace..cebbaec9e2 100644 --- a/example/v1beta2/env_vars/redis-standalone.yaml +++ b/example/v1beta2/env_vars/redis-standalone.yaml @@ -19,11 +19,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/redis-cluster.yaml b/example/v1beta2/redis-cluster.yaml index 41d2728f1e..9e15e8ff82 100644 --- a/example/v1beta2/redis-cluster.yaml +++ b/example/v1beta2/redis-cluster.yaml @@ -20,11 +20,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/redis-replication.yaml b/example/v1beta2/redis-replication.yaml index 7e945c1985..c94ac409cc 100644 --- a/example/v1beta2/redis-replication.yaml +++ b/example/v1beta2/redis-replication.yaml @@ -20,11 +20,11 @@ spec: limits: cpu: 101m memory: 128Mi -# redisSecret: -# name: redis-secret -# key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/redis-standalone.yaml b/example/v1beta2/redis-standalone.yaml index dc016de8b3..99e1d2aaa5 100644 --- a/example/v1beta2/redis-standalone.yaml +++ b/example/v1beta2/redis-standalone.yaml @@ -19,11 +19,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/sidecar_features/sidecar.yaml b/example/v1beta2/sidecar_features/sidecar.yaml index 9e721d2589..4bfe52feb7 100644 --- a/example/v1beta2/sidecar_features/sidecar.yaml +++ b/example/v1beta2/sidecar_features/sidecar.yaml @@ -16,11 +16,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/topology_spread_constraints/redis-cluster.yaml b/example/v1beta2/topology_spread_constraints/redis-cluster.yaml index 21bd089014..bb26537f2d 100644 --- a/example/v1beta2/topology_spread_constraints/redis-cluster.yaml +++ b/example/v1beta2/topology_spread_constraints/redis-cluster.yaml @@ -22,9 +22,9 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password + # redisSecret: + # name: redis-secret + # key: password redisLeader: topologySpreadConstraints: - maxSkew: 1 diff --git a/example/v1beta2/volume_mount/redis-cluster.yaml b/example/v1beta2/volume_mount/redis-cluster.yaml index ffe6afde77..28df3a60f1 100644 --- a/example/v1beta2/volume_mount/redis-cluster.yaml +++ b/example/v1beta2/volume_mount/redis-cluster.yaml @@ -23,8 +23,8 @@ spec: redisSecret: name: redis-secret key: password - # imagePullSecrets: - # - name: regcred + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/volume_mount/redis-replication.yaml b/example/v1beta2/volume_mount/redis-replication.yaml index 453c3363d1..c8011b27cd 100644 --- a/example/v1beta2/volume_mount/redis-replication.yaml +++ b/example/v1beta2/volume_mount/redis-replication.yaml @@ -17,11 +17,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest diff --git a/example/v1beta2/volume_mount/redis-standalone.yaml b/example/v1beta2/volume_mount/redis-standalone.yaml index 086fd39dd1..6b86bf57b5 100644 --- a/example/v1beta2/volume_mount/redis-standalone.yaml +++ b/example/v1beta2/volume_mount/redis-standalone.yaml @@ -16,11 +16,11 @@ spec: limits: cpu: 101m memory: 128Mi - # redisSecret: - # name: redis-secret - # key: password - # imagePullSecrets: - # - name: regcred + # redisSecret: + # name: redis-secret + # key: password + # imagePullSecrets: + # - name: regcred redisExporter: enabled: false image: quay.io/opstree/redis-exporter:latest From 1cf6cca7e0ab30a5ef9acdc6f11832913081c86b Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Fri, 6 Mar 2026 06:43:36 +0800 Subject: [PATCH 14/20] fix!: use ca.crt instead of ca.key, resolve startup failure when using cert-manager (#1644) * fix: use ca.crt instead of ca.key, resolve startup failure when using cert-manager Signed-off-by: Ryan Yin * docs: fix minor typos (#1674) Signed-off-by: Your Name Co-authored-by: Your Name * feat: Add sentinel support to redis-replication chart (#1684) * feat(helm): add sentinel support to redis-replication chart The RedisReplication CRD supports an embedded spec.sentinel field, but the redis-replication Helm chart does not template it. This forces users to use kubectl patch after Helm install, which creates a race condition with the operator reconciler. Add a sentinel section to values.yaml (disabled by default) and template all supported sentinel fields in redis-replication.yaml, including container config, auth, and sentinel-specific parameters. Fixes #1683 Signed-off-by: Vadim Iushmanov * fix(helm): remove unsupported replication sentinel values * docs(helm): regenerate redis-replication chart docs * fix --------- Signed-off-by: Vadim Iushmanov Co-authored-by: Vadim Iushmanov Co-authored-by: Yang Wu --------- Signed-off-by: Ryan Yin Signed-off-by: Your Name Signed-off-by: Vadim Iushmanov Co-authored-by: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Co-authored-by: Your Name Co-authored-by: Vadim Yushmanov <111231219+vvyushmanov@users.noreply.github.com> Co-authored-by: Vadim Iushmanov Co-authored-by: Yang Wu --- api/common/v1beta2/common_types.go | 2 +- charts/redis-cluster/README.md | 2 +- charts/redis-cluster/values.yaml | 2 +- charts/redis-replication/README.md | 2 +- charts/redis-replication/values.yaml | 2 +- charts/redis-sentinel/README.md | 2 +- charts/redis-sentinel/values.yaml | 2 +- charts/redis/README.md | 2 +- charts/redis/values.yaml | 2 +- .../en/docs/Configuration/Redis/_index.md | 2 +- .../docs/Configuration/RedisCluster/_index.md | 2 +- .../Configuration/RedisReplication/_index.md | 2 +- example/v1beta2/tls_enabled/redis-cluster.yaml | 2 +- .../v1beta2/tls_enabled/redis-replication.yaml | 2 +- .../v1beta2/tls_enabled/redis-standalone.yaml | 2 +- internal/agent/bootstrap/redis/config.go | 2 +- internal/agent/bootstrap/sentinel/config.go | 4 ++-- internal/k8sutils/redis-cluster_test.go | 4 ++-- internal/k8sutils/redis-replication_test.go | 2 +- internal/k8sutils/redis-sentinel_test.go | 2 +- internal/k8sutils/redis-standalone_test.go | 2 +- internal/k8sutils/secrets_test.go | 6 +++--- internal/k8sutils/statefulset.go | 10 +++++----- internal/k8sutils/statefulset_test.go | 18 +++++++++--------- tests/testdata/redis-cluster.yaml | 2 +- tests/testdata/redis-replication.yaml | 2 +- tests/testdata/redis-sentinel.yaml | 2 +- tests/testdata/redis-standalone.yaml | 2 +- 28 files changed, 44 insertions(+), 44 deletions(-) diff --git a/api/common/v1beta2/common_types.go b/api/common/v1beta2/common_types.go index 67ce009cb6..4819d892f8 100644 --- a/api/common/v1beta2/common_types.go +++ b/api/common/v1beta2/common_types.go @@ -178,7 +178,7 @@ type AdditionalVolume struct { // TLS Configuration for redis instances // +k8s:deepcopy-gen=true type TLSConfig struct { - CaKeyFile string `json:"ca,omitempty"` + CaCertFile string `json:"ca,omitempty"` CertKeyFile string `json:"cert,omitempty"` KeyFile string `json:"key,omitempty"` // Reference to secret which contains the certificates diff --git a/charts/redis-cluster/README.md b/charts/redis-cluster/README.md index 8485d44641..4206c1d217 100644 --- a/charts/redis-cluster/README.md +++ b/charts/redis-cluster/README.md @@ -46,7 +46,7 @@ helm delete --namespace | Key | Type | Default | Description | |-----|------|---------|-------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/charts/redis-cluster/values.yaml b/charts/redis-cluster/values.yaml index 8190a7d40a..c67540510e 100644 --- a/charts/redis-cluster/values.yaml +++ b/charts/redis-cluster/values.yaml @@ -238,7 +238,7 @@ podSecurityContext: # serviceAccountName: redis-sa TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/charts/redis-replication/README.md b/charts/redis-replication/README.md index fb23ef803d..77cf57c0fe 100644 --- a/charts/redis-replication/README.md +++ b/charts/redis-replication/README.md @@ -44,7 +44,7 @@ helm delete --namespace | Key | Type | Default | Description | |-----|------|---------|-------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/charts/redis-replication/values.yaml b/charts/redis-replication/values.yaml index a7bee5b70e..11348aae95 100644 --- a/charts/redis-replication/values.yaml +++ b/charts/redis-replication/values.yaml @@ -177,7 +177,7 @@ topologySpreadConstraints: [] serviceAccountName: "" TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/charts/redis-sentinel/README.md b/charts/redis-sentinel/README.md index 4a0fa322c0..aee298cd10 100644 --- a/charts/redis-sentinel/README.md +++ b/charts/redis-sentinel/README.md @@ -44,7 +44,7 @@ helm delete --namespace | Key | Type | Default | Description | |-----|------|---------|-------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/charts/redis-sentinel/values.yaml b/charts/redis-sentinel/values.yaml index f0176b6212..cb09dfc9da 100644 --- a/charts/redis-sentinel/values.yaml +++ b/charts/redis-sentinel/values.yaml @@ -165,7 +165,7 @@ topologySpreadConstraints: [] serviceAccountName: "" TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/charts/redis/README.md b/charts/redis/README.md index fdfee22404..bbe2fec0a7 100644 --- a/charts/redis/README.md +++ b/charts/redis/README.md @@ -43,7 +43,7 @@ helm delete --namespace | Key | Type | Default | Description | |-----|------|---------|-------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/charts/redis/values.yaml b/charts/redis/values.yaml index 186a55f85b..b40ae25bc3 100644 --- a/charts/redis/values.yaml +++ b/charts/redis/values.yaml @@ -160,7 +160,7 @@ topologySpreadConstraints: [] serviceAccountName: "" TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/docs/content/en/docs/Configuration/Redis/_index.md b/docs/content/en/docs/Configuration/Redis/_index.md index f5cd13bae0..8e566ae99e 100644 --- a/docs/content/en/docs/Configuration/Redis/_index.md +++ b/docs/content/en/docs/Configuration/Redis/_index.md @@ -13,7 +13,7 @@ Redis standalone configuration can be customized by [values.yaml](https://github | Key | Type | Default | Description | |-----------------------------------------------------------------|--------|--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/docs/content/en/docs/Configuration/RedisCluster/_index.md b/docs/content/en/docs/Configuration/RedisCluster/_index.md index 66bb9b8896..7782527c07 100644 --- a/docs/content/en/docs/Configuration/RedisCluster/_index.md +++ b/docs/content/en/docs/Configuration/RedisCluster/_index.md @@ -13,7 +13,7 @@ Redis cluster can be customized by [values.yaml](https://github.com/OT-CONTAINER | Key | Type | Default | Description | |-------------------------------------------------------------------------|--------|--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/docs/content/en/docs/Configuration/RedisReplication/_index.md b/docs/content/en/docs/Configuration/RedisReplication/_index.md index 3894e27fc6..fbd622faf4 100644 --- a/docs/content/en/docs/Configuration/RedisReplication/_index.md +++ b/docs/content/en/docs/Configuration/RedisReplication/_index.md @@ -13,7 +13,7 @@ Redis replication configuration can be customized by [values.yaml](https://githu | Key | Type | Default | Description | |-----|------|---------|-------------| -| TLS.ca | string | `"ca.key"` | | +| TLS.ca | string | `"ca.crt"` | | | TLS.cert | string | `"tls.crt"` | | | TLS.key | string | `"tls.key"` | | | TLS.secret.secretName | string | `""` | | diff --git a/example/v1beta2/tls_enabled/redis-cluster.yaml b/example/v1beta2/tls_enabled/redis-cluster.yaml index eaaaf6f6a5..21cf809704 100644 --- a/example/v1beta2/tls_enabled/redis-cluster.yaml +++ b/example/v1beta2/tls_enabled/redis-cluster.yaml @@ -7,7 +7,7 @@ spec: clusterSize: 3 TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/example/v1beta2/tls_enabled/redis-replication.yaml b/example/v1beta2/tls_enabled/redis-replication.yaml index f5009686c7..7c07f6e08e 100644 --- a/example/v1beta2/tls_enabled/redis-replication.yaml +++ b/example/v1beta2/tls_enabled/redis-replication.yaml @@ -8,7 +8,7 @@ metadata: spec: clusterSize: 3 TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/example/v1beta2/tls_enabled/redis-standalone.yaml b/example/v1beta2/tls_enabled/redis-standalone.yaml index 39f156e1ab..cd9ae404c4 100644 --- a/example/v1beta2/tls_enabled/redis-standalone.yaml +++ b/example/v1beta2/tls_enabled/redis-standalone.yaml @@ -7,7 +7,7 @@ metadata: name: redis-standalone spec: TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/internal/agent/bootstrap/redis/config.go b/internal/agent/bootstrap/redis/config.go index 964e5dd51a..43a8467d5d 100644 --- a/internal/agent/bootstrap/redis/config.go +++ b/internal/agent/bootstrap/redis/config.go @@ -100,7 +100,7 @@ func GenerateConfig() error { if tlsMode == "true" { cfg.Append("tls-cert-file", util.CoalesceEnv1("REDIS_TLS_CERT", "")) cfg.Append("tls-key-file", util.CoalesceEnv1("REDIS_TLS_CERT_KEY", "")) - cfg.Append("tls-ca-cert-file", util.CoalesceEnv1("REDIS_TLS_CA_KEY", "")) + cfg.Append("tls-ca-cert-file", util.CoalesceEnv1("REDIS_TLS_CA_CERT", "")) cfg.Append("tls-auth-clients", "optional") cfg.Append("tls-replication", "yes") diff --git a/internal/agent/bootstrap/sentinel/config.go b/internal/agent/bootstrap/sentinel/config.go index c95ecc2cb2..e23e1f9694 100644 --- a/internal/agent/bootstrap/sentinel/config.go +++ b/internal/agent/bootstrap/sentinel/config.go @@ -103,13 +103,13 @@ func GenerateConfig() error { if tlsMode, ok := util.CoalesceEnv("TLS_MODE", ""); ok && tlsMode == "true" { redisTLSCert, _ := util.CoalesceEnv("REDIS_TLS_CERT", "") redisTLSCertKey, _ := util.CoalesceEnv("REDIS_TLS_CERT_KEY", "") - redisTLSCAKey, _ := util.CoalesceEnv("REDIS_TLS_CA_KEY", "") + redisTLSCACert, _ := util.CoalesceEnv("REDIS_TLS_CA_CERT", "") cfg.Append("port", "0") cfg.Append("tls-port", "26379") cfg.Append("tls-cert-file", redisTLSCert) cfg.Append("tls-key-file", redisTLSCertKey) - cfg.Append("tls-ca-cert-file", redisTLSCAKey) + cfg.Append("tls-ca-cert-file", redisTLSCACert) cfg.Append("tls-auth-clients", "optional") // Sentinel should use tls for replication connection. cfg.Append("tls-replication", "yes") diff --git a/internal/k8sutils/redis-cluster_test.go b/internal/k8sutils/redis-cluster_test.go index 39a593f55d..5a160ca728 100644 --- a/internal/k8sutils/redis-cluster_test.go +++ b/internal/k8sutils/redis-cluster_test.go @@ -265,7 +265,7 @@ func Test_generateRedisClusterContainerParams(t *testing.T) { SecretKey: ptr.To("password"), PersistenceEnabled: ptr.To(true), TLSConfig: &common.TLSConfig{ - CaKeyFile: "ca.key", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ @@ -376,7 +376,7 @@ func Test_generateRedisClusterContainerParams(t *testing.T) { SecretKey: ptr.To("password"), PersistenceEnabled: ptr.To(true), TLSConfig: &common.TLSConfig{ - CaKeyFile: "ca.key", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ diff --git a/internal/k8sutils/redis-replication_test.go b/internal/k8sutils/redis-replication_test.go index 0a2607e8af..631945c0c9 100644 --- a/internal/k8sutils/redis-replication_test.go +++ b/internal/k8sutils/redis-replication_test.go @@ -177,7 +177,7 @@ func Test_generateRedisReplicationContainerParams(t *testing.T) { SecretKey: ptr.To("password"), PersistenceEnabled: ptr.To(true), TLSConfig: &common.TLSConfig{ - CaKeyFile: "ca.key", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ diff --git a/internal/k8sutils/redis-sentinel_test.go b/internal/k8sutils/redis-sentinel_test.go index c97c83035a..b985569740 100644 --- a/internal/k8sutils/redis-sentinel_test.go +++ b/internal/k8sutils/redis-sentinel_test.go @@ -167,7 +167,7 @@ func Test_generateRedisSentinelContainerParams(t *testing.T) { SecretName: ptr.To("redis-secret"), SecretKey: ptr.To("password"), TLSConfig: &common.TLSConfig{ - CaKeyFile: "ca.key", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ diff --git a/internal/k8sutils/redis-standalone_test.go b/internal/k8sutils/redis-standalone_test.go index 6616862b64..99645aaa72 100644 --- a/internal/k8sutils/redis-standalone_test.go +++ b/internal/k8sutils/redis-standalone_test.go @@ -172,7 +172,7 @@ func Test_generateRedisStandaloneContainerParams(t *testing.T) { SecretKey: ptr.To("password"), PersistenceEnabled: ptr.To(true), TLSConfig: &common.TLSConfig{ - CaKeyFile: "ca.key", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ diff --git a/internal/k8sutils/secrets_test.go b/internal/k8sutils/secrets_test.go index cdc98c99fd..7f236f7537 100644 --- a/internal/k8sutils/secrets_test.go +++ b/internal/k8sutils/secrets_test.go @@ -129,7 +129,7 @@ func Test_getRedisTLSConfig(t *testing.T) { }, Spec: rcvb2.RedisClusterSpec{ TLS: &common.TLSConfig{ - CaKeyFile: "ca.crt", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ @@ -157,7 +157,7 @@ func Test_getRedisTLSConfig(t *testing.T) { }, Spec: rcvb2.RedisClusterSpec{ TLS: &common.TLSConfig{ - CaKeyFile: "ca.crt", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ @@ -195,7 +195,7 @@ func Test_getRedisTLSConfig(t *testing.T) { }, Spec: rcvb2.RedisClusterSpec{ TLS: &common.TLSConfig{ - CaKeyFile: "ca.crt", + CaCertFile: "ca.crt", CertKeyFile: "tls.crt", KeyFile: "tls.key", Secret: corev1.SecretVolumeSource{ diff --git a/internal/k8sutils/statefulset.go b/internal/k8sutils/statefulset.go index 1339d5c25d..37d2780365 100644 --- a/internal/k8sutils/statefulset.go +++ b/internal/k8sutils/statefulset.go @@ -549,7 +549,7 @@ func GenerateAuthAndTLSArgs(enableAuth, enableTLS bool) (string, string) { authArgs = " -a \"${REDIS_PASSWORD}\"" } if enableTLS { - tlsArgs = " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_KEY}\"" + tlsArgs = " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_CERT}\"" } return authArgs, tlsArgs } @@ -659,8 +659,8 @@ func GenerateTLSEnvironmentVariables(tlsconfig *commonapi.TLSConfig) []corev1.En tlsCert := "tls.crt" tlsCertKey := "tls.key" - if tlsconfig.CaKeyFile != "" { - caCert = tlsconfig.CaKeyFile + if tlsconfig.CaCertFile != "" { + caCert = tlsconfig.CaCertFile } if tlsconfig.CertKeyFile != "" { tlsCert = tlsconfig.CertKeyFile @@ -674,7 +674,7 @@ func GenerateTLSEnvironmentVariables(tlsconfig *commonapi.TLSConfig) []corev1.En Value: "true", }) envVars = append(envVars, corev1.EnvVar{ - Name: "REDIS_TLS_CA_KEY", + Name: "REDIS_TLS_CA_CERT", Value: path.Join(root, caCert), }) envVars = append(envVars, corev1.EnvVar{ @@ -848,7 +848,7 @@ func getProbeInfo(probe *corev1.Probe, sentinel, enableTLS, enableAuth bool) *co redisHealthCheck = append(redisHealthCheck, "-a", "${REDIS_PASSWORD}") } if enableTLS { - redisHealthCheck = append(redisHealthCheck, "--tls", "--cert", "${REDIS_TLS_CERT}", "--key", "${REDIS_TLS_CERT_KEY}", "--cacert", "${REDIS_TLS_CA_KEY}") + redisHealthCheck = append(redisHealthCheck, "--tls", "--cert", "${REDIS_TLS_CERT}", "--key", "${REDIS_TLS_CERT_KEY}", "--cacert", "${REDIS_TLS_CA_CERT}") } redisHealthCheck = append(redisHealthCheck, "ping") diff --git a/internal/k8sutils/statefulset_test.go b/internal/k8sutils/statefulset_test.go index e146952e98..5b6d70841f 100644 --- a/internal/k8sutils/statefulset_test.go +++ b/internal/k8sutils/statefulset_test.go @@ -31,8 +31,8 @@ func TestGenerateAuthAndTLSArgs(t *testing.T) { }{ {"NoAuthNoTLS", false, false, "", ""}, {"AuthOnly", true, false, " -a \"${REDIS_PASSWORD}\"", ""}, - {"TLSOnly", false, true, "", " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_KEY}\""}, - {"AuthAndTLS", true, true, " -a \"${REDIS_PASSWORD}\"", " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_KEY}\""}, + {"TLSOnly", false, true, "", " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_CERT}\""}, + {"AuthAndTLS", true, true, " -a \"${REDIS_PASSWORD}\"", " --tls --cert \"${REDIS_TLS_CERT}\" --key \"${REDIS_TLS_CERT_KEY}\" --cacert \"${REDIS_TLS_CA_CERT}\""}, } for _, tt := range tests { @@ -1454,7 +1454,7 @@ func TestGenerateInitContainerDefWithSecurityContext(t *testing.T) { func TestGenerateTLSEnvironmentVariables(t *testing.T) { tlsConfig := &common.TLSConfig{ - CaKeyFile: "test_ca.crt", + CaCertFile: "test_ca.crt", CertKeyFile: "test_tls.crt", KeyFile: "test_tls.key", } @@ -1467,7 +1467,7 @@ func TestGenerateTLSEnvironmentVariables(t *testing.T) { Value: "true", }, { - Name: "REDIS_TLS_CA_KEY", + Name: "REDIS_TLS_CA_CERT", Value: path.Join("/tls/", "test_ca.crt"), }, { @@ -1505,7 +1505,7 @@ func TestGetEnvironmentVariables(t *testing.T) { secretKey: ptr.To("test-key"), persistenceEnabled: ptr.To(true), tlsConfig: &common.TLSConfig{ - CaKeyFile: "test_ca.crt", + CaCertFile: "test_ca.crt", CertKeyFile: "test_tls.crt", KeyFile: "test_tls.key", Secret: corev1.SecretVolumeSource{ @@ -1527,7 +1527,7 @@ func TestGetEnvironmentVariables(t *testing.T) { {Name: "PERSISTENCE_ENABLED", Value: "true"}, {Name: "REDIS_ADDR", Value: "redis://localhost:26379"}, {Name: "TLS_MODE", Value: "true"}, - {Name: "REDIS_TLS_CA_KEY", Value: path.Join("/tls/", "test_ca.crt")}, + {Name: "REDIS_TLS_CA_CERT", Value: path.Join("/tls/", "test_ca.crt")}, {Name: "REDIS_TLS_CERT", Value: path.Join("/tls/", "test_tls.crt")}, {Name: "REDIS_TLS_CERT_KEY", Value: path.Join("/tls/", "test_tls.key")}, {Name: "REDIS_PASSWORD", ValueFrom: &corev1.EnvVarSource{ @@ -1683,7 +1683,7 @@ func Test_getExporterEnvironmentVariables(t *testing.T) { name: "Test with tls enabled and env var", params: containerParameters{ TLSConfig: &common.TLSConfig{ - CaKeyFile: "test_ca.crt", + CaCertFile: "test_ca.crt", CertKeyFile: "test_tls.crt", KeyFile: "test_tls.key", Secret: corev1.SecretVolumeSource{ @@ -1723,7 +1723,7 @@ func TestGenerateStatefulSetsDef(t *testing.T) { probeWithTLS := &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{"sh", "-ec", "RESP=\"$(redis-cli -h $(hostname) -p ${REDIS_PORT} --tls --cert ${REDIS_TLS_CERT} --key ${REDIS_TLS_CERT_KEY} --cacert ${REDIS_TLS_CA_KEY} ping)\"\n[ \"$RESP\" = \"PONG\" ]"}, + Command: []string{"sh", "-ec", "RESP=\"$(redis-cli -h $(hostname) -p ${REDIS_PORT} --tls --cert ${REDIS_TLS_CERT} --key ${REDIS_TLS_CERT_KEY} --cacert ${REDIS_TLS_CA_CERT} ping)\"\n[ \"$RESP\" = \"PONG\" ]"}, }, }, } @@ -1831,7 +1831,7 @@ func TestGenerateStatefulSetsDef(t *testing.T) { Value: "1.0", }, { - Name: "REDIS_TLS_CA_KEY", + Name: "REDIS_TLS_CA_CERT", Value: path.Join("/tls/", "ca.crt"), }, { diff --git a/tests/testdata/redis-cluster.yaml b/tests/testdata/redis-cluster.yaml index 448343a88a..6c3149831e 100644 --- a/tests/testdata/redis-cluster.yaml +++ b/tests/testdata/redis-cluster.yaml @@ -139,7 +139,7 @@ spec: secret: secretName: acl-secret TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/tests/testdata/redis-replication.yaml b/tests/testdata/redis-replication.yaml index 8654056cd5..c646b25a0a 100644 --- a/tests/testdata/redis-replication.yaml +++ b/tests/testdata/redis-replication.yaml @@ -112,7 +112,7 @@ spec: secret: secretName: acl-secret TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/tests/testdata/redis-sentinel.yaml b/tests/testdata/redis-sentinel.yaml index 3e22facac8..30d40d507a 100644 --- a/tests/testdata/redis-sentinel.yaml +++ b/tests/testdata/redis-sentinel.yaml @@ -91,7 +91,7 @@ spec: serviceAccountName: redis-sa terminationGracePeriodSeconds: 30 TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: diff --git a/tests/testdata/redis-standalone.yaml b/tests/testdata/redis-standalone.yaml index c2019f8fa2..f732c2edd2 100644 --- a/tests/testdata/redis-standalone.yaml +++ b/tests/testdata/redis-standalone.yaml @@ -103,7 +103,7 @@ spec: secret: secretName: acl-secret TLS: - ca: ca.key + ca: ca.crt cert: tls.crt key: tls.key secret: From 79726f2c89c4f0ed2557b92cdd2c8d1886549eae Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:44:13 +0000 Subject: [PATCH 15/20] fix: aggregate operator ClusterRole into admin (#1672) * fix: aggregate operator ClusterRole into admin Signed-off-by: Your Name * fix: add label to manager-role for RBAC aggregation Signed-off-by: Shubham Gupta --------- Signed-off-by: Your Name Signed-off-by: Shubham Gupta Co-authored-by: Your Name Co-authored-by: Shubham Gupta --- charts/redis-operator/templates/role.yaml | 1 + config/rbac/kustomization.yaml | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/charts/redis-operator/templates/role.yaml b/charts/redis-operator/templates/role.yaml index 6b4aade0c6..73dbe8c9cf 100644 --- a/charts/redis-operator/templates/role.yaml +++ b/charts/redis-operator/templates/role.yaml @@ -12,6 +12,7 @@ metadata: app.kubernetes.io/version : {{ .Chart.AppVersion }} app.kubernetes.io/component: role app.kubernetes.io/part-of : {{ .Release.Name }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" rules: - apiGroups: - redis.redis.opstreelabs.in diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 54c9aa2d27..8cd329f0f2 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -18,3 +18,15 @@ resources: # default, aiding admins in cluster management. Those roles are # not used by the {{ .ProjectName }} itself. You can comment the following lines # if you do not want those helpers be installed with your Project. + +patches: + - target: + group: rbac.authorization.k8s.io + version: v1 + kind: ClusterRole + name: manager-role + patch: | + - op: add + path: /metadata/labels + value: + rbac.authorization.k8s.io/aggregate-to-admin: "true" From b16c482cdd98aad6da20cb991e0cd469bd144056 Mon Sep 17 00:00:00 2001 From: Arpan Rajani <7279241+arpan57@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:08:27 +0000 Subject: [PATCH 16/20] fix: default leader/follower replicas to clusterSize (#1681) * fix: default leader/follower replicas to clusterSize Signed-off-by: Your Name * fix: change follower and leader replicas type to string in README Signed-off-by: Shubham Gupta * fix: update replicas type to int for leader and follower in values.yaml and README Signed-off-by: Shubham Gupta --------- Signed-off-by: Your Name Signed-off-by: Shubham Gupta Co-authored-by: Your Name Co-authored-by: Shubham Gupta --- charts/redis-cluster/README.md | 4 ++-- charts/redis-cluster/templates/follower-service.yaml | 3 ++- charts/redis-cluster/templates/follower-sm.yaml | 3 ++- charts/redis-cluster/templates/leader-service.yaml | 3 ++- charts/redis-cluster/templates/leader-sm.yaml | 3 ++- charts/redis-cluster/templates/redis-cluster.yaml | 4 ++-- charts/redis-cluster/values.yaml | 6 +++--- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/charts/redis-cluster/README.md b/charts/redis-cluster/README.md index 4206c1d217..62f367d655 100644 --- a/charts/redis-cluster/README.md +++ b/charts/redis-cluster/README.md @@ -70,7 +70,7 @@ helm delete --namespace | priorityClassName | string | `""` | | | redisCluster.clusterSize | int | `3` | Default number of replicas for both leader and follower when not explicitly set | | redisCluster.clusterVersion | string | `"v7"` | | -| redisCluster.enableMasterSlaveAntiAffinity | bool | `false` | Enable pod anti-affinity between leader and follower pods by adding the appropriate label. Notice that this requires the operator to have its mutating webhook enabled, otherwise it will only add an annotation to the RedisCluster CR. Default is false. | +| redisCluster.enableMasterSlaveAntiAffinity | bool | `false` | Enable pod anti-affinity between leader and follower pods by adding the appropriate label. Notice that this requires the operator to have its mutating webhook enabled, otherwise it will only add an annotation to the RedisCluster CR. Default is false. | | redisCluster.follower.affinity | string | `nil` | | | redisCluster.follower.livenessProbe | object | `{}` | | | redisCluster.follower.nodeSelector | string | `nil` | | @@ -103,7 +103,7 @@ helm delete --namespace | redisCluster.name | string | `""` | | | redisCluster.persistenceEnabled | bool | `true` | | | redisCluster.persistentVolumeClaimRetentionPolicy | object | `{}` | | -| redisCluster.recreateStatefulSetOnUpdateInvalid | bool | `false` | Some fields of statefulset are immutable, such as volumeClaimTemplates. When set to true, the operator will delete the statefulset and recreate it. Default is false. | +| redisCluster.recreateStatefulSetOnUpdateInvalid | bool | `false` | Some fields of statefulset are immutable, such as volumeClaimTemplates. When set to true, the operator will delete the statefulset and recreate it. Default is false. | | redisCluster.redisSecret.secretKey | string | `""` | | | redisCluster.redisSecret.secretName | string | `""` | | | redisCluster.resources | object | `{}` | | diff --git a/charts/redis-cluster/templates/follower-service.yaml b/charts/redis-cluster/templates/follower-service.yaml index 6fa6455291..26b6c28071 100644 --- a/charts/redis-cluster/templates/follower-service.yaml +++ b/charts/redis-cluster/templates/follower-service.yaml @@ -1,4 +1,5 @@ -{{- if and (gt (int .Values.redisCluster.follower.replicas) 0) (eq .Values.externalService.enabled true) }} +{{- $followerReplicas := .Values.redisCluster.follower.replicas | default .Values.redisCluster.clusterSize }} +{{- if and (gt (int $followerReplicas) 0) (eq .Values.externalService.enabled true) }} --- apiVersion: v1 kind: Service diff --git a/charts/redis-cluster/templates/follower-sm.yaml b/charts/redis-cluster/templates/follower-sm.yaml index 1c1498f126..b5137d50e6 100644 --- a/charts/redis-cluster/templates/follower-sm.yaml +++ b/charts/redis-cluster/templates/follower-sm.yaml @@ -1,4 +1,5 @@ -{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int .Values.redisCluster.follower.replicas) 0) }} +{{- $followerReplicas := .Values.redisCluster.follower.replicas | default .Values.redisCluster.clusterSize }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int $followerReplicas) 0) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor diff --git a/charts/redis-cluster/templates/leader-service.yaml b/charts/redis-cluster/templates/leader-service.yaml index e7cedd864e..cc65e6be3c 100644 --- a/charts/redis-cluster/templates/leader-service.yaml +++ b/charts/redis-cluster/templates/leader-service.yaml @@ -1,4 +1,5 @@ -{{- if and (gt (int .Values.redisCluster.leader.replicas) 0) (eq .Values.externalService.enabled true) }} +{{- $leaderReplicas := .Values.redisCluster.leader.replicas | default .Values.redisCluster.clusterSize }} +{{- if and (gt (int $leaderReplicas) 0) (eq .Values.externalService.enabled true) }} --- apiVersion: v1 kind: Service diff --git a/charts/redis-cluster/templates/leader-sm.yaml b/charts/redis-cluster/templates/leader-sm.yaml index 53216838c3..385e8f0619 100644 --- a/charts/redis-cluster/templates/leader-sm.yaml +++ b/charts/redis-cluster/templates/leader-sm.yaml @@ -1,4 +1,5 @@ -{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int .Values.redisCluster.leader.replicas) 0) }} +{{- $leaderReplicas := .Values.redisCluster.leader.replicas | default .Values.redisCluster.clusterSize }} +{{- if and (eq .Values.serviceMonitor.enabled true) (eq .Values.redisExporter.enabled true) (gt (int $leaderReplicas) 0) }} --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor diff --git a/charts/redis-cluster/templates/redis-cluster.yaml b/charts/redis-cluster/templates/redis-cluster.yaml index 8dcbb52204..c59741bfb7 100644 --- a/charts/redis-cluster/templates/redis-cluster.yaml +++ b/charts/redis-cluster/templates/redis-cluster.yaml @@ -16,14 +16,14 @@ spec: persistenceEnabled: {{ .Values.redisCluster.persistenceEnabled }} clusterVersion: {{ .Values.redisCluster.clusterVersion }} redisLeader: {{- include "redis.role" .Values.redisCluster.leader | nindent 4 }} - replicas: {{ .Values.redisCluster.leader.replicas }} + replicas: {{ .Values.redisCluster.leader.replicas | default .Values.redisCluster.clusterSize }} {{- if .Values.externalConfig.enabled }} redisConfig: additionalRedisConfig: "{{ .Values.redisCluster.name | default .Release.Name }}-ext-config" {{- end }} redisFollower: {{- include "redis.role" .Values.redisCluster.follower | nindent 4 }} - replicas: {{ .Values.redisCluster.follower.replicas }} + replicas: {{ .Values.redisCluster.follower.replicas | default .Values.redisCluster.clusterSize }} {{- if .Values.externalConfig.enabled }} redisConfig: additionalRedisConfig: "{{ .Values.redisCluster.name | default .Release.Name }}-ext-config" diff --git a/charts/redis-cluster/values.yaml b/charts/redis-cluster/values.yaml index c67540510e..559b4bc592 100644 --- a/charts/redis-cluster/values.yaml +++ b/charts/redis-cluster/values.yaml @@ -23,7 +23,7 @@ redisCluster: # memory: 128Mi minReadySeconds: 0 # -- Some fields of statefulset are immutable, such as volumeClaimTemplates. - # When set to true, the operator will delete the statefulset and recreate it. + # When set to true, the operator will delete the statefulset and recreate it. # Default is false. recreateStatefulSetOnUpdateInvalid: false # -- MaxMemoryPercentOfLimit is the percentage of the Redis container memory limit to be used as maxmemory. @@ -35,7 +35,7 @@ redisCluster: # whenScaled: Retain # -- Enable pod anti-affinity between leader and follower pods by adding the appropriate label. # Notice that this requires the operator to have its mutating webhook enabled, - # otherwise it will only add an annotation to the RedisCluster CR. + # otherwise it will only add an annotation to the RedisCluster CR. # Default is false. enableMasterSlaveAntiAffinity: false leader: @@ -83,7 +83,7 @@ redisCluster: # successThreshold: 1 # failureThreshold: 4 # initialDelaySeconds: 15 - + follower: # -- Number of Redis follower (slave) nodes. If not set, uses clusterSize value replicas: 3 From a4669608f4e968d33dca6b4d67d5aa29e2b24171 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:13:02 +0000 Subject: [PATCH 17/20] chore(deps): bump hadolint/hadolint-action from 3.1.0 to 3.3.0 (#1554) Bumps [hadolint/hadolint-action](https://github.com/hadolint/hadolint-action) from 3.1.0 to 3.3.0. - [Release notes](https://github.com/hadolint/hadolint-action/releases) - [Changelog](https://github.com/hadolint/hadolint-action/blob/master/.releaserc) - [Commits](https://github.com/hadolint/hadolint-action/compare/v3.1.0...v3.3.0) --- updated-dependencies: - dependency-name: hadolint/hadolint-action dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 45a8b2d674..4a02581af5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@v5 - name: Execute dockerlinter - uses: hadolint/hadolint-action@v3.1.0 + uses: hadolint/hadolint-action@v3.3.0 with: dockerfile: Dockerfile ignore: DL3007,DL3018 From c8e22c136e31c0ed4530a66abc2702ad37fb59ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:16:30 +0000 Subject: [PATCH 18/20] chore(deps): bump actions/setup-python from 5 to 6 (#1555) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-charts.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-charts.yaml b/.github/workflows/publish-charts.yaml index e130db64f9..19084e07f0 100644 --- a/.github/workflows/publish-charts.yaml +++ b/.github/workflows/publish-charts.yaml @@ -22,7 +22,7 @@ jobs: with: version: v3.5.4 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.9' check-latest: true From 7ad5c9e23a15425a98410693d3ec7e76d926cd22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:34:15 +0000 Subject: [PATCH 19/20] chore(deps): bump actions/stale from 9 to 10 (#1553) Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v9...v10) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '10' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 92202fa67c..1e142cf546 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -17,7 +17,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} ascending: true From 22e39a17963f46a600f9c025b213ee9f1fc26fe0 Mon Sep 17 00:00:00 2001 From: Aleksandrov Aleksandr Date: Fri, 30 Jan 2026 17:07:52 +0200 Subject: [PATCH 20/20] fix: do not create empty PVC when storage has only volumeMount When spec.storage contains only volumeMount entries (e.g. emptyDir) without volumeClaimTemplate, the operator incorrectly set PersistenceEnabled=true, causing an empty volumeClaimTemplates entry in the StatefulSet. Check whether volumeClaimTemplate is actually populated before enabling persistence. Signed-off-by: Aleksandrov Aleksandr --- internal/k8sutils/redis-replication.go | 4 +- internal/k8sutils/redis-standalone.go | 4 +- internal/k8sutils/statefulset.go | 11 +++++ internal/k8sutils/statefulset_test.go | 56 ++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/internal/k8sutils/redis-replication.go b/internal/k8sutils/redis-replication.go index 319c094d15..86b07dce24 100644 --- a/internal/k8sutils/redis-replication.go +++ b/internal/k8sutils/redis-replication.go @@ -189,7 +189,7 @@ func generateRedisReplicationContainerParams(cr *rrvb2.RedisReplication) contain if cr.Spec.LivenessProbe != nil { containerProp.LivenessProbe = cr.Spec.LivenessProbe } - if cr.Spec.Storage != nil { + if storageHasVolumeClaimTemplate(cr.Spec.Storage) { containerProp.PersistenceEnabled = &trueProperty } if cr.Spec.TLS != nil { @@ -225,7 +225,7 @@ func generateRedisReplicationInitContainerParams(cr *rrvb2.RedisReplication) ini initcontainerProp.AdditionalVolume = cr.Spec.Storage.VolumeMount.Volume initcontainerProp.AdditionalMountPath = cr.Spec.Storage.VolumeMount.MountPath } - if cr.Spec.Storage != nil { + if storageHasVolumeClaimTemplate(cr.Spec.Storage) { initcontainerProp.PersistenceEnabled = &trueProperty } } diff --git a/internal/k8sutils/redis-standalone.go b/internal/k8sutils/redis-standalone.go index 69338ec90d..36fd34f63c 100644 --- a/internal/k8sutils/redis-standalone.go +++ b/internal/k8sutils/redis-standalone.go @@ -197,7 +197,7 @@ func generateRedisStandaloneContainerParams(cr *rvb2.Redis) containerParameters if cr.Spec.LivenessProbe != nil { containerProp.LivenessProbe = cr.Spec.LivenessProbe } - if cr.Spec.Storage != nil { + if storageHasVolumeClaimTemplate(cr.Spec.Storage) { containerProp.PersistenceEnabled = &trueProperty } if cr.Spec.TLS != nil { @@ -233,7 +233,7 @@ func generateRedisStandaloneInitContainerParams(cr *rvb2.Redis) initContainerPar initcontainerProp.AdditionalVolume = cr.Spec.Storage.VolumeMount.Volume initcontainerProp.AdditionalMountPath = cr.Spec.Storage.VolumeMount.MountPath } - if cr.Spec.Storage != nil { + if storageHasVolumeClaimTemplate(cr.Spec.Storage) { initcontainerProp.PersistenceEnabled = &trueProperty } } diff --git a/internal/k8sutils/statefulset.go b/internal/k8sutils/statefulset.go index 37d2780365..ca60f40ff0 100644 --- a/internal/k8sutils/statefulset.go +++ b/internal/k8sutils/statefulset.go @@ -250,6 +250,17 @@ func syncManagedFields(stored, new *appsv1.StatefulSet) { new.ManagedFields = stored.ManagedFields } +// storageHasVolumeClaimTemplate checks if the Storage has a meaningful VolumeClaimTemplate defined +// (i.e., not just the zero value). This distinguishes between storage configured with only +// volumeMount (e.g. emptyDir) vs. storage that actually requests a PersistentVolumeClaim. +func storageHasVolumeClaimTemplate(storage *commonapi.Storage) bool { + if storage == nil { + return false + } + vct := storage.VolumeClaimTemplate + return len(vct.Spec.AccessModes) > 0 || vct.Spec.Resources.Requests != nil || vct.Spec.StorageClassName != nil || vct.Spec.VolumeName != "" +} + // hasVolumeClaimTemplates checks if the StatefulSet has VolumeClaimTemplates and if their counts match. func hasVolumeClaimTemplates(new, stored *appsv1.StatefulSet) bool { return len(new.Spec.VolumeClaimTemplates) >= 1 && len(new.Spec.VolumeClaimTemplates) == len(stored.Spec.VolumeClaimTemplates) diff --git a/internal/k8sutils/statefulset_test.go b/internal/k8sutils/statefulset_test.go index 5b6d70841f..6fa75ff834 100644 --- a/internal/k8sutils/statefulset_test.go +++ b/internal/k8sutils/statefulset_test.go @@ -48,6 +48,62 @@ func TestGenerateAuthAndTLSArgs(t *testing.T) { } } +func TestStorageHasVolumeClaimTemplate(t *testing.T) { + tests := []struct { + name string + storage *common.Storage + want bool + }{ + {"nil storage", nil, false}, + {"empty VolumeClaimTemplate", &common.Storage{}, false}, + {"only volumeMount", &common.Storage{ + VolumeMount: common.AdditionalVolume{ + Volume: []corev1.Volume{{Name: "data", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}}, + MountPath: []corev1.VolumeMount{{Name: "data", MountPath: "/data"}}, + }, + }, false}, + {"with AccessModes", &common.Storage{ + VolumeClaimTemplate: corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + }, + }, + }, true}, + {"with Resources.Requests", &common.Storage{ + VolumeClaimTemplate: corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, + }, true}, + {"with StorageClassName", &common.Storage{ + VolumeClaimTemplate: corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: ptr.To("standard"), + }, + }, + }, true}, + {"with VolumeName", &common.Storage{ + VolumeClaimTemplate: corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + VolumeName: "pv-data", + }, + }, + }, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := storageHasVolumeClaimTemplate(tt.storage) + assert.Equal(t, tt.want, got) + }) + } +} + func TestGeneratePreStopCommand(t *testing.T) { tests := []struct { name string