Documentation / Guides / Kubernetes
Accessing a Kubernetes cluster through an Enclave Gateway¶
This guide describes the recommended pattern for providing remote users with routed access and DNS name resolution into a Kubernetes cluster using an Enclave Gateway. It covers architecture decisions, DNS resolution strategies, and the Enclave configuration needed to make Kubernetes services reachable by name.
Overview¶
When remote developers or administrators need to reach Kubernetes pods, services, or the control plane without exposing them to the public internet, an Enclave Gateway provides private, policy-controlled access with automatic DNS resolution.
The pattern places the Enclave Gateway on a dedicated VM in the same network as the cluster - not inside it. Connected clients receive routes to the cluster's network ranges and have their DNS queries forwarded to the Gateway, which resolves Kubernetes service and pod names on their behalf.
Architecture¶
Place the Enclave Gateway on a dedicated VM in the same VPC (or underlying network) as the Kubernetes cluster. The Gateway VM is enrolled in Enclave and enabled to act as a Gateway in the Portal. It advertises the cluster's routable network ranges so that connected clients can reach nodes, pods, and services through the encrypted Enclave tunnel.
Remote user ──Enclave tunnel──▶ Gateway VM ──VPC routing──▶ K8s nodes / pods / services
│
└──DNS query──▶ Cluster DNS (kube-dns / CoreDNS)
DNS queries from connected clients are forwarded to the Gateway automatically by Enclave's stub resolver. The Gateway resolves them against the cluster's DNS and returns answers, filtered by policy so that clients only receive addresses they are authorised to reach.
Why the Gateway sits outside the cluster¶
Stability. A standalone VM is not subject to pod eviction, node drain, or rolling updates. DNS resolution and routing remain available during cluster upgrades and maintenance windows.
Simplicity. No Kubernetes operator, custom resource definition, or privileged DaemonSet is required inside the cluster. Enclave configuration is limited to the Gateway VM and the Portal.
Security. The Gateway VM is the single point of entry. It does not require elevated pod capabilities (NET_ADMIN, SYS_ADMIN), and Enclave's policy-based DNS filtering ensures the Gateway only returns DNS answers for addresses the querying client is authorised to reach.
How other products handle this
Other overlay network products typically recommend deploying a subnet router or routing peer as a pod inside the cluster. While this avoids provisioning a separate VM, it introduces pod lifecycle concerns - routing and DNS resolution are interrupted when pods are evicted, drained, or rescheduled - and requires granting privileged capabilities to the routing pod. Placing the Gateway outside the cluster avoids these trade-offs.
Network prerequisites¶
The Gateway VM must be able to reach the cluster resources it will advertise. In most cloud-hosted, VPC-native Kubernetes distributions (GKE with alias IPs, EKS with the VPC CNI, AKS with Azure CNI), the following ranges are routable from other VMs in the same VPC:
| Target | Example CIDR | Notes |
|---|---|---|
| Node network | 10.60.0.0/20 |
Always VPC-routable |
| Pod network | 10.60.0.0/24 per node |
VPC-routable with VPC-native networking |
| Control plane | 172.21.8.2/32 |
Private endpoint, VPC-routable |
Kubernetes Service IPs (ClusterIPs) are virtual addresses managed by kube-proxy on each node. They are not VPC-routable by default. See Making ClusterIPs reachable if clients need to access standard Kubernetes services by their ClusterIP.
The .local domain. Kubernetes uses cluster.local as its default DNS zone. Most Linux distributions reserve the .local suffix for mDNS (multicast DNS) and will not send .local queries to standard DNS resolvers. Because the Enclave Gateway runs Linux, you must configure the Gateway VM to resolve .local via standard DNS, or DNS resolution for Kubernetes names will silently fail. See The .local domain for the required configuration. The same workaround is needed on any Linux client machines that will resolve cluster.local names.
Making ClusterIPs reachable from the Gateway VM¶
When a remote user resolves a Kubernetes service name like my-app.default.svc.cluster.local, the cluster's DNS returns a ClusterIP (e.g. 10.52.12.7). The remote user's traffic to that address travels through the Enclave tunnel to the Gateway VM, which then needs to forward it into the cluster. The problem is that ClusterIPs are virtual addresses - they are not real IPs on any network interface. They only work on Kubernetes nodes, where kube-proxy intercepts packets destined for a ClusterIP and translates them to a real pod IP.
Because the Gateway VM is not a Kubernetes node, it has no kube-proxy rules and cannot forward ClusterIP traffic on its own. There are three ways to handle this:
- Option A: Route ClusterIPs through a node is the simplest option and works with all existing Kubernetes services without changing their definitions. Start here unless you have a reason not to.
- Option B: Internal LoadBalancer services give each service a stable, VPC-routable IP without any static routes on the Gateway VM, but require changing service definitions. Prefer this for production services that need dedicated, health-checked endpoints.
- Option C: Headless services return pod IPs directly, bypassing ClusterIPs entirely. Useful for development access or stateful workloads where clients connect to specific pods, but pod IPs change when pods restart.
These approaches are per-service, not per-cluster, so you can mix them - for example, route ClusterIPs through a node for general access while exposing a handful of critical production services via Internal LoadBalancers.
Option A: Route ClusterIPs through a node¶
Add a static route on the Gateway VM that sends service CIDR traffic to one or more Kubernetes node IPs. Kube-proxy on those nodes translates the ClusterIP to a real pod IP and forwards the traffic:
sudo ip route add 10.52.0.0/17 via 10.60.0.10
This makes all ClusterIP services reachable through the Gateway without changing any Kubernetes service definitions. For resilience, point the route at multiple nodes or use an internal load balancer as the next hop.
Note
The static route above is added on the Gateway VM. Persist it across reboots by adding it to the Gateway VM's network configuration (e.g. Netplan on Ubuntu or a systemd-networkd route file).
Option B: Internal LoadBalancer services¶
For production services, create a Kubernetes Service of type: LoadBalancer with an internal annotation. The cloud provider assigns a stable, VPC-routable IP that the Gateway can reach directly:
apiVersion: v1
kind: Service
metadata:
name: my-app
annotations:
# GKE
networking.gke.io/load-balancer-type: "Internal"
# EKS: service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
# AKS: service.beta.kubernetes.io/azure-load-balancer-internal: "true"
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
Option C: Headless services¶
Headless services (clusterIP: None) cause the cluster DNS to return individual pod IPs instead of a ClusterIP. Since pod IPs are VPC-routable in VPC-native clusters, the Gateway can forward traffic directly without additional routes:
apiVersion: v1
kind: Service
metadata:
name: my-app-headless
spec:
clusterIP: None
selector:
app: my-app
ports:
- port: 80
This is well-suited to development access or stateful workloads where clients connect to specific pods.
DNS resolution on the Gateway¶
The Enclave stub resolver on each client forwards DNS queries to connected Gateways automatically. The Gateway resolves those queries using its configured upstream DNS. For Kubernetes, the Gateway needs an upstream DNS server that is authoritative for the cluster.local zone.
Use one of the following approaches:
- Option A: Internal Load Balancer in front of kube-dns - works on any managed Kubernetes service (GKE, EKS, AKS). No software to install on the Gateway VM. Start here.
- Option B: Cloud provider DNS - if your cloud provider already exposes
cluster.localthrough the VPC DNS (currently GKE with Cloud DNS only), there is nothing to deploy at all. - Option C: CoreDNS on the Gateway VM - for self-hosted or bare-metal clusters, or when cluster policy prohibits exposing kube-dns outside the cluster network.
Option A: Internal Load Balancer in front of kube-dns¶
Create a Kubernetes Service that exposes the cluster's DNS (kube-dns / CoreDNS) on a VPC-routable IP. This requires no additional software on the Gateway VM:
apiVersion: v1
kind: Service
metadata:
name: kube-dns-gateway
namespace: kube-system
annotations:
# GKE
networking.gke.io/load-balancer-type: "Internal"
# EKS: service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
# AKS: service.beta.kubernetes.io/azure-load-balancer-internal: "true"
spec:
type: LoadBalancer
selector:
k8s-app: kube-dns
ports:
- name: dns-udp
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
Apply the manifest and note the assigned IP:
kubectl apply -f kube-dns-gateway.yaml
kubectl get svc -n kube-system kube-dns-gateway \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
Then configure the Gateway VM to use it as its upstream DNS:
sudo enclave set-config gateway-dns-upstream-servers "LOAD_BALANCER_IP"
sudo enclave restart
The load balancer provides a stable IP with health checks against kube-dns pods, requiring no operational overhead on the Gateway VM itself.
Option B: Cloud provider DNS¶
Some managed Kubernetes services make the cluster.local zone resolvable from the VPC's default DNS resolver. When available, this is the simplest approach because the Gateway VM's OS DNS already resolves Kubernetes names with no additional configuration.
GKE clusters with Cloud DNS enabled expose the cluster.local zone through the VPC metadata DNS resolver. Leave gateway-dns-upstream-servers unset (so the Gateway uses its OS-configured DNS) or set it explicitly:
sudo enclave set-config gateway-dns-upstream-servers "169.254.169.254"
sudo enclave restart
EKS and AKS do not natively expose cluster.local through the VPC DNS resolver. Use Option A or Option C instead.
Option C: CoreDNS on the Gateway VM¶
Choose this option when no cloud-managed DNS integration is available (self-hosted or bare-metal clusters), when cluster policy prohibits exposing kube-dns outside the cluster network, or when you want the Gateway to be fully self-contained with no dependency on services running inside the cluster for DNS. It is the most operationally involved option - you are running and maintaining a DNS server on the Gateway VM - but it keeps the cluster's DNS surface completely closed.
CoreDNS is installed directly on the Gateway VM. Its kubernetes plugin connects to the Kubernetes API server using a kubeconfig file, authenticates as a Kubernetes service account with read-only permissions, and watches for changes to Services, Endpoints, Pods, and Namespaces. It builds DNS records from that API data in real time. It does not forward queries to kube-dns inside the cluster - it is its own authoritative DNS server for cluster.local, populated directly from the API. The steps below create the service account, grant it the minimum required permissions, and generate the kubeconfig that CoreDNS uses to authenticate.
-
Install CoreDNS on the Gateway VM:
COREDNS_VERSION=1.12.1 curl -sL "https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_linux_amd64.tgz" \ | sudo tar xz -C /usr/local/bin/ -
Create RBAC and kubeconfig. CoreDNS needs read-only access to Services, Endpoints, Namespaces, and Pods:
kubectl create serviceaccount coredns-gateway -n kube-system kubectl create clusterrole coredns-gateway-reader --verb=list,watch --resource=services,endpoints,namespaces,pods kubectl create clusterrolebinding coredns-gateway-reader --clusterrole=coredns-gateway-reader --serviceaccount=kube-system:coredns-gatewayGenerate a kubeconfig for the service account and copy it to
/etc/coredns/kubeconfigon the Gateway VM. -
Create a Corefile at
/etc/coredns/Corefile:cluster.local { kubernetes cluster.local { kubeconfig /etc/coredns/kubeconfig } cache 30 errors } . { forward . /etc/resolv.conf cache 30 errors } -
Run CoreDNS as a systemd service and configure Enclave to use it:
sudo enclave set-config gateway-dns-upstream-servers "127.0.0.1" sudo enclave restart
Tip
CoreDNS resolves Service names to ClusterIPs by default. To return pod IPs instead (avoiding the need for a ClusterIP route), add endpoint_pod_names to the kubernetes block in the Corefile.
Enclave configuration¶
Enable the Gateway and add subnets¶
In the Enclave Portal, open the enrolled Gateway VM and enable "Allow this system to act as a Gateway". See Gateway deployment for detailed steps.
Add the cluster network ranges that clients should be able to reach. Enclave auto-discovers the Gateway VM's directly connected subnets; use "Add subnet" to include ranges reachable via VPC routing:
| Subnet | Name | Purpose |
|---|---|---|
10.60.0.0/20 |
K8s pod network | Node and pod access |
10.52.0.0/17 |
K8s services | ClusterIP access (if using service CIDR routing) |
172.21.8.2/32 |
K8s control plane | kubectl / API server access |
Create a Gateway Access Policy¶
Create a Gateway Access Policy with:
- Senders: a tag representing users who need cluster access (e.g.
k8s-developers) - Gateway / Subnet: the Gateway VM and the subnets listed above
Tune DNS settings¶
Adjust the upstream query timeout if the DNS path involves the Kubernetes API or a cloud DNS resolver with higher latency than a local nameserver:
sudo enclave set-config gateway-dns-upstream-timeout-ms 1000
If you want the Gateway to answer all DNS queries - not just those whose answers fall within advertised subnets - enable reply-for-all:
sudo enclave set-config gateway-dns-reply-for-all enabled
This is useful when combining Kubernetes DNS with DNS filtering, or when some resolved addresses fall outside the subnets advertised by this Gateway.
Client configuration¶
Search domains¶
Kubernetes service names are fully qualified as service.namespace.svc.cluster.local. To resolve short names like my-app or my-app.default, configure DNS search suffixes on client machines:
Add search suffixes via Group Policy, NRPT, or adapter settings:
default.svc.cluster.localsvc.cluster.localcluster.local
Add search domains in System Settings > Network > DNS > Search Domains:
default.svc.cluster.localsvc.cluster.localcluster.local
Add to /etc/systemd/resolved.conf or a drop-in file:
[Resolve]
Domains=default.svc.cluster.local svc.cluster.local cluster.local
Then restart systemd-resolved:
sudo systemctl restart systemd-resolved
Verification¶
From a connected client machine:
-
Check routes. Run
enclave statusand confirm the Gateway peer shows the expected subnets:Gateway for . . . . : 10.60.0.0/20, 10.52.0.0/17, 172.21.8.2/32 -
Resolve a Kubernetes name. The default server should be the Enclave stub resolver:
nslookup kubernetes.default.svc.cluster.local -
Access a service:
curl http://my-app.default.svc.cluster.local -
Verify kubectl (if the control-plane endpoint is advertised):
kubectl get nodes
Further considerations¶
High availability. Deploy two Gateway VMs in different availability zones, both advertising the same subnets. Enclave automatically fails over between them. See Priority, redundancy and failover.
Multi-cluster access. Each cluster gets its own Gateway VM and its own upstream DNS configuration. If multiple clusters share the same cluster.local zone, assign each Gateway a distinct Enclave tag and create separate Gateway Access Policies so that clients connect to the correct Gateway for the cluster they need.
Monitoring. Use enclave status on the Gateway VM to view DNS query metrics. For deeper visibility into what traffic flows through the Gateway, enable Network Flow Metadata export to an IPFIX collector.
DNS filtering. The Gateway can combine Kubernetes DNS with DNS filtering by chaining upstream resolvers - for example, forwarding to Pi-hole for filtering, with Pi-hole configured to forward cluster.local queries to kube-dns.
Having problems? Contact us at support@enclave.io or visit our support options.
Last updated July 30, 2026