Files
tailscale/k8s-operator/reconciler/reconciler.go
T
David BondandGitHub be0e460a20 cmd/k8s-operator,k8s-operator: Kubernetes Peer Relays (#20495)
This commit contains the Kubernetes implementation of peer relays via the new `PeerRelay` CRD. It's a mega branch consisting of the commits of other PRs gone into this work:

1. https://github.com/tailscale/tailscale/pull/20211
2. https://github.com/tailscale/tailscale/pull/20329
3. https://github.com/tailscale/tailscale/pull/20423
4. https://github.com/tailscale/tailscale/pull/20503

An instance of the `PeerRelay` CRD deploys a `StatefulSet` of containerboot instances configured to advertise themselves as peer relays using the IP addresses configured via `LoadBalancer` services on each cloud provider (with some AWS specifics as it's less automatic than its competing cloud providers). 

Per replica, a `LoadBalancer` type `Service` resource is provisioned and its IP address is used to configure the respective relay.

This has been tested with success in AWS, GCP & Azure and provides additional modification to `Service` resources via the CRD for any other kinds of deployment environments. It also contains some work that may appear to be duplication of what already exists within `cmd/k8s-operator` so we can start building an appropriate migration path for `Connector`, `ProxyGroup` etc into respective `k8s-operator/reconciler/*` packages.

Closes https://github.com/tailscale/corp/issues/34524
2026-07-20 16:37:15 +01:00

96 lines
3.4 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
// Package reconciler provides utilities for working with Kubernetes resources within controller reconciliation
// loops.
package reconciler
import (
"context"
"slices"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"tailscale.com/kube/kubetypes"
)
const (
// FinalizerName is the common finalizer used across all Tailscale Kubernetes resources.
FinalizerName = "tailscale.com/finalizer"
// LabelParentType identifies which Tailscale CRD kind owns a managed resource. Every resource that a Tailscale
// CRD reconciler creates should carry this label alongside LabelParentName.
LabelParentType = "tailscale.com/parent-resource-type"
// LabelParentName identifies the name of the Tailscale CRD that owns a managed resource. Combined with
// LabelParentType, this uniquely identifies the parent within its scope.
LabelParentName = "tailscale.com/parent-resource"
// LabelParentNamespace identifies the namespace of the owning Tailscale CRD. It is only stamped when the parent
// CRD is namespaced; cluster-scoped parents omit it.
LabelParentNamespace = "tailscale.com/parent-resource-ns"
)
// SetFinalizer adds the finalizer to the resource if not already present.
func SetFinalizer(obj client.Object) {
if idx := slices.Index(obj.GetFinalizers(), FinalizerName); idx >= 0 {
return
}
obj.SetFinalizers(append(obj.GetFinalizers(), FinalizerName))
}
// RemoveFinalizer removes the finalizer from the resource if present.
func RemoveFinalizer(obj client.Object) {
idx := slices.Index(obj.GetFinalizers(), FinalizerName)
if idx < 0 {
return
}
finalizers := obj.GetFinalizers()
obj.SetFinalizers(append(finalizers[:idx], finalizers[idx+1:]...))
}
// Labels returns the standard ownership labels stamped on every resource a Tailscale CRD reconciler creates:
// tailscale.com/managed=true, plus LabelParentType and LabelParentName. If parentNamespace is non-empty (i.e. the
// parent CRD is namespaced) it is stamped as LabelParentNamespace; cluster-scoped parents pass "".
func Labels(parentType, parentName, parentNamespace string) map[string]string {
labels := map[string]string{
kubetypes.LabelManaged: "true",
LabelParentType: parentType,
LabelParentName: parentName,
}
if parentNamespace != "" {
labels[LabelParentNamespace] = parentNamespace
}
return labels
}
// EnqueueForChild returns a handler.MapFunc that enqueues a reconcile.Request for the parent CRD of a managed child
// resource. It filters by tailscale.com/managed=true and LabelParentType, and derives the request's NamespacedName
// from LabelParentName plus LabelParentNamespace (blank namespace for cluster-scoped parents). Use it on Watches of
// child resources so drift or cloud-controller updates propagate to the owning CRD's reconciler.
func EnqueueForChild(parentType string) handler.MapFunc {
return func(_ context.Context, o client.Object) []reconcile.Request {
labels := o.GetLabels()
if labels[kubetypes.LabelManaged] != "true" || labels[LabelParentType] != parentType {
return nil
}
name := labels[LabelParentName]
if name == "" {
return nil
}
return []reconcile.Request{{NamespacedName: types.NamespacedName{
Name: name,
Namespace: labels[LabelParentNamespace],
}}}
}
}