Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Redis Operator creates/configures/manages redis-failovers atop Kubernetes.
- [Custom SecurityContext](#custom-securitycontext)
- [Custom containerSecurityContext at container level](#custom-containersecuritycontext-at-container-level)
- [Custom command](#custom-command)
- [Custom environment variables](#custom-environment-variables)
- [Custom Priority Class](#custom-priority-class)
- [Custom Service Account](#custom-service-account)
- [Custom Pod Annotations](#custom-pod-annotations)
Expand Down Expand Up @@ -257,6 +258,13 @@ By default, redis and sentinel will be called with the basic command, giving the

If necessary, this command can be changed with the `command` option inside redis/sentinel spec. An example can be found in the [custom command example file](example/redisfailover/custom-command.yaml).

### Custom environment variables

Extra environment variables can be injected into the redis and sentinel **main** containers via
`redis.env` / `sentinel.env` (standard Kubernetes `EnvVar` entries). The operator's own variables
(`REDIS_ADDR`, `REDIS_PORT`, `REDIS_USER`, `REDIS_PASSWORD`) always take precedence, so a
user-supplied variable that reuses one of those names cannot override it.

### Custom Priority Class
In order to use a custom Kubernetes [Priority Class](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass) for Redis and/or Sentinel pods, you can set the `priorityClassName` in the redis/sentinel spec, this attribute has no default and depends on the specific cluster configuration. **Note:** the operator doesn't create the referenced `Priority Class` resource.

Expand Down
2 changes: 2 additions & 0 deletions api/redisfailover/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type RedisSettings struct {
Replicas int32 `json:"replicas,omitempty"`
Port int32 `json:"port,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
Env []corev1.EnvVar `json:"env,omitempty"`
CustomConfig []string `json:"customConfig,omitempty"`
CustomCommandRenames []RedisCommandRename `json:"customCommandRenames,omitempty"`
Command []string `json:"command,omitempty"`
Expand Down Expand Up @@ -84,6 +85,7 @@ type SentinelSettings struct {
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
Replicas int32 `json:"replicas,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
Env []corev1.EnvVar `json:"env,omitempty"`
CustomConfig []string `json:"customConfig,omitempty"`
Command []string `json:"command,omitempty"`
StartupConfigMap string `json:"startupConfigMap,omitempty"`
Expand Down
14 changes: 14 additions & 0 deletions api/redisfailover/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

314 changes: 314 additions & 0 deletions charts/redisoperator/crds/databases.spotahome.com_redisfailovers.yaml

Large diffs are not rendered by default.

314 changes: 314 additions & 0 deletions manifests/databases.spotahome.com_redisfailovers.yaml

Large diffs are not rendered by default.

314 changes: 314 additions & 0 deletions manifests/kustomize/base/databases.spotahome.com_redisfailovers.yaml

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion operator/redisfailover/service/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,12 @@ func generateRedisStatefulSet(rf *redisfailoverv1.RedisFailover, labels map[stri
ss.Spec.Template.Spec.Containers = append(ss.Spec.Template.Spec.Containers, extraContainers...)
}

// User-supplied env is placed before the operator-injected vars so that, on
// duplicate names (Kubernetes last-wins), the operator's REDIS_ADDR/PORT/USER/
// PASSWORD keep precedence and can't be silently overridden.
redisEnv := getRedisEnv(rf)
ss.Spec.Template.Spec.Containers[0].Env = append(ss.Spec.Template.Spec.Containers[0].Env, redisEnv...)
mainEnv := append(ss.Spec.Template.Spec.Containers[0].Env, rf.Spec.Redis.Env...)
ss.Spec.Template.Spec.Containers[0].Env = append(mainEnv, redisEnv...)

return ss
}
Expand Down Expand Up @@ -627,6 +631,7 @@ func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[st
Image: rf.Spec.Sentinel.Image,
ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy),
SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ContainerSecurityContext),
Env: rf.Spec.Sentinel.Env,
Ports: []corev1.ContainerPort{
{
Name: "sentinel",
Expand Down
88 changes: 88 additions & 0 deletions operator/redisfailover/service/pod_env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package service_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/dnse-tech/redis-operator/log"
"github.com/dnse-tech/redis-operator/metrics"
mK8SService "github.com/dnse-tech/redis-operator/mocks/service/k8s"
rfservice "github.com/dnse-tech/redis-operator/operator/redisfailover/service"
)

// lastValueOf returns the value of the last env var with the given name, which
// is the one that takes effect under Kubernetes' last-wins semantics.
func lastValueOf(env []corev1.EnvVar, name string) (string, bool) {
value, found := "", false
for _, e := range env {
if e.Name == name {
value, found = e.Value, true
}
}
return value, found
}

func hasEnv(env []corev1.EnvVar, name, value string) bool {
for _, e := range env {
if e.Name == name && e.Value == value {
return true
}
}
return false
}

func TestRedisMainContainerEnv(t *testing.T) {
assert := assert.New(t)

rf := generateRF()
rf.Spec.Redis.Port = 6379
rf.Spec.Redis.Env = []corev1.EnvVar{
{Name: "MY_CUSTOM", Value: "hello"},
// Duplicates an operator-injected var; the operator value must still win.
{Name: "REDIS_PORT", Value: "1"},
}

var got []corev1.EnvVar
ms := &mK8SService.Services{}
ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil)
ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) {
got = args.Get(1).(*appsv1.StatefulSet).Spec.Template.Spec.Containers[0].Env
}).Return(nil)

client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy)
err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{})

assert.NoError(err)
assert.True(hasEnv(got, "MY_CUSTOM", "hello"), "custom user env must be injected")
// Operator REDIS_PORT (6379) is appended after the user's, so it wins.
port, found := lastValueOf(got, "REDIS_PORT")
assert.True(found)
assert.Equal("6379", port, "operator-injected env must take precedence over a user duplicate")
}

func TestSentinelMainContainerEnv(t *testing.T) {
assert := assert.New(t)

rf := generateRF()
rf.Spec.Sentinel.Env = []corev1.EnvVar{
{Name: "MY_CUSTOM", Value: "world"},
}

var got []corev1.EnvVar
ms := &mK8SService.Services{}
ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil)
ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) {
got = args.Get(1).(*appsv1.Deployment).Spec.Template.Spec.Containers[0].Env
}).Return(nil)

client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy)
err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{})

assert.NoError(err)
assert.True(hasEnv(got, "MY_CUSTOM", "world"), "custom user env must be injected into sentinel")
}
Loading