mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	This commit modifies the recorder node reconciler to include the environment variable added in https://github.com/tailscale/corp/pull/30058 which allows for configuration of the coordination server. Updates https://github.com/tailscale/corp/issues/29847 Signed-off-by: David Bond <davidsbond93@gmail.com>
		
			
				
	
	
		
			311 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| //go:build !plan9
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 
 | |
| 	appsv1 "k8s.io/api/apps/v1"
 | |
| 	corev1 "k8s.io/api/core/v1"
 | |
| 	rbacv1 "k8s.io/api/rbac/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
 | |
| 	"tailscale.com/types/ptr"
 | |
| 	"tailscale.com/version"
 | |
| )
 | |
| 
 | |
| func tsrStatefulSet(tsr *tsapi.Recorder, namespace string, loginServer string) *appsv1.StatefulSet {
 | |
| 	return &appsv1.StatefulSet{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:            tsr.Name,
 | |
| 			Namespace:       namespace,
 | |
| 			Labels:          labels("recorder", tsr.Name, tsr.Spec.StatefulSet.Labels),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 			Annotations:     tsr.Spec.StatefulSet.Annotations,
 | |
| 		},
 | |
| 		Spec: appsv1.StatefulSetSpec{
 | |
| 			Replicas: ptr.To[int32](1),
 | |
| 			Selector: &metav1.LabelSelector{
 | |
| 				MatchLabels: labels("recorder", tsr.Name, tsr.Spec.StatefulSet.Pod.Labels),
 | |
| 			},
 | |
| 			Template: corev1.PodTemplateSpec{
 | |
| 				ObjectMeta: metav1.ObjectMeta{
 | |
| 					Name:        tsr.Name,
 | |
| 					Namespace:   namespace,
 | |
| 					Labels:      labels("recorder", tsr.Name, tsr.Spec.StatefulSet.Pod.Labels),
 | |
| 					Annotations: tsr.Spec.StatefulSet.Pod.Annotations,
 | |
| 				},
 | |
| 				Spec: corev1.PodSpec{
 | |
| 					ServiceAccountName: tsrServiceAccountName(tsr),
 | |
| 					Affinity:           tsr.Spec.StatefulSet.Pod.Affinity,
 | |
| 					SecurityContext:    tsr.Spec.StatefulSet.Pod.SecurityContext,
 | |
| 					ImagePullSecrets:   tsr.Spec.StatefulSet.Pod.ImagePullSecrets,
 | |
| 					NodeSelector:       tsr.Spec.StatefulSet.Pod.NodeSelector,
 | |
| 					Tolerations:        tsr.Spec.StatefulSet.Pod.Tolerations,
 | |
| 					Containers: []corev1.Container{
 | |
| 						{
 | |
| 							Name: "recorder",
 | |
| 							Image: func() string {
 | |
| 								image := tsr.Spec.StatefulSet.Pod.Container.Image
 | |
| 								if image == "" {
 | |
| 									image = fmt.Sprintf("tailscale/tsrecorder:%s", selfVersionImageTag())
 | |
| 								}
 | |
| 
 | |
| 								return image
 | |
| 							}(),
 | |
| 							ImagePullPolicy: tsr.Spec.StatefulSet.Pod.Container.ImagePullPolicy,
 | |
| 							Resources:       tsr.Spec.StatefulSet.Pod.Container.Resources,
 | |
| 							SecurityContext: tsr.Spec.StatefulSet.Pod.Container.SecurityContext,
 | |
| 							Env:             env(tsr, loginServer),
 | |
| 							EnvFrom: func() []corev1.EnvFromSource {
 | |
| 								if tsr.Spec.Storage.S3 == nil || tsr.Spec.Storage.S3.Credentials.Secret.Name == "" {
 | |
| 									return nil
 | |
| 								}
 | |
| 
 | |
| 								return []corev1.EnvFromSource{{
 | |
| 									SecretRef: &corev1.SecretEnvSource{
 | |
| 										LocalObjectReference: corev1.LocalObjectReference{
 | |
| 											Name: tsr.Spec.Storage.S3.Credentials.Secret.Name,
 | |
| 										},
 | |
| 									},
 | |
| 								}}
 | |
| 							}(),
 | |
| 							Command: []string{"/tsrecorder"},
 | |
| 							VolumeMounts: []corev1.VolumeMount{
 | |
| 								{
 | |
| 									Name:      "data",
 | |
| 									MountPath: "/data",
 | |
| 									ReadOnly:  false,
 | |
| 								},
 | |
| 							},
 | |
| 						},
 | |
| 					},
 | |
| 					Volumes: []corev1.Volume{
 | |
| 						{
 | |
| 							Name: "data",
 | |
| 							VolumeSource: corev1.VolumeSource{
 | |
| 								EmptyDir: &corev1.EmptyDirVolumeSource{},
 | |
| 							},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func tsrServiceAccount(tsr *tsapi.Recorder, namespace string) *corev1.ServiceAccount {
 | |
| 	return &corev1.ServiceAccount{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:            tsrServiceAccountName(tsr),
 | |
| 			Namespace:       namespace,
 | |
| 			Labels:          labels("recorder", tsr.Name, nil),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 			Annotations:     tsr.Spec.StatefulSet.Pod.ServiceAccount.Annotations,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func tsrServiceAccountName(tsr *tsapi.Recorder) string {
 | |
| 	sa := tsr.Spec.StatefulSet.Pod.ServiceAccount
 | |
| 	name := tsr.Name
 | |
| 	if sa.Name != "" {
 | |
| 		name = sa.Name
 | |
| 	}
 | |
| 
 | |
| 	return name
 | |
| }
 | |
| 
 | |
| func tsrRole(tsr *tsapi.Recorder, namespace string) *rbacv1.Role {
 | |
| 	return &rbacv1.Role{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:            tsr.Name,
 | |
| 			Namespace:       namespace,
 | |
| 			Labels:          labels("recorder", tsr.Name, nil),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 		},
 | |
| 		Rules: []rbacv1.PolicyRule{
 | |
| 			{
 | |
| 				APIGroups: []string{""},
 | |
| 				Resources: []string{"secrets"},
 | |
| 				Verbs: []string{
 | |
| 					"get",
 | |
| 					"patch",
 | |
| 					"update",
 | |
| 				},
 | |
| 				ResourceNames: []string{
 | |
| 					tsr.Name,                      // Contains the auth key.
 | |
| 					fmt.Sprintf("%s-0", tsr.Name), // Contains the node state.
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				APIGroups: []string{""},
 | |
| 				Resources: []string{"events"},
 | |
| 				Verbs: []string{
 | |
| 					"get",
 | |
| 					"create",
 | |
| 					"patch",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func tsrRoleBinding(tsr *tsapi.Recorder, namespace string) *rbacv1.RoleBinding {
 | |
| 	return &rbacv1.RoleBinding{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:            tsr.Name,
 | |
| 			Namespace:       namespace,
 | |
| 			Labels:          labels("recorder", tsr.Name, nil),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 		},
 | |
| 		Subjects: []rbacv1.Subject{
 | |
| 			{
 | |
| 				Kind:      "ServiceAccount",
 | |
| 				Name:      tsrServiceAccountName(tsr),
 | |
| 				Namespace: namespace,
 | |
| 			},
 | |
| 		},
 | |
| 		RoleRef: rbacv1.RoleRef{
 | |
| 			Kind: "Role",
 | |
| 			Name: tsr.Name,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func tsrAuthSecret(tsr *tsapi.Recorder, namespace string, authKey string) *corev1.Secret {
 | |
| 	return &corev1.Secret{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Namespace:       namespace,
 | |
| 			Name:            tsr.Name,
 | |
| 			Labels:          labels("recorder", tsr.Name, nil),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 		},
 | |
| 		StringData: map[string]string{
 | |
| 			"authkey": authKey,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func tsrStateSecret(tsr *tsapi.Recorder, namespace string) *corev1.Secret {
 | |
| 	return &corev1.Secret{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:            fmt.Sprintf("%s-0", tsr.Name),
 | |
| 			Namespace:       namespace,
 | |
| 			Labels:          labels("recorder", tsr.Name, nil),
 | |
| 			OwnerReferences: tsrOwnerReference(tsr),
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func env(tsr *tsapi.Recorder, loginServer string) []corev1.EnvVar {
 | |
| 	envs := []corev1.EnvVar{
 | |
| 		{
 | |
| 			Name: "TS_AUTHKEY",
 | |
| 			ValueFrom: &corev1.EnvVarSource{
 | |
| 				SecretKeyRef: &corev1.SecretKeySelector{
 | |
| 					LocalObjectReference: corev1.LocalObjectReference{
 | |
| 						Name: tsr.Name,
 | |
| 					},
 | |
| 					Key: "authkey",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			Name: "POD_NAME",
 | |
| 			ValueFrom: &corev1.EnvVarSource{
 | |
| 				FieldRef: &corev1.ObjectFieldSelector{
 | |
| 					// Secret is named after the pod.
 | |
| 					FieldPath: "metadata.name",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			Name: "POD_UID",
 | |
| 			ValueFrom: &corev1.EnvVarSource{
 | |
| 				FieldRef: &corev1.ObjectFieldSelector{
 | |
| 					FieldPath: "metadata.uid",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			Name:  "TS_STATE",
 | |
| 			Value: "kube:$(POD_NAME)",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:  "TSRECORDER_HOSTNAME",
 | |
| 			Value: "$(POD_NAME)",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:  "TSRECORDER_LOGIN_SERVER",
 | |
| 			Value: loginServer,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, env := range tsr.Spec.StatefulSet.Pod.Container.Env {
 | |
| 		envs = append(envs, corev1.EnvVar{
 | |
| 			Name:  string(env.Name),
 | |
| 			Value: env.Value,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if tsr.Spec.Storage.S3 != nil {
 | |
| 		envs = append(envs,
 | |
| 			corev1.EnvVar{
 | |
| 				Name:  "TSRECORDER_DST",
 | |
| 				Value: fmt.Sprintf("s3://%s", tsr.Spec.Storage.S3.Endpoint),
 | |
| 			},
 | |
| 			corev1.EnvVar{
 | |
| 				Name:  "TSRECORDER_BUCKET",
 | |
| 				Value: tsr.Spec.Storage.S3.Bucket,
 | |
| 			},
 | |
| 		)
 | |
| 	} else {
 | |
| 		envs = append(envs, corev1.EnvVar{
 | |
| 			Name:  "TSRECORDER_DST",
 | |
| 			Value: "/data/recordings",
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if tsr.Spec.EnableUI {
 | |
| 		envs = append(envs, corev1.EnvVar{
 | |
| 			Name:  "TSRECORDER_UI",
 | |
| 			Value: "true",
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return envs
 | |
| }
 | |
| 
 | |
| func labels(app, instance string, customLabels map[string]string) map[string]string {
 | |
| 	l := make(map[string]string, len(customLabels)+3)
 | |
| 	for k, v := range customLabels {
 | |
| 		l[k] = v
 | |
| 	}
 | |
| 
 | |
| 	// ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
 | |
| 	l["app.kubernetes.io/name"] = app
 | |
| 	l["app.kubernetes.io/instance"] = instance
 | |
| 	l["app.kubernetes.io/managed-by"] = "tailscale-operator"
 | |
| 
 | |
| 	return l
 | |
| }
 | |
| 
 | |
| func tsrOwnerReference(owner metav1.Object) []metav1.OwnerReference {
 | |
| 	return []metav1.OwnerReference{*metav1.NewControllerRef(owner, tsapi.SchemeGroupVersion.WithKind("Recorder"))}
 | |
| }
 | |
| 
 | |
| // selfVersionImageTag returns the container image tag of the running operator
 | |
| // build.
 | |
| func selfVersionImageTag() string {
 | |
| 	meta := version.GetMeta()
 | |
| 	var versionPrefix string
 | |
| 	if meta.UnstableBranch {
 | |
| 		versionPrefix = "unstable-"
 | |
| 	}
 | |
| 	return fmt.Sprintf("%sv%s", versionPrefix, meta.MajorMinorPatch)
 | |
| }
 |