Version note
This article was written for NKP 2.17. The current release is 2.18. See the Upgrade to NKP 2.18 series for the latest guidance.
Nutanix Kubernetes Platform (NKP) | Part 6: Operations
Deploying a cluster is day one. Keeping it running, observable, backed up, and up to date is every day after.
This guide covers the operational aspects of running NKP in production: monitoring and alerting, centralized logging, backup and disaster recovery, cluster upgrades, and the troubleshooting patterns we use across client environments. This is the final part of the series, building on everything from Part 1 through Part 5.
Monitoring
The NKP Monitoring Stack
NKP ships a complete monitoring stack (Pro/Ultimate license):
| Component | What It Does |
|---|---|
| kube-prometheus-stack | Prometheus (metrics collection) + Grafana (visualization) + AlertManager (alerting) |
| prometheus-adapter | Exposes custom metrics for Horizontal Pod Autoscaler (HPA) |
| Thanos (Ultimate) | Multi-cluster metrics aggregation with long-term S3 storage |
| centralized-grafana (Ultimate) | Single Grafana instance with dashboards from all clusters |
| Karma (Ultimate) | AlertManager dashboard for multi-cluster alert visualization |
Access Grafana
Grafana is accessible through the Kommander dashboard or directly:
# Via Kommander dashboard → Monitoring → Grafana
# Or check the service URL:
kubectl -n kommander get svc centralized-grafana
Default dashboards included:
- Cluster overview (CPU, memory, disk, network)
- Node metrics (per-node resource utilization)
- Pod metrics (per-pod resource usage and limits)
- Kubernetes API server (request latency, error rates)
- etcd (leader elections, disk fsync latency, DB size)
- Kommander platform services (HelmRelease health)
Custom Dashboards and Alerts
Add Custom Scrape Targets
Monitor your own applications by creating a ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app-monitor
namespace: my-app
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 30s
path: /metrics
Custom Alerting Rules
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-app-alerts
namespace: my-app
spec:
groups:
- name: my-app
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate on {{ $labels.instance }}"
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total{namespace="my-app"}[15m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
AlertManager Routing
Route alerts to Slack, email, PagerDuty, or any webhook:
# Via kube-prometheus-stack values in kommander.yaml
alertmanager:
config:
route:
group_by: ['alertname', 'namespace']
group_wait: 2m
group_interval: 5m
repeat_interval: 1h
receiver: "null"
routes:
# Silence Watchdog (heartbeat alert)
- match:
alertname: Watchdog
receiver: "null"
# Critical alerts → Slack
- match:
severity: critical
receiver: slack-critical
# Warning alerts → email
- match:
severity: warning
receiver: email-team
receivers:
- name: "null"
- name: slack-critical
slack_configs:
- channel: '#nkp-alerts'
send_resolved: true
title: '{{ .GroupLabels.alertname }}'
text: >-
{{ range .Alerts }}*{{ .Labels.severity }}*:
{{ .Annotations.summary }}{{ end }}
- name: email-team
email_configs:
- to: 'platform-team@corp.local'
from: 'nkp-alerts@corp.local'
smarthost: 'smtp.corp.local:587'
Multi-Cluster Monitoring (Ultimate)
With an Ultimate license, Thanos aggregates metrics from all clusters into a single query layer:
- Thanos Sidecar runs alongside each cluster's Prometheus, uploading TSDB blocks to S3
- Thanos StoreGateway on the management cluster reads historical data from S3
- Thanos Compactor downsamples old data (30d raw → 90d at 5m → 365d at 1h resolution)
- Thanos Query federates queries across all Prometheus instances and the StoreGateway
- Centralized Grafana connects to Thanos Query for a single pane of glass
Verify Thanos is working:
# Check Thanos sidecar logs
kubectl logs prometheus-kube-prometheus-stack-prometheus-0 \
-n kommander -c thanos-sidecar --tail=20
# Check StoreGateway
kubectl get pods -n kommander | grep thanos-storegateway
# Check Compactor
kubectl get pods -n kommander | grep thanos-compactor
Metrics Retention and Storage
| Tier | Local Retention | Long-Term Storage |
|---|---|---|
| Prometheus (local PVC) | 15-30 days (configurable) | N/A |
| Thanos (S3) | Unlimited | Downsample: 30d raw, 90d@5m, 365d@1h |
Tune retention via ConfigMap override:
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-prometheus-stack-overrides
namespace: kommander
data:
values.yaml: |
prometheus:
prometheusSpec:
retention: 30d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nutanix-volume
resources:
requests:
storage: 100Gi
Logging
The NKP Logging Stack
| Component | Role |
|---|---|
| Fluent Bit | DaemonSet on every node. Collects container logs, systemd logs, kernel logs |
| Fluentd | Aggregator (managed by logging-operator). Buffers and forwards to Loki |
| Grafana Loki | Log storage and query engine. S3-backed for durability |
| Grafana | Log visualization via the Loki data source |
Pods → stdout/stderr → Fluent Bit (DaemonSet) → Fluentd (aggregator) → Loki → S3
Query Logs in Grafana
Access logs via Grafana → Explore → Loki data source. LogQL examples:
# All logs from a namespace
{namespace="my-app"}
# Dex authentication errors
{namespace="kommander", container="dex"} |= "error"
# Structured log filtering (JSON logs)
{namespace="my-app"} | json | level="error"
# Rate of error logs over time
rate({namespace="my-app"} |= "error" [5m])
# Top 10 pods by log volume
topk(10, sum by(pod) (rate({namespace="my-app"}[1h])))
Forward Logs to External SIEM
For compliance (NIS2, DORA), logs often need to go to a central SIEM alongside NKP's internal Loki.
Configure Fluent Bit outputs via kommander.yaml or ConfigMap override:
apps:
fluent-bit:
values: |
config:
outputs: |
# Forward to Splunk HEC
[OUTPUT]
Name splunk
Match *
Host splunk.corp.local
Port 8088
TLS On
Splunk_Token ${HEC_TOKEN}
# Forward to Elasticsearch
[OUTPUT]
Name es
Match *
Host es.corp.local
Port 9200
Index nkp-logs
TLS On
# Keep Loki as well (dual output)
[OUTPUT]
Name loki
Match *
Host grafana-loki-loki-distributed-gateway.kommander.svc
Port 80
Log Retention
Loki retention is configured in the grafana-loki values:
loki:
structuredConfig:
limits_config:
retention_period: 360h # 15 days
For compliance requiring longer retention, forward logs to your SIEM and retain them there. Loki is optimized for recent log queries, not long-term archival.
Backup and Disaster Recovery
Velero on NKP
Velero handles backup and restore for both Kubernetes resources and persistent volumes:
- Namespace-level backup: Kubernetes manifests + PV snapshots
- Scheduled backups: CronJob-style scheduling with configurable retention
- Cross-cluster restore: Restore backups to a different cluster for DR
Configure a Backup Target
Velero uses S3-compatible storage as its backup target. With Nutanix Objects:
# Already configured in kommander.yaml (see Part 4)
velero:
values: |
configuration:
features: EnableCSI
backupStorageLocation:
- name: nutanix-objects
bucket: nkp-velero
provider: "aws"
default: true
config:
region: us-east-1
s3ForcePathStyle: "false"
insecureSkipTLSVerify: "true"
s3Url: "https://objects.yourdomain.local"
profile: ntnx-object-nkp
credential:
key: aws
name: velero-nutanix-credentials
Verify the backup location is available:
kubectl get backupstoragelocation -n kommander
# Should show "Available" phase
Create and Schedule Backups
# One-time backup of the management cluster
velero backup create mgmt-backup-$(date +%Y%m%d) \
--include-namespaces kommander,kommander-flux,capx-system,caren-system,cert-manager
# Scheduled backup (daily at 2 AM, 30-day retention)
velero schedule create daily-mgmt \
--schedule="0 2 * * *" \
--include-namespaces kommander,kommander-flux,capx-system,caren-system \
--ttl 720h
What to back up:
| Cluster | Namespaces |
|---|---|
| Management | kommander, kommander-flux, capx-system, caren-system, cert-manager, ntnx-system |
| Workload | Application namespaces, PVCs with important data |
Restore
# List available backups
velero backup get
# Restore to same cluster
velero restore create --from-backup mgmt-backup-20260401
# Restore specific namespaces only
velero restore create --from-backup mgmt-backup-20260401 \
--include-namespaces kommander,capx-system
Note
PV restore requires CSI snapshot support (enabled by default with features: EnableCSI). CRDs must exist on the target cluster before restoring resources that depend on them.
etcd Backup (Management Cluster)
For full disaster recovery of the management cluster, take etcd snapshots directly:
# SSH to a control plane node
ssh ${SSH_USERNAME}@<cp-node-ip>
# Take snapshot
sudo etcdctl snapshot save /tmp/etcd-snapshot.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Verify snapshot
sudo etcdctl snapshot status /tmp/etcd-snapshot.db --write-table
This is your last resort if Velero backups are unavailable. Automate it with a CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: etcd-backup
namespace: kube-system
spec:
schedule: "0 */6 * * *" # Every 6 hours
jobTemplate:
spec:
template:
spec:
hostNetwork: true
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
containers:
- name: etcd-backup
# Match your cluster's etcd version (3.5.24-0 on NKP 2.17.1 / K8s 1.34.3):
# kubectl -n kube-system get pod -l component=etcd -o jsonpath='{.items[0].spec.containers[0].image}'
image: registry.k8s.io/etcd:3.5.24-0
command: ["/bin/sh", "-c"]
args:
- |
etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
volumeMounts:
- name: etcd-certs
mountPath: /etc/kubernetes/pki/etcd
readOnly: true
- name: backup
mountPath: /backup
volumes:
- name: etcd-certs
hostPath:
path: /etc/kubernetes/pki/etcd
- name: backup
persistentVolumeClaim:
claimName: etcd-backup-pvc
restartPolicy: OnFailure
Cluster Upgrades
Upgrade Strategy
NKP upgrades follow a strict order:
1. NKP CLI (on jump host)
2. Management cluster
3. Workload clusters (one at a time)
NKP enforces an N-1 version policy: the management cluster must be at least the same version as any workload cluster it manages. You cannot upgrade a workload cluster past the management cluster version.
| Upgrade Type | Example | What Changes |
|---|---|---|
| Patch | 2.17.0 → 2.17.1 | Bug fixes, security patches, OS image update |
| Minor | 2.16 → 2.17 | New features, Kubernetes version bump (1.33 → 1.34) |
Pre-Upgrade Checks
Before upgrading, validate the cluster is healthy:
# Check all nodes are Ready
kubectl get nodes
# Check all HelmReleases are healthy
kubectl -n kommander get helmreleases
# Check CAPI cluster status
kubectl get clusters.cluster.x-k8s.io -A
# Check for deprecated APIs (NKP Insights or Pluto)
kubectl get insights -n kommander # if Insights is enabled
Upgrade the Management Cluster
# 1. Install new CLI version
tar -xzvf nkp_v2.18.0_linux_amd64.tar.gz
sudo mv nkp /usr/local/bin/
nkp version
# 2. Upload new OS image to Prism Central
# Download from portal, upload via PC UI
# 3. Upgrade
nkp upgrade cluster nutanix \
--cluster-name ${CLUSTER_NAME} \
--vm-image nkp-rocky-9.7-release-cis-1.35.0 \
--kubeconfig ${CLUSTER_NAME}.conf
Use the full image name as it appears in Prism Central (e.g. nkp-rocky-9.7-release-cis-1.35.0-<build-timestamp>.qcow2). If the control plane and worker pools run different images, replace --vm-image with --control-plane-vm-image <image> and --worker-vm-images <pool>=<image>[,<pool>=<image>] (all worker pools must be listed).
What happens during upgrade:
- Control plane nodes are replaced one at a time (rolling). New node joins, old node is drained and deleted
- Workers are replaced in batches (configurable). Workloads get rescheduled
- Kommander components are updated via Flux CD
- Zero downtime if applications have multiple replicas and proper PodDisruptionBudgets
Upgrade Workload Clusters
Same command, different cluster name and kubeconfig:
nkp upgrade cluster nutanix \
--cluster-name team-platform \
--vm-image nkp-rocky-9.7-release-cis-1.35.0 \
--kubeconfig mgmt-cluster.conf
Air-Gapped Upgrades
Same seed-transfer-push cycle as the initial deployment:
- Download new NKP bundle and OS image on an internet-connected machine
- Transfer to the air-gapped bastion
- Push new images to the local registry:
nkp push bundle --bundle <new-bundle.tar> ... - Upload new OS image to Prism Central
- Load new bootstrap image:
docker load --input konvoy-bootstrap-image-v2.18.0.tar - Run the upgrade command with the new image names
Support Bundles
When you need Nutanix support, collect a diagnostic bundle:
nkp diagnose --kubeconfig ${CLUSTER_NAME}.conf --bundle-name-prefix "${CLUSTER_NAME}-"
What it collects:
- Node logs (kubelet, containerd, kernel, systemd)
- Pod logs from all namespaces
- Kubernetes events
- CAPI resource state
- Helm release status
- Node resource usage and disk space
- etcd health
The output is a tarball you send to Nutanix support.
NKP Insights (AI-Powered Troubleshooting)
NKP Insights (Ultimate license) provides automated security scanning and troubleshooting:
| Scanner | What It Does | Default Schedule |
|---|---|---|
| Polaris | Configuration best practices (resource limits, security context, probes) | Every 37 minutes |
| Pluto | Deprecated Kubernetes API detection | Every 41 minutes |
| Kube-bench | CIS Kubernetes benchmark compliance | Every 35 minutes |
| Nova | Helm chart version checking | Every 34 minutes (disabled by default) |
| Trivy | CVE vulnerability scanning | Every 2 hours (disabled by default) |
Enable Trivy scanning via ConfigMap override:
apiVersion: v1
kind: ConfigMap
metadata:
name: nkp-insights-overrides
namespace: kommander
data:
values.yaml: |
trivy:
enabled: true
schedule: "@every 2h"
Access Insights through the Kommander dashboard → Insights tab.
Troubleshooting Patterns
Node Issues
| Symptom | Debug | Fix |
|---|---|---|
| Node NotReady | kubectl describe node <name>, check kubelet logs via SSH |
Restart kubelet, check disk/memory pressure conditions |
| Disk pressure | df -h on node, check /var/lib/containerd |
Clean unused images: crictl rmi --prune |
| OOM kills | dmesg | grep -i oom, check pod resource limits |
Increase worker RAM or set proper resource limits |
| Node unreachable | Check VM status in Prism Central, ping node IP | Verify VM is running, check subnet/VLAN connectivity |
Platform Service Issues
| Symptom | Debug | Fix |
|---|---|---|
| Dashboard unreachable | Check Traefik pod + MetalLB + DNS | kubectl -n kommander get svc traefik, verify EXTERNAL-IP |
| Auth failures | Check Dex logs | kubectl -n kommander logs -l app.kubernetes.io/name=dex |
| Metrics missing | Check Prometheus targets in Grafana | Grafana → Status → Targets |
| HelmRelease degraded | Check Flux status | kubectl -n kommander describe helmrelease <name> |
| Logs not appearing | Check Fluent Bit + Loki | kubectl -n kommander logs -l app.kubernetes.io/name=fluent-bit |
CAPI / Cluster Lifecycle Issues
| Symptom | Debug | Fix |
|---|---|---|
| Machine stuck Provisioning | CAPX controller logs | kubectl -n capx-system logs -l control-plane=controller-manager |
| Cluster stuck Deleting | Finalizers blocking deletion | Check for stuck finalizers on Cluster/Machine objects |
| Pivot failure | Bootstrap cluster logs | docker logs $(docker ps -q --filter name=konvoy) |
| VM not created in PC | PC credentials or permissions | Verify global-nutanix-credentials secret, check PC user role |
Recovering a Stuck HelmRelease
If a HelmRelease enters exhausted or rollback in progress state:
# Suspend the HelmRelease
kubectl -n kommander patch helmrelease <NAME> \
--type='json' \
-p='[{"op": "replace", "path": "/spec/suspend", "value": true}]'
# Unsuspend to trigger fresh reconciliation
kubectl -n kommander patch helmrelease <NAME> \
--type='json' \
-p='[{"op": "replace", "path": "/spec/suspend", "value": false}]'
If the entire Kommander installation is broken, reinstall is safe to retry:
nkp install kommander \
--installer-config kommander.yaml \
--kubeconfig ${CLUSTER_NAME}.conf \
--wait-timeout 1h
Emergency: Management Cluster Recovery (2/3 CP Lost)
If two out of three control plane nodes are permanently lost:
# SSH to the surviving CP node
ssh ${SSH_USERNAME}@<surviving-cp-ip>
# Stop etcd
sudo systemctl stop etcd
# Force new single-node cluster
sudo etcdctl snapshot restore /var/lib/etcd/member/snap/db \
--data-dir=/var/lib/etcd-restored \
--name=$(hostname) \
--initial-cluster="$(hostname)=https://$(hostname):2380" \
--initial-advertise-peer-urls="https://$(hostname):2380"
# Replace data directory
sudo mv /var/lib/etcd /var/lib/etcd-broken
sudo mv /var/lib/etcd-restored /var/lib/etcd
# Restart etcd and kubelet
sudo systemctl start etcd
sudo systemctl restart kubelet
Warning
This is a last-resort procedure. It results in a single-node etcd cluster. You must immediately scale up new control plane nodes and restore HA. Always maintain regular etcd snapshots to avoid this scenario.
Operational Checklist
Daily
- Check Grafana dashboards for anomalies (CPU, memory, disk)
- Review AlertManager / Karma for active alerts
- Verify HelmRelease health:
kubectl -n kommander get hr
Weekly
- Check node resource utilization trends
- Review Velero backup status:
velero backup get - Check for pending NKP or Kubernetes updates
- Review NKP Insights findings (if enabled)
Monthly
- Test backup restore procedure (restore to a test cluster)
- Review and rotate credentials (PC user, registry, S3 keys)
- Review Gatekeeper audit logs for policy violations
- Check certificate expiration dates:
kubectl get certificates -A
Quarterly
- Plan NKP version upgrades
- Review capacity planning (node count, storage utilization)
- Security audit: RBAC bindings, network policies, CIS compliance
- Test disaster recovery procedure (etcd restore)
What's Next
This wraps up the NKP deployment and operations series. Here's the full reading order:
- Part 1: Architecture & Key Concepts: what NKP is and how it works
- Part 2: Prerequisites & Deployment: internet-connected deployment
- Part 3: Air-Gapped Deployment: registry mirror and bundle methods
- Part 4: Day 2 Configuration: auth, RBAC, storage, certificates
- Part 5: Workload Clusters & Applications: fleet management, GitOps
- Part 6: Operations: monitoring, backup, upgrades (you are here)
Every part includes real commands and configurations from our ITCS Lab running NKP 2.17.1 on Nutanix AHV.
Summary
Operations is where NKP earns its keep. The monitoring stack (Prometheus, Grafana, Thanos) gives you visibility from pod-level metrics to multi-cluster aggregation. The logging pipeline (Fluent Bit, Loki) captures everything and can forward to external SIEMs for compliance. Velero and etcd snapshots protect against data loss. Rolling upgrades keep you current without downtime. And NKP Insights automates the security scanning that would otherwise require manual CIS audits. Pair these built-in tools with the operational checklist above and you have a production-grade Kubernetes platform that satisfies both technical and regulatory requirements.


