Skip to content

Commit a4dc723

Browse files
feat: hot reload tls
1 parent eb928c7 commit a4dc723

File tree

5 files changed

+78
-11
lines changed

5 files changed

+78
-11
lines changed

api/rediscluster/v1beta2/rediscluster_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ type RedisClusterStatus struct {
121121
// +kubebuilder:default=0
122122
ReadyLeaderReplicas int32 `json:"readyLeaderReplicas,omitempty"`
123123
// +kubebuilder:default=0
124-
ReadyFollowerReplicas int32 `json:"readyFollowerReplicas,omitempty"`
124+
ReadyFollowerReplicas int32 `json:"readyFollowerReplicas,omitempty"`
125+
TLSLastVersion string `json:"tlsLastVersion,omitempty"`
125126
}
126127

127128
type RedisClusterState string

config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7858,6 +7858,8 @@ spec:
78587858
type: string
78597859
state:
78607860
type: string
7861+
tlsLastVersion:
7862+
type: string
78617863
type: object
78627864
required:
78637865
- spec

internal/cmd/manager/cmd.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,13 @@ func setupControllers(mgr ctrl.Manager, k8sClient kubernetes.Interface, maxConcu
239239
return err
240240
}
241241
if err := (&redisclustercontroller.Reconciler{
242-
Client: mgr.GetClient(),
243-
K8sClient: k8sClient,
244-
Healer: healer,
245-
Checker: redis.NewChecker(k8sClient),
246-
Recorder: mgr.GetEventRecorderFor("rediscluster-controller"),
247-
StatefulSet: k8sutils.NewStatefulSetService(k8sClient),
242+
Client: mgr.GetClient(),
243+
K8sClient: k8sClient,
244+
Healer: healer,
245+
Checker: redis.NewChecker(k8sClient),
246+
Recorder: mgr.GetEventRecorderFor("rediscluster-controller"),
247+
StatefulSet: k8sutils.NewStatefulSetService(k8sClient),
248+
ResourceWatcher: intctrlutil.NewResourceWatcher(),
248249
}).SetupWithManager(mgr, controller.Options{MaxConcurrentReconciles: maxConcurrentReconciles}); err != nil {
249250
setupLog.Error(err, "unable to create controller", "controller", "RedisCluster")
250251
return err

internal/controller/rediscluster/rediscluster_controller.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ package rediscluster
1919
import (
2020
"context"
2121
"fmt"
22+
"k8s.io/apimachinery/pkg/types"
2223
"reflect"
24+
"sigs.k8s.io/controller-runtime/pkg/handler"
25+
"strconv"
2326
"time"
2427

2528
rcvb2 "github.com/OT-CONTAINER-KIT/redis-operator/api/rediscluster/v1beta2"
@@ -49,10 +52,11 @@ const (
4952
type Reconciler struct {
5053
client.Client
5154
k8sutils.StatefulSet
52-
Healer redis.Healer
53-
Checker redis.Checker
54-
K8sClient kubernetes.Interface
55-
Recorder record.EventRecorder
55+
Healer redis.Healer
56+
Checker redis.Checker
57+
K8sClient kubernetes.Interface
58+
Recorder record.EventRecorder
59+
ResourceWatcher *intctrlutil.ResourceWatcher
5660
}
5761

5862
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -84,6 +88,21 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
8488
return intctrlutil.RequeueE(ctx, err, "failed to add finalizer")
8589
}
8690

91+
// hotreload tls
92+
if instance.Spec.TLS != nil {
93+
r.ResourceWatcher.Watch(
94+
ctx,
95+
types.NamespacedName{
96+
Namespace: instance.Namespace,
97+
Name: instance.Spec.TLS.Secret.SecretName,
98+
},
99+
types.NamespacedName{
100+
Namespace: instance.Namespace,
101+
Name: instance.Name,
102+
},
103+
)
104+
}
105+
87106
// Check if the cluster is downscaled
88107
if leaderCount := r.GetStatefulSetReplicas(ctx, instance.Namespace, instance.Name+"-leader"); leaderReplicas < leaderCount {
89108
if !r.IsStatefulSetReady(ctx, instance.Namespace, instance.Name+"-leader") || !r.IsStatefulSetReady(ctx, instance.Namespace, instance.Name+"-follower") {
@@ -201,6 +220,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
201220
return intctrlutil.Reconciled()
202221
}
203222

223+
if instance.Spec.TLS != nil {
224+
err := r.reloadTLS(ctx, instance, int(leaderReplicas), int(followerReplicas))
225+
if err != nil {
226+
log.FromContext(ctx).Error(err, "hotReloadTLS failed, will retry later")
227+
return intctrlutil.RequeueAfter(ctx, 30*time.Second, "Retry hotReloadTLS")
228+
}
229+
}
230+
204231
// Mark the cluster status as bootstrapping if all the leader and follower nodes are ready
205232
if instance.Status.ReadyLeaderReplicas != leaderReplicas || instance.Status.ReadyFollowerReplicas != followerReplicas {
206233
requeue, err := r.updateStatus(ctx, instance, rcvb2.RedisClusterStatus{
@@ -349,6 +376,24 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
349376
return intctrlutil.RequeueAfter(ctx, time.Second*10, "")
350377
}
351378

379+
func (r *Reconciler) reloadTLS(ctx context.Context, rc *rcvb2.RedisCluster, leaderReplicas, followerReplicas int) error {
380+
log.FromContext(ctx).Info("hotReloadTLS: reloading TLS configuration")
381+
for i := 0; i < followerReplicas; i++ {
382+
err := k8sutils.HotReloadTLS(ctx, r.K8sClient, rc, rc.Name+"-follower-"+strconv.Itoa(i))
383+
if err != nil {
384+
return fmt.Errorf("RedisCluster controller -> failed reloading tls in follower: %w", err)
385+
}
386+
}
387+
for j := 0; j < leaderReplicas; j++ {
388+
err := k8sutils.HotReloadTLS(ctx, r.K8sClient, rc, rc.Name+"-leader-"+strconv.Itoa(j))
389+
if err != nil {
390+
return fmt.Errorf("RedisCluster controller -> failed reloading tls in leader: %w", err)
391+
}
392+
}
393+
log.FromContext(ctx).Info("hotReloadTLS: reloaded TLS configuration has been completed")
394+
return nil
395+
}
396+
352397
func (r *Reconciler) updateStatus(ctx context.Context, rc *rcvb2.RedisCluster, status rcvb2.RedisClusterStatus) (requeue bool, err error) {
353398
if reflect.DeepEqual(rc.Status, status) {
354399
return false, nil
@@ -380,5 +425,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options)
380425
For(&rcvb2.RedisCluster{}).
381426
Owns(&appsv1.StatefulSet{}).
382427
WithOptions(opts).
428+
Watches(&rcvb2.RedisCluster{}, &handler.EnqueueRequestForObject{}).
429+
Watches(&corev1.Secret{}, r.ResourceWatcher).
383430
Complete(r)
384431
}

internal/k8sutils/redis.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,22 @@ func createRedisReplicationCommand(ctx context.Context, client kubernetes.Interf
289289
return cmd
290290
}
291291

292+
func HotReloadTLS(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster, podName string) error {
293+
redisClient := configureRedisClient(ctx, client, cr, podName)
294+
commands := []struct{ k, v string }{
295+
{"tls-cert-file", "/tls/tls.crt"},
296+
{"tls-key-file", "/tls/tls.key"},
297+
{"tls-ca-cert-file", "/tls/ca.crt"},
298+
}
299+
for _, cmd := range commands {
300+
if err := redisClient.ConfigSet(ctx, cmd.k, cmd.v).Err(); err != nil {
301+
log.FromContext(ctx).Error(err, "hotReloadTLS: Failed to set tls config", "cmd", cmd, "on pod", podName)
302+
return err
303+
}
304+
}
305+
return nil
306+
}
307+
292308
// ExecuteRedisReplicationCommand will execute the replication command
293309
func ExecuteRedisReplicationCommand(ctx context.Context, client kubernetes.Interface, cr *rcvb2.RedisCluster) {
294310
var podIP string

0 commit comments

Comments
 (0)