Skip to main content

Kubernetes operator

The operator publishes cluster Services through Tuna tunnels and manages your account's domains and TCP ports with Kubernetes resources. The cluster connects to Tuna with outbound connections: you don't need a LoadBalancer, a public IP or open inbound ports.

Operator resources (group k8s.tuna.am/v1alpha1):

  • Tunnel — an HTTP or TCP tunnel to a Service;
  • Ingress with ingressClassName: tuna — path routing to several Services behind one domain;
  • Domain — a permanent subdomain or a custom domain in your account;
  • TCPEndpoint — a reserved TCP port with an alias.

This is not the same as tuna kubernetes: that tunnel gives temporary access to the Kubernetes API, while the operator publishes applications from the cluster.

Requirements​

  • Kubernetes 1.32 or newer;
  • Nelm (recommended) or Helm 4;
  • an API key with write access to endpoints and domains (tunnels:write) or full API access.

Installation​

The chart is in the code.tuna.am package registry.

Nelm​

Nelm shows the change plan before applying it and updates CRDs together with the release:

export NELM_FEAT_REMOTE_CHARTS=true
nelm repo add tuna https://code.tuna.am/api/packages/tuna/helm
nelm release plan install -n tuna-system -r tuna-operator tuna/tuna-k8s-operator \
--set credentials.apiKey=<API key>
nelm release install -n tuna-system -r tuna-operator tuna/tuna-k8s-operator \
--set credentials.apiKey=<API key>

Helm​

helm repo add tuna https://code.tuna.am/api/packages/tuna/helm
helm repo update
helm install tuna-operator tuna/tuna-k8s-operator \
--namespace tuna-system --create-namespace \
--set credentials.apiKey=<API key>

Where to keep the key​

  • credentials.apiKey — the chart creates a Secret;

  • credentials.existingSecret — your own Secret with the api-key key, for example from External Secrets Operator or Sealed Secrets;

  • extraEnv — the TUNA_API_KEY variable from a secret injector, without a Secret in Kubernetes. An example for Bank-Vaults:

    podAnnotations:
    vault.security.banzaicloud.io/vault-addr: https://vault.vault.svc:8200
    vault.security.banzaicloud.io/vault-role: tuna-operator
    extraEnv:
    - name: TUNA_API_KEY
    value: vault:secret/data/tuna#api-key

Other parameters are in values.yaml and the chart README.

Tunnel to a Service​

apiVersion: k8s.tuna.am/v1alpha1
kind: Tunnel
metadata:
name: web
spec:
protocol: http
subdomain: my-app
upstream:
url: http://web:8080

The public address appears in the resource status:

kubectl get tunnel web

You can give a tunnel its own key from a Secret — the api-key field; the key needs the tunnels:connect scope:

spec:
apiKeySecretRef:
name: team-tuna-api-key

Ingress with path routing​

The Ingress host must be a reserved domain: a Domain resource or a domain from the dashboard. kubectl get domain my-app shows the full domain name.

apiVersion: k8s.tuna.am/v1alpha1
kind: Domain
metadata:
name: my-app
spec:
subdomain: my-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
spec:
ingressClassName: tuna
rules:
- host: my-app.a.2na.am
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: web
port:
name: http

The operator creates one tunnel per host. Path and backend changes apply without reconnecting. Prefixes match whole path segments: /api matches /api/users but not /apix. The backend gets the public Host header, as well as X-Forwarded-Host and X-Forwarded-Proto.

Limitations: only Service backends are supported, rules without a host and wildcard hosts are skipped, and several Ingresses for one host aren't coordinated.

Custom domain​

apiVersion: k8s.tuna.am/v1alpha1
kind: Domain
metadata:
name: api-example-com
spec:
domain: api.example.com
reclaimPolicy: Retain

The DNS records to create at your DNS provider appear in status.dnsRecords. If the DNS check fails, the operator restarts it every 5 minutes. You can't switch an existing resource between subdomain and domain — create a new one.

TCP port​

apiVersion: k8s.tuna.am/v1alpha1
kind: TCPEndpoint
metadata:
name: postgres
spec:
alias: postgres
---
apiVersion: k8s.tuna.am/v1alpha1
kind: Tunnel
metadata:
name: postgres
spec:
protocol: tcp
portOrAlias: postgres
upstream:
url: tcp://postgres:5432

Field reference: kubectl explain tunnel.spec, kubectl explain domain.spec, kubectl explain tcpendpoint.spec. More examples are in the chart's examples/ directory.

Existing reservations​

If the domain or alias already exists in your account — created in the dashboard or by another cluster — Domain and TCPEndpoint adopt that reservation instead of failing. Such a resource has status.adopted: true, and the operator never deletes the reservation.

For other resources, reclaimPolicy: Delete (the default) releases the reservation when the resource is deleted, and Retain keeps it. If the reservation is deleted in the dashboard, the operator creates it again.

Status and errors​

Each resource reports its state in the Ready condition and in Kubernetes events:

kubectl describe tunnel web

Ingress has no conditions, so check its events: TunnelConnected, TunnelStopped, RoutesUpdated, RuleSkipped, BackendSkipped.

Security​

  • Tunnel and Ingress publish only Services in their own namespace. IP addresses, external hosts, Services in other namespaces and Services of type ExternalName are rejected. The agentManager.allowExternalUpstreams=true parameter lifts this restriction, but then the right to create a Tunnel or Ingress in any namespace means the right to publish any address the operator pod can reach.
  • Each tunnel has its own temporary token, revoked when the resource is deleted. Tokens of resources deleted while the operator was down are revoked automatically.
  • Pods run as an unprivileged user, with a read-only root filesystem and no capabilities. Only the tunnel component reads Secrets and Services, and only with get.
  • Tunnels run inside the operator pod: traffic goes from Tuna through the pod to the Service. The tunnel pod has one replica, so tunnels reconnect when you upgrade the operator.

Upgrading​

With Nelm, run the same nelm release plan install and nelm release install with a new --chart-version: CRDs are updated together with the release.

Helm doesn't update CRDs, so apply them before upgrading the chart:

helm pull tuna/tuna-k8s-operator --untar --version <version>
kubectl apply -f tuna-k8s-operator/crds/
helm upgrade tuna-operator tuna/tuna-k8s-operator --namespace tuna-system --reuse-values --version <version>

Uninstalling​

Delete the resources first: Domain and TCPEndpoint have finalizers that need a running operator.

kubectl delete tunnels,domains,tcpendpoints --all --all-namespaces
nelm release uninstall -n tuna-system -r tuna-operator # or: helm uninstall tuna-operator -n tuna-system
kubectl delete crd tunnels.k8s.tuna.am domains.k8s.tuna.am tcpendpoints.k8s.tuna.am