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
LoadBalancer
typeService
with thetailscale
loadBalancerClass
that fronts your workload. - Annotate an existing
Service
that fronts your workload. - Create an
Ingress
resource fronting aService
orService
s 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.type
toLoadBalancer
. - Set
spec.loadBalancerClass
totailscale
.
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 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.ingressClassName
totailscale
. - Set
tls.hosts
to 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"],
},
...
]
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
Service
entirely. - If you use the
tailscale.com/expose
annotation, remove the annotation. - If you use an
Ingress
resource, delete it or change or unsetspec.ingressClassName
.
Deleting a Service
's Tailscale node in the admin console 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.
Tailscale Services are in alpha. You must contact Tailscale Support to enable them for your tailnet.
The HA mode lets you:
-
Expose a Kubernetes
Service
orIngress
resource to your tailnet through multiple active ingress proxies, to prevent downtime during proxyPod
restarts. -
Expose many
Service
andIngress
resources to your tailnet using a smaller number of proxyPod
s.
Prerequisites
-
Contact Tailscale Support to enable Tailscale Services for your tailnet.
-
Ensure that the OAuth client credentials used by the Tailscale Kubernetes Operator have
VIPServices
,Devices Core
andAuth Keys
write scopes.If you are updating credentials for an existing installation, you must recreate the Kubernetes Operator's
Pod
. -
Create a
ProxyGroup
withspec.type
set 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
Ingress
resource that references theProxyGroup
you created in the previous step. Thespec.tls.hosts
field can contain (at most) a single entry that determines the first label of the DNS name by which theIngress
will 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
Ingress
resource 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
Service
that references theProxyGroup
you created in the previous step. You can use thetailscale.com/hostname
annotation to set the first label of the DNS name by which theService
will 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"],
"ip": ["icmp:*"],
"dst": ["tag:eu-cluster:*"],
},
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
Service
s if they are running in a cluster that allocates IPv6 addresses toService
s. -
Proxies used to expose cloud services using Tailscale ExternalName
Service
s 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/tags
on an already exposedService
doesn'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/hostname
on an already exposedService
doesn't update the machine name until you clean up and re-expose theService
. - Cluster-ingress using Kubernetes
Ingress
resource 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.