@@ -20,9 +20,11 @@ import (
2020 "context"
2121 "fmt"
2222
23+ certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
2324 appsv1 "k8s.io/api/apps/v1"
2425 corev1 "k8s.io/api/core/v1"
2526 "k8s.io/apimachinery/pkg/api/errors"
27+ "k8s.io/apimachinery/pkg/api/meta"
2628 "k8s.io/apimachinery/pkg/api/resource"
2729 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830 "k8s.io/apimachinery/pkg/util/intstr"
@@ -39,6 +41,7 @@ const (
3941 telemetryCertSuffix = "-telemetry-tls"
4042 telemetryServiceName = "jumpstarter-telemetry"
4143 telemetryComponentApp = "jumpstarter-telemetry"
44+ telemetrySASuffix = "-telemetry"
4245)
4346
4447// reconcileTelemetryDeploymentStage reconciles only the telemetry Deployment (and cleanup).
@@ -49,18 +52,53 @@ func (r *JumpstarterReconciler) reconcileTelemetryDeploymentStage(ctx context.Co
4952 return r .cleanupTelemetry (ctx , jumpstarter )
5053 }
5154
55+ if err := r .reconcileTelemetryServiceAccount (ctx , jumpstarter ); err != nil {
56+ return fmt .Errorf ("failed to reconcile telemetry service account: %w" , err )
57+ }
58+
5259 if err := r .reconcileTelemetryDeployment (ctx , jumpstarter ); err != nil {
5360 return fmt .Errorf ("failed to reconcile telemetry deployment: %w" , err )
5461 }
5562
5663 return nil
5764}
5865
66+ // reconcileTelemetryServiceAccount creates a dedicated, no-RBAC ServiceAccount for
67+ // the telemetry pod. The telemetry binary has no need for Kubernetes API access;
68+ // giving it the controller-manager SA would grant it far more privilege than required.
69+ func (r * JumpstarterReconciler ) reconcileTelemetryServiceAccount (ctx context.Context , jumpstarter * operatorv1alpha1.Jumpstarter ) error {
70+ log := logf .FromContext (ctx )
71+ saName := jumpstarter .Name + telemetrySASuffix
72+
73+ desired := & corev1.ServiceAccount {
74+ ObjectMeta : metav1.ObjectMeta {
75+ Name : saName ,
76+ Namespace : jumpstarter .Namespace ,
77+ Labels : telemetryLabels (jumpstarter ),
78+ },
79+ }
80+
81+ existing := & corev1.ServiceAccount {}
82+ existing .Name = saName
83+ existing .Namespace = jumpstarter .Namespace
84+
85+ op , err := controllerutil .CreateOrUpdate (ctx , r .Client , existing , func () error {
86+ existing .Labels = desired .Labels
87+ return controllerutil .SetControllerReference (jumpstarter , existing , r .Scheme )
88+ })
89+ if err != nil {
90+ return err
91+ }
92+
93+ log .V (1 ).Info ("Telemetry ServiceAccount reconciled" , "name" , saName , "operation" , op )
94+ return nil
95+ }
96+
5997// reconcileTelemetryServiceStage reconciles only the telemetry ClusterIP Service.
6098// It is called from the Services/networking stage of the reconcile loop.
6199func (r * JumpstarterReconciler ) reconcileTelemetryServiceStage (ctx context.Context , jumpstarter * operatorv1alpha1.Jumpstarter ) error {
62100 if jumpstarter .Spec .Telemetry == nil || ! jumpstarter .Spec .Telemetry .Enabled {
63- return nil
101+ return r . cleanupTelemetryService ( ctx , jumpstarter )
64102 }
65103
66104 if err := r .reconcileTelemetryService (ctx , jumpstarter ); err != nil {
@@ -70,10 +108,28 @@ func (r *JumpstarterReconciler) reconcileTelemetryServiceStage(ctx context.Conte
70108 return nil
71109}
72110
111+ // cleanupTelemetryService removes only the telemetry Service when telemetry is disabled.
112+ // This provides symmetric cleanup so the Service isn't orphaned if the reconcile loop
113+ // order changes (e.g. services reconciled before deployments).
114+ func (r * JumpstarterReconciler ) cleanupTelemetryService (ctx context.Context , jumpstarter * operatorv1alpha1.Jumpstarter ) error {
115+ log := logf .FromContext (ctx )
116+
117+ svc := & corev1.Service {}
118+ svc .Name = telemetryServiceName
119+ svc .Namespace = jumpstarter .Namespace
120+ if err := r .Delete (ctx , svc ); err != nil && ! errors .IsNotFound (err ) {
121+ return fmt .Errorf ("failed to delete telemetry service: %w" , err )
122+ } else if err == nil {
123+ log .Info ("Deleted telemetry service" , "name" , telemetryServiceName )
124+ }
125+
126+ return nil
127+ }
128+
73129// reconcileTelemetryDeployment creates or updates the telemetry Deployment.
74130func (r * JumpstarterReconciler ) reconcileTelemetryDeployment (ctx context.Context , jumpstarter * operatorv1alpha1.Jumpstarter ) error {
75131 log := logf .FromContext (ctx )
76- desiredDeployment := r . createTelemetryDeployment (jumpstarter )
132+ desiredDeployment := createTelemetryDeployment (jumpstarter )
77133
78134 existingDeployment := & appsv1.Deployment {}
79135 existingDeployment .Name = desiredDeployment .Name
@@ -119,8 +175,14 @@ func (r *JumpstarterReconciler) reconcileTelemetryDeployment(ctx context.Context
119175 return err
120176 }
121177
122- log .Info ("Telemetry deployment reconciled" ,
123- "name" , existingDeployment .Name , "namespace" , existingDeployment .Namespace , "operation" , op )
178+ switch op {
179+ case controllerutil .OperationResultNone :
180+ log .V (1 ).Info ("Telemetry deployment is up to date" ,
181+ "name" , existingDeployment .Name , "namespace" , existingDeployment .Namespace )
182+ default :
183+ log .Info ("Telemetry deployment reconciled" ,
184+ "name" , existingDeployment .Name , "namespace" , existingDeployment .Namespace , "operation" , op )
185+ }
124186
125187 switch op {
126188 case controllerutil .OperationResultCreated :
@@ -166,15 +228,8 @@ func (r *JumpstarterReconciler) reconcileTelemetryService(ctx context.Context, j
166228 existingService .Namespace = desiredService .Namespace
167229
168230 op , err := controllerutil .CreateOrUpdate (ctx , r .Client , existingService , func () error {
169- if existingService .CreationTimestamp .IsZero () {
170- existingService .Labels = desiredService .Labels
171- existingService .Spec .Type = desiredService .Spec .Type
172- existingService .Spec .Selector = desiredService .Spec .Selector
173- existingService .Spec .Ports = desiredService .Spec .Ports
174- return controllerutil .SetControllerReference (jumpstarter , existingService , r .Scheme )
175- }
176-
177231 existingService .Labels = desiredService .Labels
232+ existingService .Spec .Type = desiredService .Spec .Type
178233 existingService .Spec .Selector = desiredService .Spec .Selector
179234 existingService .Spec .Ports = desiredService .Spec .Ports
180235 return controllerutil .SetControllerReference (jumpstarter , existingService , r .Scheme )
@@ -185,14 +240,29 @@ func (r *JumpstarterReconciler) reconcileTelemetryService(ctx context.Context, j
185240 return err
186241 }
187242
188- log .Info ("Telemetry service reconciled" ,
189- "name" , existingService .Name , "namespace" , existingService .Namespace , "operation" , op )
243+ switch op {
244+ case controllerutil .OperationResultNone :
245+ log .V (1 ).Info ("Telemetry service is up to date" ,
246+ "name" , existingService .Name , "namespace" , existingService .Namespace )
247+ case controllerutil .OperationResultCreated :
248+ log .Info ("Telemetry service reconciled" ,
249+ "name" , existingService .Name , "namespace" , existingService .Namespace , "operation" , op )
250+ r .emitEventf (jumpstarter , corev1 .EventTypeNormal , "TelemetryServiceCreated" ,
251+ "Telemetry service created: name=%s namespace=%s" ,
252+ existingService .Name , existingService .Namespace )
253+ case controllerutil .OperationResultUpdated :
254+ log .Info ("Telemetry service reconciled" ,
255+ "name" , existingService .Name , "namespace" , existingService .Namespace , "operation" , op )
256+ r .emitEventf (jumpstarter , corev1 .EventTypeNormal , "TelemetryServiceUpdated" ,
257+ "Telemetry service updated: name=%s namespace=%s" ,
258+ existingService .Name , existingService .Namespace )
259+ }
190260
191261 return nil
192262}
193263
194264// createTelemetryDeployment builds the desired Deployment for the telemetry service.
195- func ( r * JumpstarterReconciler ) createTelemetryDeployment (jumpstarter * operatorv1alpha1.Jumpstarter ) * appsv1.Deployment {
265+ func createTelemetryDeployment (jumpstarter * operatorv1alpha1.Jumpstarter ) * appsv1.Deployment {
196266 t := jumpstarter .Spec .Telemetry
197267 labels := telemetryLabels (jumpstarter )
198268
@@ -201,51 +271,6 @@ func (r *JumpstarterReconciler) createTelemetryDeployment(jumpstarter *operatorv
201271 replicas = * t .Replicas
202272 }
203273
204- envVars := []corev1.EnvVar {
205- {
206- Name : "CONTROLLER_KEY" ,
207- ValueFrom : & corev1.EnvVarSource {
208- SecretKeyRef : & corev1.SecretKeySelector {
209- LocalObjectReference : corev1.LocalObjectReference {
210- Name : "jumpstarter-controller-secret" ,
211- },
212- Key : "key" ,
213- },
214- },
215- },
216- }
217-
218- var volumeMounts []corev1.VolumeMount
219- var volumes []corev1.Volume
220-
221- // Add TLS certificate mount when cert-manager is enabled
222- var tlsSecretName string
223- if jumpstarter .Spec .CertManager .Enabled {
224- tlsSecretName = GetTelemetryCertSecretName (jumpstarter )
225- }
226-
227- if tlsSecretName != "" {
228- envVars = append (envVars ,
229- corev1.EnvVar {Name : "EXTERNAL_CERT_PEM" , Value : "/tls/tls.crt" },
230- corev1.EnvVar {Name : "EXTERNAL_KEY_PEM" , Value : "/tls/tls.key" },
231- )
232- volumeMounts = append (volumeMounts , corev1.VolumeMount {
233- Name : "tls-certs" ,
234- MountPath : "/tls" ,
235- ReadOnly : true ,
236- })
237- defaultMode := int32 (420 )
238- volumes = append (volumes , corev1.Volume {
239- Name : "tls-certs" ,
240- VolumeSource : corev1.VolumeSource {
241- Secret : & corev1.SecretVolumeSource {
242- SecretName : tlsSecretName ,
243- DefaultMode : & defaultMode ,
244- },
245- },
246- })
247- }
248-
249274 return & appsv1.Deployment {
250275 ObjectMeta : metav1.ObjectMeta {
251276 Name : fmt .Sprintf ("%s-telemetry" , jumpstarter .Name ),
@@ -283,8 +308,6 @@ func (r *JumpstarterReconciler) createTelemetryDeployment(jumpstarter *operatorv
283308 Args : []string {
284309 fmt .Sprintf ("--grpc-bind=:%d" , telemetryPort ),
285310 },
286- Env : envVars ,
287- VolumeMounts : volumeMounts ,
288311 Ports : []corev1.ContainerPort {
289312 {
290313 ContainerPort : int32 (telemetryPort ),
@@ -320,30 +343,29 @@ func (r *JumpstarterReconciler) createTelemetryDeployment(jumpstarter *operatorv
320343 TerminationMessagePath : "/dev/termination-log" ,
321344 TerminationMessagePolicy : corev1 .TerminationMessageReadFile ,
322345 SecurityContext : & corev1.SecurityContext {
323- AllowPrivilegeEscalation : boolPtr (false ),
346+ AllowPrivilegeEscalation : ptr . To (false ),
324347 Capabilities : & corev1.Capabilities {
325348 Drop : []corev1.Capability {"ALL" },
326349 },
327350 },
328351 },
329352 },
330- Volumes : volumes ,
331353 SecurityContext : & corev1.PodSecurityContext {
332- RunAsNonRoot : boolPtr (true ),
354+ RunAsNonRoot : ptr . To (true ),
333355 SeccompProfile : & corev1.SeccompProfile {
334356 Type : corev1 .SeccompProfileTypeRuntimeDefault ,
335357 },
336358 },
337- ServiceAccountName : fmt . Sprintf ( "%s-controller-manager" , jumpstarter .Name ) ,
359+ ServiceAccountName : jumpstarter .Name + telemetrySASuffix ,
338360 },
339361 },
340362 },
341363 }
342364}
343365
344366// cleanupTelemetry removes telemetry resources when telemetry is disabled.
345- // Owned resources (Deployment, Service) are deleted; the CR's garbage collection
346- // will handle removing any cert-manager Certificate .
367+ // GC cannot remove the cert-manager Certificate while the Jumpstarter CR still
368+ // exists (GC only fires when the owner is deleted), so it is deleted explicitly .
347369func (r * JumpstarterReconciler ) cleanupTelemetry (ctx context.Context , jumpstarter * operatorv1alpha1.Jumpstarter ) error {
348370 log := logf .FromContext (ctx )
349371
@@ -368,11 +390,31 @@ func (r *JumpstarterReconciler) cleanupTelemetry(ctx context.Context, jumpstarte
368390 log .Info ("Deleted telemetry service" , "name" , telemetryServiceName )
369391 }
370392
393+ certName := getTelemetryCertSecretName (jumpstarter )
394+ cert := & certmanagerv1.Certificate {}
395+ cert .Name = certName
396+ cert .Namespace = jumpstarter .Namespace
397+ if err := r .Delete (ctx , cert ); err != nil && ! errors .IsNotFound (err ) && ! meta .IsNoMatchError (err ) {
398+ return fmt .Errorf ("failed to delete telemetry certificate: %w" , err )
399+ } else if err == nil {
400+ log .Info ("Deleted telemetry certificate" , "name" , certName )
401+ }
402+
403+ saName := jumpstarter .Name + telemetrySASuffix
404+ sa := & corev1.ServiceAccount {}
405+ sa .Name = saName
406+ sa .Namespace = jumpstarter .Namespace
407+ if err := r .Delete (ctx , sa ); err != nil && ! errors .IsNotFound (err ) {
408+ return fmt .Errorf ("failed to delete telemetry service account: %w" , err )
409+ } else if err == nil {
410+ log .Info ("Deleted telemetry service account" , "name" , saName )
411+ }
412+
371413 return nil
372414}
373415
374- // GetTelemetryCertSecretName returns the name of the telemetry TLS secret.
375- func GetTelemetryCertSecretName (js * operatorv1alpha1.Jumpstarter ) string {
416+ // getTelemetryCertSecretName returns the name of the telemetry TLS secret.
417+ func getTelemetryCertSecretName (js * operatorv1alpha1.Jumpstarter ) string {
376418 return js .Name + telemetryCertSuffix
377419}
378420
0 commit comments