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 theapi-keykey, for example from External Secrets Operator or Sealed Secrets; -
extraEnv— theTUNA_API_KEYvariable 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:8200vault.security.banzaicloud.io/vault-role: tuna-operatorextraEnv:- name: TUNA_API_KEYvalue: 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
ExternalNameare rejected. TheagentManager.allowExternalUpstreams=trueparameter 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