Skip to content
Closed
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
build-operator:
runs-on: ubuntu-latest
env:
IMG: ghcr.io/dragonflydb/operator:${{ github.sha }}
IMG: ghcr.io/mvasilenko/dragonfly-operator:${{ github.sha }}

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
needs: build-operator
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
env:
IMG: ghcr.io/dragonflydb/operator:${{ github.ref_name }}
IMG: ghcr.io/mvasilenko/dragonfly-operator:${{ github.ref_name }}
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -84,15 +84,15 @@ jobs:

helm package charts/dragonfly-operator

helm push dragonfly-operator-${{ env.VERSION }}.tgz oci://ghcr.io/dragonflydb/dragonfly-operator/helm
helm push dragonfly-operator-${{ env.VERSION }}.tgz oci://ghcr.io/mvasilenko/dragonfly-operator/helm

- name: Build and Publish image into GHCR
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
push: true
tags: ghcr.io/dragonflydb/operator:${{ github.ref_name }}
tags: ghcr.io/mvasilenko/dragonfly-operator:${{ github.ref_name }}
platforms: |
linux/amd64
linux/arm64
Expand All @@ -112,6 +112,6 @@ jobs:
body: |
Release ${{ github.ref_name }}

Docker image: ghcr.io/dragonflydb/operator:${{ github.ref_name }}
Helm chart: oci://ghcr.io/dragonflydb/dragonfly-operator/helm
Docker image: ghcr.io/mvasilenko/dragonfly-operator:${{ github.ref_name }}
Helm chart: oci://ghcr.io/mvasilenko/dragonfly-operator/helm
draft: true
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,39 @@ kubectl patch dragonfly dragonfly-sample --type merge -p '{"spec":{"resources":{

To add authentication to the dragonfly pods, you either set the `DFLY_requirepass` environment variable, or add the `--requirepass` argument.

### Customising health-check probe scripts

The operator generates default liveness, readiness, and startup probe scripts and mounts them via ConfigMaps. You can replace any probe with your own script using the `custom*ProbeConfigMap` fields.

Scripts run inside the Dragonfly container and have access to `HEALTHCHECK_PORT` (admin port 9999 — no TLS, no auth).

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: dragonfly-sample-probes
namespace: default
data:
liveness-check.sh: |
#!/bin/sh
RESPONSE=$(timeout 4 redis-cli -h localhost -p ${HEALTHCHECK_PORT:-9999} PING 2>/dev/null)
case "$RESPONSE" in
PONG|*LOADING*) exit 0 ;;
*) exit 1 ;;
esac
---
apiVersion: dragonflydb.io/v1alpha1
kind: Dragonfly
metadata:
name: dragonfly-sample
spec:
replicas: 1
customLivenessProbeConfigMap:
name: dragonfly-sample-probes
```

> **Override precedence:** `spec.additionalVolumes` with a matching volume name (`liveness-probe`, `readiness-probe`, `startup-probe`) takes precedence over `custom*ProbeConfigMap`. Do not use both for the same probe.

### Deleting a Dragonfly instance

To delete a Dragonfly instance, you can run
Expand Down
15 changes: 15 additions & 0 deletions api/v1alpha1/dragonfly_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ type DragonflySpec struct {
// +optional
// +kubebuilder:validation:Optional
Pdb *PdbSpec `json:"pdb,omitempty"`

// (Optional) Custom ConfigMap with key "liveness-check.sh" to override the default liveness probe.
// +optional
// +kubebuilder:validation:Optional
CustomLivenessProbeConfigMap *corev1.LocalObjectReference `json:"customLivenessProbeConfigMap,omitempty"`

// (Optional) Custom ConfigMap with key "readiness-check.sh" to override the default readiness probe.
// +optional
// +kubebuilder:validation:Optional
CustomReadinessProbeConfigMap *corev1.LocalObjectReference `json:"customReadinessProbeConfigMap,omitempty"`

// (Optional) Custom ConfigMap with key "startup-check.sh" to override the default startup probe.
// +optional
// +kubebuilder:validation:Optional
CustomStartupProbeConfigMap *corev1.LocalObjectReference `json:"customStartupProbeConfigMap,omitempty"`
}

// PdbSpec defines the desired state of the PodDisruptionBudget
Expand Down
15 changes: 15 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

4 changes: 2 additions & 2 deletions charts/dragonfly-operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v1.5.0
version: v1.5.4

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v1.5.0"
appVersion: "v1.5.4"
2 changes: 1 addition & 1 deletion charts/dragonfly-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ rbacProxy:

manager:
image:
repository: docker.dragonflydb.io/dragonflydb/operator
repository: ghcr.io/mvasilenko/dragonfly-operator
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
Expand Down
45 changes: 45 additions & 0 deletions config/crd/bases/dragonflydb.io_dragonflies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4461,6 +4461,51 @@ spec:
type: string
type: object
type: object
customLivenessProbeConfigMap:
description: (Optional) Custom ConfigMap with key "liveness-check.sh"
to override the default liveness probe.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
customReadinessProbeConfigMap:
description: (Optional) Custom ConfigMap with key "readiness-check.sh"
to override the default readiness probe.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
customStartupProbeConfigMap:
description: (Optional) Custom ConfigMap with key "startup-check.sh"
to override the default startup probe.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
enableReplicationReadinessGate:
description: |-
(Optional) When enabled, adds a custom readiness gate to pods that prevents
Expand Down
4 changes: 2 additions & 2 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: docker.dragonflydb.io/dragonflydb/operator
newTag: v1.5.0
newName: ghcr.io/mvasilenko/dragonfly-operator
newTag: v1.5.3
15 changes: 8 additions & 7 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- configmaps
- pods
- services
verbs:
Expand All @@ -24,6 +18,13 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
Expand Down
41 changes: 22 additions & 19 deletions e2e/dragonfly_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,9 @@ var _ = Describe("Dragonfly Lifecycle tests", Ordered, FlakeAttempts(3), func()
err := k8sClient.Create(ctx, &df)
Expect(err).To(BeNil())

// Wait until Dragonfly object is marked initialized
waitForDragonflyPhase(ctx, k8sClient, name, namespace, controller.PhaseResourcesCreated, 2*time.Minute)
waitForStatefulSetReady(ctx, k8sClient, name, namespace, 2*time.Minute)

// Wait for master election, then for all replicas to be Ready
waitForDragonflyPhase(ctx, k8sClient, name, namespace, controller.PhaseReady, 3*time.Minute)
waitForStatefulSetReady(ctx, k8sClient, name, namespace, 3*time.Minute)
})

var ss appsv1.StatefulSet
Expand Down Expand Up @@ -750,28 +749,32 @@ var _ = Describe("Dragonfly tiering test with single replica", Ordered, FlakeAtt
defer close(stopChan)
defer rc.Close()

// Defensive: FlakeAttempts retries re-run only this It, not the preceding
// "Should create successfully" It, so residual data from a prior failed attempt
// would leak into the pre-insert assertion below. Flush to start clean.
Expect(rc.FlushAll(ctx).Err()).To(BeNil())

// Poll until tiered counters drain to zero (stash drain is async).
Eventually(func() int64 {
info, _ := rc.Info(ctx, "tiered").Result()
n, _ := parseTieredEntriesFromInfo(info)
return n
}, 10*time.Second, 200*time.Millisecond).Should(Equal(int64(0)))

// Insert BIG value (>64B so it is eligible for tiering)
const size = 1 << 20 // 1 MiB
payload := make([]byte, size)
_, _ = rand.Read(payload)

infoStr, err := rc.Info(ctx, "tiered").Result()
Expect(err).To(BeNil())

entries, err := parseTieredEntriesFromInfo(infoStr)
Expect(err).To(BeNil())
Expect(entries).To(Equal(int64(0))) // make sure this matches your expectation

Expect(rc.Set(ctx, "foo", payload, 0).Err()).To(BeNil())

// Inserted one big key, tiered entries should be 1
infoStr, err = rc.Info(ctx, "tiered").Result()
Expect(err).To(BeNil())

fmt.Println("Tiered entried Info: ", infoStr)
entries, err = parseTieredEntriesFromInfo(infoStr)
Expect(err).To(BeNil())
Expect(entries).To(Equal(int64(1))) // make sure this matches your expectation
// Tiering stash is asynchronous — poll until the 1MB value has been stashed
// rather than reading INFO once immediately after SET (which races the stash).
Eventually(func() int64 {
info, _ := rc.Info(ctx, "tiered").Result()
n, _ := parseTieredEntriesFromInfo(info)
return n
}, 30*time.Second, 200*time.Millisecond).Should(Equal(int64(1)))

// Fetch and compare by size
data, err := rc.Get(ctx, "foo").Bytes()
Expand Down
2 changes: 2 additions & 0 deletions internal/controller/dragonfly_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type DragonflyReconciler struct {
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
Expand Down Expand Up @@ -158,6 +159,7 @@ func (r *DragonflyReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&dfv1alpha1.Dragonfly{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&appsv1.StatefulSet{}, builder.MatchEveryOwner).
Owns(&corev1.Service{}, builder.MatchEveryOwner).
Owns(&corev1.ConfigMap{}, builder.MatchEveryOwner).
Owns(&networkingv1.NetworkPolicy{}, builder.MatchEveryOwner).
Named("Dragonfly").
Complete(r)
Expand Down
6 changes: 6 additions & 0 deletions internal/controller/dragonfly_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,12 @@ func resourceSpecsEqual(desired, existing client.Object) bool {
if !reflect.DeepEqual(desired.GetLabels(), existing.GetLabels()) || !reflect.DeepEqual(desired.GetAnnotations(), existing.GetAnnotations()) {
return false
}
// ConfigMaps store content in .Data, not .Spec — compare Data directly.
if cmDesired, ok := desired.(*corev1.ConfigMap); ok {
if cmExisting, ok := existing.(*corev1.ConfigMap); ok {
return reflect.DeepEqual(cmDesired.Data, cmExisting.Data)
}
}
// Compare only the .Spec field using reflection
desiredV := reflect.ValueOf(desired).Elem()
existingV := reflect.ValueOf(existing).Elem()
Expand Down
4 changes: 2 additions & 2 deletions internal/controller/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ func TestSelectMasterCandidate(t *testing.T) {
tests := []struct {
name string
pods []corev1.Pod
readyPods map[string]bool
readyPods map[string]bool // pod names that are considered ready
offsets map[string]int64 // per-pod offsets
wantName string
wantName string // expected winner; "" means nil result
}{
{
name: "no pods",
Expand Down
18 changes: 18 additions & 0 deletions internal/resources/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ const (

OperatorControlPlaneLabelKey = "control-plane"
OperatorControlPlaneLabelValue = "controller-manager"

// Probe ConfigMap suffixes — appended to df.Name
LivenessProbeConfigMapSuffix = "liveness-probe"
ReadinessProbeConfigMapSuffix = "readiness-probe"
StartupProbeConfigMapSuffix = "startup-probe"

// Script keys — must match the filename in the ConfigMap data
LivenessScriptKey = "liveness-check.sh"
ReadinessScriptKey = "readiness-check.sh"
StartupScriptKey = "startup-check.sh"

// ProbeMountPath is the directory where all probe scripts are mounted
ProbeMountPath = "/etc/dragonfly/probes"

// Volume names for the three probe ConfigMaps
LivenessProbeVolumeName = "liveness-probe"
ReadinessProbeVolumeName = "readiness-probe"
StartupProbeVolumeName = "startup-probe"
)

var DefaultDragonflyArgs = []string{
Expand Down
Loading