Skip to content

Control plane

Il filtro sui namespace applicativi non si applica: qui si guarda esattamente ciò che altrove viene escluso.

Precedenza assoluta: se qualcosa in questa pagina è rosso, chiudilo prima di indagare sulle applicazioni. Un etcd lento produce sintomi applicativi in ogni namespace contemporaneamente, e ognuno di quei sintomi sembra un problema a sé.

Triage
API server · etcd
Cluster operator
Scheduler e controller manager
Monitoring stackil monitoraggio del monitoraggio
Comandi · Alert

Triage

T1. Error rate dell’API server

# richieste al secondo
sum(rate(apiserver_request_total{code=~"5.."}[5m]))

Deve essere ~0. Se non lo è, tutto il resto delle metriche è inaffidabile.

T2. Latenza fsync di etcd

# secondi
histogram_quantile(0.99,
sum by (le, instance) (rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m]))
)

T3. Cluster operator degradati

# elenco: ogni serie restituita soddisfa la condizione
cluster_operator_conditions{condition="Degraded"} == 1

T4. Membri etcd

# numero di serie
count(etcd_server_has_leader)
# 1 = il membro ha un leader, 0 = quorum a rischio
etcd_server_has_leader == 0

Devono essere tre e tutti con leader. Uno senza leader significa quorum a rischio.

T5. Target di scrape irraggiungibili

# numero di serie
count by (job) (up == 0)

API server

Error rate scomposto

# richieste al secondo
sum by (code, verb, resource) (
rate(apiserver_request_total{code=~"5.."}[5m])
) > 0
CodiceCausa tipica
503un webhook di ammissione non risponde
504etcd lento
500bug, certificati, panic del controller
429rate limiting di priority & fairness: sovraccarico

Latenza p99 per verb

# secondi
histogram_quantile(0.99,
sum by (le, verb) (
rate(apiserver_request_duration_seconds_bucket{
verb!~"CONNECT|WATCH",
subresource!~"log|exec|portforward|attach|proxy"
}[5m])
)
)

L’esclusione di WATCH e CONNECT è obbligatoria: sono connessioni long-lived e la loro durata non è latenza. Includerle produce numeri assurdi.

SLO di riferimento: GET/POST/PUT/DELETE sotto 1 s, LIST sotto 5 s (le LIST su risorse numerose sono naturalmente lente).

Latenza per risorsa

# secondi
topk(10,
histogram_quantile(0.99,
sum by (le, resource, verb) (
rate(apiserver_request_duration_seconds_bucket{
verb!~"CONNECT|WATCH", subresource!~"log|exec|portforward|attach|proxy"
}[5m])
)
)
)

Identifica quale risorsa sta rallentando l’API. Se è una CRD, l’operator che la gestisce ne ha create troppe o il suo webhook è lento.

Chi bombarda l’API

# richieste al secondo
topk(20,
sum by (resource, verb, client) (
rate(apiserver_request_total[5m])
)
)

Il label client porta lo user-agent. È la query che identifica l’operator che polla ogni secondo invece di usare le watch — causa frequente di degrado del control plane senza nessuna causa apparente.

# richieste al secondo
topk(10, sum by (client) (rate(apiserver_request_total[5m])))

Richieste in volo

# numero di richieste in corso
sum by (request_kind) (apiserver_current_inflight_requests)

Se si avvicina a max-requests-inflight (default 400 mutating, 800 read-only), l’API server rifiuta con 429 e i client fanno backoff.

Priority & fairness

# eventi al secondo
sum by (priority_level, flow_schema) (
rate(apiserver_flowcontrol_rejected_requests_total[5m])
) > 0
# numero di richieste accodate
apiserver_flowcontrol_current_inqueue_requests > 0

APF sta scartando o accodando richieste per proteggere l’API server: sintomo di sovraccarico, non causa.

Webhook di ammissione

# secondi
histogram_quantile(0.99,
sum by (le, name, type) (
rate(apiserver_admission_webhook_admission_duration_seconds_bucket[5m])
)
)
# rifiuti al secondo
sum by (name, rejected) (
rate(apiserver_admission_webhook_rejection_count[5m])
) > 0

Un webhook lento rallenta ogni creazione di risorsa che intercetta. Se ha failurePolicy: Fail e smette di rispondere, blocca completamente la creazione di quelle risorse — sintomo: i deploy si fermano e oc apply va in timeout.

Terminal window
oc get validatingwebhookconfiguration
oc get mutatingwebhookconfiguration

Certificati in scadenza

# giorni
min(kube_certificatesigningrequest_cert_not_after - time()) / 86400
# secondi
apiserver_client_certificate_expiration_seconds_bucket{le="604800"} > 0

Su cluster air-gapped con rotazione manuale è una verifica da schedulare.

Terminal window
oc get csr | grep -i pending
oc -n openshift-kube-apiserver-operator get secret -o json | jq -r '.items[] | select(.metadata.annotations."auth.openshift.io/certificate-not-after") | "\(.metadata.name)\t\(.metadata.annotations."auth.openshift.io/certificate-not-after")"'

Consumo dell’API server

# GiB
sum by (pod) (
container_memory_working_set_bytes{
namespace="openshift-kube-apiserver", container="kube-apiserver"
}
) / 1024^3
# core CPU
sum by (pod) (
rate(container_cpu_usage_seconds_total{
namespace="openshift-kube-apiserver", container="kube-apiserver"
}[5m])
)

Un kube-apiserver che consuma sempre più memoria sta accumulando watch: spesso un operator che ne apre e non ne chiude.


etcd

etcd è il database del cluster. Ogni sua lentezza si propaga a tutto.

Latenza di fsync WAL

# secondi
histogram_quantile(0.99,
sum by (le, instance) (rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m]))
)
ValoreLettura
< 0.010sano (soglia Red Hat: 10 ms)
0.010 – 0.025degrado: il cluster inizia a sembrare lento
> 0.025grave: rischio di perdita del quorum

È la metrica singola più importante del control plane. Su cluster VMware la causa è quasi sempre il datastore condiviso: etcd vuole un disco dedicato a bassa latenza, e va detto a chi gestisce la farm.

Latenza di commit sul backend

# secondi
histogram_quantile(0.99,
sum by (le, instance) (rate(etcd_disk_backend_commit_duration_seconds_bucket[5m]))
)

Soglia: 0.025.

Cambi di leader

# conteggio nell'ultima ora
increase(etcd_server_leader_changes_seen_total[1h])

Deve essere 0. Ogni cambio è un’interruzione di scrittura di qualche secondo su tutto il cluster. Cause: latenza di rete tra i master, un master sotto stress, disco lento.

Membri e quorum

# 1 = il membro ha un leader, 0 = quorum a rischio
etcd_server_has_leader
# numero di serie
count(etcd_server_has_leader == 1)
# 1 = questo membro è il leader
etcd_server_is_leader

Con tre membri, il quorum è due: perderne due significa cluster read-only.

Proposte

# conteggio nell'ultima ora
increase(etcd_server_proposals_failed_total[1h]) > 0
# numero di proposte non ancora applicate
etcd_server_proposals_pending

Proposte pendenti in crescita = etcd non tiene il ritmo delle scritture.

# eventi al secondo
rate(etcd_server_proposals_committed_total[5m])

Dimensione del database

# byte
etcd_mvcc_db_total_size_in_bytes / 1024^3
# GiB
etcd_mvcc_db_total_size_in_use_in_bytes / 1024^3
# MiB
(etcd_mvcc_db_total_size_in_bytes - etcd_mvcc_db_total_size_in_use_in_bytes) / 1024^2

La differenza è frammentazione: spazio allocato ma non in uso, recuperabile con defrag. Sopra i 500 MiB vale la pena farlo.

Quota di default su OCP: 8 GiB. Al raggiungimento etcd va in NOSPACE e il cluster diventa read-only: niente deploy, niente scale, niente scheduling.

Terminal window
# Stato dei membri
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl endpoint status -w table --cluster
# Salute
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl endpoint health --cluster
# Defrag, un membro alla volta, MAI in parallelo
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl defrag --command-timeout=60s

Il defrag blocca il membro su cui gira per la durata dell’operazione: eseguirlo su tutti i master insieme fa perdere il quorum.

Su OCP 4.9+ esiste anche il defrag automatico gestito dall’operator:

Terminal window
oc -n openshift-etcd-operator logs -l app=etcd-operator --tail=100 | grep -i defrag

Latenza di rete tra i peer

# secondi
histogram_quantile(0.99,
sum by (le, To) (rate(etcd_network_peer_round_trip_time_seconds_bucket[5m]))
)

Sopra 0.050 tra due master il quorum è a rischio. Su cluster stretched tra due sale è la metrica da tenere sotto controllo.

Traffico tra peer

# MiB
rate(etcd_network_peer_sent_bytes_total[5m]) / 1024^2
# MiB
rate(etcd_network_peer_received_bytes_total[5m]) / 1024^2

Numero di oggetti

# conteggio
topk(15, apiserver_storage_objects)

Identifica cosa sta gonfiando etcd. Colpevoli tipici: events, secrets accumulati da operator, pods in stato Failed mai eliminati, CRD generate da un controller in loop, Job non ripuliti.

# conteggio
apiserver_storage_objects{resource="events"}
# conteggio
apiserver_storage_objects{resource=~"secrets|configmaps"}

Watcher attivi

# numero di watch aperte
topk(15, sum by (resource) (apiserver_longrunning_requests{verb="WATCH"}))

Un numero di watch che cresce senza sosta indica un client che le apre e non le chiude.

Disco di etcd

# percentuale (0-100)
100 * (
1 - (
node_filesystem_avail_bytes{mountpoint=~"/var/lib/etcd|/var/lib"}
/
node_filesystem_size_bytes{mountpoint=~"/var/lib/etcd|/var/lib"}
)
)
# secondi per secondo (frazione di tempo)
rate(node_disk_write_time_seconds_total{instance=~".*master.*"}[5m])
/
rate(node_disk_writes_completed_total{instance=~".*master.*"}[5m])

Vedi Volumi e Nodo.


Cluster operator

Degraded

# elenco: ogni serie restituita soddisfa la condizione
cluster_operator_conditions{condition="Degraded"} == 1

Non Available

# elenco: ogni serie restituita soddisfa la condizione
cluster_operator_conditions{condition="Available"} == 0

Progressing da troppo tempo

# elenco: ogni serie restituita soddisfa la condizione
cluster_operator_conditions{condition="Progressing"} == 1

Fuori da un upgrade, un CO che resta Progressing oltre 15 minuti è bloccato.

Versioni

# numero di serie
count by (version) (cluster_operator_conditions{condition="Available"})

Durante un upgrade convivono più versioni. Se dopo la fine ci sono ancora versioni miste, l’upgrade non è realmente completo.

# 1 = operator attivo, 0 = non attivo
cluster_operator_up

Stato del cluster version operator

# 1 se la condizione è vera
cluster_version{type="completed"}
# numero di aggiornamenti disponibili
cluster_version_available_updates
# 1 = capability abilitata
cluster_version_capability{name!=""}
Terminal window
oc get co
oc get clusterversion
oc describe co <nome> | sed -n '/Conditions:/,/Extension:/p'
oc adm upgrade

Scheduler e controller manager

Latenza di scheduling

# secondi
histogram_quantile(0.99,
sum by (le) (rate(scheduler_e2e_scheduling_duration_seconds_bucket[5m]))
)
# secondi
histogram_quantile(0.99,
sum by (le) (rate(scheduler_scheduling_attempt_duration_seconds_bucket[5m]))
)

Su cluster grandi con molti vincoli (affinity, taint, topology spread) lo scheduling diventa costoso e i pod restano Pending più a lungo di quanto sembri giustificato.

Tentativi falliti

# eventi al secondo
sum by (result) (rate(scheduler_schedule_attempts_total[5m]))

Il label result distingue scheduled, unschedulable, error. Un tasso alto di unschedulable significa cluster pieno o vincoli impossibili.

Coda dello scheduler

# numero di pod in coda allo scheduler
scheduler_pending_pods

Controller manager

# eventi al secondo
rate(workqueue_adds_total{job="kube-controller-manager"}[5m])
# numero di elementi in coda
workqueue_depth{job="kube-controller-manager"}
# secondi
histogram_quantile(0.99,
sum by (le, name) (rate(workqueue_queue_duration_seconds_bucket{job="kube-controller-manager"}[5m]))
)

Una workqueue che cresce significa che un controller non riesce a smaltire il lavoro: le riconciliazioni si accumulano e le modifiche agli oggetti impiegano sempre più tempo a produrre effetti.

Leader election

# conteggio nell'ultima ora
increase(leader_election_master_status[1h])
# elenco: ogni serie restituita soddisfa la condizione
leader_election_master_status == 0

Cambi frequenti di leader tra i componenti del control plane indicano instabilità di rete o master sotto stress.


Monitoring stack

Il monitoraggio che smette di funzionare è il modo peggiore di scoprire un problema.

Target irraggiungibili

# elenco: ogni serie restituita soddisfa la condizione
up == 0
# numero di serie
count by (job, namespace) (up == 0)

Prometheus vicino al limite di memoria

# percentuale 0-100
100 * (
container_memory_working_set_bytes{
namespace="openshift-monitoring", pod=~"prometheus-k8s-.*", container="prometheus"
}
/
kube_pod_container_resource_limits{
namespace="openshift-monitoring", pod=~"prometheus-k8s-.*", container="prometheus", resource="memory"
}
)

Prometheus in OOM perde i dati non ancora scritti su disco. Su cluster con molte serie è un evento ricorrente.

Serie temporali attive

# numero di serie temporali attive
prometheus_tsdb_head_series
# eventi al secondo
rate(prometheus_tsdb_head_series_created_total[5m])

Un tasso di creazione alto e costante significa churn: serie che nascono e muoiono continuamente, tipicamente perché un label contiene qualcosa di variabile (nome del pod in una metrica applicativa, ID di richiesta, timestamp).

Cardinalità per metrica

# numero di serie
topk(20, count by (__name__) ({__name__=~".+"}))

Query molto costosa: lanciala una volta, mai in dashboard. Identifica le metriche con più serie, che sono la causa di un Prometheus che consuma troppa memoria.

Alternativa più leggera, via API:

Terminal window
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
curl -s http://localhost:9090/api/v1/status/tsdb | jq '.data.seriesCountByMetricName[:20]'
Terminal window
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
curl -s http://localhost:9090/api/v1/status/tsdb | jq '.data.labelValueCountByLabelName[:20]'

La seconda mostra quali label hanno più valori distinti: è lì che si trova il colpevole della cardinalità.

Campioni scartati

# eventi al secondo
sum by (job) (rate(prometheus_target_scrapes_exceeded_sample_limit_total[5m])) > 0

Un target che supera il sampleLimit viene scartato interamente: non perdi qualche serie, perdi tutto quel target.

# eventi al secondo
sum by (job) (rate(prometheus_target_scrapes_sample_duplicate_timestamp_total[5m])) > 0

Durata degli scrape

# secondi
topk(20, scrape_duration_seconds)

Uno scrape che dura più dell’intervallo produce buchi nei dati.

# secondi
scrape_duration_seconds > 10

Valutazione delle regole

# secondi
prometheus_rule_group_last_duration_seconds
/
prometheus_rule_group_interval_seconds

Sopra 1 le regole impiegano più del loro intervallo per essere valutate: gli alert arrivano in ritardo o non arrivano.

# secondi
topk(10, prometheus_rule_group_last_duration_seconds)

Identifica il gruppo di regole più lento — di solito quello con una subquery lunga dentro un alert.

Storage di Prometheus

# GiB
prometheus_tsdb_storage_blocks_bytes / 1024^3
# percentuale (0-100)
100 * (
1 - kubelet_volume_stats_available_bytes{namespace="openshift-monitoring"}
/ kubelet_volume_stats_capacity_bytes{namespace="openshift-monitoring"}
)

Alertmanager

# contatore cumulativo di notifiche fallite
alertmanager_notifications_failed_total
# eventi al secondo
rate(alertmanager_notifications_failed_total[5m]) > 0
# numero di alert noti ad Alertmanager
alertmanager_alerts

Se le notifiche falliscono, gli alert scattano e nessuno lo sa. Su cluster air-gapped la causa tipica è il proxy o il certificato del receiver.

Terminal window
oc -n openshift-monitoring logs -l alertmanager=main -c alertmanager --tail=100 | grep -i "error\|failed"

Alert attivi

# elenco degli alert attivi (valore sempre 1)
ALERTS{alertstate="firing"}
# numero di serie
count by (severity) (ALERTS{alertstate="firing"})
# numero di serie
count by (alertname) (ALERTS{alertstate="firing"})

Utile per capire se un alert sta facendo rumore: se lo stesso alertname è firing su decine di target da settimane, o va corretto o va rimosso.

User workload monitoring

# elenco: ogni serie restituita soddisfa la condizione
up{namespace="openshift-user-workload-monitoring"} == 0
# numero di serie
count by (namespace) (
up{job!~"kubelet|node-exporter|apiserver", namespace!~"openshift-.*|kube-.*"}
)

Quanti target applicativi sono effettivamente monitorati per namespace.


Comandi

Terminal window
# Stato generale
oc get co
oc get clusterversion
oc get nodes
oc adm upgrade
# etcd
oc -n openshift-etcd get pods
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl endpoint status -w table --cluster
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl endpoint health --cluster
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- etcdctl member list -w table
# Cosa occupa etcd
oc -n openshift-etcd exec etcd-<master-0> -c etcdctl -- \
etcdctl get / --prefix --keys-only | sed -E 's|/([^/]+)/([^/]+)/.*|\1/\2|' | sort | uniq -c | sort -rn | head -20
# API server
oc -n openshift-kube-apiserver get pods
oc -n openshift-kube-apiserver logs -l app=openshift-kube-apiserver --tail=100 | grep -i error
# Webhook che possono bloccare tutto
oc get validatingwebhookconfiguration -o custom-columns=NAME:.metadata.name,POLICY:'.webhooks[*].failurePolicy'
oc get mutatingwebhookconfiguration -o custom-columns=NAME:.metadata.name,POLICY:'.webhooks[*].failurePolicy'
# Eventi accumulati
oc get events -A --no-headers | wc -l
# Monitoring
oc -n openshift-monitoring get pods
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
curl -s http://localhost:9090/api/v1/status/tsdb | jq '.data.seriesCountByMetricName[:15]'
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
promtool query instant http://localhost:9090 'count(up == 0)'
# Alert attivi
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
promtool query instant http://localhost:9090 'ALERTS{alertstate="firing"}'

Alert

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: control-plane-custom
namespace: openshift-monitoring
labels:
role: alert-rules
spec:
groups:
- name: controlplane.etcd
rules:
- alert: EtcdFsyncSlow
expr: |
histogram_quantile(0.99,
sum by (le, instance) (rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m]))
) > 0.025
for: 10m
labels:
severity: critical
annotations:
summary: "etcd {{ $labels.instance }}: fsync p99 a {{ $value | printf \"%.3f\" }}s"
description: "Oltre la soglia Red Hat di 25ms. Rischio di perdita del quorum. Causa tipica: latenza del datastore. Escalation infrastruttura."
- alert: EtcdDatabaseNearQuota
expr: etcd_mvcc_db_total_size_in_bytes > 7 * 1024^3
for: 15m
labels:
severity: critical
annotations:
summary: "etcd {{ $labels.instance }}: DB a {{ $value | humanize1024 }}B"
description: "Vicino alla quota di 8GiB. Al raggiungimento il cluster diventa read-only. Verificare frammentazione e valutare un defrag (un membro alla volta)."
- alert: EtcdFragmentationHigh
expr: |
(etcd_mvcc_db_total_size_in_bytes - etcd_mvcc_db_total_size_in_use_in_bytes)
> 1024^3
for: 6h
labels:
severity: info
annotations:
summary: "etcd {{ $labels.instance }}: {{ $value | humanize1024 }}B di frammentazione"
description: "Recuperabile con defrag. Eseguire un membro alla volta, mai in parallelo."
- name: controlplane.apiserver
rules:
- alert: APIServerAdmissionWebhookSlow
expr: |
histogram_quantile(0.99,
sum by (le, name) (rate(apiserver_admission_webhook_admission_duration_seconds_bucket[5m]))
) > 1
for: 10m
labels:
severity: warning
annotations:
summary: "Webhook {{ $labels.name }}: p99 a {{ $value | printf \"%.2f\" }}s"
description: "Rallenta ogni creazione di risorsa che intercetta. Con failurePolicy Fail può bloccarle del tutto."
- alert: APIServerFlowControlRejecting
expr: |
sum by (priority_level) (rate(apiserver_flowcontrol_rejected_requests_total[5m])) > 0
for: 10m
labels:
severity: warning
annotations:
summary: "APF sta rifiutando richieste su {{ $labels.priority_level }}"
description: "L'API server si sta proteggendo dal sovraccarico. Identificare il client con il rate più alto."
- name: controlplane.monitoring
rules:
- alert: PrometheusMemoryNearLimit
expr: |
container_memory_working_set_bytes{
namespace="openshift-monitoring", pod=~"prometheus-k8s-.*", container="prometheus"
}
/
kube_pod_container_resource_limits{
namespace="openshift-monitoring", pod=~"prometheus-k8s-.*", container="prometheus", resource="memory"
} > 0.85
for: 15m
labels:
severity: warning
annotations:
summary: "Prometheus all'{{ $value | humanizePercentage }} del memory limit"
description: "Un OOM di Prometheus perde i dati non ancora persistiti. Verificare la cardinalità."
- alert: PrometheusSeriesChurnHigh
expr: rate(prometheus_tsdb_head_series_created_total[10m]) > 100
for: 30m
labels:
severity: warning
annotations:
summary: "Prometheus: {{ $value | printf \"%.0f\" }} nuove serie/s"
description: "Churn elevato: un label con valori variabili (pod name, request id, timestamp) in una metrica applicativa."
- alert: PrometheusRuleEvaluationSlow
expr: |
prometheus_rule_group_last_duration_seconds
/ prometheus_rule_group_interval_seconds > 0.8
for: 15m
labels:
severity: warning
annotations:
summary: "Gruppo di regole {{ $labels.rule_group }} lento"
description: "Valutazione oltre l'80% dell'intervallo. Gli alert arriveranno in ritardo. Cercare subquery lunghe."
- alert: AlertmanagerNotificationsFailing
expr: rate(alertmanager_notifications_failed_total[5m]) > 0
for: 10m
labels:
severity: critical
annotations:
summary: "Alertmanager: notifiche fallite verso {{ $labels.integration }}"
description: "Gli alert scattano e nessuno li riceve. Su cluster air-gapped verificare proxy e certificati."
- alert: ScrapeTargetsDown
expr: count by (job) (up == 0) > 0
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $value }} target del job {{ $labels.job }} irraggiungibili"

etcdHighFsyncDurations, etcdMembersDown, KubeAPIErrorBudgetBurn e ClusterOperatorDegraded esistono già nativamente. Verifica prima di duplicare:

Terminal window
oc -n openshift-monitoring get prometheusrule -o yaml | grep -E "alert: (etcd|KubeAPI|ClusterOperator)"

Soglie di riferimento

MetricaSanoCritico
API 5xx rate~0> 0.1/s
API p99 (escl. LIST)< 1 s> 3 s
Webhook p99< 0.1 s> 1 s
etcd fsync p99< 10 ms> 25 ms
etcd backend commit p99< 25 ms> 50 ms
etcd leader changes / h0> 0
etcd DB size< 4 GiB> 7 GiB
etcd peer RTT p99< 20 ms> 50 ms
Cluster operator Degraded0> 0
up == 00> 0
Prometheus mem / limit< 70%> 85%
Rule eval / interval< 50%> 80%

Vedi anche

  • Nodo — i master come nodi
  • Volumi — disco di etcd
  • Rete — OVN e ingress
  • Hub — convenzioni e recording rule