-
Notifications
You must be signed in to change notification settings - Fork 0
fix: improve teleport operator timing and eliminate race conditions #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,14 +18,22 @@ package controller | |
|
|
||
| import ( | ||
| "context" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/giantswarm/microerror" | ||
| "github.com/go-logr/logr" | ||
| corev1 "k8s.io/api/core/v1" | ||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| capi "sigs.k8s.io/cluster-api/api/v1beta1" | ||
| ctrl "sigs.k8s.io/controller-runtime" | ||
| "sigs.k8s.io/controller-runtime/pkg/builder" | ||
| "sigs.k8s.io/controller-runtime/pkg/handler" | ||
| "sigs.k8s.io/controller-runtime/pkg/predicate" | ||
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
| "sigs.k8s.io/controller-runtime/pkg/source" | ||
|
|
||
| "github.com/giantswarm/teleport-operator/internal/pkg/config" | ||
| "github.com/giantswarm/teleport-operator/internal/pkg/key" | ||
|
|
@@ -63,6 +71,7 @@ type ClusterReconciler struct { | |
| // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile | ||
| func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { | ||
| log := r.Log.WithValues("cluster", req.NamespacedName) | ||
| start := time.Now() | ||
|
|
||
| cluster := &capi.Cluster{} | ||
| if err := r.Client.Get(ctx, req.NamespacedName, cluster); err != nil { | ||
|
|
@@ -72,7 +81,7 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct | |
| return ctrl.Result{}, microerror.Mask(err) | ||
| } | ||
|
|
||
| log.Info("Reconciling cluster", "cluster", cluster) | ||
| log.Info("Reconciling cluster", "cluster", cluster, "creation_time", cluster.CreationTimestamp, "reconcile_start", start) | ||
|
|
||
| appsEnabled, err := r.Teleport.AreTeleportAppsEnabled(ctx, cluster.Name, cluster.Namespace) | ||
| if err != nil { | ||
|
|
@@ -254,12 +263,127 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct | |
|
|
||
| // We need to requeue to check the teleport token validity | ||
| // and update secret for the cluster, if it expires | ||
| log.Info("Reconcile completed", "duration", time.Since(start)) | ||
| return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil | ||
| } | ||
|
|
||
| // SetupWithManager sets up the controller with the Manager. | ||
| func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
| return ctrl.NewControllerManagedBy(mgr). | ||
| For(&capi.Cluster{}). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this line suggests that the operator should be reacting immediately to Cluster CR creation, so the 3 minute lag time is external to the operator
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah this should be the issue, watcher means the operator responds immediately to new clusters, the delay is happening in the tbot deployment or in the Teleport API calls.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When a new Cluster CR is created, do we spin up or re-deploy tbot? If so, then I think the solution here is to immediately create the placeholder secret so that e2e can wait for it to be populated, and then fill it in once tbot is done |
||
| Watches( | ||
| &source.Kind{Type: &corev1.Secret{}}, | ||
| handler.EnqueueRequestsFromMapFunc(r.findClustersForKubeconfigSecret), | ||
| builder.WithPredicates(predicate.NewPredicateFuncs(r.isKubeconfigSecret)), | ||
| ). | ||
| Watches( | ||
| &source.Kind{Type: &corev1.ConfigMap{}}, | ||
| handler.EnqueueRequestsFromMapFunc(r.findClustersForTbotConfigMap), | ||
| builder.WithPredicates(predicate.NewPredicateFuncs(r.isTbotConfigMap)), | ||
| ). | ||
|
Comment on lines
+274
to
+283
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it necessary to watch the resources we create?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we have to do so, if we want to use the watchers architecture |
||
| Complete(r) | ||
| } | ||
|
|
||
| // isKubeconfigSecret checks if a secret is a teleport kubeconfig secret we should watch | ||
| func (r *ClusterReconciler) isKubeconfigSecret(obj client.Object) bool { | ||
| secret, ok := obj.(*corev1.Secret) | ||
| if !ok { | ||
| return false | ||
| } | ||
|
|
||
| // Check if it's in the teleport bot namespace and matches our naming pattern | ||
| if secret.Namespace != key.TeleportBotNamespace { | ||
| return false | ||
| } | ||
|
|
||
| // Check if it matches teleport kubeconfig secret naming pattern: teleport-{cluster}-kubeconfig | ||
| return strings.HasPrefix(secret.Name, "teleport-") && strings.HasSuffix(secret.Name, "-kubeconfig") | ||
| } | ||
|
|
||
| // isTbotConfigMap checks if a configmap is a tbot configmap we should watch | ||
| func (r *ClusterReconciler) isTbotConfigMap(obj client.Object) bool { | ||
| cm, ok := obj.(*corev1.ConfigMap) | ||
| if !ok { | ||
| return false | ||
| } | ||
|
|
||
| // Check if it's in the teleport bot namespace and matches our naming pattern | ||
| if cm.Namespace != key.TeleportBotNamespace { | ||
| return false | ||
| } | ||
|
|
||
| // Check if it matches tbot configmap naming pattern: teleport-tbot-{cluster}-config | ||
| return strings.HasPrefix(cm.Name, "teleport-tbot-") && strings.HasSuffix(cm.Name, "-config") | ||
| } | ||
|
Comment on lines
+288
to
+317
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If these are truly necessary, they should be |
||
|
|
||
| // findClustersForKubeconfigSecret maps kubeconfig secret changes back to cluster reconcile requests | ||
| func (r *ClusterReconciler) findClustersForKubeconfigSecret(obj client.Object) []reconcile.Request { | ||
| secret, ok := obj.(*corev1.Secret) | ||
| if !ok { | ||
| return nil | ||
| } | ||
|
|
||
| // Extract cluster name from secret name: teleport-{cluster}-kubeconfig -> {cluster} | ||
| if !strings.HasPrefix(secret.Name, "teleport-") || !strings.HasSuffix(secret.Name, "-kubeconfig") { | ||
| return nil | ||
| } | ||
|
|
||
| clusterName := secret.Name[len("teleport-") : len(secret.Name)-len("-kubeconfig")] | ||
|
|
||
| // Find the cluster in all organization namespaces | ||
| clusters := &capi.ClusterList{} | ||
| if err := r.Client.List(context.TODO(), clusters); err != nil { | ||
| r.Log.Error(err, "Failed to list clusters for kubeconfig secret", "secret", secret.Name) | ||
| return nil | ||
| } | ||
|
|
||
| var requests []reconcile.Request | ||
| for _, cluster := range clusters.Items { | ||
| if cluster.Name == clusterName { | ||
| requests = append(requests, reconcile.Request{ | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: cluster.Name, | ||
| Namespace: cluster.Namespace, | ||
| }, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return requests | ||
| } | ||
|
|
||
| // findClustersForTbotConfigMap maps tbot configmap changes back to cluster reconcile requests | ||
| func (r *ClusterReconciler) findClustersForTbotConfigMap(obj client.Object) []reconcile.Request { | ||
| cm, ok := obj.(*corev1.ConfigMap) | ||
| if !ok { | ||
| return nil | ||
| } | ||
|
|
||
| // Extract cluster name from configmap name: teleport-tbot-{cluster}-config -> {cluster} | ||
| if !strings.HasPrefix(cm.Name, "teleport-tbot-") || !strings.HasSuffix(cm.Name, "-config") { | ||
| return nil | ||
| } | ||
|
|
||
| clusterName := cm.Name[len("teleport-tbot-") : len(cm.Name)-len("-config")] | ||
|
|
||
| // Find the cluster in all organization namespaces | ||
| clusters := &capi.ClusterList{} | ||
| if err := r.Client.List(context.TODO(), clusters); err != nil { | ||
| r.Log.Error(err, "Failed to list clusters for tbot configmap", "configmap", cm.Name) | ||
| return nil | ||
| } | ||
|
|
||
| var requests []reconcile.Request | ||
| for _, cluster := range clusters.Items { | ||
| if cluster.Name == clusterName { | ||
| requests = append(requests, reconcile.Request{ | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: cluster.Name, | ||
| Namespace: cluster.Namespace, | ||
| }, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return requests | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine to measure the reconciliation time. If we do so, it would be nice to have it as a metric
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, we will go with metrics