cmd/k8s-operator: ProxyClass annotation for Services and Ingresses (#16363)

* cmd/k8s-operator: ProxyClass annotation for Services and Ingresses

Previously, the ProxyClass could only be configured for Services and
Ingresses via a Label. This adds the ability to set it via an
Annotation, but prioritizes the Label if both a Label and Annotation are
set.

Updates #14323

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* Update cmd/k8s-operator/operator.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>

* Update cmd/k8s-operator/operator.go

Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>

* cmd/k8s-operator: ProxyClass annotation for Services and Ingresses

Previously, the ProxyClass could only be configured for Services and
Ingresses via a Label. This adds the ability to set it via an
Annotation, but prioritizes the Label if both a Label and Annotation are
set.

Updates #14323

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

---------

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>
Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
Tom Meadows
2025-06-30 12:08:35 +01:00
committed by GitHub
parent f85e4bcb32
commit 2fc247573b
6 changed files with 398 additions and 28 deletions
+67 -2
View File
@@ -54,6 +54,7 @@ import (
"tailscale.com/tsnet"
"tailscale.com/tstime"
"tailscale.com/types/logger"
"tailscale.com/util/set"
"tailscale.com/version"
)
@@ -307,6 +308,7 @@ func runReconcilers(opts reconcilerOpts) {
proxyPriorityClassName: opts.proxyPriorityClassName,
tsFirewallMode: opts.proxyFirewallMode,
}
err = builder.
ControllerManagedBy(mgr).
Named("service-reconciler").
@@ -327,6 +329,10 @@ func runReconcilers(opts reconcilerOpts) {
if err != nil {
startlog.Fatalf("could not create service reconciler: %v", err)
}
if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(corev1.Service), indexServiceProxyClass, indexProxyClass); err != nil {
startlog.Fatalf("failed setting up ProxyClass indexer for Services: %v", err)
}
ingressChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("ingress"))
// If a ProxyClassChanges, enqueue all Ingresses labeled with that
// ProxyClass's name.
@@ -351,6 +357,10 @@ func runReconcilers(opts reconcilerOpts) {
if err != nil {
startlog.Fatalf("could not create ingress reconciler: %v", err)
}
if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(networkingv1.Ingress), indexIngressProxyClass, indexProxyClass); err != nil {
startlog.Fatalf("failed setting up ProxyClass indexer for Ingresses: %v", err)
}
lc, err := opts.tsServer.LocalClient()
if err != nil {
startlog.Fatalf("could not get local client: %v", err)
@@ -797,6 +807,16 @@ func managedResourceHandlerForType(typ string) handler.MapFunc {
}
}
// indexProxyClass is used to select ProxyClass-backed objects which are
// locally indexed in the cache for efficient listing without requiring labels.
func indexProxyClass(o client.Object) []string {
if !hasProxyClassAnnotation(o) {
return nil
}
return []string{o.GetAnnotations()[LabelAnnotationProxyClass]}
}
// proxyClassHandlerForSvc returns a handler that, for a given ProxyClass,
// returns a list of reconcile requests for all Services labeled with
// tailscale.com/proxy-class: <proxy class name>.
@@ -804,16 +824,37 @@ func proxyClassHandlerForSvc(cl client.Client, logger *zap.SugaredLogger) handle
return func(ctx context.Context, o client.Object) []reconcile.Request {
svcList := new(corev1.ServiceList)
labels := map[string]string{
LabelProxyClass: o.GetName(),
LabelAnnotationProxyClass: o.GetName(),
}
if err := cl.List(ctx, svcList, client.MatchingLabels(labels)); err != nil {
logger.Debugf("error listing Services for ProxyClass: %v", err)
return nil
}
reqs := make([]reconcile.Request, 0)
seenSvcs := make(set.Set[string])
for _, svc := range svcList.Items {
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&svc)})
seenSvcs.Add(fmt.Sprintf("%s/%s", svc.Namespace, svc.Name))
}
svcAnnotationList := new(corev1.ServiceList)
if err := cl.List(ctx, svcAnnotationList, client.MatchingFields{indexServiceProxyClass: o.GetName()}); err != nil {
logger.Debugf("error listing Services for ProxyClass: %v", err)
return nil
}
for _, svc := range svcAnnotationList.Items {
nsname := fmt.Sprintf("%s/%s", svc.Namespace, svc.Name)
if seenSvcs.Contains(nsname) {
continue
}
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&svc)})
seenSvcs.Add(nsname)
}
return reqs
}
}
@@ -825,16 +866,36 @@ func proxyClassHandlerForIngress(cl client.Client, logger *zap.SugaredLogger) ha
return func(ctx context.Context, o client.Object) []reconcile.Request {
ingList := new(networkingv1.IngressList)
labels := map[string]string{
LabelProxyClass: o.GetName(),
LabelAnnotationProxyClass: o.GetName(),
}
if err := cl.List(ctx, ingList, client.MatchingLabels(labels)); err != nil {
logger.Debugf("error listing Ingresses for ProxyClass: %v", err)
return nil
}
reqs := make([]reconcile.Request, 0)
seenIngs := make(set.Set[string])
for _, ing := range ingList.Items {
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
seenIngs.Add(fmt.Sprintf("%s/%s", ing.Namespace, ing.Name))
}
ingAnnotationList := new(networkingv1.IngressList)
if err := cl.List(ctx, ingAnnotationList, client.MatchingFields{indexIngressProxyClass: o.GetName()}); err != nil {
logger.Debugf("error listing Ingreses for ProxyClass: %v", err)
return nil
}
for _, ing := range ingAnnotationList.Items {
nsname := fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)
if seenIngs.Contains(nsname) {
continue
}
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
seenIngs.Add(nsname)
}
return reqs
}
}
@@ -1500,6 +1561,10 @@ func hasProxyGroupAnnotation(obj client.Object) bool {
return obj.GetAnnotations()[AnnotationProxyGroup] != ""
}
func hasProxyClassAnnotation(obj client.Object) bool {
return obj.GetAnnotations()[LabelAnnotationProxyClass] != ""
}
func id(ctx context.Context, lc *local.Client) (string, error) {
st, err := lc.StatusWithoutPeers(ctx)
if err != nil {