Documentation
Guides
Kubernetes Deployment

Kubernetes Deployment

Deploy the NAT self-hosted server on Kubernetes using the manifests in deploy/k8s/. The default configuration runs 2 replicas behind a ClusterIP Service on port 8080 with three PersistentVolumeClaims for scan data, model weights, and regression results.

For simpler single-host deployments, see the Docker Deployment guide. Use this guide when you need horizontal scaling, rolling updates, or cluster-native ingress.

Prerequisites

  • kubectl configured and pointed at your target cluster
  • Cluster version Kubernetes 1.24+
  • A default StorageClass that supports ReadWriteOnce (or update the PVC specs for your storage provider)
  • The deploy/k8s/ manifests from the NAT self-hosted distribution

The deploy/k8s/ manifests are included with NAT's self-hosted distribution. If you installed NAT via pip install nat-engine, the manifests are available in your site-packages directory. You can also download them from your NAT dashboard under Settings → Downloads or contact sales@nat-testing.io for access.

Quick-start deployment

Create the namespace

kubectl create namespace nat

Apply the manifests

kubectl apply -f configmap.yaml -n nat
kubectl apply -f deployment.yaml -n nat
kubectl apply -f service.yaml -n nat

Verify the rollout

kubectl rollout status deployment/nat -n nat
# deployment "nat" successfully rolled out
 
kubectl get pods -n nat
# NAME                   READY   STATUS    RESTARTS   AGE
# nat-7d4f9b8c6-abcde    1/1     Running   0          2m
# nat-7d4f9b8c6-fghij    1/1     Running   0          2m

Confirm health

kubectl port-forward svc/nat 8080:8080 -n nat &
curl http://localhost:8080/api/v1/health
# {"status":"ok","version":"1.x.x"}

Configuration via ConfigMap

The configmap.yaml sets environment variables for all NAT pods:

# deploy/k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nat-config
data:
  NAT_HOST: "0.0.0.0"
  NAT_PORT: "8080"
  NAT_LOG_LEVEL: "info"
  NAT_WORKERS: "4"

Edit the ConfigMap to change settings, then roll out the deployment:

kubectl edit configmap nat-config -n nat
kubectl rollout restart deployment/nat -n nat
VariableDescriptionDefault
NAT_HOSTBind address0.0.0.0
NAT_PORTListen port8080
NAT_LOG_LEVELLog verbosity (debug, info, warn, error)info
NAT_WORKERSNumber of worker processes4

Sensitive configuration

Store secrets (database URL, API keys) in a Kubernetes Secret rather than the ConfigMap:

kubectl create secret generic nat-secrets \
  --from-literal=DATABASE_URL='postgresql://user:pass@postgres:5432/nat' \
  --from-literal=NAT_SECRET_KEY='your-secret-key' \
  -n nat

Reference the secret in your Deployment's envFrom:

envFrom:
  - configMapRef:
      name: nat-config
  - secretRef:
      name: nat-secrets
⚠️

Always run alembic upgrade head against the database before deploying a new NAT version that includes schema changes. Run it as a Kubernetes Job or init container to keep migrations in sync with the rollout.

Persistent storage

The deployment uses three PersistentVolumeClaims:

PVCMount pathDefault sizePurpose
nat-scans-pvc/data/scans10GiScan results and HTML reports
nat-weights-pvc/data/weights5GiModel weights cache
nat-regression-pvc/data/regression5GiRegression test baselines

PVCs are created automatically when you apply deployment.yaml. To inspect them:

kubectl get pvc -n nat

To resize a PVC, edit it and patch the request (requires a StorageClass that supports volume expansion):

kubectl patch pvc nat-scans-pvc -n nat \
  -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Exposing the service

Quick access for local development or debugging — no DNS or load balancer needed:

kubectl port-forward svc/nat 8080:8080 -n nat
# NAT dashboard available at http://localhost:8080

Scaling

Manual scaling

kubectl scale deployment nat --replicas=4 -n nat

Horizontal Pod Autoscaler

Create an HPA to scale automatically based on CPU utilization:

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nat
  namespace: nat
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nat
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60  # scale up when average CPU exceeds 60%
kubectl apply -f hpa.yaml -n nat
kubectl get hpa -n nat

Updating the image

Rolling updates with zero downtime:

kubectl set image deployment/nat nat=natengine/nat:1.3.0 -n nat
kubectl rollout status deployment/nat -n nat

Roll back if the new version is unhealthy:

kubectl rollout undo deployment/nat -n nat

Troubleshooting

Pods stuck in Pending

kubectl describe pod <pod-name> -n nat

Common causes:

  • Insufficient cluster resources — check node capacity
  • PVC can't be provisioned — check StorageClass and PVC events
  • Image pull failure — verify the image name and any registry credentials

Pods in CrashLoopBackOff

kubectl logs <pod-name> -n nat --previous

Common causes:

  • Missing DATABASE_URL secret — ensure the Secret exists and is referenced
  • Failed database migration — run alembic upgrade head and check for errors
  • Wrong NAT_HOST — must be 0.0.0.0 (not 127.0.0.1) inside a container

Health probe failures

The deployment configures liveness and readiness probes at GET /api/v1/health. If probes fail:

# Check the health endpoint directly from inside a pod
kubectl exec -it <pod-name> -n nat -- curl http://localhost:8080/api/v1/health

Adjust initialDelaySeconds in the Deployment spec if the server takes longer than expected to start.

Next steps

Was this helpful?