| name | nais |
| description | Nais-deployment, GCP-ressurser, pod-lifecycle og feilsøking på plattformen |
| license | MIT |
| compatibility | Application deployed on Nais (Kubernetes on GCP) |
| metadata | {"domain":"platform","tags":"nais kubernetes gcp deployment infrastructure troubleshooting"} |
Nais Platform Skill
Patterns and procedures for deploying, configuring, and troubleshooting applications on Nais (Kubernetes on GCP).
When to Use
- Creating or modifying Nais manifests
- Adding GCP resources (PostgreSQL, Kafka, buckets)
- Configuring access policies and ingress
- Troubleshooting pod startup failures or crashes
- Understanding pod lifecycle and graceful shutdown
Commands
kubectl get pods -n <namespace> -l app=<app-name>
kubectl logs -n <namespace> -l app=<app-name> --tail=100
kubectl describe pod -n <namespace> <pod-name>
kubectl get app -n <namespace> <app-name> -o yaml
kubectl rollout restart deployment/<app-name> -n <namespace>
kubectl port-forward -n <namespace> svc/<app-name> 8080:80
Nais Manifest Structure
Every Nais application requires:
apiVersion: nais.io/v1alpha1
kind: Application
metadata:
name: app-name
namespace: team-namespace
labels:
team: team-namespace
spec:
image: {{ image }}
port: 8080
prometheus:
enabled: true
path: /metrics
liveness:
path: /isalive
initialDelay: 5
readiness:
path: /isready
initialDelay: 5
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 512Mi
Pod Lifecycle and Graceful Shutdown
When Kubernetes terminates a pod on NAIS:
- K8s notifies load balancer and pod simultaneously — LB starts draining connections
- NAIS preStop hook runs
sleep 5 — gives LB time to stop routing new traffic
- App receives SIGTERM — no new requests arrive from LB
- App drains in-flight requests and exits
- After
terminationGracePeriodSeconds (default 30s): SIGKILL
Key insight: Readiness probes are NOT involved in shutdown. Your app just needs to handle SIGTERM, finish in-flight requests, and exit cleanly.
Common anti-patterns:
- ❌ Setting readiness to
false on SIGTERM — unnecessary
- ❌
terminationGracePeriodSeconds too low — must be > 5s (preStop) + drain time
- ❌ Adding a
preStop hook with extra sleep — NAIS already injects sleep 5
Common Tasks
Adding PostgreSQL Database
gcp:
sqlInstances:
- type: POSTGRES_15
databases:
- name: myapp-db
envVarPrefix: DB
Application receives: DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD
Configuring Kafka
kafka:
pool: nav-dev
Azure AD Authentication
azure:
application:
enabled: true
tenant: nav.no
TokenX for Service-to-Service
tokenx:
enabled: true
accessPolicy:
inbound:
rules:
- application: calling-app
namespace: calling-namespace
outbound:
rules:
- application: downstream-app
namespace: downstream-namespace
Ingress Configuration
ingresses:
- https://myapp.intern.dev.nav.no
- https://myapp.dev.nav.no
Scaling
replicas:
min: 2
max: 4
cpuThresholdPercentage: 80
Resource Recommendations
| Size | CPU request | Memory request | Memory limit |
|---|
| Small | 50m | 256Mi | 512Mi |
| Medium | 100m | 512Mi | 1Gi |
| Large | 200m | 1Gi | 2Gi |
Never set CPU limits — causes throttling. Use requests only.
Troubleshooting
Pod Not Starting
- Check logs:
kubectl logs -n namespace pod-name
- Check events:
kubectl describe pod -n namespace pod-name
- Verify health endpoints return 200 OK
- Check resource limits (memory/CPU)
Database Connection Issues
- Verify database exists in GCP Console
- Check environment variables are injected
- Verify Cloud SQL Proxy is running
- Check network policies allow connection
Kafka Connection Issues
- Verify
kafka.pool is correct (nav-dev/nav-prod)
- Check Kafka credentials are injected
- Verify SSL configuration
- Check topic exists and permissions are correct
Deployment Workflow
- Create
.nais/app.yaml manifest
- Implement health endpoints (
/isalive, /isready, /metrics)
- Test locally with Docker
- Deploy to dev environment
- Verify metrics in Grafana
- Check logs in Loki
- Create prod manifest (
.nais/app-prod.yaml)
- Deploy to production
Gotchas
accessPolicy defaults to deny-all — you must explicitly allow traffic
- Don't set CPU limits — only requests (limits cause throttling)
- Memory limits are mandatory — missing limits cause OOM cluster issues
- NAIS injects
preStop: sleep 5 — don't add your own
{{ image }} in manifest is replaced by CI/CD — don't hardcode images
- Environment-specific manifests (
app-dev.yaml, app-prod.yaml) are the norm
Boundaries
✅ Always
- Include liveness, readiness, and metrics endpoints
- Set memory limits
- Define explicit
accessPolicy for network traffic
- Use environment-specific manifests
- Run
kubectl get app <name> -o yaml to verify deployment
⚠️ Ask First
- Changing production resource limits or replicas
- Adding new GCP resources (cost implications)
- Modifying network policies (
accessPolicy)
- Changing Kafka topic configurations
- Adding new ingress domains
🚫 Never
- Store secrets in Git
- Deploy directly without CI/CD pipeline
- Skip health endpoints
- Set CPU limits
- Remove memory limits