Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 489af31

Browse files
committedFeb 7, 2024·
Replicate medusa bucket secrets to each Cass DC
1 parent 75cc260 commit 489af31

File tree

12 files changed

+168
-16
lines changed

12 files changed

+168
-16
lines changed
 

‎CHANGELOG/CHANGELOG-1.12.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen
1515

1616
## unreleased
1717

18+
* [ENHANCEMENT] [#1159](https://github.com/k8ssandra/k8ssandra-operator/issues/1159) Replicate bucket key secrets to namespaces hosting clusters
1819
* [CHANGE] Upgrade to Medusa v0.17.2
1920
* [CHANGE] [#1158](https://github.com/k8ssandra/k8ssandra-operator/issues/1158) Use the MedusaConfiguration API when creating Medusa configuration
2021
* [CHANGE] [#1050](https://github.com/k8ssandra/k8ssandra-operator/issues/1050) Remove unnecessary requeues in the Medusa controllers

‎controllers/k8ssandra/medusa_reconciler.go

+70-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"github.com/adutra/goalesce"
77
medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
8+
"k8s.io/apimachinery/pkg/api/errors"
89
"k8s.io/apimachinery/pkg/types"
910

1011
"github.com/go-logr/logr"
@@ -36,7 +37,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
3637
if medusaSpec != nil {
3738
logger.Info("Medusa is enabled")
3839

39-
mergeResult := mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
40+
mergeResult := r.mergeStorageProperties(ctx, remoteClient, namespace, medusaSpec, logger, kc)
4041
if mergeResult.IsError() {
4142
return result.Error(mergeResult.GetError())
4243
}
@@ -49,6 +50,11 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
4950
return result.Error(fmt.Errorf("medusa encryption certificates were not provided despite client encryption being enabled"))
5051
}
5152
}
53+
54+
recRes := r.reconcileBucketSecrets(ctx, remoteClient, kc, logger, namespace)
55+
if recRes.IsError() {
56+
return recRes
57+
}
5258
if medusaSpec.StorageProperties.StorageSecretRef.Name == "" {
5359
return result.Error(fmt.Errorf("medusa storage secret is not defined for storage provider %s", medusaSpec.StorageProperties.StorageProvider))
5460
}
@@ -92,7 +98,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
9298
// Reconcile the Medusa standalone deployment
9399
kcKey := utils.GetKey(kc)
94100
desiredMedusaStandalone.SetLabels(labels.CleanedUpByLabels(kcKey))
95-
recRes := reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *desiredMedusaStandalone)
101+
recRes = reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *desiredMedusaStandalone)
96102
switch {
97103
case recRes.IsError():
98104
return recRes
@@ -213,7 +219,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusaConfigMap(
213219
return result.Continue()
214220
}
215221

216-
func mergeStorageProperties(
222+
func (r *K8ssandraClusterReconciler) mergeStorageProperties(
217223
ctx context.Context,
218224
remoteClient client.Client,
219225
namespace string,
@@ -229,7 +235,7 @@ func mergeStorageProperties(
229235
configKey := types.NamespacedName{Namespace: namespace, Name: medusaSpec.MedusaConfigurationRef.Name}
230236
if err := remoteClient.Get(ctx, configKey, storageProperties); err != nil {
231237
logger.Error(err, fmt.Sprintf("failed to get MedusaConfiguration %s", configKey))
232-
return result.Error(err)
238+
return result.RequeueSoon(r.DefaultDelay)
233239
}
234240
// check if the StorageProperties from the cluster have the prefix field set
235241
// it is required to be present because that's the single thing that differentiates backups of two different clusters
@@ -247,3 +253,63 @@ func mergeStorageProperties(
247253
mergedProperties.DeepCopyInto(&desiredKc.Spec.Medusa.StorageProperties)
248254
return result.Continue()
249255
}
256+
257+
func (r *K8ssandraClusterReconciler) reconcileBucketSecrets(ctx context.Context, remoteClient client.Client, kc *api.K8ssandraCluster, logger logr.Logger, namespace string) result.ReconcileResult {
258+
logger.Info("Reconciling Medusa bucket secrets")
259+
medusaSpec := kc.Spec.Medusa
260+
// there is nothing to reconcile if we're not using Medusa configuration reference
261+
if medusaSpec.MedusaConfigurationRef.Name == "" {
262+
logger.Info("MedusaConfigurationRef is not set, skipping bucket secret reconciliation")
263+
return result.Continue()
264+
}
265+
266+
// fetch the referenced configuration
267+
medusaConfigNamespace := utils.FirstNonEmptyString(medusaSpec.MedusaConfigurationRef.Namespace, namespace)
268+
medusaConfigName := medusaSpec.MedusaConfigurationRef.Name
269+
medusaConfig := &medusaapi.MedusaConfiguration{}
270+
medusaConfigKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: medusaConfigName}
271+
if err := remoteClient.Get(ctx, medusaConfigKey, medusaConfig); err != nil {
272+
logger.Error(err, "failed to get MedusaConfiguration", "key", medusaConfigKey)
273+
return result.RequeueSoon(r.DefaultDelay)
274+
}
275+
276+
// fetch the referenced medusa configuration's bucket secret
277+
bucketSecretName := medusaConfig.Spec.StorageProperties.StorageSecretRef.Name
278+
bucketSecret := &corev1.Secret{}
279+
bucketSecretKey := types.NamespacedName{Namespace: medusaConfigNamespace, Name: bucketSecretName}
280+
err := remoteClient.Get(ctx, bucketSecretKey, bucketSecret)
281+
if err != nil {
282+
logger.Error(err, "failed to get bucket secret", "key", bucketSecretKey)
283+
return result.RequeueSoon(r.DefaultDelay)
284+
}
285+
286+
// write the secret into the namespace of the K8ssandraCluster
287+
clusterBucketSecret := bucketSecret.DeepCopy()
288+
clusterBucketSecret.ResourceVersion = ""
289+
clusterBucketSecret.Name = fmt.Sprintf("%s-%s", kc.Name, bucketSecret.Name)
290+
clusterBucketSecret.Namespace = kc.Namespace
291+
labels.SetReplicatedBy(clusterBucketSecret, utils.GetKey(kc))
292+
err = remoteClient.Create(ctx, clusterBucketSecret)
293+
if err != nil {
294+
if !errors.IsAlreadyExists(err) {
295+
logger.Error(err, fmt.Sprintf("failed to create cluster bucket secret %s", clusterBucketSecret))
296+
return result.Error(err)
297+
}
298+
// we already have the bucket secret, so continue to updating the cluster (it might have failed before)
299+
}
300+
301+
// finally, update the storage properties to point to a newly created secret
302+
if kc.Spec.Medusa.StorageProperties.StorageSecretRef.Name == clusterBucketSecret.Name {
303+
logger.Info("The cluster bucket secret is already set")
304+
return result.Continue()
305+
} else {
306+
newSecretRef := corev1.LocalObjectReference{Name: clusterBucketSecret.Name}
307+
kc.Spec.Medusa.StorageProperties.StorageSecretRef = newSecretRef
308+
if err = remoteClient.Update(ctx, kc); err != nil {
309+
logger.Error(err, "failed to update K8ssandraCluster, re-queueing")
310+
return result.RequeueSoon(r.DefaultDelay)
311+
}
312+
logger.Info("Medusa bucket secrets successfully updated", "cluster", kc.Name, "namespace", kc.Namespace)
313+
return result.Continue()
314+
}
315+
}

‎docs/content/en/tasks/backup-restore/_index.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ spec:
8888
# size: 100Mi
8989
```
9090

91-
The definition above requires a secret named `medusa-bucket-key` to be created in the target namespace before the `K8ssandraCluster` object gets created. Use the following format for this secret:
91+
The definition above requires a secret named `medusa-bucket-key` to be present in the target namespace before the `K8ssandraCluster` object gets created. Use the following format for this secret:
9292

9393
```yaml
9494
apiVersion: v1
@@ -106,9 +106,11 @@ stringData:
106106
107107
The file should always specify `credentials` as shown in the example above; in that section, provide the expected format and credential values that are expected by Medusa for the chosen storage backend. For more, refer to the [Medusa documentation](https://github.com/thelastpickle/cassandra-medusa/blob/master/docs/Installation.md) to know which file format should used for each supported storage backend.
108108

109-
A successful deployment should inject a new init container named `medusa-restore` and a new container named `medusa` in the Cassandra StatefulSet pods.
109+
If using a shared Medusa configuration (see below), this secret can be created in the same namespace as the `MedusaConfiguration` object. The K8ssandra operator will then make sure the secret is replicated to the namespaces hosting the Cassandra clusters.
110110

111-
## Using shared medusa configuration properties
111+
A successful deployment should inject a new init container named `medusa-restore` and a new container named `medusa` in the Cassandra StatefulSet pods.
112+
113+
## Using shared Medusa configuration properties
112114

113115
Medusa configuration properties can be shared across multiple K8ssandraClusters by creating a `MedusaConfiguration` custom resource in the Control Plane K8ssandra cluster.
114116
Example:

‎test/e2e/cluster_scope_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func multiDcMultiCluster(t *testing.T, ctx context.Context, klusterNamespace str
2323

2424
dc1Key := framework.ClusterKey{K8sContext: f.DataPlaneContexts[0], NamespacedName: types.NamespacedName{Namespace: dc1Namespace, Name: "dc1"}}
2525
checkDatacenterReady(t, ctx, dc1Key, f)
26+
checkBucketKeyPresent(t, f, ctx, dc1Namespace, dc1Key.K8sContext, k8ssandra)
2627

2728
t.Log("check k8ssandra cluster status")
2829
require.Eventually(func() bool {
@@ -41,6 +42,7 @@ func multiDcMultiCluster(t *testing.T, ctx context.Context, klusterNamespace str
4142

4243
dc2Key := framework.ClusterKey{K8sContext: f.DataPlaneContexts[1], NamespacedName: types.NamespacedName{Namespace: dc2Namespace, Name: "dc2"}}
4344
checkDatacenterReady(t, ctx, dc2Key, f)
45+
checkBucketKeyPresent(t, f, ctx, dc2Namespace, dc2Key.K8sContext, k8ssandra)
4446

4547
t.Log("check k8ssandra cluster status")
4648
require.Eventually(func() bool {

‎test/e2e/medusa_test.go

+27-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
)
2323

2424
const (
25-
backupName = "backup1"
26-
clusterName = "test"
25+
backupName = "backup1"
26+
clusterName = "test"
27+
globalBucketSecretName = "global-bucket-key"
2728
)
2829

2930
func createSingleMedusaJob(t *testing.T, ctx context.Context, namespace string, f *framework.E2eFramework) {
@@ -69,6 +70,11 @@ func createMultiMedusaJob(t *testing.T, ctx context.Context, namespace string, f
6970
dc1Key := framework.ClusterKey{K8sContext: "kind-k8ssandra-0", NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}}
7071
dc2Key := framework.ClusterKey{K8sContext: "kind-k8ssandra-1", NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc2"}}
7172

73+
// Check that Medusa's bucket key has been replicated to the current namespace
74+
for _, dcKey := range []framework.ClusterKey{dc1Key, dc2Key} {
75+
checkBucketKeyPresent(t, f, ctx, namespace, dcKey.K8sContext, kc)
76+
}
77+
7278
// Check that both DCs are ready and have Medusa containers
7379
for _, dcKey := range []framework.ClusterKey{dc1Key, dc2Key} {
7480
checkDatacenterReady(t, ctx, dcKey, f)
@@ -112,6 +118,25 @@ func createMultiDcSingleMedusaJob(t *testing.T, ctx context.Context, namespace s
112118
verifyBackupJobFinished(t, ctx, f, dcKey, backupKey)
113119
}
114120

121+
func checkBucketKeyPresent(t *testing.T, f *framework.E2eFramework, ctx context.Context, namespace string, k8sContext string, kc *k8ssandraapi.K8ssandraCluster) {
122+
require := require.New(t)
123+
124+
// work out the name of the replicated bucket key. should be "clusterName-<original-bucket-key-name>"
125+
localBucketKeyName := fmt.Sprintf("%s-%s", kc.Name, globalBucketSecretName)
126+
127+
// Check that the bucket key has been replicated to the current namespace
128+
bucketKey := &corev1.Secret{}
129+
require.Eventually(func() bool {
130+
err := f.Get(ctx, framework.NewClusterKey(k8sContext, namespace, localBucketKeyName), bucketKey)
131+
if err != nil {
132+
return false
133+
}
134+
return true
135+
}, polling.medusaConfigurationReady.timeout, polling.medusaConfigurationReady.interval,
136+
fmt.Sprintf("Error getting the Medusa bucket key secret. Context: %s, ClusterName: %s, Namespace: %s", k8sContext, kc.SanitizedName(), namespace),
137+
)
138+
}
139+
115140
func checkMedusaContainersExist(t *testing.T, ctx context.Context, namespace string, dcKey framework.ClusterKey, f *framework.E2eFramework, kc *api.K8ssandraCluster) {
116141
require := require.New(t)
117142
// Get the Cassandra pod

‎test/e2e/suite_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func TestOperator(t *testing.T) {
277277
clusterScoped: true,
278278
sutNamespace: "test-0",
279279
additionalNamespaces: []string{"test-1", "test-2"},
280+
installMinio: true,
280281
}))
281282
})
282283
t.Run("CreateSingleMedusaJob", e2eTest(ctx, &e2eTestOpts{

‎test/testdata/fixtures/multi-dc-cluster-scope/k8ssandra.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ metadata:
44
name: test
55
namespace: test-0
66
spec:
7+
medusa:
8+
medusaConfigurationRef:
9+
name: global-medusa-config
10+
namespace: test-0
11+
storageProperties:
12+
prefix: test
713
cassandra:
814
serverVersion: "3.11.14"
915
storageConfig:
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
resources:
4+
- medusa-config.yaml
45
- k8ssandra.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: global-bucket-key
5+
namespace: test-1
6+
type: Opaque
7+
stringData:
8+
# Note that this currently has to be set to credentials!
9+
credentials: |-
10+
[default]
11+
aws_access_key_id = k8ssandra
12+
aws_secret_access_key = k8ssandra
13+
---
14+
apiVersion: medusa.k8ssandra.io/v1alpha1
15+
kind: MedusaConfiguration
16+
metadata:
17+
name: global-medusa-config
18+
namespace: test-1
19+
spec:
20+
storageProperties:
21+
storageProvider: s3_compatible
22+
bucketName: k8ssandra-medusa
23+
storageSecretRef:
24+
name: global-bucket-key
25+
host: minio-service.minio.svc.cluster.local
26+
port: 9000
27+
secure: false

‎test/testdata/fixtures/multi-dc-encryption-medusa/k8ssandra.yaml

+2-7
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@ metadata:
2121
name: test
2222
spec:
2323
medusa:
24+
medusaConfigurationRef:
25+
name: global-medusa-config
2426
storageProperties:
25-
storageProvider: s3_compatible
26-
bucketName: k8ssandra-medusa
2727
prefix: test
28-
storageSecretRef:
29-
name: medusa-bucket-key
30-
host: minio-service.minio.svc.cluster.local
31-
port: 9000
32-
secure: false
3328
certificatesSecretRef:
3429
name: client-certificates
3530
cassandra:
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
resources:
4+
- medusa-config.yaml
45
- k8ssandra.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: global-bucket-key
5+
type: Opaque
6+
stringData:
7+
# Note that this currently has to be set to credentials!
8+
credentials: |-
9+
[default]
10+
aws_access_key_id = k8ssandra
11+
aws_secret_access_key = k8ssandra
12+
---
13+
apiVersion: medusa.k8ssandra.io/v1alpha1
14+
kind: MedusaConfiguration
15+
metadata:
16+
name: global-medusa-config
17+
spec:
18+
storageProperties:
19+
storageProvider: s3_compatible
20+
bucketName: k8ssandra-medusa
21+
storageSecretRef:
22+
name: global-bucket-key
23+
host: minio-service.minio.svc.cluster.local
24+
port: 9000
25+
secure: false

0 commit comments

Comments
 (0)
Please sign in to comment.