Repository: OT-CONTAINER-KIT/redis-operator

Issue:
Cluster domain is not configurable
redis-operator version: 0.22.1

**Does this issue reproduce with the latest release?**
Yes


</pre></details>

**What did you do?**

Cluster domain is not configurable.


**What did you expect to see?**
I need the cluster domain to be configurable and not hardcoded defaulted to `cluster.local`.
A lot of clusters do not run on the default `cluster.local`.



Discussion:

Solution patch:
diff --git a/charts/redis-operator/README.md b/charts/redis-operator/README.md
index 97f02ea2..b75e0365 100644
--- a/charts/redis-operator/README.md
+++ b/charts/redis-operator/README.md
@@ -122,6 +122,7 @@ kubectl create secret tls <webhook-server-cert> --key tls.key --cert tls.crt -n
 | redisOperator.podLabels | object | `{}` |  |
 | redisOperator.pprof.bindAddress | string | `":6060"` |  |
 | redisOperator.pprof.enabled | bool | `false` |  |
+| redisOperator.serviceDNSDomain | string | `"cluster.local"` | The DNS domain suffix used for Kubernetes service discovery. Default is "cluster.local". Change this if your cluster uses a custom DNS domain. |
 | redisOperator.watchNamespace | string | `""` |  |
 | redisOperator.webhook | bool | `false` |  |
 | replicas | int | `1` |  |
diff --git a/charts/redis-operator/templates/operator-deployment.yaml b/charts/redis-operator/templates/operator-deployment.yaml
index 3e3dbd27..3e528de3 100644
--- a/charts/redis-operator/templates/operator-deployment.yaml
+++ b/charts/redis-operator/templates/operator-deployment.yaml
@@ -94,6 +94,10 @@ spec:
         - name: WATCH_NAMESPACE
           value: {{ .Values.redisOperator.watchNamespace | quote }}
         {{- end }}
+        {{- if .Values.redisOperator.serviceDNSDomain }}
+        - name: SERVICE_DNS_DOMAIN
+          value: {{ .Values.redisOperator.serviceDNSDomain | quote }}
+        {{- end }}
         {{- range $env := .Values.redisOperator.env }}
         - name: {{ $env.name }}
           value: {{ $env.value | quote }}
diff --git a/charts/redis-operator/values.yaml b/charts/redis-operator/values.yaml
index f5b9288f..f601d2e6 100644
--- a/charts/redis-operator/values.yaml
+++ b/charts/redis-operator/values.yaml
@@ -16,6 +16,9 @@ redisOperator:
   extraArgs: []
   # When not specified, the operator will watch all namespaces. It can be set to a specific namespace or multiple namespaces separated by commas.
   watchNamespace: ""
+  # -- The DNS domain suffix used for Kubernetes service discovery.
+  # Default is "cluster.local". Change this if your cluster uses a custom DNS domain.
+  serviceDNSDomain: "cluster.local"
   env: []
   # If set to true, webhook server will be enabled for masterSlaveAntiAffinity feature
   # When enabled, you need to either:
diff --git a/internal/controller/common/redis/heal.go b/internal/controller/common/redis/heal.go
index 991d781b..d6d97dca 100644
--- a/internal/controller/common/redis/heal.go
+++ b/internal/controller/common/redis/heal.go
@@ -10,6 +10,7 @@ import (
 	commonapi "github.com/OT-CONTAINER-KIT/redis-operator/api/common/v1beta2"
 	rsvb2 "github.com/OT-CONTAINER-KIT/redis-operator/api/redissentinel/v1beta2"
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/controller/common"
+	"github.com/OT-CONTAINER-KIT/redis-operator/internal/env"
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/service/redis"
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/util/cryptutil"
 	v1 "k8s.io/api/core/v1"
@@ -254,7 +255,7 @@ func createConnectionInfo(ctx context.Context, pod v1.Pod, password string, tlsC
 	// Configure TLS if enabled
 	if tlsConfig != nil && tlsConfig.Secret.SecretName != "" {
 		serviceName := common.GetHeadlessServiceNameFromPodName(pod.Name)
-		connInfo.Host = fmt.Sprintf("%s.%s.%s.svc.cluster.local", pod.Name, serviceName, namespace)
+		connInfo.Host = fmt.Sprintf("%s.%s.%s.svc.%s", pod.Name, serviceName, namespace, env.GetServiceDNSDomain())
 		// Get TLS configuration
 		tlsCfg := getRedisTLSConfig(ctx, k8sClient, namespace, tlsConfig.Secret.SecretName)
 		connInfo.TLSConfig = tlsCfg
diff --git a/internal/controller/redissentinel/redissentinel_controller.go b/internal/controller/redissentinel/redissentinel_controller.go
index 61f6594b..d2b6cbc5 100644
--- a/internal/controller/redissentinel/redissentinel_controller.go
+++ b/internal/controller/redissentinel/redissentinel_controller.go
@@ -10,6 +10,7 @@ import (
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/controller/common"
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/controller/common/redis"
 	intctrlutil "github.com/OT-CONTAINER-KIT/redis-operator/internal/controllerutil"
+	"github.com/OT-CONTAINER-KIT/redis-operator/internal/env"
 	"github.com/OT-CONTAINER-KIT/redis-operator/internal/k8sutils"
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -132,7 +133,7 @@ func (r *RedisSentinelReconciler) reconcileSentinel(ctx context.Context, instanc
 		return intctrlutil.RequeueE(ctx, err, "")
 	} else {
 		if instance.Spec.RedisSentinelConfig.ResolveHostnames == "yes" {
-			monitorAddr = fmt.Sprintf("%s.%s.%s.svc.cluster.local", master.Name, common.GetHeadlessServiceNameFromPodName(master.Name), rr.Namespace)
+			monitorAddr = fmt.Sprintf("%s.%s.%s.svc.%s", master.Name, common.GetHeadlessServiceNameFromPodName(master.Name), rr.Namespace, env.GetServiceDNSDomain())
 		} else {
 			monitorAddr = master.Status.PodIP
 		}
diff --git a/internal/env/env.go b/internal/env/env.go
index 75cff703..97c1133c 100644
--- a/internal/env/env.go
+++ b/internal/env/env.go
@@ -20,6 +20,8 @@ import (
 	"os"
 	"strconv"
 	"strings"
+
+	"github.com/OT-CONTAINER-KIT/redis-operator/internal/util"
 )
 
 // Environment variable keys
@@ -38,8 +40,14 @@ const (
 
 	// OperatorImageEnv defines the image of the operator
 	OperatorImageEnv = "OPERATOR_IMAGE"
+
+	ServiceDNSDomain = "SERVICE_DNS_DOMAIN"
 )
 
+func GetServiceDNSDomain() string {
+	return util.Coalesce(os.Getenv(ServiceDNSDomain), "cluster.local")
+}
+
 // GetOperatorImage returns the image of the operator
 func GetOperatorImage() string {
 	return os.Getenv(OperatorImageEnv)
diff --git a/internal/k8sutils/redis.go b/internal/k8sutils/redis.go
index c7a32435..18785fc6 100644
--- a/internal/k8sutils/redis.go
+++ b/internal/k8sutils/redis.go
@@ -15,6 +15,7 @@ import (
 	rcvb2 "github.com/OT-CONTAINER-KIT/redis-operator/api/rediscluster/v1beta2"
 	rrvb2 "github.com/OT-CONTAINER-KIT/redis-operator/api/redisreplication/v1beta2"
 	common "github.com/OT-CONTAINER-KIT/redis-operator/internal/controller/common"
+	"github.com/OT-CONTAINER-KIT/redis-operator/internal/env"
 	retry "github.com/avast/retry-go"
 	redis "github.com/redis/go-redis/v9"
 	"github.com/samber/lo"
@@ -33,7 +34,7 @@ type RedisDetails struct {
 }
 
 func (rd *RedisDetails) FQDN() string {
-	return fmt.Sprintf("%s.%s.%s.svc", rd.PodName, common.GetHeadlessServiceNameFromPodName(rd.PodName), rd.Namespace)
+	return fmt.Sprintf("%s.%s.%s.svc.%s", rd.PodName, common.GetHeadlessServiceNameFromPodName(rd.PodName), rd.Namespace, env.GetServiceDNSDomain())
 }
 
 func (rd *RedisDetails) String() string {
@@ -692,7 +693,7 @@ func configureRedisReplicationClient(ctx context.Context, client kubernetes.Inte
 }
 
 func getRedisReplicationHostname(redisInfo RedisDetails, cr *rrvb2.RedisReplication) string {
-	return fmt.Sprintf("%s.%s-headless.%s.svc.cluster.local", redisInfo.PodName, cr.Name, cr.Namespace)
+	return fmt.Sprintf("%s.%s-headless.%s.svc.%s", redisInfo.PodName, cr.Name, cr.Namespace, env.GetServiceDNSDomain())
 }
 
 // Get Redis nodes by it's role i.e. master, slave and sentinel



Test patch:
diff --git a/internal/k8sutils/redis_test.go b/internal/k8sutils/redis_test.go
index b8e10a09..97c8ab1a 100644
--- a/internal/k8sutils/redis_test.go
+++ b/internal/k8sutils/redis_test.go
@@ -339,9 +339,9 @@ func TestCreateMultipleLeaderRedisCommand(t *testing.T) {
 			},
 			expectedCommands: []string{
 				"redis-cli", "--cluster", "create",
-				"mycluster-leader-0.mycluster-leader-headless.default.svc:6379",
-				"mycluster-leader-1.mycluster-leader-headless.default.svc:6379",
-				"mycluster-leader-2.mycluster-leader-headless.default.svc:6379",
+				"mycluster-leader-0.mycluster-leader-headless.default.svc.cluster.local:6379",
+				"mycluster-leader-1.mycluster-leader-headless.default.svc.cluster.local:6379",
+				"mycluster-leader-2.mycluster-leader-headless.default.svc.cluster.local:6379",
 				"--cluster-yes",
 			},
 		},
@@ -455,8 +455,8 @@ func TestCreateRedisReplicationCommand(t *testing.T) {
 			},
 			expectedCommand: []string{
 				"redis-cli", "--cluster", "add-node",
-				"redis-cluster-follower-0.redis-cluster-follower-headless.default.svc:6379",
-				"redis-cluster-leader-0.redis-cluster-leader-headless.default.svc:6379",
+				"redis-cluster-follower-0.redis-cluster-follower-headless.default.svc.cluster.local:6379",
+				"redis-cluster-leader-0.redis-cluster-leader-headless.default.svc.cluster.local:6379",
 				"--cluster-slave",
 				"-a", "password",
 			},
@@ -495,8 +495,8 @@ func TestCreateRedisReplicationCommand(t *testing.T) {
 			},
 			expectedCommand: []string{
 				"redis-cli", "--cluster", "add-node",
-				"redis-cluster-follower-0.redis-cluster-follower-headless.default.svc:6379",
-				"redis-cluster-leader-0.redis-cluster-leader-headless.default.svc:6379",
+				"redis-cluster-follower-0.redis-cluster-follower-headless.default.svc.cluster.local:6379",
+				"redis-cluster-leader-0.redis-cluster-leader-headless.default.svc.cluster.local:6379",
 				"--cluster-slave",
 			},
 		},