cmd/k8s-operator: Set login server on tsrecorder nodes (#16443)

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>
This commit is contained in:
David Bond 2025-07-03 15:53:35 +01:00 committed by GitHub
parent 3a4b439c62
commit 5dc11d50f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 24 additions and 11 deletions

View File

@ -82,6 +82,7 @@ func main() {
tsFirewallMode = defaultEnv("PROXY_FIREWALL_MODE", "")
defaultProxyClass = defaultEnv("PROXY_DEFAULT_CLASS", "")
isDefaultLoadBalancer = defaultBool("OPERATOR_DEFAULT_LOAD_BALANCER", false)
loginServer = strings.TrimSuffix(defaultEnv("OPERATOR_LOGIN_SERVER", ""), "/")
)
var opts []kzap.Opts
@ -115,7 +116,7 @@ func main() {
hostinfo.SetApp(kubetypes.AppAPIServerProxy)
}
s, tsc := initTSNet(zlog)
s, tsc := initTSNet(zlog, loginServer)
defer s.Close()
restConfig := config.GetConfigOrDie()
apiproxy.MaybeLaunchAPIServerProxy(zlog, restConfig, s, mode)
@ -131,6 +132,7 @@ func main() {
proxyTags: tags,
proxyFirewallMode: tsFirewallMode,
defaultProxyClass: defaultProxyClass,
loginServer: loginServer,
}
runReconcilers(rOpts)
}
@ -138,14 +140,13 @@ func main() {
// initTSNet initializes the tsnet.Server and logs in to Tailscale. It uses the
// CLIENT_ID_FILE and CLIENT_SECRET_FILE environment variables to authenticate
// with Tailscale.
func initTSNet(zlog *zap.SugaredLogger) (*tsnet.Server, tsClient) {
func initTSNet(zlog *zap.SugaredLogger, loginServer string) (*tsnet.Server, tsClient) {
var (
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
kubeSecret = defaultEnv("OPERATOR_SECRET", "")
operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
loginServer = strings.TrimSuffix(defaultEnv("OPERATOR_LOGIN_SERVER", ""), "/")
)
startlog := zlog.Named("startup")
if clientIDPath == "" || clientSecretPath == "" {
@ -610,6 +611,7 @@ func runReconcilers(opts reconcilerOpts) {
l: opts.log.Named("recorder-reconciler"),
clock: tstime.DefaultClock{},
tsClient: opts.tsClient,
loginServer: opts.loginServer,
})
if err != nil {
startlog.Fatalf("could not create Recorder reconciler: %v", err)
@ -693,6 +695,8 @@ type reconcilerOpts struct {
// class for proxies that do not have a ProxyClass set.
// this is defined by an operator env variable.
defaultProxyClass string
// loginServer is the coordination server URL that should be used by managed resources.
loginServer string
}
// enqueueAllIngressEgressProxySvcsinNS returns a reconcile request for each

View File

@ -59,6 +59,7 @@ type RecorderReconciler struct {
clock tstime.Clock
tsNamespace string
tsClient tsClient
loginServer string
mu sync.Mutex // protects following
recorders set.Slice[types.UID] // for recorders gauge
@ -202,7 +203,7 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco
}); err != nil {
return fmt.Errorf("error creating RoleBinding: %w", err)
}
ss := tsrStatefulSet(tsr, r.tsNamespace)
ss := tsrStatefulSet(tsr, r.tsNamespace, r.loginServer)
if _, err := createOrUpdate(ctx, r.Client, r.tsNamespace, ss, func(s *appsv1.StatefulSet) {
s.ObjectMeta.Labels = ss.ObjectMeta.Labels
s.ObjectMeta.Annotations = ss.ObjectMeta.Annotations

View File

@ -17,7 +17,7 @@ import (
"tailscale.com/version"
)
func tsrStatefulSet(tsr *tsapi.Recorder, namespace string) *appsv1.StatefulSet {
func tsrStatefulSet(tsr *tsapi.Recorder, namespace string, loginServer string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: tsr.Name,
@ -59,7 +59,7 @@ func tsrStatefulSet(tsr *tsapi.Recorder, namespace string) *appsv1.StatefulSet {
ImagePullPolicy: tsr.Spec.StatefulSet.Pod.Container.ImagePullPolicy,
Resources: tsr.Spec.StatefulSet.Pod.Container.Resources,
SecurityContext: tsr.Spec.StatefulSet.Pod.Container.SecurityContext,
Env: env(tsr),
Env: env(tsr, loginServer),
EnvFrom: func() []corev1.EnvFromSource {
if tsr.Spec.Storage.S3 == nil || tsr.Spec.Storage.S3.Credentials.Secret.Name == "" {
return nil
@ -201,7 +201,7 @@ func tsrStateSecret(tsr *tsapi.Recorder, namespace string) *corev1.Secret {
}
}
func env(tsr *tsapi.Recorder) []corev1.EnvVar {
func env(tsr *tsapi.Recorder, loginServer string) []corev1.EnvVar {
envs := []corev1.EnvVar{
{
Name: "TS_AUTHKEY",
@ -239,6 +239,10 @@ func env(tsr *tsapi.Recorder) []corev1.EnvVar {
Name: "TSRECORDER_HOSTNAME",
Value: "$(POD_NAME)",
},
{
Name: "TSRECORDER_LOGIN_SERVER",
Value: loginServer,
},
}
for _, env := range tsr.Spec.StatefulSet.Pod.Container.Env {

View File

@ -90,7 +90,7 @@ func TestRecorderSpecs(t *testing.T) {
},
}
ss := tsrStatefulSet(tsr, tsNamespace)
ss := tsrStatefulSet(tsr, tsNamespace, tsLoginServer)
// StatefulSet-level.
if diff := cmp.Diff(ss.Annotations, tsr.Spec.StatefulSet.Annotations); diff != "" {
@ -124,7 +124,7 @@ func TestRecorderSpecs(t *testing.T) {
}
// Container-level.
if diff := cmp.Diff(ss.Spec.Template.Spec.Containers[0].Env, env(tsr)); diff != "" {
if diff := cmp.Diff(ss.Spec.Template.Spec.Containers[0].Env, env(tsr, tsLoginServer)); diff != "" {
t.Errorf("(-got +want):\n%s", diff)
}
if diff := cmp.Diff(ss.Spec.Template.Spec.Containers[0].Image, tsr.Spec.StatefulSet.Pod.Container.Image); diff != "" {

View File

@ -25,7 +25,10 @@ import (
"tailscale.com/tstest"
)
const tsNamespace = "tailscale"
const (
tsNamespace = "tailscale"
tsLoginServer = "example.tailscale.com"
)
func TestRecorder(t *testing.T) {
tsr := &tsapi.Recorder{
@ -51,6 +54,7 @@ func TestRecorder(t *testing.T) {
recorder: fr,
l: zl.Sugar(),
clock: cl,
loginServer: tsLoginServer,
}
t.Run("invalid_spec_gives_an_error_condition", func(t *testing.T) {
@ -234,7 +238,7 @@ func expectRecorderResources(t *testing.T, fc client.WithWatch, tsr *tsapi.Recor
role := tsrRole(tsr, tsNamespace)
roleBinding := tsrRoleBinding(tsr, tsNamespace)
serviceAccount := tsrServiceAccount(tsr, tsNamespace)
statefulSet := tsrStatefulSet(tsr, tsNamespace)
statefulSet := tsrStatefulSet(tsr, tsNamespace, tsLoginServer)
if shouldExist {
expectEqual(t, fc, auth)