From 8ecce0b9e10f35f09d41c289b3cfcd061b194698 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Wed, 10 Sep 2025 15:11:31 +0100 Subject: [PATCH] k8s-operator,ipn: add new ProxyGroup Type peer-relay Change-Id: Ia5f5bab08c4ca234714c993cd8742fb3f22b1d0b Signed-off-by: Tom Proctor --- .../crds/tailscale.com_proxygroups.yaml | 1 + .../deploy/manifests/operator.yaml | 1 + cmd/k8s-operator/proxygroup.go | 11 ++++++ cmd/k8s-operator/proxygroup_specs.go | 35 +++++++++++-------- ipn/conf.go | 4 +++ k8s-operator/api.md | 4 +-- .../apis/v1alpha1/types_proxygroup.go | 3 +- 7 files changed, 41 insertions(+), 18 deletions(-) diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml index 98ca1c378..50d4cf594 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml @@ -148,6 +148,7 @@ spec: - egress - ingress - kube-apiserver + - peer-relay x-kubernetes-validations: - rule: self == oldSelf message: ProxyGroup type is immutable diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index 766d7f0d6..eaff5b8ad 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -3050,6 +3050,7 @@ spec: - egress - ingress - kube-apiserver + - peer-relay type: string x-kubernetes-validations: - message: ProxyGroup type is immutable diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index debeb5c6b..ac8858bd5 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -98,6 +98,7 @@ type ProxyGroupReconciler struct { egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge ingressProxyGroups set.Slice[types.UID] // for ingress proxygroups gauge apiServerProxyGroups set.Slice[types.UID] // for kube-apiserver proxygroups gauge + peerRelayProxyGroups set.Slice[types.UID] // for proxygroups configured as a peer relay } func (r *ProxyGroupReconciler) logger(name string) *zap.SugaredLogger { @@ -1010,10 +1011,13 @@ func (r *ProxyGroupReconciler) ensureAddedToGaugeForProxyGroup(pg *tsapi.ProxyGr r.ingressProxyGroups.Add(pg.UID) case tsapi.ProxyGroupTypeKubernetesAPIServer: r.apiServerProxyGroups.Add(pg.UID) + case tsapi.ProxyGroupTypePeerRelay: + r.peerRelayProxyGroups.Add(pg.UID) } gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + // gaugePeerRelayProxyGroupResources.Set(int64(r.peerRelayProxyGroups.Len())) } // ensureRemovedFromGaugeForProxyGroup ensures the gauge metric for the ProxyGroup resource type is updated when the @@ -1026,10 +1030,13 @@ func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.Pro r.ingressProxyGroups.Remove(pg.UID) case tsapi.ProxyGroupTypeKubernetesAPIServer: r.apiServerProxyGroups.Remove(pg.UID) + case tsapi.ProxyGroupTypePeerRelay: + r.peerRelayProxyGroups.Remove(pg.UID) } gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + // gaugePeerRelayProxyGroupResources.Set(int64(r.peerRelayProxyGroups.Len())) } func pgTailscaledConfig(pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string, loginServer string) (tailscaledConfigs, error) { @@ -1055,6 +1062,10 @@ func pgTailscaledConfig(pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, idx int32, a conf.StaticEndpoints = staticEndpoints } + if pg.Spec.Type == tsapi.ProxyGroupTypePeerRelay { + conf.RelayServerPort = ptr.To(7777) + } + return map[tailcfg.CapabilityVersion]ipn.ConfigVAlpha{ pgMinCapabilityVersion: *conf, }, nil diff --git a/cmd/k8s-operator/proxygroup_specs.go b/cmd/k8s-operator/proxygroup_specs.go index e185499f0..13e26e345 100644 --- a/cmd/k8s-operator/proxygroup_specs.go +++ b/cmd/k8s-operator/proxygroup_specs.go @@ -119,16 +119,18 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string }) } - volumes = append(volumes, corev1.Volume{ - Name: proxyConfigVolName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: proxyConfigVolName, + if pg.Spec.Type == tsapi.ProxyGroupTypeEgress || pg.Spec.Type == tsapi.ProxyGroupTypeIngress { + volumes = append(volumes, corev1.Volume{ + Name: proxyConfigVolName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: proxyConfigVolName, + }, }, }, - }, - }) + }) + } return volumes }() @@ -150,11 +152,13 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string }) } - mounts = append(mounts, corev1.VolumeMount{ - Name: proxyConfigVolName, - MountPath: "/etc/proxies", - ReadOnly: true, - }) + if pg.Spec.Type == tsapi.ProxyGroupTypeEgress || pg.Spec.Type == tsapi.ProxyGroupTypeIngress { + mounts = append(mounts, corev1.VolumeMount{ + Name: proxyConfigVolName, + MountPath: "/etc/proxies", + ReadOnly: true, + }) + } return mounts }() @@ -198,7 +202,8 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string }) } - if pg.Spec.Type == tsapi.ProxyGroupTypeEgress { + switch pg.Spec.Type { + case tsapi.ProxyGroupTypeEgress: envs = append(envs, // TODO(irbekrm): in 1.80 we deprecated TS_EGRESS_SERVICES_CONFIG_PATH in favour of // TS_EGRESS_PROXIES_CONFIG_PATH. Remove it in 1.84. @@ -218,7 +223,7 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string Name: "TS_ENABLE_HEALTH_CHECK", Value: "true", }) - } else { // ingress + case tsapi.ProxyGroupTypeIngress: envs = append(envs, corev1.EnvVar{ Name: "TS_INTERNAL_APP", Value: kubetypes.AppProxyGroupIngress, diff --git a/ipn/conf.go b/ipn/conf.go index 2c9fb2fd1..ee13bd415 100644 --- a/ipn/conf.go +++ b/ipn/conf.go @@ -50,6 +50,8 @@ type ConfigVAlpha struct { // should advertise amongst its wireguard endpoints. StaticEndpoints []netip.AddrPort `json:",omitempty"` + RelayServerPort *int `json:",omitempty"` // if set, the port to listen on for peer relay connections + // TODO(bradfitz,maisem): future something like: // Profile map[string]*Config // keyed by alice@gmail.com, corp.com (TailnetSID) } @@ -155,5 +157,7 @@ func (c *ConfigVAlpha) ToPrefs() (MaskedPrefs, error) { if c.AdvertiseServices != nil { mp.AdvertiseServices = c.AdvertiseServices } + mp.RelayServerPortSet = true + mp.RelayServerPort = c.RelayServerPort return mp, nil } diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 79c8469e1..6d09ab985 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -716,7 +716,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `type` _[ProxyGroupType](#proxygrouptype)_ | Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver.
Type is immutable once a ProxyGroup is created. | | Enum: [egress ingress kube-apiserver]
Type: string
| +| `type` _[ProxyGroupType](#proxygrouptype)_ | Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver.
Type is immutable once a ProxyGroup is created. | | Enum: [egress ingress kube-apiserver peer-relay]
Type: string
| | `tags` _[Tags](#tags)_ | Tags that the Tailscale devices will be tagged with. Defaults to [tag:k8s].
If you specify custom tags here, make sure you also make the operator
an owner of these tags.
See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
Tags cannot be changed once a ProxyGroup device has been created.
Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$`
Type: string
| | `replicas` _integer_ | Replicas specifies how many replicas to create the StatefulSet with.
Defaults to 2. | | Minimum: 0
| | `hostnamePrefix` _[HostnamePrefix](#hostnameprefix)_ | HostnamePrefix is the hostname prefix to use for tailnet devices created
by the ProxyGroup. Each device will have the integer number from its
StatefulSet pod appended to this prefix to form the full hostname.
HostnamePrefix can contain lower case letters, numbers and dashes, it
must not start with a dash and must be between 1 and 62 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}$`
Type: string
| @@ -749,7 +749,7 @@ _Underlying type:_ _string_ _Validation:_ -- Enum: [egress ingress kube-apiserver] +- Enum: [egress ingress kube-apiserver peer-relay] - Type: string _Appears in:_ diff --git a/k8s-operator/apis/v1alpha1/types_proxygroup.go b/k8s-operator/apis/v1alpha1/types_proxygroup.go index 28fd9e009..08225c933 100644 --- a/k8s-operator/apis/v1alpha1/types_proxygroup.go +++ b/k8s-operator/apis/v1alpha1/types_proxygroup.go @@ -150,13 +150,14 @@ type TailnetDevice struct { } // +kubebuilder:validation:Type=string -// +kubebuilder:validation:Enum=egress;ingress;kube-apiserver +// +kubebuilder:validation:Enum=egress;ingress;kube-apiserver;peer-relay type ProxyGroupType string const ( ProxyGroupTypeEgress ProxyGroupType = "egress" ProxyGroupTypeIngress ProxyGroupType = "ingress" ProxyGroupTypeKubernetesAPIServer ProxyGroupType = "kube-apiserver" + ProxyGroupTypePeerRelay ProxyGroupType = "peer-relay" ) // +kubebuilder:validation:Type=string