Skip to content
Draft
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
# Re-include scripts
!internal/controller/scripts/readiness-check.sh
!internal/controller/scripts/liveness-check.sh
!internal/controller/scripts/start-valkey.sh
8 changes: 8 additions & 0 deletions api/v1alpha1/valkeycluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ type ValkeyClusterSpec struct {
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`

// Allow external access to each Valkey pod via a NodePort Service
// +optional
AllowExternalAccess bool `json:"allowExternalAccess,omitempty"`

// Use a one-statefulset-per-pod model instead of one-deployment-per-pod.
// +optional
UseStatefulSetPerPod bool `json:"useStatefulSetPerPod,omitempty"`

// Metrics exporter options
// +kubebuilder:default:={enabled:true}
// +optional
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/valkey.io_valkeyclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,10 @@ spec:
x-kubernetes-list-type: atomic
type: object
type: object
allowExternalAccess:
description: Allow external access to each Valkey pod via a NodePort
Service
type: boolean
exporter:
default:
enabled: true
Expand Down Expand Up @@ -1164,6 +1168,9 @@ spec:
type: string
type: object
type: array
useStatefulSetPerPod:
description: Use a one-statefulset-per-pod model instead of one-deployment-per-pod.
type: boolean
type: object
status:
default:
Expand Down
17 changes: 10 additions & 7 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- deployments
- statefulsets
verbs:
- create
- delete
Expand All @@ -37,13 +47,6 @@ rules:
- patch
- update
- watch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- apiGroups:
- valkey.io
resources:
Expand Down
81 changes: 70 additions & 11 deletions internal/controller/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package controller

import (
"strconv"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -34,8 +36,53 @@
Image: image,
Resources: cluster.Spec.Resources,
Command: []string{
"valkey-server",
"/config/valkey.conf",
"/scripts/start-valkey.sh",
},
Env: []corev1.EnvVar{
{
Name: "VALKEY_NODE_IP",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.hostIP",
},
},
},
{
Name: "VALKEY_ENABLE_EXTERNAL_ANNOUNCE",
Value: strconv.FormatBool(cluster.Spec.AllowExternalAccess),
},
{
Name: "VALKEY_NODE_PORT",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.annotations['" + ExternalAccessNodePortAnnotationKey + "']",
},
},
},
{
Name: "VALKEY_NODE_BUS_PORT",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.annotations['" + ExternalAccessBusPortAnnotationKey + "']",
},
},
},
{
Name: "POD_NAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
{
Name: "VALKEY_BASE_NODE_PORT",

Check failure on line 79 in internal/controller/deployment.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

File is not properly formatted (gofmt)
Value: "30000",
},
{
Name: "VALKEY_REPLICA_COUNT",
Value: strconv.Itoa(int(cluster.Spec.Replicas)),
},
},
Ports: []corev1.ContainerPort{
{
Expand Down Expand Up @@ -105,6 +152,10 @@
MountPath: "/config",
ReadOnly: true,
},
{
Name: "data",
MountPath: "/data",
},
},
},
}
Expand All @@ -116,22 +167,25 @@
return containers
}

func createClusterDeployment(cluster *valkeyiov1alpha1.ValkeyCluster) *appsv1.Deployment {
func createClusterDeployment(cluster *valkeyiov1alpha1.ValkeyCluster, replicas int32, name string) *appsv1.Deployment {
containers := generateContainersDef(cluster)
deployment := &appsv1.Deployment{
labelsWithTarget := copyMap(labels(cluster))
labelsWithTarget[ExternalAccessTargetKey] = name

return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
GenerateName: cluster.Name + "-",
Namespace: cluster.Namespace,
Labels: labels(cluster),
Name: name,
Namespace: cluster.Namespace,
Labels: labels(cluster),
},
Spec: appsv1.DeploymentSpec{
Replicas: func(i int32) *int32 { return &i }(1),
Replicas: func(i int32) *int32 { return &i }(replicas),
Selector: &metav1.LabelSelector{
MatchLabels: labels(cluster),
MatchLabels: labelsWithTarget,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels(cluster),
Labels: labelsWithTarget,
},
Spec: corev1.PodSpec{
Containers: containers,
Expand Down Expand Up @@ -160,10 +214,15 @@
},
},
},
{
Name: "data",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
},
},
},
},
}
return deployment
}
64 changes: 7 additions & 57 deletions internal/controller/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
Expand All @@ -40,70 +38,22 @@ func TestCreateClusterDeployment(t *testing.T) {
Image: "container:version",
},
}
d := createClusterDeployment(cluster)
if d.Name != "" {
t.Errorf("Expected empty name field, got %v", d.Name)
d := createClusterDeployment(cluster, 2, "mycluster-0-0")
if d.Name != "mycluster-0-0" {
t.Errorf("Expected %v, got %v", "mycluster-0-0", d.Name)
}
if d.GenerateName != "mycluster-" {
t.Errorf("Expected %v, got %v", "mycluster-", d.GenerateName)
}
if *d.Spec.Replicas != 1 {
t.Errorf("Expected %v, got %v", 1, d.Spec.Replicas)
if *d.Spec.Replicas != 2 {
t.Errorf("Expected %v, got %v", 2, d.Spec.Replicas)
}
if len(d.Spec.Template.Spec.Containers) != 1 {
t.Errorf("Expected %v, got %v", 1, len(d.Spec.Template.Spec.Containers))
}
if d.Spec.Template.Spec.Containers[0].Image != "container:version" {
t.Errorf("Expected %v, got %v", "container:version", d.Spec.Template.Spec.Containers[0].Image)
}
}

func TestCreateClusterDeployment_SetsPodAntiAffinity(t *testing.T) {
antiAffinity := &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app.kubernetes.io/instance": "mycluster",
},
},
TopologyKey: "kubernetes.io/hostname",
},
},
},
}
cluster := &valkeyv1.ValkeyCluster{
ObjectMeta: metav1.ObjectMeta{Name: "mycluster"},
Spec: valkeyv1.ValkeyClusterSpec{
Image: "container:version",
Affinity: antiAffinity,
},
}

d := createClusterDeployment(cluster)

got := d.Spec.Template.Spec.Affinity
if diff := cmp.Diff(antiAffinity, got, cmpopts.EquateEmpty()); diff != "" {
t.Fatalf("affinity mismatch (-want +got):\n%s", diff)
}
}

func TestCreateClusterDeployment_SetsNodeSelector(t *testing.T) {
nodeSelector := map[string]string{
"disktype": "ssd",
if d.Spec.Template.Labels[ExternalAccessTargetKey] != "mycluster-0-0" {
t.Errorf("Expected %v, got %v", "mycluster-0-0", d.Spec.Template.Labels[ExternalAccessTargetKey])
}
cluster := &valkeyv1.ValkeyCluster{
ObjectMeta: metav1.ObjectMeta{Name: "mycluster"},
Spec: valkeyv1.ValkeyClusterSpec{
Image: "container:version",
NodeSelector: nodeSelector,
},
}

d := createClusterDeployment(cluster)

assert.Equal(t, nodeSelector, d.Spec.Template.Spec.NodeSelector, "node selector should match spec")
}

func TestGenerateContainersDef(t *testing.T) {
Expand Down
Loading
Loading