Expose a Kubernetes cluster workload to your tailnet (cluster ingress)
You can use the Tailscale Kubernetes operator to expose a Kubernetes cluster workload to your tailnet in three ways:
- Create a
LoadBalancertypeServicewith thetailscaleloadBalancerClassthat fronts your workload. - Annotate an existing
Servicethat fronts your workload. - Create an
Ingressresource fronting aServiceorServices for the workloads you wish to expose.
Prerequisites
Exposing a cluster workload using a Kubernetes Service
Exposing a cluster workload via a tailscale Load Balancer Service
Create a new Kubernetes Service of type LoadBalancer:
- Set
spec.typetoLoadBalancer. - Set
spec.loadBalancerClasstotailscale.
After provisioning completes, the Service status will show the fully-qualified domain name of the Service in your tailnet. You can view the Service status by running kubectl get service <service name>.
You should also see a new node with that name appear in the Machines page of the admin console.
Exposing a cluster workload by annotating an existing Service
If the Service you want to expose already exists, you can expose it to Tailscale using object annotations.
Edit the Service and under metadata.annotations, add the annotation tailscale.com/expose with the value "true". Note that "true" is quoted because annotation values are strings, and an unquoted true will be incorrectly interpreted as a boolean.
In this mode, Kubernetes doesn't tell you the Tailscale machine name. You can look up the node in the Machines page of the admin console to learn its machine name. By default, the machine name of an exposed Service is <k8s-namespace>-<k8s-servicename>, but it can be changed.
Exposing cluster workloads using a Kubernetes Ingress
You can expose cluster workloads either to your tailnet or the public internet over TLS using an Ingress resource.
When using an Ingress resource, you also get the ability to identify callers using HTTP headers injected by the Ingress proxy.
Ingress resources only support TLS, and are only exposed over HTTPS using a MagicDNS name and publicly
trusted certificates from LetsEncrypt. You must enable HTTPS and MagicDNS on your
tailnet.
Edit the Ingress resource you want to expose to use the Ingress class tailscale:
- Set
spec.ingressClassNametotailscale. - Set
tls.hoststo the desired host name of the Tailscale node. Only the first label is used. See custom machine names for more details.
For example, to expose an Ingress resource nginx to your tailnet:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
spec:
defaultBackend:
service:
name: nginx
port:
number: 80
ingressClassName: tailscale
tls:
- hosts:
- nginx
The backend is HTTP by default. To use HTTPS on the backend, either set the port name to https or the port number to 443:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
spec:
defaultBackend:
service:
name: nginx
port:
name: https
ingressClassName: tailscale
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: https
port: 443
targetPort: 443
type: ClusterIP
A single Ingress resource can be used to front multiple backend Services:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
spec:
ingressClassName: tailscale
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ui-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
Currently the only supported Ingress path type is Prefix. Requests for paths with other path types will be routed according to Prefix rules.
A Tailscale Ingress can only be accessed on port 443.
Exposing a Service to the public internet using Ingress and Tailscale Funnel
You can also use the Tailscale Kubernetes Operator to expose an Ingress resource in your Kubernetes cluster to the public internet using Tailscale Funnel. To do so:
-
Add a
tailscale.com/funnel: "true"annotation:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: funnel annotations: tailscale.com/funnel: "true" spec: defaultBackend: service: name: funnel port: number: 80 ingressClassName: tailscale tls: - hosts: - funnel -
Update the access control policies for your tailnet to allow Kubernetes Operator proxy services to use Tailscale Funnel.
Add a node attribute to allow nodes created by the Kubernetes operator to use Funnel:
"nodeAttrs": [
{
"target": ["tag:k8s"], // tag that Tailscale Operator uses to tag proxies; defaults to 'tag:k8s'
"attr": ["funnel"],
},
// Additional noteAttrs as needed
]
Note that even if your policy has the funnel attribute assigned to autogroup:member (the default), you still need to add it to the tag used by proxies because autogroup:member does not include tagged devices.
Removing a Service
Any of the following actions remove a Kubernetes Service you exposed from your tailnet:
- Delete the
Serviceentirely. - If you use the
tailscale.com/exposeannotation, remove the annotation. - If you use an
Ingressresource, delete it or change or unsetspec.ingressClassName.
Deleting a Service's Tailscale node in the Machines page does not clean up the Kubernetes state associated with that Service.
High availability
Tailscale versions 1.84 and later support deploying Tailscale Kubernetes Operator's ingress proxies in high availability (HA) mode, using ProxyGroup and a new Tailscale feature, Tailscale Services.
The HA mode lets you:
-
Expose a Kubernetes
ServiceorIngressresource to your tailnet through multiple active ingress proxies, to prevent downtime during proxyPodrestarts. -
Expose many
ServiceandIngressresources to your tailnet using a smaller number of proxyPods.
Prerequisites
-
Ensure that the OAuth client credentials used by the Tailscale Kubernetes Operator have
Services,Devices CoreandAuth Keyswrite scopes.If you are updating credentials for an existing installation, you must recreate the Kubernetes Operator's
Pod. -
Create a
ProxyGroupwithspec.typeset toingress:apiVersion: tailscale.com/v1alpha1 kind: ProxyGroup metadata: name: ingress-proxies spec: type: ingress
Refer to the ProxyGroup API documentation for all available configuration options.
Expose a Tailscale Ingress in HA mode
-
Create a Tailscale
Ingressresource that references theProxyGroupyou created in the previous step. Thespec.tls.hostsfield can contain (at most) a single entry that determines the first label of the DNS name by which theIngresswill be exposed to the tailnet. If unset, it defaults to<ingress-name>-<namespace>.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx annotations: tailscale.com/proxy-group: ingress-proxies spec: defaultBackend: service: name: nginx port: number: 80 ingressClassName: tailscale tls: - hosts: - nginx -
Wait for the
Ingressresource to become ready.
kubectl wait --timeout=80s ingress nginx --for=jsonpath='{.status.loadBalancer.ingress[0].ports[0].port}'=443
-
Ensure that your Tailscale client accepts routes. Clients other than Linux accept routes by default.
-
Access the cluster workload from a Tailscale client:
kubectl get ingress nginx
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx tailscale * nginx.tailxyz.ts.net 80, 443 15s
curl https://nginx.tailxyz.ts.net
...
Expose a Tailscale Service in HA mode
-
Create a Tailscale LoadBalancer
Servicethat references theProxyGroupyou created in the previous step. You can use thetailscale.com/hostnameannotation to set the first label of the DNS name by which theServicewill be exposed to the tailnet. If unset, it defaults to<service-name>-<namespace>.apiVersion: v1 kind: Service metadata: name: nginx tailscale.com/hostname: nginx annotations: tailscale.com/proxy-group: ingress-proxies spec: ports: - name: http port: 80 targetPort: 80 type: LoadBalancer loadBalancerClass: tailscale -
Wait for the resources to be configured.
kubectl wait svc nginx --for condition=TailscaleIngressSvcConfigured
- Access the cluster workload from the tailnet.
kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.96.194.223 100.91.19.147 80:31967/TCP 5m16s
curl http://nginx.tailxyz.ts.net
...
You can expose an annotated Tailscale ingress Service in the same way.
Configure permissions
By default, the ProxyGroup proxies are tagged with the tag tag:k8s.
You can configure tags using the .tags field in the ProxyGroup spec.
You must ensure that the tag by which you tagged the operator's OAuth client credentials is a tagOwner of the ProxyGroup device tags.
Unlike with non-HA proxies, the proxy tags are not used to grant access to the cluster apps exposed using the proxies.
For each HA Service or Ingress exposed on a ProxyGroup, the Kubernetes Operator creates a Tailscale Service.
Each ProxyGroup proxy advertises the Tailscale Service, by configuring itself as a backend for the tailnet traffic for the Tailscale Service.
The Tailscale IP address and DNS name given to the Ingress or Service are the IP addresses and DNS name of the Tailscale Service.
You can tag Tailscale Service and use the tag to configure which tailnet devices can advertise the Service and which tailnet identities can access it.
By default, the Kubernetes Operator tags all Tailscale Services with a tag tag:k8s.
You can configure Tailscale Service tags using tailscale.com/tags annotation on the Service or Ingress resource.
Ensure that ProxyGroup devices can advertise the Tailscale Service
To permit ProxyGroup devices to advertise a Tailscale Service, use the autoApprovers section of the tailnet policy file.
For example, to let ProxyGroup devices with the tag tag:eu-cluster to advertise Tailscale Services with tag tag:monitoring, add the following to your tailnet policy file:
"autoApprovers": {
"services": {
"tag:monitoring": ["tag:eu-cluster"],
},
}
Configure access
You can use Tailscale Service tags to control access to it.
For example, to let the user group group:eng to access Tailscale Services with the tag tag:monitoring exposed on a ProxyGroup with the tag tag:eu-cluster, add the following to your tailnet policy file:
"grants": [
{
"src": ["group:eng"],
"dst": ["tag:monitoring"],
"ip": ["*"],
},
{
"src": ["group:eng"],
"dst": ["tag:eu-cluster:*"],
"ip": ["icmp:*"],
},
]
The requirement to permit access to the ProxyGroup devices to access Tailscale Services is a temporary limitation.
IPv6 support
Ingress
To proxy traffic to IPv6 backends, you might need to disable IPv4 tailnet addresses for the proxy tailnet nodes.
You need to disable IPv4 tailnet addresses for:
-
Proxies used to expose cluster workloads using Tailscale ingress
Services if they are running in a cluster that allocates IPv6 addresses toServices. -
Proxies used to expose cloud services using Tailscale ExternalName
Services if the cloud services have IPv6 addresses.
You can disable tailnet IPv4 addresses for a specific tag using a disable-ipv4 node attribute.
The following node attributes configuration example disables IPv4 addresses for all nodes tagged with tag:k8s:
"nodeAttrs": [
{
"target": ["tag:k8s"],
"attr": [
"disable-ipv4",
],
},
]
Tailnet IPv6 connectivity does not depend on host support for IPv6, so you can disable IPv4 addresses for nodes running on hosts that do not support IPv6.
Similarly, tailnet clients can connect to proxies with only tailnet IPv6 addresses even if they aren't running on hosts with IPv6 support.
Customization
Learn how to customize the operator and resources it manages.
Troubleshooting
Learn how to troubleshoot the operator and resources it manages.
Limitations
- Tags are only considered during initial provisioning. That is, editing
tailscale.com/tagson an already exposedServicedoesn't update the tags until you clean up and re-expose theService. - The requested machine name is only considered during initial provisioning. That is, editing
tailscale.com/hostnameon an already exposedServicedoesn't update the machine name until you clean up and re-expose theService. - Cluster-ingress using Kubernetes
Ingressresource requires TLS certificates. Currently, the certificates are provisioned on the first connect. This means that the first connection might be slow or even time out.
