Tailscale Kubernetes Operator architecture
Last validated:
The Tailscale Kubernetes Operator has a collection of use cases that can be combined as needed. The following diagrams illustrate how the Operator implements each use case. Each diagram shows which resources run inside the Kubernetes cluster and which are external. Resources inside the cluster are managed by the Operator after it has been deployed.
API server proxy
The API server proxy lets tailnet devices securely access the Kubernetes API server. It can run in two modes:
In-process
The operator runs the API server proxy within its own process. The client connects to the operator's tailnet address, and the operator proxies the request to the kube-apiserver.
If the proxy is running in "noauth" mode, it forwards HTTP requests unmodified. In "auth" mode, it deletes any existing auth headers and adds impersonation headers before forwarding. A request with impersonation headers looks something like:
GET /api/v1/namespaces/default/pods HTTP/1.1
Host: k8s-api.example.com
Authorization: Bearer <operator-service-account-token>
Impersonate-Group: tailnet-readers
Accept: application/json
Dedicated (ProxyGroup)
The API server proxy can also be deployed as a dedicated ProxyGroup, running as separate pods from the operator. This provides high availability and separates the proxy lifecycle from the operator.
L3 ingress
Refer to the L3 ingress documentation for configuration details.
The user deploys an app to the default namespace and creates a normal Service that selects the app's Pods. Either add the annotation tailscale.com/expose: "true" or specify .spec.type as Loadbalancer and .spec.loadBalancerClass as tailscale. The operator creates an ingress proxy that lets devices anywhere on the tailnet access the Service.
The proxy Pod uses iptables or nftables rules to DNAT traffic bound for the proxy's tailnet IP address to the Service's internal ClusterIP instead.
L7 ingress
Refer to the L7 ingress documentation for configuration details.
The L7 ingress architecture diagram is similar to L3 ingress. It is configured through an Ingress object instead of a Service, and uses tailscale serve to accept traffic instead of configuring iptables or nftables rules. The operator uses tailscaled's local API (SetServeConfig) to set serve config, not the tailscale serve command.
L3 egress
- The user deploys a
Servicewithtype: ExternalNameand an annotationtailscale.com/tailnet-fqdn: db.tails-scales.ts.net. - The operator creates a proxy Pod managed by a single replica
StatefulSet, and a headlessServicepointing at the proxy Pod. - The operator updates the
ExternalNameService'sspec.externalNamefield to point at the headlessServiceit created in the previous step.
Refer to the L3 egress documentation for configuration details.
(Optional) If the user also adds the tailscale.com/proxy-group: egress-proxies annotation to their ExternalName Service, the operator skips creating a proxy Pod and instead points the headless Service at the existing ProxyGroup's pods. In this case, ports are also required in the ExternalName Service spec. The following diagram shows a more representative view.
ProxyGroup
The ProxyGroup custom resource manages a collection of proxy Pods. It supports both egress and ingress configurations.
Egress
The ProxyGroup custom resource manages a collection of proxy Pods that can be configured to egress traffic out of the cluster through ExternalName Services. A ProxyGroup is both a high availability (HA) version of L3 egress, and a mechanism to serve multiple ExternalName Services on a single set of Tailscale devices (coalescing).
In this diagram, the ProxyGroup is named pg. The Secrets associated with the ProxyGroup Pods are omitted for simplicity. They are similar to the L3 egress case, but there is a pair of config and state Secrets per Pod.
Each ExternalName Service defines which ports should be mapped to their defined egress target. The operator maps from these ports to randomly chosen ephemeral ports through the ClusterIP Service and its EndpointSlice. The operator then generates the egress ConfigMap that tells the ProxyGroup Pods which incoming ports map to which egress targets.
Refer to the ProxyGroup egress documentation for configuration details.
Ingress
A ProxyGroup can also serve as a highly available set of proxies for an Ingress resource. The -0 Pod is always the replica that issues a certificate from Let's Encrypt.
If the same Ingress config is applied in multiple clusters, ProxyGroup proxies from each cluster are valid targets for the ts.net DNS name. The proxy each client is routed to depends on the same rules as for high availability subnet routers, and is encoded in the client's netmap.
Connector
The Connector custom resource can deploy either a subnet router, an exit node, or an app connector. The following diagram shows all three, but only one workflow can be configured per Connector resource.
Refer to the subnet router and exit node documentation and app connector documentation for configuration details.
Recorder nodes
The Recorder custom resource makes it easier to deploy tsrecorder to a cluster. It supports a single replica.
Refer to the Recorder documentation for configuration details.