{"slug":"expose-a-tailnet-service-to-your-kubernetes-cluster-cluster-egress","title":"Expose a tailnet service to your Kubernetes cluster (cluster egress)","tags":["tailscale","containers"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Expose a tailnet service to your Kubernetes cluster (cluster egress)\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nExposing tailnet services to your Kubernetes clusteris currently [in beta](https://tailscale.com/docs/reference/tailscale-release-stages#beta).\r\n\r\nYou can make services that are external to your cluster, but available in your tailnet, available to your Kubernetes cluster workloads by making the associated tailnet node accessible from the cluster.\r\n\r\nYou can configure the operator to set up an in-cluster egress proxy for a tailnet device by creating a Kubernetes `Service` that specifies a tailnet device either by its [Tailscale IP address](https://tailscale.com/docs/concepts/ip-and-dns-addresses) or its [MagicDNS name](https://tailscale.com/docs/features/magicdns). In both cases your cluster workloads will refer to the tailnet service by the Kubernetes `Service` name.\r\n\r\n## [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#prerequisites)\r\n\r\n- [Set up the Kubernetes Operator](https://tailscale.com/docs/features/kubernetes-operator#setup).\r\n\r\n## [Expose a tailnet node to your cluster using its Tailscale MagicDNS name](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#expose-a-tailnet-node-to-your-cluster-using-its-tailscale-magicdns-name)\r\n\r\n1. Ensure that [**MagicDNS** is enabled for your cluster](https://tailscale.com/docs/features/magicdns#enabling-magicdns).\r\n\r\n2. Create a Kubernetes `Service` of type [`ExternalName`](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) annotated with the MagicDNS name of the tailnet device that you wish to make available:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-fqdn: <Tailscale MagicDNS name>\r\n     name: rds-staging   # service name\r\nspec:\r\n     externalName: placeholder   # any value - will be overwritten by operator\r\n     type: ExternalName\r\n```\r\n\r\n\r\nNote that the value of the `tailscale.com/tailnet-fqdn` annotation must be the full MagicDNS name of the tailnet service (not just hostname). The final dot is optional.\r\n\r\n## [Expose a tailnet node to your cluster using its Tailscale IP address](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#expose-a-tailnet-node-to-your-cluster-using-its-tailscale-ip-address)\r\n\r\n1. Create a Kubernetes `Service` of type [`ExternalName`](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) annotated with the Tailscale IP address of the tailnet node you want to make available:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-ip: <Tailscale IP address>\r\n     name: rds-staging   # service name\r\nspec:\r\n     externalName: placeholder   # any value - will be overwritten by operator\r\n     type: ExternalName\r\n```\r\n\r\n\r\nThe value of the `tailscale.com/tailnet-ip` annotation can be either a tailnet IPv4 or IPv6 address or an IPv4 or IPv6 address exposed by a Tailscale subnet router.\r\nIP address ranges are not supported. 4via6 addresses are also not supported.\r\nRefer to the [Access an IP address behind a subnet router](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#ip-behind-subnet) section for more information on how to access an IP address behind a subnet router.\r\n\r\n### [Expose a tailnet HTTPS service to your cluster workloads](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#expose-a-tailnet-https-service-to-your-cluster-workloads)\r\n\r\nCluster workloads that need access to tailnet services that use [Tailscale HTTPS](https://tailscale.com/docs/how-to/set-up-https-certificates) likely need to refer to those services by their MagicDNS names for the TLS handshake to succeed.\r\n\r\nSometimes, cluster workloads must access a service running in the same cluster, which is exposed over [Tailscale Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) through its MagicDNS name. Both of these use cases require the ability of the workloads to resolve the MagicDNS names of the services they need to access.\r\n\r\nWith Kubernetes operator v1.66 and later, you can configure the cluster to resolve MagicDNS names of the tailnet services exposed using [cluster egress proxies](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress) and the MagicDNS names of cluster workloads exposed to tailnet using [Tailscale Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress).\r\n\r\nYou can configure the operator to deploy a nameserver for `ts.net` DNS names of the tailnet services exposed using [cluster egress proxies](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress) and for [Tailscale Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) in the cluster, then add the nameserver as a [stub nameserver](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#configuration-of-stub-domain-and-upstream-nameserver-using-coredns) to your cluster DNS plugin.\r\n\r\n1. Create a `DNSConfig` custom resource:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: DNSConfig\r\nmetadata:\r\n     name: ts-dns\r\nspec:\r\n     nameserver:\r\n       image:\r\n         repo: tailscale/k8s-nameserver\r\n         tag: unstable\r\n```\r\n\r\n\r\n\r\nThe `DNSConfig` custom resource tells the Tailscale Kubernetes Operator to deploy a nameserver in the operator's namespace and dynamically populate the nameserver with the following DNS records:\r\n   - `A` records mapping [cluster egress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress) proxy `Pod`s' IP addresses to the MagicDNS names of the exposed tailnet services\r\n\r\n   - `A` records mapping [Tailscale Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) proxy `Pod`s' IP addresses to the MagicDNS names of the `Ingress`es\r\n2. Find the IP address of the nameserver:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl get dnsconfig ts-dns\r\n```\r\n\r\n\r\n\r\nFor example, this command will return information similar to the following:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nNAME     NAMESERVERIP\r\nts-dns   10.100.124.196\r\n```\r\n\r\n3. (If your cluster uses CoreDNS) update the `Corefile`.\r\n\r\n[Update `Corefile` in `coredns``ConfigMap` in `kube-system` namespace](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#configuration-of-stub-domain-and-upstream-nameserver-using-coredns) with a stanza for `ts.net` stub nameserver:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```txt\r\nCorefile: |\r\n         .:53 {\r\n           ...\r\n         }\r\n         ts.net {\r\n           errors\r\n           cache 30\r\n           forward . 10.100.124.196\r\n         }\r\n```\r\n\r\n4. (If your cluster uses `kube-dns`) update the `kube-dns` config.\r\n\r\n[Update `kube-dns``ConfigMap` in `kube-system` namespace](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/kube-dns#stub-domains) to add a stub nameserver for `ts.net` DNS names:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\ndata:\r\n       stubDomains: |\r\n         {\r\n           \"ts.net\": [\\\r\n             \"10.100.124.196\"\\\r\n           ]\r\n         }\r\n```\r\n\r\n5. Access HTTPS tailnet services from the Kubernetes cluster.\r\n\r\nCreate an [egress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress)`Service`:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-fqdn: \"<full MagicDNS name of the tailnet node>\"\r\n     name: ts-egress\r\nspec:\r\n     externalName: unused\r\n     type: ExternalName\r\n```\r\n\r\n\r\n\r\nThe operator automatically populates the nameserver's configuration with an `A` record mapping the MagicDNS name of the exposed tailnet service to proxy `Pod`'s IP address.\r\nThis record lets your cluster workloads access the tailnet service using its MagicDNS name.\r\n\r\n6. Access a Tailscale `Ingress` using its MagicDNS name from the cluster:\r\n\r\nCreate a [Tailscale `Ingress`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) resource with a `tailscale.com/experimental-forward-cluster-traffic-via-ingress` annotation:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n     name: nginx\r\n     annotations:\r\n       tailscale.com/experimental-forward-cluster-traffic-via-ingress: \"true\"\r\nspec:\r\n     defaultBackend:\r\n       service:\r\n         name: nginx\r\n         port:\r\n           name: https\r\n     ingressClassName: tailscale\r\n```\r\n\r\n\r\n\r\nThe operator automatically populates the nameserver's configuration with an `A` record mapping the MagicDNS name of the Tailscale `Ingress` to the proxy `Pod`'s IP address.\r\nFor `Ingress` resources annotated with `tailscale.com/experimental-forward-cluster-traffic-via-ingress`, the operator also ensures that the proxy created for that `Ingress` listens on its `Pod` IP address, so it can be accessed by cluster workloads.\r\n\r\n\r\nWe are actively seeking feedback for MagicDNS name resolution in cluster, especially with regards to further automating the workflow — do [reach out](https://github.com/tailscale/tailscale/issues/new/choose) if you have feedback or suggestions.\r\n\r\nIn Tailscale version 1.90 or later, this feature supports clusters with IPv6 `Pod` CIDRs as well as IPv4 CIDRs. Earlier versions only support IPv4 CIDRs.\r\n\r\n## [Configure an egress `Service` using `ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#configure-an-egress-service-using-proxygroup)\r\n\r\nThe operator by default creates a `StatefulSet` with a single proxy `Pod` for each egress `Service`.\r\n\r\nIf you want to expose the egress `Service`s on multiple `Pod`s redundantly or coalesce a number of egress\r\n`Service`s on a smaller number of proxy `Pod`s, you can instead use a pre-created [`ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator#proxy-group).\r\n\r\nTo expose an egress `Service` on a `ProxyGroup`:\r\n\r\n1. Follow the instructions to pre-create an [egress `ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator#proxy-group).\r\n\r\n2. Create an ExternalName `Service` that references the `ProxyGroup` and the tailnet target:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\n       apiVersion: v1\r\n       kind: Service\r\n       metadata:\r\n         annotations:\r\n           tailscale.com/tailnet-fqdn: \"<full MagicDNS name of the tailnet node>\"\r\n           tailscale.com/proxy-group: \"<ProxyGroup name>\"\r\n         name: ts-egress\r\n         namespace: default\r\n       spec:\r\n         externalName: placeholder # any value - will be overwritten by the operator\r\n         type: ExternalName\r\n         ports:\r\n      - port: 8080\r\n        protocol: TCP\r\n        name: web # any value\r\n      - port: 3002\r\n        protocol: TCP\r\n        name: debug # any value\r\n```\r\n\r\nYou can specify the tailnet target either [by a MagicDNS name](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#tailnet-node-dnsname-expose) or [tailnet IP address](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#tailnet-node-ip-expose).\r\n\r\nThe ExternalName Service must explicitly specify all port numbers that you want to access in the tailnet service in the `spec.ports` section.\r\nCluster traffic received to the ports (and protocols) specified on the ExternalName Service will be proxied to the same ports of the tailnet target.\r\n\r\nNote that setting `service.spec.ports` fields other than `port`, `protocol` and `name` will have no effect.\r\n\r\n1. (Optional) Wait for the `Service` to become ready:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nkubectl wait svc ts-egress --for=condition=TailscaleEgressSvcReady=true\r\n```\r\n\r\n2. Cluster workloads can now access the tailnet target by ExternalName `Service`'s DNS name, for example:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\ncurl ts-egress.default.svc.cluster.local:8080\r\n```\r\n\r\n\r\nCluster traffic for ts-egress `Service` will be round robin load balanced across the `ProxyGroup` replicas.\r\nAny number (restricted by resource consumption) of egress `Service`s can reference a single `ProxyGroup`.\r\nYou can also create multiple egress `ProxyGroup`s.\r\n\r\n## [Access an IP address behind a subnet router](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#access-an-ip-address-behind-a-subnet-router)\r\n\r\nIt is currently not possible to access 4via6 IP addresses using the egress proxy.\r\n\r\nIf you have a service with a static IP address that is behind a subnet router, you can make it accessible to cluster workloads using egress proxies.\r\n\r\n1. Create a `ProxyClass` to ensure that the egress proxy accepts advertised subnet routes:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: ProxyClass\r\nmetadata:\r\n     generation: 2\r\n     name: accept-routes\r\nspec:\r\n     tailscale:\r\n       acceptRoutes: true\r\n```\r\n\r\n2. Create an ExternalName `Service` that references the `ProxyClass` and the target IP behind the subnet router:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-ip: \"<IP-behind-the-subnet-router>\"\r\n       tailscale.com/proxy-class: accept-routes\r\n     name: ts-egress\r\nspec:\r\n     externalName: unused\r\n     type: ExternalName\r\n```\r\n\r\n\r\nCluster workloads should now be able to access the target behind the subnet router using the ExternalName `Service`'s cluster DNS name. For example:\r\n\r\n```shell\r\ncurl ts-egress.default.svc.cluster.local:8080\r\n```\r\n\r\n## [Validation](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#validation)\r\n\r\nWait for the Tailscale Kubernetes Operator to update `spec.externalName` of the Kubernetes `Service` that you created. The `Service` external name should get set to the [Kubernetes DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services) of another Kubernetes `Service` that is fronting the egress proxy in `tailscale` namespace. The proxy is responsible for routing traffic to the exposed Tailscale node over the tailnet.\r\n\r\nAfter the `Service` external name gets updated, workloads in your cluster should be able to access the exposed tailnet service by referring to it using the [Kubernetes DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services) of the `Service` that you created.\r\n\r\n## [Customization](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#customization)\r\n\r\n[Customize the operator and resources it manages](https://tailscale.com/docs/features/kubernetes-operator/how-to/customize).\r\n\r\n## [Troubleshooting](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#troubleshooting)\r\n\r\n[Troubleshoot the operator and resources it manages](https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator).\r\n\r\n## [Limitations](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress\\#limitations)\r\n\r\n- Egress to external services supports using an IPv4 or IPv6 address for a single route in the `tailscale.com/tailnet-ip` annotation, but not IP ranges.\r\n- Egress to external services currently only supports clusters where privileged pods are permitted (that is, GKE Autopilot is not supported).\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Expose a tailnet service to your Kubernetes cluster (cluster egress)</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>Exposing tailnet services to your Kubernetes clusteris currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#beta\">in beta</a>.</p>\n<p>You can make services that are external to your cluster, but available in your tailnet, available to your Kubernetes cluster workloads by making the associated tailnet node accessible from the cluster.</p>\n<p>You can configure the operator to set up an in-cluster egress proxy for a tailnet device by creating a Kubernetes <code>Service</code> that specifies a tailnet device either by its <a href=\"https://tailscale.com/docs/concepts/ip-and-dns-addresses\">Tailscale IP address</a> or its <a href=\"https://tailscale.com/docs/features/magicdns\">MagicDNS name</a>. In both cases your cluster workloads will refer to the tailnet service by the Kubernetes <code>Service</code> name.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#prerequisites\">Prerequisites</a></h2>\n<ul>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator#setup\">Set up the Kubernetes Operator</a>.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#expose-a-tailnet-node-to-your-cluster-using-its-tailscale-magicdns-name\">Expose a tailnet node to your cluster using its Tailscale MagicDNS name</a></h2>\n<ol>\n<li>\n<p>Ensure that <a href=\"https://tailscale.com/docs/features/magicdns#enabling-magicdns\"><strong>MagicDNS</strong> is enabled for your cluster</a>.</p>\n</li>\n<li>\n<p>Create a Kubernetes <code>Service</code> of type <a href=\"https://kubernetes.io/docs/concepts/services-networking/service/#externalname\"><code>ExternalName</code></a> annotated with the MagicDNS name of the tailnet device that you wish to make available:</p>\n</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-fqdn: &#x3C;Tailscale MagicDNS name>\r\n     name: rds-staging   # service name\r\nspec:\r\n     externalName: placeholder   # any value - will be overwritten by operator\r\n     type: ExternalName\n</code></pre>\n<p>Note that the value of the <code>tailscale.com/tailnet-fqdn</code> annotation must be the full MagicDNS name of the tailnet service (not just hostname). The final dot is optional.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#expose-a-tailnet-node-to-your-cluster-using-its-tailscale-ip-address\">Expose a tailnet node to your cluster using its Tailscale IP address</a></h2>\n<ol>\n<li>Create a Kubernetes <code>Service</code> of type <a href=\"https://kubernetes.io/docs/concepts/services-networking/service/#externalname\"><code>ExternalName</code></a> annotated with the Tailscale IP address of the tailnet node you want to make available:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-ip: &#x3C;Tailscale IP address>\r\n     name: rds-staging   # service name\r\nspec:\r\n     externalName: placeholder   # any value - will be overwritten by operator\r\n     type: ExternalName\n</code></pre>\n<p>The value of the <code>tailscale.com/tailnet-ip</code> annotation can be either a tailnet IPv4 or IPv6 address or an IPv4 or IPv6 address exposed by a Tailscale subnet router.\r\nIP address ranges are not supported. 4via6 addresses are also not supported.\r\nRefer to the <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#ip-behind-subnet\">Access an IP address behind a subnet router</a> section for more information on how to access an IP address behind a subnet router.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#expose-a-tailnet-https-service-to-your-cluster-workloads\">Expose a tailnet HTTPS service to your cluster workloads</a></h3>\n<p>Cluster workloads that need access to tailnet services that use <a href=\"https://tailscale.com/docs/how-to/set-up-https-certificates\">Tailscale HTTPS</a> likely need to refer to those services by their MagicDNS names for the TLS handshake to succeed.</p>\n<p>Sometimes, cluster workloads must access a service running in the same cluster, which is exposed over <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale Ingress</a> through its MagicDNS name. Both of these use cases require the ability of the workloads to resolve the MagicDNS names of the services they need to access.</p>\n<p>With Kubernetes operator v1.66 and later, you can configure the cluster to resolve MagicDNS names of the tailnet services exposed using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress\">cluster egress proxies</a> and the MagicDNS names of cluster workloads exposed to tailnet using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale Ingress</a>.</p>\n<p>You can configure the operator to deploy a nameserver for <code>ts.net</code> DNS names of the tailnet services exposed using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress\">cluster egress proxies</a> and for <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale Ingress</a> in the cluster, then add the nameserver as a <a href=\"https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#configuration-of-stub-domain-and-upstream-nameserver-using-coredns\">stub nameserver</a> to your cluster DNS plugin.</p>\n<ol>\n<li>Create a <code>DNSConfig</code> custom resource:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: DNSConfig\r\nmetadata:\r\n     name: ts-dns\r\nspec:\r\n     nameserver:\r\n       image:\r\n         repo: tailscale/k8s-nameserver\r\n         tag: unstable\n</code></pre>\n<p>The <code>DNSConfig</code> custom resource tells the Tailscale Kubernetes Operator to deploy a nameserver in the operator's namespace and dynamically populate the nameserver with the following DNS records:</p>\n<ul>\n<li>\n<p><code>A</code> records mapping <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress\">cluster egress</a> proxy <code>Pod</code>s' IP addresses to the MagicDNS names of the exposed tailnet services</p>\n</li>\n<li>\n<p><code>A</code> records mapping <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale Ingress</a> proxy <code>Pod</code>s' IP addresses to the MagicDNS names of the <code>Ingress</code>es</p>\n</li>\n</ul>\n<ol start=\"2\">\n<li>Find the IP address of the nameserver:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl get dnsconfig ts-dns\n</code></pre>\n<p>For example, this command will return information similar to the following:</p>\n<pre><code class=\"language-shell\">NAME     NAMESERVERIP\r\nts-dns   10.100.124.196\n</code></pre>\n<ol start=\"3\">\n<li>(If your cluster uses CoreDNS) update the <code>Corefile</code>.</li>\n</ol>\n<p><a href=\"https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#configuration-of-stub-domain-and-upstream-nameserver-using-coredns\">Update <code>Corefile</code> in <code>coredns``ConfigMap</code> in <code>kube-system</code> namespace</a> with a stanza for <code>ts.net</code> stub nameserver:</p>\n<pre><code class=\"language-txt\">Corefile: |\r\n         .:53 {\r\n           ...\r\n         }\r\n         ts.net {\r\n           errors\r\n           cache 30\r\n           forward . 10.100.124.196\r\n         }\n</code></pre>\n<ol start=\"4\">\n<li>(If your cluster uses <code>kube-dns</code>) update the <code>kube-dns</code> config.</li>\n</ol>\n<p><a href=\"https://docs.cloud.google.com/kubernetes-engine/docs/concepts/kube-dns#stub-domains\">Update <code>kube-dns``ConfigMap</code> in <code>kube-system</code> namespace</a> to add a stub nameserver for <code>ts.net</code> DNS names:</p>\n<pre><code class=\"language-yaml\">data:\r\n       stubDomains: |\r\n         {\r\n           \"ts.net\": [\\\r\n             \"10.100.124.196\"\\\r\n           ]\r\n         }\n</code></pre>\n<ol start=\"5\">\n<li>Access HTTPS tailnet services from the Kubernetes cluster.</li>\n</ol>\n<p>Create an <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#cluster-egress\">egress</a><code>Service</code>:</p>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-fqdn: \"&#x3C;full MagicDNS name of the tailnet node>\"\r\n     name: ts-egress\r\nspec:\r\n     externalName: unused\r\n     type: ExternalName\n</code></pre>\n<p>The operator automatically populates the nameserver's configuration with an <code>A</code> record mapping the MagicDNS name of the exposed tailnet service to proxy <code>Pod</code>'s IP address.\r\nThis record lets your cluster workloads access the tailnet service using its MagicDNS name.</p>\n<ol start=\"6\">\n<li>Access a Tailscale <code>Ingress</code> using its MagicDNS name from the cluster:</li>\n</ol>\n<p>Create a <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale <code>Ingress</code></a> resource with a <code>tailscale.com/experimental-forward-cluster-traffic-via-ingress</code> annotation:</p>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n     name: nginx\r\n     annotations:\r\n       tailscale.com/experimental-forward-cluster-traffic-via-ingress: \"true\"\r\nspec:\r\n     defaultBackend:\r\n       service:\r\n         name: nginx\r\n         port:\r\n           name: https\r\n     ingressClassName: tailscale\n</code></pre>\n<p>The operator automatically populates the nameserver's configuration with an <code>A</code> record mapping the MagicDNS name of the Tailscale <code>Ingress</code> to the proxy <code>Pod</code>'s IP address.\r\nFor <code>Ingress</code> resources annotated with <code>tailscale.com/experimental-forward-cluster-traffic-via-ingress</code>, the operator also ensures that the proxy created for that <code>Ingress</code> listens on its <code>Pod</code> IP address, so it can be accessed by cluster workloads.</p>\n<p>We are actively seeking feedback for MagicDNS name resolution in cluster, especially with regards to further automating the workflow — do <a href=\"https://github.com/tailscale/tailscale/issues/new/choose\">reach out</a> if you have feedback or suggestions.</p>\n<p>In Tailscale version 1.90 or later, this feature supports clusters with IPv6 <code>Pod</code> CIDRs as well as IPv4 CIDRs. Earlier versions only support IPv4 CIDRs.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#configure-an-egress-service-using-proxygroup\">Configure an egress <code>Service</code> using <code>ProxyGroup</code></a></h2>\n<p>The operator by default creates a <code>StatefulSet</code> with a single proxy <code>Pod</code> for each egress <code>Service</code>.</p>\n<p>If you want to expose the egress <code>Service</code>s on multiple <code>Pod</code>s redundantly or coalesce a number of egress\r\n<code>Service</code>s on a smaller number of proxy <code>Pod</code>s, you can instead use a pre-created <a href=\"https://tailscale.com/docs/features/kubernetes-operator#proxy-group\"><code>ProxyGroup</code></a>.</p>\n<p>To expose an egress <code>Service</code> on a <code>ProxyGroup</code>:</p>\n<ol>\n<li>\n<p>Follow the instructions to pre-create an <a href=\"https://tailscale.com/docs/features/kubernetes-operator#proxy-group\">egress <code>ProxyGroup</code></a>.</p>\n</li>\n<li>\n<p>Create an ExternalName <code>Service</code> that references the <code>ProxyGroup</code> and the tailnet target:</p>\n</li>\n</ol>\n<pre><code class=\"language-yaml\">       apiVersion: v1\r\n       kind: Service\r\n       metadata:\r\n         annotations:\r\n           tailscale.com/tailnet-fqdn: \"&#x3C;full MagicDNS name of the tailnet node>\"\r\n           tailscale.com/proxy-group: \"&#x3C;ProxyGroup name>\"\r\n         name: ts-egress\r\n         namespace: default\r\n       spec:\r\n         externalName: placeholder # any value - will be overwritten by the operator\r\n         type: ExternalName\r\n         ports:\r\n      - port: 8080\r\n        protocol: TCP\r\n        name: web # any value\r\n      - port: 3002\r\n        protocol: TCP\r\n        name: debug # any value\n</code></pre>\n<p>You can specify the tailnet target either <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#tailnet-node-dnsname-expose\">by a MagicDNS name</a> or <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#tailnet-node-ip-expose\">tailnet IP address</a>.</p>\n<p>The ExternalName Service must explicitly specify all port numbers that you want to access in the tailnet service in the <code>spec.ports</code> section.\r\nCluster traffic received to the ports (and protocols) specified on the ExternalName Service will be proxied to the same ports of the tailnet target.</p>\n<p>Note that setting <code>service.spec.ports</code> fields other than <code>port</code>, <code>protocol</code> and <code>name</code> will have no effect.</p>\n<ol>\n<li>(Optional) Wait for the <code>Service</code> to become ready:</li>\n</ol>\n<pre><code class=\"language-shell\">kubectl wait svc ts-egress --for=condition=TailscaleEgressSvcReady=true\n</code></pre>\n<ol start=\"2\">\n<li>Cluster workloads can now access the tailnet target by ExternalName <code>Service</code>'s DNS name, for example:</li>\n</ol>\n<pre><code class=\"language-shell\">curl ts-egress.default.svc.cluster.local:8080\n</code></pre>\n<p>Cluster traffic for ts-egress <code>Service</code> will be round robin load balanced across the <code>ProxyGroup</code> replicas.\r\nAny number (restricted by resource consumption) of egress <code>Service</code>s can reference a single <code>ProxyGroup</code>.\r\nYou can also create multiple egress <code>ProxyGroup</code>s.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#access-an-ip-address-behind-a-subnet-router\">Access an IP address behind a subnet router</a></h2>\n<p>It is currently not possible to access 4via6 IP addresses using the egress proxy.</p>\n<p>If you have a service with a static IP address that is behind a subnet router, you can make it accessible to cluster workloads using egress proxies.</p>\n<ol>\n<li>Create a <code>ProxyClass</code> to ensure that the egress proxy accepts advertised subnet routes:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: ProxyClass\r\nmetadata:\r\n     generation: 2\r\n     name: accept-routes\r\nspec:\r\n     tailscale:\r\n       acceptRoutes: true\n</code></pre>\n<ol start=\"2\">\n<li>Create an ExternalName <code>Service</code> that references the <code>ProxyClass</code> and the target IP behind the subnet router:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n     annotations:\r\n       tailscale.com/tailnet-ip: \"&#x3C;IP-behind-the-subnet-router>\"\r\n       tailscale.com/proxy-class: accept-routes\r\n     name: ts-egress\r\nspec:\r\n     externalName: unused\r\n     type: ExternalName\n</code></pre>\n<p>Cluster workloads should now be able to access the target behind the subnet router using the ExternalName <code>Service</code>'s cluster DNS name. For example:</p>\n<pre><code class=\"language-shell\">curl ts-egress.default.svc.cluster.local:8080\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#validation\">Validation</a></h2>\n<p>Wait for the Tailscale Kubernetes Operator to update <code>spec.externalName</code> of the Kubernetes <code>Service</code> that you created. The <code>Service</code> external name should get set to the <a href=\"https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services\">Kubernetes DNS name</a> of another Kubernetes <code>Service</code> that is fronting the egress proxy in <code>tailscale</code> namespace. The proxy is responsible for routing traffic to the exposed Tailscale node over the tailnet.</p>\n<p>After the <code>Service</code> external name gets updated, workloads in your cluster should be able to access the exposed tailnet service by referring to it using the <a href=\"https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services\">Kubernetes DNS name</a> of the <code>Service</code> that you created.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#customization\">Customization</a></h2>\n<p><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/customize\">Customize the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#troubleshooting\">Troubleshooting</a></h2>\n<p><a href=\"https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator\">Troubleshoot the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-egress#limitations\">Limitations</a></h2>\n<ul>\n<li>Egress to external services supports using an IPv4 or IPv6 address for a single route in the <code>tailscale.com/tailnet-ip</code> annotation, but not IP ranges.</li>\n<li>Egress to external services currently only supports clusters where privileged pods are permitted (that is, GKE Autopilot is not supported).</li>\n</ul>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}