Skip to main content

Overview

Tailscale provides secure VPN access to the Kubernetes cluster and its services. The Tailscale operator manages subnet routing and service exposure, allowing authorized devices to access cluster resources remotely without complex firewall configurations.

HelmRelease Configuration

Tailscale is deployed using the Tailscale operator Helm chart:
overlays/base/tailscale/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: tailscale-operator
spec:
  interval: 5m
  chart:
    spec:
      chart: tailscale-operator
      version: "=1.92.4"
      sourceRef:
        kind: HelmRepository
        name: tailscale
Tailscale operator version 1.92.4 is installed from the official Tailscale Helm repository.

Subnet Router Configuration

The cluster uses a Tailscale Connector resource to advertise subnet routes:
overlays/kimawesome/infrastructure/vpn/router/router.yaml
apiVersion: tailscale.com/v1alpha1
kind: Connector
metadata:
  name: subnet-router
spec:
  subnetRouter:
    advertiseRoutes:
      - 192.168.0.0/16
      - 192.168.10.3/32

Advertised Routes

192.168.0.0/16
string
Local network range containing cluster nodes and services
192.168.10.3/32
string
Specific host IP for targeted access

Service Exposure

Services can be exposed via Tailscale using service annotations:
overlays/kimawesome/applications/dns-server/tailscale-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: bind9-tailscale
  annotations:
    tailscale.com/expose: "true"
spec:
  type: LoadBalancer
  loadBalancerClass: tailscale
  selector:
    app: bind9
  ports:
    - name: dns-tcp
      port: 53
      protocol: TCP
    - name: dns-udp
      port: 53
      protocol: UDP
Setting loadBalancerClass: tailscale directs the LoadBalancer to use Tailscale instead of MetalLB, making the service accessible only to authorized Tailscale network members.

Authentication

The Tailscale operator requires authentication credentials stored in a Kubernetes secret:
apiVersion: v1
kind: Secret
metadata:
  name: operator-oauth
  namespace: tailscale
stringData:
  client_id: <your-oauth-client-id>
  client_secret: <your-oauth-client-secret>
1

Create OAuth Client

Generate OAuth credentials in the Tailscale admin console under Settings → OAuth clients
2

Seal the Secret

Use sealed-secrets to encrypt the OAuth credentials for Git storage:
kubectl create secret generic operator-oauth \
  --namespace=tailscale \
  --from-literal=client_id=<your-id> \
  --from-literal=client_secret=<your-secret> \
  --dry-run=client -o yaml | \
kubeseal --format=yaml > sealed-operator-oauth.yaml
3

Commit to Git

Add the sealed secret to your repository and let Flux apply it

Access Control

Tailscale provides several access control mechanisms:

ACL Tags

Tag the subnet router to control which Tailscale users can access it:
{
  "tagOwners": {
    "tag:k8s-operator": ["[email protected]"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["group:admins"],
      "dst": ["tag:k8s-operator:*"]
    }
  ]
}

Service-Level Access

Control access to individual exposed services using Tailscale ACLs referencing service hostnames.

Verification

1

Check Operator Status

Verify the Tailscale operator is running:
kubectl get pods -n tailscale
kubectl logs -n tailscale -l app=tailscale-operator
2

Check Connector Status

Verify the subnet router is advertising routes:
kubectl get connector -n tailscale
kubectl describe connector subnet-router -n tailscale
3

Verify Routes in Tailscale

Check the Tailscale admin console to confirm subnet routes are visible and approved
4

Test Connectivity

From a device on your Tailscale network, test connectivity:
# Ping a cluster node
ping 192.168.0.101

# Test DNS service
dig @192.168.10.3 example.kim.tec.br

Use Cases

Remote Administration

Access kubectl, SSH, and cluster services from anywhere securely

Service Exposure

Expose services to authorized users without public internet exposure

Multi-Site Access

Connect multiple sites or home labs securely

Developer Access

Allow developers to access development services remotely

Troubleshooting

Check the Connector resource status:
kubectl describe connector subnet-router -n tailscale
Common issues:
  • OAuth credentials are incorrect or expired
  • Routes need to be manually approved in Tailscale admin console
  • Subnet router needs IP forwarding enabled on the node
Verify the service has the correct annotations:
kubectl get svc -n <namespace> -o yaml | grep -A 2 annotations
Check that tailscale.com/expose: "true" is set and loadBalancerClass: tailscale is configured.
Check operator logs:
kubectl logs -n tailscale -l app=tailscale-operator --tail=100
Common causes:
  • Missing or invalid OAuth secret
  • Insufficient RBAC permissions
  • Network policy blocking operator communication

Security Considerations

Tailscale provides encrypted access to your cluster. However:
  • Ensure OAuth credentials are properly sealed before committing to Git
  • Review and approve subnet routes in the Tailscale admin console
  • Use ACL tags to restrict access to authorized users only
  • Regularly audit connected devices and remove unused ones

Best Practices

  • Use separate OAuth clients for different clusters
  • Enable MFA for Tailscale user accounts with cluster access
  • Monitor Tailscale audit logs for suspicious activity
  • Rotate OAuth credentials periodically
  • Use ACL tags to implement least-privilege access

Configuration Reference

Connector Spec

subnetRouter
object
required
Configuration for advertising subnet routes to the Tailscale network

Service Annotations

tailscale.com/expose
string
Set to "true" to expose the service via Tailscale
tailscale.com/hostname
string
Optional custom hostname for the exposed service (defaults to service name)
tailscale.com/tags
string
Comma-separated list of ACL tags to apply to the service

Sealed Secrets

Learn how to encrypt OAuth credentials for Git storage

Networking

Understand cluster networking with Cilium

Load Balancer

Learn about MetalLB for public service exposure

DNS Server

See how DNS is exposed via Tailscale

External Documentation

Build docs developers (and LLMs) love