cmd/k8s-operator: add DNS policy and config support to ProxyClass (#16887)

DNS configuration support to ProxyClass, allowing users to customize DNS resolution for Tailscale proxy pods.

Fixes #16886

Signed-off-by: Raj Singh <raj@tailscale.com>
This commit is contained in:
Raj Singh 2025-09-30 05:33:50 -04:00 committed by GitHub
parent 9aa16bf97b
commit a45473c4c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 154 additions and 0 deletions

View File

@ -1046,6 +1046,62 @@ spec:
type: object
additionalProperties:
type: string
dnsConfig:
description: |-
DNSConfig defines DNS parameters for the proxy Pod in addition to those generated from DNSPolicy.
When DNSPolicy is set to "None", DNSConfig must be specified.
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config
type: object
properties:
nameservers:
description: |-
A list of DNS name server IP addresses.
This will be appended to the base nameservers generated from DNSPolicy.
Duplicated nameservers will be removed.
type: array
items:
type: string
x-kubernetes-list-type: atomic
options:
description: |-
A list of DNS resolver options.
This will be merged with the base options generated from DNSPolicy.
Duplicated entries will be removed. Resolution options given in Options
will override those that appear in the base DNSPolicy.
type: array
items:
description: PodDNSConfigOption defines DNS resolver options of a pod.
type: object
properties:
name:
description: |-
Name is this DNS resolver option's name.
Required.
type: string
value:
description: Value is this DNS resolver option's value.
type: string
x-kubernetes-list-type: atomic
searches:
description: |-
A list of DNS search domains for host-name lookup.
This will be appended to the base search paths generated from DNSPolicy.
Duplicated search paths will be removed.
type: array
items:
type: string
x-kubernetes-list-type: atomic
dnsPolicy:
description: |-
DNSPolicy defines how DNS will be configured for the proxy Pod.
By default the Tailscale Kubernetes Operator does not set a DNS policy (uses cluster default).
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
type: string
enum:
- ClusterFirstWithHostNet
- ClusterFirst
- Default
- None
imagePullSecrets:
description: |-
Proxy Pod's image pull Secrets.

View File

@ -1574,6 +1574,62 @@ spec:
Annotations must be valid Kubernetes annotations.
https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set
type: object
dnsConfig:
description: |-
DNSConfig defines DNS parameters for the proxy Pod in addition to those generated from DNSPolicy.
When DNSPolicy is set to "None", DNSConfig must be specified.
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config
properties:
nameservers:
description: |-
A list of DNS name server IP addresses.
This will be appended to the base nameservers generated from DNSPolicy.
Duplicated nameservers will be removed.
items:
type: string
type: array
x-kubernetes-list-type: atomic
options:
description: |-
A list of DNS resolver options.
This will be merged with the base options generated from DNSPolicy.
Duplicated entries will be removed. Resolution options given in Options
will override those that appear in the base DNSPolicy.
items:
description: PodDNSConfigOption defines DNS resolver options of a pod.
properties:
name:
description: |-
Name is this DNS resolver option's name.
Required.
type: string
value:
description: Value is this DNS resolver option's value.
type: string
type: object
type: array
x-kubernetes-list-type: atomic
searches:
description: |-
A list of DNS search domains for host-name lookup.
This will be appended to the base search paths generated from DNSPolicy.
Duplicated search paths will be removed.
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
dnsPolicy:
description: |-
DNSPolicy defines how DNS will be configured for the proxy Pod.
By default the Tailscale Kubernetes Operator does not set a DNS policy (uses cluster default).
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
enum:
- ClusterFirstWithHostNet
- ClusterFirst
- Default
- None
type: string
imagePullSecrets:
description: |-
Proxy Pod's image pull Secrets.

View File

@ -906,6 +906,12 @@ func applyProxyClassToStatefulSet(pc *tsapi.ProxyClass, ss *appsv1.StatefulSet,
ss.Spec.Template.Spec.Tolerations = wantsPod.Tolerations
ss.Spec.Template.Spec.PriorityClassName = wantsPod.PriorityClassName
ss.Spec.Template.Spec.TopologySpreadConstraints = wantsPod.TopologySpreadConstraints
if wantsPod.DNSPolicy != nil {
ss.Spec.Template.Spec.DNSPolicy = *wantsPod.DNSPolicy
}
if wantsPod.DNSConfig != nil {
ss.Spec.Template.Spec.DNSConfig = wantsPod.DNSConfig
}
// Update containers.
updateContainer := func(overlay *tsapi.Container, base corev1.Container) corev1.Container {

View File

@ -87,6 +87,15 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) {
},
},
},
DNSPolicy: ptr.To(corev1.DNSClusterFirstWithHostNet),
DNSConfig: &corev1.PodDNSConfig{
Nameservers: []string{"1.1.1.1", "8.8.8.8"},
Searches: []string{"example.com", "test.local"},
Options: []corev1.PodDNSConfigOption{
{Name: "ndots", Value: ptr.To("2")},
{Name: "edns0"},
},
},
TailscaleContainer: &tsapi.Container{
SecurityContext: &corev1.SecurityContext{
Privileged: ptr.To(true),
@ -200,6 +209,8 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) {
wantSS.Spec.Template.Spec.InitContainers[0].Image = "ghcr.io/my-repo/tailscale:v0.01testsomething"
wantSS.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = "IfNotPresent"
wantSS.Spec.Template.Spec.PriorityClassName = proxyClassAllOpts.Spec.StatefulSet.Pod.PriorityClassName
wantSS.Spec.Template.Spec.DNSPolicy = corev1.DNSClusterFirstWithHostNet
wantSS.Spec.Template.Spec.DNSConfig = proxyClassAllOpts.Spec.StatefulSet.Pod.DNSConfig
gotSS := applyProxyClassToStatefulSet(proxyClassAllOpts, nonUserspaceProxySS.DeepCopy(), new(tailscaleSTSConfig), zl.Sugar())
if diff := cmp.Diff(gotSS, wantSS); diff != "" {
@ -239,6 +250,8 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) {
wantSS.Spec.Template.Spec.Containers[0].ImagePullPolicy = "IfNotPresent"
wantSS.Spec.Template.Spec.Containers[0].Image = "ghcr.io/my-repo/tailscale:v0.01testsomething"
wantSS.Spec.Template.Spec.PriorityClassName = proxyClassAllOpts.Spec.StatefulSet.Pod.PriorityClassName
wantSS.Spec.Template.Spec.DNSPolicy = corev1.DNSClusterFirstWithHostNet
wantSS.Spec.Template.Spec.DNSConfig = proxyClassAllOpts.Spec.StatefulSet.Pod.DNSConfig
gotSS = applyProxyClassToStatefulSet(proxyClassAllOpts, userspaceProxySS.DeepCopy(), new(tailscaleSTSConfig), zl.Sugar())
if diff := cmp.Diff(gotSS, wantSS); diff != "" {
t.Errorf("Unexpected result applying ProxyClass with all options to a StatefulSet for a userspace proxy (-got +want):\n%s", diff)

View File

@ -537,6 +537,8 @@ _Appears in:_
| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#toleration-v1-core) array_ | Proxy Pod's tolerations.<br />By default Tailscale Kubernetes operator does not apply any<br />tolerations.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
| `topologySpreadConstraints` _[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#topologyspreadconstraint-v1-core) array_ | Proxy Pod's topology spread constraints.<br />By default Tailscale Kubernetes operator does not apply any topology spread constraints.<br />https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ | | |
| `priorityClassName` _string_ | PriorityClassName for the proxy Pod.<br />By default Tailscale Kubernetes operator does not apply any priority class.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
| `dnsPolicy` _[DNSPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#dnspolicy-v1-core)_ | DNSPolicy defines how DNS will be configured for the proxy Pod.<br />By default the Tailscale Kubernetes Operator does not set a DNS policy (uses cluster default).<br />https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy | | Enum: [ClusterFirstWithHostNet ClusterFirst Default None] <br /> |
| `dnsConfig` _[PodDNSConfig](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#poddnsconfig-v1-core)_ | DNSConfig defines DNS parameters for the proxy Pod in addition to those generated from DNSPolicy.<br />When DNSPolicy is set to "None", DNSConfig must be specified.<br />https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config | | |
#### PortRange

View File

@ -303,6 +303,17 @@ type Pod struct {
// https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling
// +optional
PriorityClassName string `json:"priorityClassName,omitempty"`
// DNSPolicy defines how DNS will be configured for the proxy Pod.
// By default the Tailscale Kubernetes Operator does not set a DNS policy (uses cluster default).
// https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
// +kubebuilder:validation:Enum=ClusterFirstWithHostNet;ClusterFirst;Default;None
// +optional
DNSPolicy *corev1.DNSPolicy `json:"dnsPolicy,omitempty"`
// DNSConfig defines DNS parameters for the proxy Pod in addition to those generated from DNSPolicy.
// When DNSPolicy is set to "None", DNSConfig must be specified.
// https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config
// +optional
DNSConfig *corev1.PodDNSConfig `json:"dnsConfig,omitempty"`
}
// +kubebuilder:validation:XValidation:rule="!(has(self.serviceMonitor) && self.serviceMonitor.enable && !self.enable)",message="ServiceMonitor can only be enabled if metrics are enabled"

View File

@ -574,6 +574,16 @@ func (in *Pod) DeepCopyInto(out *Pod) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.DNSPolicy != nil {
in, out := &in.DNSPolicy, &out.DNSPolicy
*out = new(corev1.DNSPolicy)
**out = **in
}
if in.DNSConfig != nil {
in, out := &in.DNSConfig, &out.DNSConfig
*out = new(corev1.PodDNSConfig)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pod.