Skip to content

Workload

Il livello tra il pod e il namespace. È dove si risponde alla domanda che conta davvero in un incident: il servizio è giù o solo degradato?

Un pod che restarta mentre le altre repliche reggono è un ticket. Lo stesso pod quando le repliche disponibili sono zero è un incident.

Triagedisponibilità e rollout
Deployment · StatefulSet · DaemonSet
Job e CronJob
Consumo aggregatoCPU, memoria, restart
Sbilanciamento tra repliche
HPA · PDB
Comandi oc · Alert

Il join fondamentale

Le metriche di cAdvisor e kube-state-metrics sono per pod: non portano il nome del Deployment. La recording rule preinstallata risolve la catena Pod → ReplicaSet → owner:

* on(namespace, pod) group_left(workload, workload_type)
max by (namespace, pod, workload, workload_type) (namespace_workload_pod:kube_pod_owner:relabel)

Gestisce insieme Deployment, StatefulSet, DaemonSet e Job, esponendo workload e workload_type.

Verifica che esista sul tuo cluster:

# numero di serie
count by (workload_type) (namespace_workload_pod:kube_pod_owner:relabel)

Se manca, il ripiego è label_replace sull’hash del ReplicaSet — funziona ma si rompe sui nomi contenenti trattini seguiti da stringhe alfanumeriche:

* on(namespace, pod) group_left(deployment)
label_replace(
kube_pod_owner{owner_kind="ReplicaSet"},
"deployment", "$1", "owner_name", "^(.+)-[a-z0-9]+$"
)

Triage

T1. Workload con repliche mancanti

# conteggio
(
kube_deployment_status_replicas_available{namespace!~"kube-.*|openshift-.*|default"}
<
kube_deployment_spec_replicas{namespace!~"kube-.*|openshift-.*|default"}
)

T2. Workload completamente giù

# elenco: ogni serie restituita soddisfa la condizione
kube_deployment_status_replicas_available{namespace!~"kube-.*|openshift-.*|default"} == 0
and
kube_deployment_spec_replicas{namespace!~"kube-.*|openshift-.*|default"} > 0

I casi da guardare per primi: il servizio non risponde.

T3. Frazione di repliche disponibili

# percentuale 0-100
100 * (
topk(20,
kube_deployment_status_replicas_available{namespace!~"kube-.*|openshift-.*|default"}
/
kube_deployment_spec_replicas{namespace!~"kube-.*|openshift-.*|default"}
) < 1
)

Ordina per gravità reale: 0.5 su un servizio a 2 repliche è più serio di 0.9 su uno a 10.

T4. Rollout bloccati

# elenco: ogni serie restituita soddisfa la condizione
kube_deployment_status_condition{condition="Progressing", status="false"} == 1

T5. Restart per workload

# conteggio nelle ultime 6 ore
topk(20,
sum by (namespace, workload, workload_type) (
increase(kube_pod_container_status_restarts_total{
namespace!~"kube-.*|openshift-.*|default"
}[6h])
* on(namespace, pod) group_left(workload, workload_type)
max by (namespace, pod, workload, workload_type) (namespace_workload_pod:kube_pod_owner:relabel)
)
) > 0

Deployment

Repliche: desiderate, disponibili, pronte, aggiornate

# conteggio · sostituisci $ns
kube_deployment_spec_replicas{namespace="$ns"}
# conteggio · sostituisci $ns
kube_deployment_status_replicas_available{namespace="$ns"}
# conteggio · sostituisci $ns
kube_deployment_status_replicas_ready{namespace="$ns"}
# conteggio · sostituisci $ns
kube_deployment_status_replicas_updated{namespace="$ns"}

I quattro numeri raccontano insieme lo stato del rollout:

SituazioneLettura
updated < specrollout in corso o bloccato
available < readyi pod sono pronti ma il minReadySeconds non è ancora trascorso
ready < availableanomalia: pod che hanno perso la readiness dopo essere stati disponibili
available == 0, spec > 0servizio giù

Deployment in rollout

# conteggio
kube_deployment_status_replicas_updated{namespace!~"kube-.*|openshift-.*|default"}
!=
kube_deployment_spec_replicas{namespace!~"kube-.*|openshift-.*|default"}

Rollout fermo da troppo tempo

# secondi
(
kube_deployment_status_replicas_updated != kube_deployment_spec_replicas
)
and
(
(time() - kube_deployment_status_observed_generation) > 600
)

Il progressDeadlineSeconds di default è 600. Superato, il Deployment va in Progressing=False con ProgressDeadlineExceeded e smette di provarci.

Generazione osservata vs desiderata

# differenza fra generazione desiderata e osservata: 0 = allineato · sostituisci $ns
kube_deployment_metadata_generation{namespace="$ns"}
-
kube_deployment_status_observed_generation{namespace="$ns"}

Diverso da zero = il controller non ha ancora preso in carico l’ultima modifica. Se resta diverso da zero, il deployment controller è in affanno o bloccato.

Strategia di rollout

# numero di pod, o percentuale se il manifest la esprime così · sostituisci $ns
kube_deployment_spec_strategy_rollingupdate_max_unavailable{namespace="$ns"}
# numero di pod, o percentuale se il manifest la esprime così · sostituisci $ns
kube_deployment_spec_strategy_rollingupdate_max_surge{namespace="$ns"}

maxUnavailable alto su un servizio a poche repliche significa che durante ogni deploy il servizio è degradato. Su un frontend con SLA, maxUnavailable: 0 e maxSurge: 1 è la configurazione corretta — costa una replica in più durante il rollout.

Condizioni

# elenco: ogni serie restituita soddisfa la condizione · sostituisci $ns
kube_deployment_status_condition{namespace="$ns"} == 1
conditionstatusSignificato
Availablefalsemeno repliche disponibili del minAvailable implicito
Progressingfalserollout fallito o in stallo
ReplicaFailuretrueil ReplicaSet non riesce a creare pod: quota, SCC, node selector

ReplicaFailure=True è quello che si dimentica: il rollout sembra fermo senza motivo, e la causa è che il ReplicaSet non può nemmeno creare i pod.

Terminal window
oc describe rs -n $ns -l app=$workload | sed -n '/Events:/,$p'

StatefulSet

# conteggio · sostituisci $ns
kube_statefulset_replicas{namespace="$ns"}
# conteggio · sostituisci $ns
kube_statefulset_status_replicas_ready{namespace="$ns"}
# conteggio · sostituisci $ns
kube_statefulset_status_replicas_current{namespace="$ns"}
# conteggio · sostituisci $ns
kube_statefulset_status_replicas_updated{namespace="$ns"}

StatefulSet degradati

# conteggio
kube_statefulset_status_replicas_ready{namespace!~"kube-.*|openshift-.*|default"}
<
kube_statefulset_replicas{namespace!~"kube-.*|openshift-.*|default"}

Revisione corrente vs aggiornata

# sempre 1: il label revision porta l'hash della ControllerRevision · sostituisci $ns
kube_statefulset_status_current_revision{namespace="$ns"}
# sempre 1: il label revision porta l'hash della ControllerRevision · sostituisci $ns
kube_statefulset_status_update_revision{namespace="$ns"}

Gli StatefulSet aggiornano un pod alla volta, in ordine inverso di ordinale, e si fermano al primo che non diventa Ready. Un rollout bloccato su un pod significa che tutti quelli con ordinale inferiore restano alla versione vecchia, indefinitamente.

Terminal window
oc get sts -n $ns $workload -o jsonpath='{.status}' | jq
oc rollout status sts/$workload -n $ns --timeout=30s

Ordinale del pod bloccato

# elenco: ogni serie restituita soddisfa la condizione · sostituisci $ns, $workload
kube_pod_status_ready{namespace="$ns", pod=~"$workload-[0-9]+", condition="false"} == 1

Il pod con l’ordinale più alto non pronto è quello che blocca la catena.


DaemonSet

# numero di pod del DaemonSet · sostituisci $ns
kube_daemonset_status_desired_number_scheduled{namespace="$ns"}
# numero di pod del DaemonSet · sostituisci $ns
kube_daemonset_status_number_ready{namespace="$ns"}
# numero di pod del DaemonSet · sostituisci $ns
kube_daemonset_status_number_unavailable{namespace="$ns"}

DaemonSet incompleti

# numero di pod del DaemonSet
kube_daemonset_status_number_ready
!=
kube_daemonset_status_desired_number_scheduled

Query da tenere d’occhio anche sui namespace di piattaforma, senza il filtro applicativo: ovnkube-node, node-exporter, machine-config-daemon sono DaemonSet, e se non sono completi hai nodi con funzionalità mancanti che si manifestano come problemi applicativi apparentemente casuali.

# numero di pod del DaemonSet
kube_daemonset_status_number_ready{namespace=~"openshift-.*"}
!=
kube_daemonset_status_desired_number_scheduled{namespace=~"openshift-.*"}

Su quali nodi manca

Terminal window
oc get pods -n $ns -l app=$ds -o wide --sort-by=.spec.nodeName
oc get nodes -o name | sed 's|node/||' | sort > /tmp/all-nodes
oc get pods -n $ns -l app=$ds -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort > /tmp/ds-nodes
comm -23 /tmp/all-nodes /tmp/ds-nodes

I nodi elencati non hanno il pod del DaemonSet: taint non tollerato, risorse insufficienti, o il pod è in Pending.

Misscheduled

# numero di pod del DaemonSet
kube_daemonset_status_number_misscheduled > 0

Pod del DaemonSet su nodi dove non dovrebbero stare: succede quando si cambia il nodeSelector e i vecchi pod non vengono rimossi.


Job e CronJob

Job falliti

# numero di pod del Job
kube_job_status_failed{namespace!~"kube-.*|openshift-.*|default"} > 0

Job attivi

# numero di pod del Job
kube_job_status_active{namespace!~"kube-.*|openshift-.*|default"} > 0

Job che girano da troppo

# secondi
(time() - kube_job_status_start_time{namespace!~"kube-.*|openshift-.*|default"}) / 60
> 60
and
kube_job_status_active > 0

Job attivi da più di un’ora. Senza activeDeadlineSeconds, un Job appeso resta appeso per sempre e occupa risorse.

Durata dei Job completati

# minuti · sostituisci $ns
(
kube_job_status_completion_time{namespace="$ns"}
-
kube_job_status_start_time{namespace="$ns"}
) / 60

Minuti di esecuzione. Un aumento della durata nel tempo, a parità di dati, è una regressione da segnalare.

CronJob: ultima schedulazione

# ore
(time() - kube_cronjob_status_last_schedule_time{namespace!~"kube-.*|openshift-.*|default"}) / 3600

Ore dall’ultima esecuzione. Confronta con la frequenza dichiarata:

# sempre 1: il label schedule porta l'espressione cron · sostituisci $ns
kube_cronjob_info{namespace="$ns"}

Il label schedule porta l’espressione cron.

CronJob sospesi

# elenco: ogni serie restituita soddisfa la condizione
kube_cronjob_spec_suspend{namespace!~"kube-.*|openshift-.*|default"} == 1

Un CronJob sospeso e dimenticato è la causa classica di elaborazioni ferme senza nessun errore in evidenza.

CronJob che non parte da troppo

# secondi
(time() - kube_cronjob_status_last_schedule_time) > 86400
and
kube_cronjob_spec_suspend == 0

Non sospeso ma fermo da più di un giorno. Cause: concurrencyPolicy: Forbid con un job precedente ancora appeso, startingDeadlineSeconds superato, controller in errore.

Job non ripuliti

# numero di serie
count by (namespace) (kube_job_complete{namespace!~"kube-.*|openshift-.*|default"} == 1)

Senza ttlSecondsAfterFinished, i Job completati restano nell’API e gonfiano etcd. Su cluster con CronJob frequenti se ne accumulano migliaia.

Terminal window
oc get jobs -A --no-headers | wc -l
oc get jobs -n $ns --sort-by=.status.completionTime | head -20

Consumo aggregato per workload

CPU

# core CPU
topk(20,
sum by (namespace, workload, workload_type) (
rate(container_cpu_usage_seconds_total{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}[5m])
* on(namespace, pod) group_left(workload, workload_type)
max by (namespace, pod, workload, workload_type) (namespace_workload_pod:kube_pod_owner:relabel)
)
)

Memoria

# GiB
topk(20,
sum by (namespace, workload, workload_type) (
container_memory_working_set_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
* on(namespace, pod) group_left(workload, workload_type)
max by (namespace, pod, workload, workload_type) (namespace_workload_pod:kube_pod_owner:relabel)
)
) / 1024^3

CPU media per replica

# core CPU · sostituisci $ns
sum by (namespace, workload) (
rate(container_cpu_usage_seconds_total{namespace="$ns", container!="", container!="POD"}[5m])
* on(namespace, pod) group_left(workload)
max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel{workload_type="deployment"})
)
/
sum by (namespace, workload) (
count by (namespace, pod) (kube_pod_info{namespace="$ns"})
* on(namespace, pod) group_left(workload)
max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel{workload_type="deployment"})
)

Serve per decidere se scalare orizzontalmente ha senso: se la media per replica non scende aggiungendo repliche, il carico non è distribuito — sessioni sticky, lock, singolo consumer di coda.

Crescita nelle ultime 24 ore

# byte · sostituisci $ns
sum by (namespace, workload) (
container_memory_working_set_bytes{namespace="$ns", container!="", container!="POD"}
* on(namespace, pod) group_left(workload) max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel)
)
-
sum by (namespace, workload) (
container_memory_working_set_bytes{namespace="$ns", container!="", container!="POD"} offset 24h
* on(namespace, pod) group_left(workload) max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel)
)

Se il workload è stato riavviato nel frattempo il confronto non ha senso: incrocia sempre con i restart.

Frazione di repliche che ha restartato

# numero di serie · sostituisci $ns
count by (namespace, workload) (
(increase(kube_pod_container_status_restarts_total{namespace="$ns"}[6h]) > 0)
* on(namespace, pod) group_left(workload) max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel)
)
/
count by (namespace, workload) (
kube_pod_info{namespace="$ns"}
* on(namespace, pod) group_left(workload) max by (namespace, pod, workload) (namespace_workload_pod:kube_pod_owner:relabel)
)

1 = tutte, quindi il problema è del workload. < 0.3 = solo alcune, guarda i nodi su cui girano.


Sbilanciamento tra repliche

Repliche stateless dietro lo stesso Service devono comportarsi in modo simile. Le deviazioni sono diagnostiche.

CPU

# core CPU · sostituisci $ns, $workload
sum by (pod) (
rate(container_cpu_usage_seconds_total{
namespace="$ns", pod=~"$workload-.*", container!="", container!="POD"
}[5m])
)

Memoria

# MiB · sostituisci $ns, $workload
sum by (pod) (
container_memory_working_set_bytes{
namespace="$ns", pod=~"$workload-.*", container!="", container!="POD"
}
) / 1024^2

Traffico ricevuto

# MiB · sostituisci $ns, $workload
sum by (pod) (
rate(container_network_receive_bytes_total{namespace="$ns", pod=~"$workload-.*"}[5m])
) / 1024^2

Una replica a zero traffico è fuori dagli endpoint (readiness fallita). Una a 5× le altre indica sessioni sticky o un client che non rifà la risoluzione DNS.

Coefficiente di variazione

# byte · sostituisci $ns, $workload
stddev by (namespace) (
sum by (namespace, pod) (
container_memory_working_set_bytes{namespace="$ns", pod=~"$workload-.*", container!="", container!="POD"}
)
)
/
avg by (namespace) (
sum by (namespace, pod) (
container_memory_working_set_bytes{namespace="$ns", pod=~"$workload-.*", container!="", container!="POD"}
)
)

Deviazione standard normalizzata sulla media. Sotto 0.2 le repliche sono omogenee. Sopra 0.5 c’è qualcosa che le rende diverse tra loro, e vale la pena capire cosa prima di dimensionare.

Distribuzione sui nodi

# numero di serie · sostituisci $ns, $workload
count by (node) (
kube_pod_info{namespace="$ns", pod=~"$workload-.*"}
)

Tutte le repliche sullo stesso nodo = manca l’anti-affinity, e un reboot di quel nodo porta giù il servizio nonostante le repliche multiple.

Terminal window
oc get pod -n $ns -l app=$workload -o json | jq '.items[0].spec.affinity'

Versioni diverse in esecuzione

# numero di serie · sostituisci $ns, $workload
count by (namespace, image) (
kube_pod_container_info{namespace="$ns", pod=~"$workload-.*"}
)

Più di una riga fuori da un rollout attivo = rollout parziale rimasto a metà.


HPA

# conteggio · sostituisci $ns
kube_horizontalpodautoscaler_status_current_replicas{namespace="$ns"}
# conteggio · sostituisci $ns
kube_horizontalpodautoscaler_status_desired_replicas{namespace="$ns"}
# conteggio · sostituisci $ns
kube_horizontalpodautoscaler_spec_min_replicas{namespace="$ns"}
# conteggio · sostituisci $ns
kube_horizontalpodautoscaler_spec_max_replicas{namespace="$ns"}

HPA al massimo

# conteggio
kube_horizontalpodautoscaler_status_current_replicas
==
kube_horizontalpodautoscaler_spec_max_replicas

L’HPA ha esaurito il margine: da qui in poi il carico non viene più assorbito. È il momento in cui bisogna decidere se alzare maxReplicas o se il problema è a valle (database, dipendenza).

HPA che non riesce a scalare

# elenco: ogni serie restituita soddisfa la condizione
kube_horizontalpodautoscaler_status_condition{condition="ScalingLimited", status="true"} == 1
# elenco: ogni serie restituita soddisfa la condizione
kube_horizontalpodautoscaler_status_condition{condition="AbleToScale", status="false"} == 1

AbleToScale=False significa che l’HPA non riesce nemmeno a leggere le metriche: metrics-server assente, o le request non sono impostate (l’HPA su CPU richiede requests.cpu, altrimenti non ha un denominatore).

Terminal window
oc describe hpa -n $ns $hpa | sed -n '/Conditions:/,$p'
oc adm top pod -n $ns

Flapping

# numero di variazioni nella finestra · sostituisci $ns
changes(kube_horizontalpodautoscaler_status_desired_replicas{namespace="$ns"}[1h])

Molti cambi in un’ora = l’HPA sta oscillando. Si corregge con behavior.scaleDown.stabilizationWindowSeconds.


PodDisruptionBudget

# numero di pod · sostituisci $ns
kube_poddisruptionbudget_status_desired_healthy{namespace="$ns"}
# numero di pod · sostituisci $ns
kube_poddisruptionbudget_status_current_healthy{namespace="$ns"}
# numero di pod · sostituisci $ns
kube_poddisruptionbudget_status_pod_disruptions_allowed{namespace="$ns"}

PDB che bloccano il drain

# elenco: ogni serie restituita soddisfa la condizione
kube_poddisruptionbudget_status_pod_disruptions_allowed == 0

Query da lanciare prima di ogni manutenzione dei nodi. Un PDB con zero disruption consentite blocca oc adm drain indefinitamente, e te ne accorgi a finestra di manutenzione già aperta.

Terminal window
oc get pdb -A
oc get events -A --field-selector reason=EvictionBlocked --sort-by=.lastTimestamp | tail

PDB mal configurati

# conteggio
kube_poddisruptionbudget_status_desired_healthy
>=
kube_poddisruptionbudget_status_expected_pods

minAvailable uguale o maggiore del numero di repliche: il PDB non permetterà mai nessuna disruption. È un errore di configurazione, non una scelta.

Workload senza PDB

Non c’è una metrica diretta. Confronto lato oc:

Terminal window
oc get deploy -n $ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | sort > /tmp/deploys
oc get pdb -n $ns -o jsonpath='{range .items[*]}{.spec.selector.matchLabels.app}{"\n"}{end}' | sort > /tmp/pdbs
comm -23 /tmp/deploys /tmp/pdbs

Su workload di produzione con più repliche, l’assenza di PDB significa che un drain può portarle giù tutte insieme.


Comandi oc

Terminal window
# Stato di tutti i workload di un namespace
oc get deploy,sts,ds,cronjob -n $ns
# Rollout in corso
oc rollout status deploy/$workload -n $ns --timeout=60s
# Storico e rollback
oc rollout history deploy/$workload -n $ns
oc rollout undo deploy/$workload -n $ns
oc rollout undo deploy/$workload -n $ns --to-revision=3
# Riavvio controllato (rolling, non delete)
oc rollout restart deploy/$workload -n $ns
# Perché il ReplicaSet non crea pod
oc describe rs -n $ns -l app=$workload | sed -n '/Events:/,$p'
# Deployment degradati in tutto il cluster
oc get deploy -A -o json | jq -r '
.items[] | select(.status.availableReplicas // 0 < .spec.replicas)
| "\(.metadata.namespace)\t\(.metadata.name)\t\(.status.availableReplicas // 0)/\(.spec.replicas)"'
# Job falliti
oc get jobs -A --field-selector status.successful=0 -o wide
# Log dell'ultimo pod di un CronJob
oc logs -n $ns job/$(oc get jobs -n $ns --sort-by=.status.startTime -o jsonpath='{.items[-1].metadata.name}')
# Anti-affinity configurata
oc get deploy -n $ns $workload -o jsonpath='{.spec.template.spec.affinity}' | jq
# Scale manuale
oc scale deploy/$workload -n $ns --replicas=5

Alert

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: workload-health
namespace: <ns>
labels:
role: alert-rules
spec:
groups:
- name: workload.availability
rules:
- alert: DeploymentDown
expr: |
kube_deployment_status_replicas_available == 0
and kube_deployment_spec_replicas > 0
for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.deployment }} ({{ $labels.namespace }}): nessuna replica disponibile"
description: "Servizio non raggiungibile da 5 minuti."
- alert: DeploymentReplicasMismatch
expr: |
kube_deployment_status_replicas_available < kube_deployment_spec_replicas
for: 20m
labels:
severity: warning
annotations:
summary: "{{ $labels.deployment }}: {{ $value }} repliche disponibili su quelle attese"
description: "Capacità ridotta da 20 minuti. Verificare se è un rollout in corso o pod che non partono."
- alert: DeploymentRolloutStuck
expr: |
kube_deployment_status_condition{condition="Progressing", status="false"} == 1
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $labels.deployment }}: rollout in stallo"
description: "ProgressDeadlineExceeded. Verificare oc describe rs per il motivo."
- alert: StatefulSetDegraded
expr: |
kube_statefulset_status_replicas_ready < kube_statefulset_replicas
for: 20m
labels:
severity: warning
annotations:
summary: "{{ $labels.statefulset }}: {{ $value }} repliche ready sulle attese"
description: "Gli StatefulSet aggiornano un pod alla volta: un pod bloccato ferma tutta la catena."
- alert: DaemonSetIncomplete
expr: |
kube_daemonset_status_number_ready
!= kube_daemonset_status_desired_number_scheduled
for: 30m
labels:
severity: warning
annotations:
summary: "{{ $labels.daemonset }}: manca su {{ $value }} nodi"
- name: workload.jobs
rules:
- alert: JobFailed
expr: kube_job_status_failed > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Job {{ $labels.job_name }} ({{ $labels.namespace }}) fallito"
- alert: JobRunningTooLong
expr: |
(time() - kube_job_status_start_time) > 3600
and kube_job_status_active > 0
for: 10m
labels:
severity: warning
annotations:
summary: "Job {{ $labels.job_name }} attivo da oltre un'ora"
description: "Senza activeDeadlineSeconds un Job appeso resta appeso indefinitamente."
- alert: CronJobNotScheduled
expr: |
(time() - kube_cronjob_status_last_schedule_time) > 86400
and kube_cronjob_spec_suspend == 0
for: 1h
labels:
severity: warning
annotations:
summary: "CronJob {{ $labels.cronjob }} non parte da oltre 24 ore"
- name: workload.scaling
rules:
- alert: HPAAtMaxReplicas
expr: |
kube_horizontalpodautoscaler_status_current_replicas
== kube_horizontalpodautoscaler_spec_max_replicas
for: 30m
labels:
severity: warning
annotations:
summary: "HPA {{ $labels.horizontalpodautoscaler }} al massimo delle repliche"
description: "Margine esaurito: il carico ulteriore non viene più assorbito."
- alert: HPAUnableToScale
expr: |
kube_horizontalpodautoscaler_status_condition{condition="AbleToScale", status="false"} == 1
for: 15m
labels:
severity: warning
annotations:
summary: "HPA {{ $labels.horizontalpodautoscaler }} non riesce a scalare"
description: "Verificare metrics-server e che i pod abbiano requests.cpu impostate."
- alert: PDBBlocksDrain
expr: kube_poddisruptionbudget_status_pod_disruptions_allowed == 0
for: 1h
labels:
severity: info
annotations:
summary: "PDB {{ $labels.poddisruptionbudget }}: zero disruption consentite"
description: "Un drain dei nodi si bloccherà su questo PDB. Verificare prima della manutenzione."

Vedi anche