diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml
index b07e9f692..542cf1049 100644
--- a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml
+++ b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml
@@ -1557,6 +1557,13 @@ spec:
May also be set in PodSecurityContext. If set in both SecurityContext and
PodSecurityContext, the value specified in SecurityContext takes precedence.
type: string
+ serviceAccountName:
+ description: |-
+ The service account to use for the Recorder's StatefulSet. If not set,
+ the operator will create a service account with the same name as the
+ Recorder resource.
+ https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#service-account
+ type: string
tolerations:
description: |-
Tolerations for Recorder Pods. By default, the operator does not apply
diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml
index 9bfbd533f..e9bfa29d0 100644
--- a/cmd/k8s-operator/deploy/manifests/operator.yaml
+++ b/cmd/k8s-operator/deploy/manifests/operator.yaml
@@ -4552,6 +4552,13 @@ spec:
type: string
type: object
type: object
+ serviceAccountName:
+ description: |-
+ The service account to use for the Recorder's StatefulSet. If not set,
+ the operator will create a service account with the same name as the
+ Recorder resource.
+ https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#service-account
+ type: string
tolerations:
description: |-
Tolerations for Recorder Pods. By default, the operator does not apply
diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go
index e9e6b2c6c..c12964a16 100644
--- a/cmd/k8s-operator/tsrecorder.go
+++ b/cmd/k8s-operator/tsrecorder.go
@@ -169,14 +169,17 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco
}); err != nil {
return fmt.Errorf("error creating state Secret: %w", err)
}
- sa := tsrServiceAccount(tsr, r.tsNamespace)
- if _, err := createOrUpdate(ctx, r.Client, r.tsNamespace, sa, func(s *corev1.ServiceAccount) {
- s.ObjectMeta.Labels = sa.ObjectMeta.Labels
- s.ObjectMeta.Annotations = sa.ObjectMeta.Annotations
- s.ObjectMeta.OwnerReferences = sa.ObjectMeta.OwnerReferences
- }); err != nil {
- return fmt.Errorf("error creating ServiceAccount: %w", err)
- }
+ // Create the ServiceAccount only if the user hasn't specified a custom name
+ if tsr.Spec.StatefulSet.Pod.ServiceAccountName == "" {
+ sa := tsrServiceAccount(tsr, r.tsNamespace)
+ if _, err := createOrUpdate(ctx, r.Client, r.tsNamespace, sa, func(s *corev1.ServiceAccount) {
+ s.ObjectMeta.Labels = sa.ObjectMeta.Labels
+ s.ObjectMeta.Annotations = sa.ObjectMeta.Annotations
+ s.ObjectMeta.OwnerReferences = sa.ObjectMeta.OwnerReferences
+ }); err != nil {
+ return fmt.Errorf("error creating ServiceAccount: %w", err)
+ }
+ }
role := tsrRole(tsr, r.tsNamespace)
if _, err := createOrUpdate(ctx, r.Client, r.tsNamespace, role, func(r *rbacv1.Role) {
r.ObjectMeta.Labels = role.ObjectMeta.Labels
diff --git a/cmd/k8s-operator/tsrecorder_specs.go b/cmd/k8s-operator/tsrecorder_specs.go
index 4a7bf9887..47086559e 100644
--- a/cmd/k8s-operator/tsrecorder_specs.go
+++ b/cmd/k8s-operator/tsrecorder_specs.go
@@ -39,7 +39,13 @@ func tsrStatefulSet(tsr *tsapi.Recorder, namespace string) *appsv1.StatefulSet {
Annotations: tsr.Spec.StatefulSet.Pod.Annotations,
},
Spec: corev1.PodSpec{
- ServiceAccountName: tsr.Name,
+ ServiceAccountName: func() string {
+ if tsr.Spec.StatefulSet.Pod.ServiceAccountName != "" {
+ return tsr.Spec.StatefulSet.Pod.ServiceAccountName
+ }
+
+ return tsr.Name
+ }(),
Affinity: tsr.Spec.StatefulSet.Pod.Affinity,
SecurityContext: tsr.Spec.StatefulSet.Pod.SecurityContext,
ImagePullSecrets: tsr.Spec.StatefulSet.Pod.ImagePullSecrets,
@@ -144,25 +150,30 @@ func tsrRole(tsr *tsapi.Recorder, namespace string) *rbacv1.Role {
}
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: tsr.Name,
- Namespace: namespace,
- },
- },
- RoleRef: rbacv1.RoleRef{
- Kind: "Role",
- Name: tsr.Name,
- },
- }
+ saName := tsr.Spec.StatefulSet.Pod.ServiceAccountName
+ if saName == "" {
+ saName = tsr.Name
+ }
+
+ 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: saName,
+ Namespace: namespace,
+ },
+ },
+ RoleRef: rbacv1.RoleRef{
+ Kind: "Role",
+ Name: tsr.Name,
+ },
+ }
}
func tsrAuthSecret(tsr *tsapi.Recorder, namespace string, authKey string) *corev1.Secret {
diff --git a/k8s-operator/api.md b/k8s-operator/api.md
index 190f99d24..89507ca10 100644
--- a/k8s-operator/api.md
+++ b/k8s-operator/api.md
@@ -726,6 +726,7 @@ _Appears in:_
| `imagePullSecrets` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#localobjectreference-v1-core) array_ | Image pull Secrets for Recorder Pods.
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec | | |
| `nodeSelector` _object (keys:string, values:string)_ | Node selector rules for Recorder Pods. By default, the operator does
not apply any node selector rules.
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#toleration-v1-core) array_ | Tolerations for Recorder Pods. By default, the operator does not apply
any tolerations.
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
+| `serviceAccountName` _string_ | The service account to use for the Recorder's StatefulSet. If not set,
the operator will create a service account with the same name as the
Recorder resource.
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#service-account | | |
#### RecorderSpec
diff --git a/k8s-operator/apis/v1alpha1/types_recorder.go b/k8s-operator/apis/v1alpha1/types_recorder.go
index 6e5416ea5..5fbad6ea6 100644
--- a/k8s-operator/apis/v1alpha1/types_recorder.go
+++ b/k8s-operator/apis/v1alpha1/types_recorder.go
@@ -94,6 +94,7 @@ type RecorderStatefulSet struct {
// Configuration for pods created by the Recorder's StatefulSet.
// +optional
Pod RecorderPod `json:"pod,omitempty"`
+
}
type RecorderPod struct {
@@ -142,6 +143,13 @@ type RecorderPod struct {
// https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
+
+ // The service account to use for the Recorder's StatefulSet. If not set,
+ // the operator will create a service account with the same name as the
+ // Recorder resource.
+ // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#service-account
+ // +optional
+ ServiceAccountName string `json:"serviceAccountName,omitempty"`
}
type RecorderContainer struct {