Tailscale Deployment Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Deploy Tailscale Operator with HA subnet router advertising 192.168.20.0/22 and exit node capability.
Architecture: Tailscale Operator manages a Connector CRD that creates 2 HA replica pods joining the tailnet as subnet routers. OAuth credentials stored in Vault, pulled via ExternalSecret.
Tech Stack: Tailscale Operator Helm chart, ExternalSecrets, Vault KV, ArgoCD GitOps
Prerequisites (Manual Steps)
Section titled “Prerequisites (Manual Steps)”Before starting implementation, complete these manual steps:
1. Create Tailscale OAuth Client
Section titled “1. Create Tailscale OAuth Client”- Go to Tailscale Admin Console → Settings → OAuth clients
- Create new OAuth client with scopes:
devices:write,routes:write - Note the Client ID and Client Secret
2. Store Credentials in Vault
Section titled “2. Store Credentials in Vault”vault kv put secret/fzymgc-house/cluster/tailscale/oauth \ clientId="<your-client-id>" \ clientSecret="<your-client-secret>"3. (Optional) Add ACL autoApprovers
Section titled “3. (Optional) Add ACL autoApprovers”In Tailscale Admin Console → Access Controls, add:
"autoApprovers": { "routes": { "192.168.20.0/22": ["tag:k8s-operator"] }, "exitNode": ["tag:k8s-operator"]}Task 1: Create App Directory Structure
Section titled “Task 1: Create App Directory Structure”Files:
- Create:
argocd/app-configs/tailscale/kustomization.yaml
Step 1: Create the directory and kustomization file
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources: - namespace.yaml - external-secret.yaml - connector.yamlStep 2: Validate YAML syntax
Run: yamllint argocd/app-configs/tailscale/kustomization.yaml
Expected: No errors
Step 3: Commit
git add argocd/app-configs/tailscale/kustomization.yamlgit commit -m "feat(tailscale): add kustomization for tailscale app"Task 2: Create Namespace Manifest
Section titled “Task 2: Create Namespace Manifest”Files:
- Create:
argocd/app-configs/tailscale/namespace.yaml
Step 1: Create the namespace manifest
apiVersion: v1kind: Namespacemetadata: name: tailscale labels: app.kubernetes.io/name: tailscaleStep 2: Validate YAML syntax
Run: yamllint argocd/app-configs/tailscale/namespace.yaml
Expected: No errors
Step 3: Commit
git add argocd/app-configs/tailscale/namespace.yamlgit commit -m "feat(tailscale): add namespace manifest"Task 3: Create ExternalSecret for OAuth Credentials
Section titled “Task 3: Create ExternalSecret for OAuth Credentials”Files:
- Create:
argocd/app-configs/tailscale/external-secret.yaml
Step 1: Create the ExternalSecret manifest
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: tailscale-oauth namespace: tailscalespec: refreshPolicy: Periodic refreshInterval: 1h secretStoreRef: name: vault kind: ClusterSecretStore target: name: tailscale-oauth creationPolicy: Owner deletionPolicy: Delete data: - secretKey: oauth-client-id remoteRef: key: fzymgc-house/cluster/tailscale/oauth property: clientId - secretKey: oauth-client-secret remoteRef: key: fzymgc-house/cluster/tailscale/oauth property: clientSecretStep 2: Validate YAML syntax
Run: yamllint argocd/app-configs/tailscale/external-secret.yaml
Expected: No errors
Step 3: Commit
git add argocd/app-configs/tailscale/external-secret.yamlgit commit -m "feat(tailscale): add ExternalSecret for OAuth credentials"Task 4: Create Connector Resource
Section titled “Task 4: Create Connector Resource”Files:
- Create:
argocd/app-configs/tailscale/connector.yaml
Step 1: Create the Connector manifest
apiVersion: tailscale.com/v1alpha1kind: Connectormetadata: name: home-network namespace: tailscalespec: hostname: k8s-subnet-router subnetRouter: advertiseRoutes: - "192.168.20.0/22" exitNode: true tags: - "tag:k8s-operator"Step 2: Validate YAML syntax
Run: yamllint argocd/app-configs/tailscale/connector.yaml
Expected: No errors
Step 3: Commit
git add argocd/app-configs/tailscale/connector.yamlgit commit -m "feat(tailscale): add Connector for subnet router and exit node"Task 5: Create Helm Values File
Section titled “Task 5: Create Helm Values File”Files:
- Create:
argocd/app-configs/tailscale/values.yaml
Step 1: Create the Helm values file
operatorConfig: defaultTags: - "tag:k8s-operator"
oauth: clientId: "" clientSecret: "" clientIdFromSecret: name: tailscale-oauth key: oauth-client-id clientSecretFromSecret: name: tailscale-oauth key: oauth-client-secret
apiServerProxyConfig: mode: "noauth"
resources: limits: memory: 256Mi requests: cpu: 50m memory: 128MiStep 2: Validate YAML syntax
Run: yamllint argocd/app-configs/tailscale/values.yaml
Expected: No errors
Step 3: Commit
git add argocd/app-configs/tailscale/values.yamlgit commit -m "feat(tailscale): add Helm values for operator configuration"Task 6: Create ArgoCD Application
Section titled “Task 6: Create ArgoCD Application”Files:
- Create:
argocd/cluster-app/templates/tailscale.yaml
Step 1: Create the ArgoCD Application manifest
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: tailscale namespace: argocd annotations: argocd.argoproj.io/sync-wave: "0"spec: project: core-services sources: - chart: tailscale-operator repoURL: https://pkgs.tailscale.com/helmcharts targetRevision: "1.82.0" helm: releaseName: tailscale-operator valueFiles: - $values/argocd/app-configs/tailscale/values.yaml - repoURL: https://github.com/fzymgc-house/selfhosted-cluster targetRevision: HEAD ref: values - repoURL: https://github.com/fzymgc-house/selfhosted-cluster targetRevision: HEAD path: argocd/app-configs/tailscale destination: server: https://kubernetes.default.svc namespace: tailscale syncPolicy: automated: prune: true selfHeal: true syncOptions: - ServerSideApply=true - CreateNamespace=true - RespectIgnoreDifferences=true ignoreDifferences: - kind: ExternalSecret group: external-secrets.io jqPathExpressions: - .spec.data[].remoteRef.conversionStrategy - .spec.data[].remoteRef.decodingStrategy - .spec.data[].remoteRef.metadataPolicyStep 2: Validate YAML syntax
Run: yamllint argocd/cluster-app/templates/tailscale.yaml
Expected: No errors
Step 3: Commit
git add argocd/cluster-app/templates/tailscale.yamlgit commit -m "feat(tailscale): add ArgoCD Application for tailscale-operator"Task 7: Update Velero Backup Exclusions
Section titled “Task 7: Update Velero Backup Exclusions”Files:
- Modify:
argocd/app-configs/velero/backup-schedule.yaml
Step 1: Read current backup schedule
Check the excludedNamespaces list in the backup schedule.
Step 2: Add tailscale to excluded namespaces
Add tailscale to the Operators category in excludedNamespaces.
Step 3: Commit
git add argocd/app-configs/velero/backup-schedule.yamlgit commit -m "chore(velero): exclude tailscale namespace from backups"Task 8: Update Documentation
Section titled “Task 8: Update Documentation”Files:
- Modify:
docs/reference/services.md(add Tailscale entry) - Modify:
docs/reference/secrets.md(add Vault path)
Step 1: Add Tailscale to services catalog
Add entry for Tailscale with:
- Name: Tailscale
- Namespace: tailscale
- Purpose: Subnet router and exit node for tailnet access
- URL: N/A (not a web service)
Step 2: Add Vault path to secrets reference
Add entry:
- Path:
fzymgc-house/cluster/tailscale/oauth - Properties:
clientId,clientSecret - Consumer: tailscale-operator
Step 3: Commit
git add docs/reference/services.md docs/reference/secrets.mdgit commit -m "docs: add Tailscale to services and secrets reference"Task 9: Final Validation
Section titled “Task 9: Final Validation”Step 1: Validate all manifests with kustomize
Run: kubectl kustomize argocd/app-configs/tailscale
Expected: Combined YAML output with namespace, external-secret, and connector
Step 2: Dry-run the ArgoCD app template
Run: helm template argocd/cluster-app --show-only templates/tailscale.yaml
Expected: Valid Application manifest
Step 3: Run pre-commit checks
Run: pre-commit run --all-files
Expected: All checks pass
Post-Merge Verification
Section titled “Post-Merge Verification”After PR is merged and ArgoCD syncs:
Step 1: Check operator deployment
kubectl get pods -n tailscale -l app.kubernetes.io/name=tailscale-operatorExpected: 1/1 Running
Step 2: Check ExternalSecret sync
kubectl get externalsecret -n tailscaleExpected: SecretSynced status
Step 3: Check Connector status
kubectl get connector -n tailscaleExpected: home-network connector listed
Step 4: Check subnet router pods
kubectl get pods -n tailscale -l tailscale.com/parent-resource=home-networkExpected: 2 pods Running (HA replicas)
Step 5: Verify in Tailscale Admin Console
- Machines:
k8s-subnet-router-0andk8s-subnet-router-1connected - Routes:
192.168.20.0/22approved (or pending if no autoApprovers) - Exit node: Enabled
Step 6: Test from tailnet client
tailscale statusping 192.168.20.101Expected: Route available, ping succeeds