| name | kubernetes-operations |
| description | Kubernetes and OpenShift cluster operations, maintenance, and lifecycle management. Use this skill when:
(1) Performing cluster upgrades (K8s, OCP, EKS, GKE, AKS, ARO, ROSA)
(2) Backup and disaster recovery (etcd, Velero, cluster state)
(3) Node management: drain, cordon, scaling, replacement
(4) Capacity planning and cluster scaling
(5) Certificate rotation and management
(6) etcd maintenance and health checks
(7) Resource quota and limit range management
(8) Namespace lifecycle management
(9) Cluster migration and workload portability
(10) Monitoring and alerting configuration
(11) Log aggregation setup
(12) Cost optimization and resource rightsizing
(13) ARO (Azure Red Hat OpenShift) lifecycle management
(14) ROSA (Red Hat OpenShift on AWS) lifecycle management
|
| metadata | {"author":"cluster-skills","version":"1.1.0"} |
Kubernetes / OpenShift Cluster Operations
Day-2 operations, maintenance, and lifecycle management for production clusters.
Current Versions & Documentation (January 2026)
Key Tools & Versions
| Tool | Version | Install | Purpose |
|---|
| kubeadm | 1.31.x | Package manager | Cluster bootstrap |
| Velero | 1.15.x | Helm/CLI | Backup & restore |
| kube-prometheus-stack | v67.x | Helm | Monitoring |
| VPA | 1.3.x | kubectl apply | Vertical scaling |
| Cluster Autoscaler | 1.31.x | Helm | Node autoscaling |
| Karpenter | 1.1.x | Helm | AWS node provisioning |
| rosa | latest | brew install rosa | ROSA cluster management |
| aro | latest | brew install azure-cli | ARO cluster management |
Command Usage Convention
IMPORTANT: This skill uses kubectl as the primary command. When working with:
- OpenShift/ARO clusters: Replace
kubectl with oc
- Standard Kubernetes (AKS, EKS, GKE): Use
kubectl as shown
- ROSA clusters: Use
rosa CLI for cluster operations, oc for workload management
Node Operations
Node Lifecycle
kubectl get nodes -o wide
kubectl describe node ${NODE_NAME}
kubectl top nodes
kubectl get nodes --show-labels
kubectl describe node ${NODE} | grep -A 5 Taints
Drain and Cordon
kubectl cordon ${NODE_NAME}
kubectl drain ${NODE_NAME} \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=300s
kubectl drain ${NODE_NAME} \
--ignore-daemonsets \
--delete-emptydir-data \
--force \
--grace-period=30
kubectl uncordon ${NODE_NAME}
Cluster Autoscaler Configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.31.0
command:
- ./cluster-autoscaler
- --v=4
- --cloud-provider=${CLOUD_PROVIDER}
- --nodes=${MIN}:${MAX}:${NODE_GROUP}
- --scale-down-delay-after-add=10m
- --scale-down-unneeded-time=10m
- --scale-down-utilization-threshold=0.5
- --skip-nodes-with-local-storage=false
- --skip-nodes-with-system-pods=true
- --balance-similar-node-groups=true
Backup and Recovery
etcd Backup
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key
ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-snapshot.db --write-out=table
Velero Backup (v1.15.x)
brew install velero
velero install \
--provider aws \
--bucket ${BUCKET_NAME} \
--secret-file ./credentials-velero \
--backup-location-config region=${REGION} \
--snapshot-location-config region=${REGION} \
--plugins velero/velero-plugin-for-aws:v1.10.0 \
--use-node-agent
velero backup create ${BACKUP_NAME} \
--include-namespaces ${NAMESPACES} \
--ttl 720h \
--default-volumes-to-fs-backup
velero schedule create daily-backup \
--schedule="0 2 * * *" \
--include-namespaces ${NAMESPACES} \
--ttl 168h
velero restore create --from-backup ${BACKUP_NAME}
Velero Backup Manifest
apiVersion: velero.io/v1
kind: Backup
metadata:
name: ${BACKUP_NAME}
namespace: velero
spec:
includedNamespaces:
- ${NAMESPACE_1}
- ${NAMESPACE_2}
excludedResources:
- events
- events.events.k8s.io
storageLocation: default
volumeSnapshotLocations:
- default
ttl: 720h0m0s
snapshotVolumes: true
hooks:
resources:
- name: backup-hook
includedNamespaces:
- ${NAMESPACE}
labelSelector:
matchLabels:
app: database
pre:
- exec:
container: database
command:
- /bin/sh
- -c
- "pg_dump -U postgres > /backup/pre-backup.sql"
onError: Fail
timeout: 120s
Cluster Upgrades
Pre-Upgrade Checklist
#!/bin/bash
echo "=== Cluster Version ==="
kubectl version --short
echo -e "\n=== Node Status ==="
kubectl get nodes
echo -e "\n=== Pods Not Running ==="
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
echo -e "\n=== PDBs That May Block Drain ==="
kubectl get pdb -A
echo -e "\n=== Pending PVCs ==="
kubectl get pvc -A --field-selector=status.phase=Pending
echo -e "\n=== Deprecated APIs in Use ==="
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
AKS Upgrade (Azure)
az aks get-versions --location ${LOCATION} -o table
az aks get-upgrades --resource-group ${RG} --name ${CLUSTER} -o table
az aks upgrade --resource-group ${RG} --name ${CLUSTER} \
--kubernetes-version 1.31.0
az aks nodepool upgrade --resource-group ${RG} --cluster-name ${CLUSTER} \
--name ${NODEPOOL} --kubernetes-version 1.31.0 \
--max-surge 33%
az aks update --resource-group ${RG} --name ${CLUSTER} \
--auto-upgrade-channel stable
EKS Upgrade
aws eks update-cluster-version \
--name ${CLUSTER_NAME} \
--kubernetes-version 1.31
aws eks wait cluster-active --name ${CLUSTER_NAME}
for addon in vpc-cni coredns kube-proxy eks-pod-identity-agent; do
aws eks update-addon --cluster-name ${CLUSTER_NAME} \
--addon-name $addon \
--resolve-conflicts PRESERVE
done
aws eks update-nodegroup-version \
--cluster-name ${CLUSTER_NAME} \
--nodegroup-name ${NODEGROUP_NAME}
GKE Upgrade
gcloud container get-server-config --region ${REGION}
gcloud container clusters upgrade ${CLUSTER} --region ${REGION} \
--master --cluster-version 1.31
gcloud container clusters upgrade ${CLUSTER} --region ${REGION} \
--node-pool ${POOL} \
--cluster-version 1.31
gcloud container clusters update ${CLUSTER} --region ${REGION} \
--release-channel regular
ARO Upgrade (Azure Red Hat OpenShift)
az aro show --resource-group ${RG} --name ${CLUSTER} --query 'clusterProfile.version'
az aro list-upgrades --resource-group ${RG} --name ${CLUSTER} -o table
az aro update --resource-group ${RG} --name ${CLUSTER} \
--refresh-cluster
az aro nodepool update --resource-group ${RG} --cluster-name ${CLUSTER} \
--name ${NODEPOOL} \
--node-count ${COUNT}
az aro show --resource-group ${RG} --name ${CLUSTER} --query 'provisioningState'
ROSA Upgrade (Red Hat OpenShift on AWS)
rosa describe cluster --cluster=${CLUSTER_NAME} --output json | jq -r '.version.raw_id'
rosa list upgrade --cluster=${CLUSTER_NAME}
rosa upgrade cluster --cluster=${CLUSTER_NAME} --mode=auto
rosa upgrade nodepool ${NODEPOOL} --cluster=${CLUSTER_NAME} --mode=auto
rosa describe cluster --cluster=${CLUSTER_NAME} --output json | jq -r '.status'
OpenShift Upgrade
oc adm upgrade
oc get clusterversion
oc get clusterversion version -o jsonpath='{.spec.channel}'
oc adm upgrade channel stable-4.17
oc adm upgrade --to-latest
oc adm upgrade --to=4.17.5
watch -n 10 'oc get clusterversion && oc get clusteroperators'
Resource Management
Resource Quotas
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: ${NAMESPACE}
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
persistentvolumeclaims: "10"
requests.storage: 100Gi
Limit Ranges
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: ${NAMESPACE}
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: 50m
memory: 64Mi
Check Resource Usage
kubectl describe quota -n ${NAMESPACE}
kubectl top pods -n ${NAMESPACE} --sort-by=memory
kubectl top pods -n ${NAMESPACE} --sort-by=cpu
kubectl describe nodes | grep -A 5 "Allocated resources"
Certificate Management
Check Certificate Expiry
kubeadm certs check-expiration
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
for cert in /etc/kubernetes/pki/*.crt; do
echo "=== $cert ==="
openssl x509 -in $cert -noout -dates
done
Rotate Certificates
kubeadm certs renew all
crictl pods --name kube-apiserver -q | xargs crictl stopp
crictl pods --name kube-controller-manager -q | xargs crictl stopp
crictl pods --name kube-scheduler -q | xargs crictl stopp
Monitoring Setup
Prometheus Stack (kube-prometheus-stack v67.x)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set prometheus.prometheusSpec.retention=30d \
--set prometheus.prometheusSpec.replicas=2 \
--set prometheus.prometheusSpec.resources.requests.memory=2Gi \
--set alertmanager.alertmanagerSpec.replicas=3 \
--set grafana.persistence.enabled=true
kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80
Custom ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ${APP_NAME}
namespace: monitoring
labels:
release: prometheus
spec:
namespaceSelector:
matchNames:
- ${NAMESPACE}
selector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
endpoints:
- port: metrics
interval: 30s
path: /metrics
Cost Optimization
VerticalPodAutoscaler
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: ${APP_NAME}-vpa
namespace: ${NAMESPACE}
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: ${APP_NAME}
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 50m
memory: 64Mi
maxAllowed:
cpu: 4
memory: 8Gi
Namespace Lifecycle
Namespace Template
apiVersion: v1
kind: Namespace
metadata:
name: ${NAMESPACE}
labels:
app.kubernetes.io/managed-by: cluster-skills
environment: ${ENVIRONMENT}
team: ${TEAM}
annotations:
owner: ${OWNER_EMAIL}
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: default-quota
namespace: ${NAMESPACE}
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: ${NAMESPACE}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Disaster Recovery
Full Cluster Recovery Checklist
- Restore etcd - See etcd restore section
- Verify Control Plane
kubectl get nodes
kubectl get pods -n kube-system
kubectl cluster-info
- Restore Workloads (Velero)
velero restore create --from-backup ${BACKUP_NAME}
- Verify Application Health
kubectl get pods -A
kubectl get svc -A
- Verify DNS and Networking
kubectl run dns-test --image=busybox --rm -it --restart=Never -- nslookup kubernetes