Compare commits
2 Commits
c431fc36c5
...
8cb03c4e81
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cb03c4e81 | ||
|
|
1a2cca2cd2 |
90
add-on/Postgres_Restore.txt
Normal file
90
add-on/Postgres_Restore.txt
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
kubectl get cluster.postgresql.cnpg.io -n <namespace>
|
||||
kubectl get backup -n <namespace>
|
||||
|
||||
|
||||
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: <pgclustername>-restored
|
||||
spec:
|
||||
instances: <instancenumber>
|
||||
|
||||
storage:
|
||||
size: <dbstorage>
|
||||
storageClass: csi-rbdfs-sc
|
||||
|
||||
walStorage:
|
||||
storageClass: csi-rbdfs-sc
|
||||
size: <dbstorage>
|
||||
|
||||
bootstrap:
|
||||
recovery:
|
||||
source: <pgclustername>-origin
|
||||
|
||||
backup:
|
||||
name: sporteams-backup-20260718020000 # nome dell'oggetto Backup da kubectl get backup -n <namespace>
|
||||
recoveryTarget: # opzionale in assenza applica tutto il journal (wal) a partire dal backup
|
||||
targetTime: "2026-07-18 15:30:00.000000+00" # timestamp in cui si ferma rebuild dal WAL
|
||||
|
||||
externalClusters:
|
||||
- name: <pgclustername>-origin
|
||||
barmanObjectStore:
|
||||
endpointURL: http://minio.minio.svc.cluster.local:9000
|
||||
destinationPath: s3://cnpgbackup2/<pgclustername>
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: minio-backup-creds
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: minio-backup-creds
|
||||
key: ACCESS_SECRET_KEY
|
||||
wal:
|
||||
compression: gzip
|
||||
maxParallel: 2
|
||||
data:
|
||||
compression: gzip
|
||||
immediateCheckpoint: false
|
||||
jobs: 2
|
||||
|
||||
# Dopo il restore, il cluster ripristinato può avere un backup indipendente:
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
endpointURL: http://minio.minio.svc.cluster.local:9000
|
||||
destinationPath: s3://cnpgbackup2/<pgclustername>-restored
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: minio-backup-creds
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: minio-backup-creds
|
||||
key: ACCESS_SECRET_KEY
|
||||
wal:
|
||||
compression: gzip
|
||||
maxParallel: 2
|
||||
data:
|
||||
compression: gzip
|
||||
immediateCheckpoint: false
|
||||
jobs: 2
|
||||
retentionPolicy: "30d"
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "300"
|
||||
shared_buffers: "1GB"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
|
||||
|
||||
### Utility varie ##########
|
||||
kubectl get cluster.postgresql.cnpg.io -n stsharedservices-dev
|
||||
kubectl -n minio exec -it minio-0 -- sh
|
||||
mc alias set local http://localhost:9000 minioadmin KAYQE1QA7uwUZ8uI
|
||||
mc ls local cnpgbackup2
|
||||
173
add-on/monitoring/files/01-kube-prometheus-stack-values.yaml
Normal file
173
add-on/monitoring/files/01-kube-prometheus-stack-values.yaml
Normal file
@@ -0,0 +1,173 @@
|
||||
# =====================================================================
|
||||
# values.yaml per kube-prometheus-stack su RKE2 (installazione diretta,
|
||||
# senza Rancher Manager)
|
||||
#
|
||||
# Repo: https://prometheus-community.github.io/helm-charts
|
||||
# Chart: kube-prometheus-stack
|
||||
#
|
||||
# Install:
|
||||
# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
# helm repo update
|
||||
# helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
|
||||
# -n monitoring --create-namespace \
|
||||
# -f 01-kube-prometheus-stack-values.yaml
|
||||
# =====================================================================
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Prometheus
|
||||
# ---------------------------------------------------------------------
|
||||
prometheus:
|
||||
prometheusSpec:
|
||||
retention: 15d
|
||||
retentionSize: "10GB"
|
||||
|
||||
# Adatta alla storageClass disponibile nel tuo cluster RKE2
|
||||
# (es. longhorn, local-path-provisioner, ceph-rbd, ecc.)
|
||||
storageSpec:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
|
||||
# Permette a Prometheus di scoprire ServiceMonitor/PodMonitor
|
||||
# creati in QUALSIASI namespace (importante per uno stack "generico"
|
||||
# dove le verticali applicative, es. CNPG, vivono in namespace propri)
|
||||
serviceMonitorSelectorNilUsesHelmValues: false
|
||||
podMonitorSelectorNilUsesHelmValues: false
|
||||
ruleSelectorNilUsesHelmValues: false
|
||||
|
||||
# Su RKE2 il control-plane espone metriche ma va abilitato esplicitamente
|
||||
# lo scraping (vedi sezioni kubeEtcd/kubeControllerManager/kubeScheduler
|
||||
# più sotto). Se il tuo cluster è "hardened" e non espone questi endpoint
|
||||
# sui nodi worker, valuta di disabilitare le relative sezioni.
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Control plane RKE2 — di default questi componenti su RKE2 girano come
|
||||
# pod statici raggiungibili sul nodo server; verificare le porte reali
|
||||
# con `kubectl get pods -n kube-system` e `netstat` sul nodo se lo
|
||||
# scraping non trova i target.
|
||||
# ---------------------------------------------------------------------
|
||||
kubeEtcd:
|
||||
enabled: true
|
||||
service:
|
||||
port: 2381
|
||||
targetPort: 2381
|
||||
|
||||
kubeControllerManager:
|
||||
enabled: true
|
||||
service:
|
||||
port: 10257
|
||||
targetPort: 10257
|
||||
# RKE2 espone il controller-manager in HTTPS con cert self-signed:
|
||||
# potrebbe servire scrapeConfig con tlsConfig.insecureSkipVerify: true
|
||||
# (vedi kube-prometheus-stack docs, sezione "kubeControllerManager").
|
||||
|
||||
kubeScheduler:
|
||||
enabled: true
|
||||
service:
|
||||
port: 10259
|
||||
targetPort: 10259
|
||||
|
||||
# Traefik è deployato di default su RKE2 ed è considerato componente
|
||||
# interno. Se lo usi come ingress controller, aggiungi un ServiceMonitor
|
||||
# dedicato puntato al suo endpoint /metrics (di solito porta 9100).
|
||||
kubeProxy:
|
||||
enabled: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Alertmanager
|
||||
# ---------------------------------------------------------------------
|
||||
alertmanager:
|
||||
alertmanagerSpec:
|
||||
retention: 120h
|
||||
storage:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
|
||||
# Config di base; per la config completa vedi il file
|
||||
# 04-alertmanager-config.yaml (AlertmanagerConfig CRD, più gestibile
|
||||
# in modo dichiarativo/GitOps rispetto a alertmanager.config qui).
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Grafana (incluso nel chart)
|
||||
# ---------------------------------------------------------------------
|
||||
grafana:
|
||||
enabled: true
|
||||
defaultDashboardsTimezone: Europe/Rome
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
|
||||
size: 5Gi
|
||||
|
||||
# Cambia in produzione: usa un Secret invece di plaintext
|
||||
adminPassword: "CHANGE_ME"
|
||||
|
||||
# Loki come datasource aggiuntivo (vedi file 03-loki-alloy)
|
||||
additionalDataSources:
|
||||
- name: Loki
|
||||
type: loki
|
||||
access: proxy
|
||||
url: http://loki.monitoring.svc.cluster.local:3100
|
||||
isDefault: false
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
|
||||
# Import automatico dashboard CNPG (ID 20417) via sidecar dashboards
|
||||
# già incluso nel chart: basta creare un ConfigMap con label
|
||||
# grafana_dashboard=1 (vedi file 02-cnpg-monitoring.yaml in fondo,
|
||||
# sezione dashboard, oppure importa manualmente da Grafana UI).
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# node-exporter
|
||||
# ---------------------------------------------------------------------
|
||||
nodeExporter:
|
||||
enabled: true
|
||||
|
||||
prometheus-node-exporter:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 30Mi
|
||||
limits:
|
||||
memory: 50Mi
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# kube-state-metrics
|
||||
# ---------------------------------------------------------------------
|
||||
kubeStateMetrics:
|
||||
enabled: true
|
||||
|
||||
kube-state-metrics:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
96
add-on/monitoring/files/02-cnpg-monitoring.yaml
Normal file
96
add-on/monitoring/files/02-cnpg-monitoring.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
# =====================================================================
|
||||
# Monitoring per CloudNativePG (CNPG)
|
||||
#
|
||||
# Applica con: kubectl apply -f 02-cnpg-monitoring.yaml
|
||||
#
|
||||
# Prerequisiti:
|
||||
# - CNPG operator già installato (namespace tipico: cnpg-system)
|
||||
# - kube-prometheus-stack già installato nel namespace "monitoring"
|
||||
# con podMonitorSelectorNilUsesHelmValues: false (vedi file 01),
|
||||
# così Prometheus scopre PodMonitor in TUTTI i namespace.
|
||||
# =====================================================================
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 1) PodMonitor per l'operatore CNPG stesso
|
||||
# (metriche interne: reconcile loop, controller-runtime, porta 8080)
|
||||
# ---------------------------------------------------------------------
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: cnpg-operator
|
||||
namespace: cnpg-system
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: cloudnative-pg
|
||||
podMetricsEndpoints:
|
||||
- port: metrics
|
||||
interval: 30s
|
||||
|
||||
---
|
||||
# ---------------------------------------------------------------------
|
||||
# 2) PodMonitor generico per TUTTI i cluster Postgres gestiti da CNPG
|
||||
#
|
||||
# Nota: se preferisci il metodo "automatico" (enablePodMonitor: true
|
||||
# nella risorsa Cluster), questo PodMonitor manuale NON serve per
|
||||
# quel cluster specifico — ma il manuale è consigliato dalla doc
|
||||
# ufficiale CNPG perché dà controllo esplicito sul lifecycle,
|
||||
# indipendente dal singolo Cluster CR.
|
||||
#
|
||||
# Il selettore usa la label standard "cnpg.io/cluster" che CNPG
|
||||
# applica automaticamente a tutti i pod di ogni cluster gestito,
|
||||
# quindi UN SOLO PodMonitor copre TUTTI i cluster Postgres presenti
|
||||
# nel namespace target (adatta matchExpressions se i tuoi cluster
|
||||
# vivono in namespace diversi: serve un PodMonitor per namespace,
|
||||
# oppure passa a un selector cross-namespace via namespaceSelector).
|
||||
# ---------------------------------------------------------------------
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: cnpg-clusters
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
namespaceSelector:
|
||||
any: true # scropa i pod CNPG in QUALSIASI namespace
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: cnpg.io/cluster
|
||||
operator: Exists
|
||||
podMetricsEndpoints:
|
||||
- port: metrics # exporter per-istanza, porta 9187
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
path: /metrics
|
||||
|
||||
---
|
||||
# ---------------------------------------------------------------------
|
||||
# 3) Import automatico dashboard CNPG ufficiale (grafana.com ID 20417)
|
||||
# tramite sidecar dashboards di Grafana (già incluso nel chart
|
||||
# kube-prometheus-stack). Basta un ConfigMap con la label
|
||||
# "grafana_dashboard: '1'" nel namespace di Grafana.
|
||||
#
|
||||
# In alternativa più semplice: Grafana UI -> Dashboards -> Import
|
||||
# -> inserisci ID 20417 -> seleziona datasource Prometheus.
|
||||
# ---------------------------------------------------------------------
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cnpg-grafana-dashboard
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
cnpg-dashboard.json: |
|
||||
{
|
||||
"annotations": {"list": []},
|
||||
"title": "CloudNativePG (import via ID 20417)",
|
||||
"__inputs": [],
|
||||
"__requires": [],
|
||||
"schemaVersion": 39,
|
||||
"panels": [],
|
||||
"_comment": "Placeholder: sostituisci con l'export JSON completo scaricato da https://grafana.com/grafana/dashboards/20417 per avere la dashboard reale, oppure importa manualmente da Grafana UI usando l'ID."
|
||||
}
|
||||
205
add-on/monitoring/files/03-alert-rules.yaml
Normal file
205
add-on/monitoring/files/03-alert-rules.yaml
Normal file
@@ -0,0 +1,205 @@
|
||||
# =====================================================================
|
||||
# PrometheusRule: alert infrastrutturali + CNPG
|
||||
# Applica con: kubectl apply -f 03-alert-rules.yaml
|
||||
# =====================================================================
|
||||
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: infra-alerts
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
groups:
|
||||
- name: node-health
|
||||
rules:
|
||||
- alert: NodeDown
|
||||
expr: up{job="node-exporter"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Nodo {{ $labels.instance }} irraggiungibile"
|
||||
description: "node-exporter non risponde da 5 minuti su {{ $labels.instance }}."
|
||||
|
||||
- alert: NodeHighCPU
|
||||
expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "CPU alta su {{ $labels.instance }}"
|
||||
description: "Uso CPU sopra 85% da 10 minuti."
|
||||
|
||||
- alert: NodeHighMemory
|
||||
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Memoria alta su {{ $labels.instance }}"
|
||||
description: "Uso memoria sopra 90% da 10 minuti."
|
||||
|
||||
- alert: NodeDiskSpaceLow
|
||||
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 15
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Spazio disco basso su {{ $labels.instance }} ({{ $labels.mountpoint }})"
|
||||
description: "Meno del 15% di spazio libero da 10 minuti."
|
||||
|
||||
- alert: NodeDiskSpaceCritical
|
||||
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 5
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Spazio disco CRITICO su {{ $labels.instance }} ({{ $labels.mountpoint }})"
|
||||
description: "Meno del 5% di spazio libero da 5 minuti."
|
||||
|
||||
- name: kubernetes-health
|
||||
rules:
|
||||
- alert: KubePodCrashLooping
|
||||
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} in crash loop"
|
||||
description: "Più di 3 restart negli ultimi 15 minuti."
|
||||
|
||||
- alert: KubePodNotReady
|
||||
expr: sum by (namespace, pod) (kube_pod_status_phase{phase=~"Pending|Unknown"}) > 0
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} non Ready da 15 minuti"
|
||||
|
||||
- alert: KubeDeploymentReplicasMismatch
|
||||
expr: kube_deployment_spec_replicas != kube_deployment_status_replicas_available
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} con repliche non allineate"
|
||||
|
||||
- alert: KubePersistentVolumeFillingUp
|
||||
expr: (kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes) * 100 < 10
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} quasi pieno"
|
||||
description: "Meno del 10% di spazio libero sul volume."
|
||||
|
||||
- alert: EtcdInsufficientMembers
|
||||
expr: sum(up{job="kube-etcd"} == 1) < ((count(up{job="kube-etcd"}) + 1) / 2)
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Quorum etcd a rischio"
|
||||
description: "Meno della maggioranza dei membri etcd è raggiungibile."
|
||||
|
||||
- name: tls-certificates
|
||||
rules:
|
||||
# Richiede blackbox_exporter con probe HTTPS configurato sui tuoi
|
||||
# endpoint esterni; vedi nota nel file README.
|
||||
- alert: TLSCertExpiringSoon
|
||||
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 15
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Certificato TLS per {{ $labels.instance }} in scadenza"
|
||||
description: "Meno di 15 giorni alla scadenza del certificato."
|
||||
|
||||
- alert: TLSCertExpired
|
||||
expr: probe_ssl_earliest_cert_expiry - time() < 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Certificato TLS per {{ $labels.instance }} SCADUTO"
|
||||
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: cnpg-alerts
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
groups:
|
||||
- name: cnpg-health
|
||||
rules:
|
||||
- alert: CNPGClusterNotHealthy
|
||||
expr: cnpg_collector_up == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Cluster CNPG {{ $labels.namespace }}/{{ $labels.pod }} non raggiungibile"
|
||||
description: "L'exporter CNPG non risponde da 5 minuti."
|
||||
|
||||
- alert: CNPGInstanceInUnexpectedRecovery
|
||||
expr: cnpg_pg_replication_in_recovery == 1 and on(pod) cnpg_pg_replication_is_wal_receiver_up == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Istanza {{ $labels.pod }} in recovery inatteso"
|
||||
description: "Verifica se il ruolo primary/standby è quello atteso."
|
||||
|
||||
- alert: CNPGReplicationLagHigh
|
||||
expr: cnpg_pg_replication_lag > 300
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Replication lag alto su {{ $labels.pod }}"
|
||||
description: "Lag di replica superiore a 300 secondi."
|
||||
|
||||
- alert: CNPGWALArchivingFailing
|
||||
expr: increase(cnpg_pg_stat_archiver_failed_count[15m]) > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Archiviazione WAL fallita su {{ $labels.pod }}"
|
||||
description: "Almeno un fallimento di archiviazione WAL negli ultimi 15 minuti — rischio per i backup e il point-in-time recovery."
|
||||
|
||||
- alert: CNPGBackupFailed
|
||||
expr: cnpg_collector_last_failed_backup_timestamp > cnpg_collector_last_available_backup_timestamp
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Ultimo backup fallito per {{ $labels.namespace }}/{{ $labels.pod }}"
|
||||
|
||||
- alert: CNPGNoRecentBackup
|
||||
expr: (time() - cnpg_collector_last_available_backup_timestamp) > 86400
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Nessun backup riuscito nelle ultime 24h per {{ $labels.namespace }}/{{ $labels.pod }}"
|
||||
|
||||
- alert: CNPGConnectionsNearLimit
|
||||
expr: (cnpg_backends_total / cnpg_pg_settings_setting{name="max_connections"}) * 100 > 85
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Connessioni Postgres vicine al limite su {{ $labels.pod }}"
|
||||
description: "Oltre l'85% di max_connections in uso."
|
||||
|
||||
# Nota: i nomi esatti delle metriche cnpg_* possono variare leggermente
|
||||
# tra versioni dell'operatore CNPG. Verifica sempre su
|
||||
# http://localhost:9090/graph con `{__name__=~"cnpg_.+"}` dopo il
|
||||
# port-forward a Prometheus, e allinea le espressioni se necessario:
|
||||
# kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090
|
||||
105
add-on/monitoring/files/04-loki-alloy-values.yaml
Normal file
105
add-on/monitoring/files/04-loki-alloy-values.yaml
Normal file
@@ -0,0 +1,105 @@
|
||||
# =====================================================================
|
||||
# Loki + Grafana Alloy — log aggregation
|
||||
#
|
||||
# Loki:
|
||||
# helm repo add grafana https://grafana.github.io/helm-charts
|
||||
# helm repo update
|
||||
# helm install loki grafana/loki -n monitoring -f loki-values.yaml
|
||||
#
|
||||
# Alloy (raccolta log, DaemonSet su ogni nodo RKE2):
|
||||
# helm install alloy grafana/alloy -n monitoring -f alloy-values.yaml
|
||||
#
|
||||
# Questo file contiene ENTRAMBI i set di values, separati da "---";
|
||||
# salvali in due file distinti se preferisci due comandi helm separati.
|
||||
# =====================================================================
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# loki-values.yaml (modalità single-binary, adatta a partire in piccolo;
|
||||
# per HA/produzione a lungo termine valutare la modalità "simple scalable"
|
||||
# con backend object storage S3-compatibile, es. MinIO)
|
||||
# ---------------------------------------------------------------------
|
||||
deploymentMode: SingleBinary
|
||||
|
||||
loki:
|
||||
auth_enabled: false
|
||||
commonConfig:
|
||||
replication_factor: 1
|
||||
storage:
|
||||
type: filesystem
|
||||
|
||||
singleBinary:
|
||||
replicas: 1
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: longhorn # <-- CAMBIA con la tua storageClass
|
||||
size: 50Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
|
||||
# Disabilita i componenti "scalable" non necessari in modalità SingleBinary
|
||||
read:
|
||||
replicas: 0
|
||||
write:
|
||||
replicas: 0
|
||||
backend:
|
||||
replicas: 0
|
||||
|
||||
gateway:
|
||||
enabled: false
|
||||
|
||||
test:
|
||||
enabled: false
|
||||
|
||||
---
|
||||
# ---------------------------------------------------------------------
|
||||
# alloy-values.yaml (DaemonSet che raccoglie log da tutti i nodi/pod
|
||||
# e li invia a Loki)
|
||||
# ---------------------------------------------------------------------
|
||||
alloy:
|
||||
configMap:
|
||||
content: |
|
||||
discovery.kubernetes "pods" {
|
||||
role = "pod"
|
||||
}
|
||||
|
||||
discovery.relabel "pods" {
|
||||
targets = discovery.kubernetes.pods.targets
|
||||
|
||||
rule {
|
||||
source_labels = ["__meta_kubernetes_namespace"]
|
||||
target_label = "namespace"
|
||||
}
|
||||
rule {
|
||||
source_labels = ["__meta_kubernetes_pod_name"]
|
||||
target_label = "pod"
|
||||
}
|
||||
rule {
|
||||
source_labels = ["__meta_kubernetes_pod_container_name"]
|
||||
target_label = "container"
|
||||
}
|
||||
}
|
||||
|
||||
loki.source.kubernetes "pods" {
|
||||
targets = discovery.relabel.pods.output
|
||||
forward_to = [loki.write.default.receiver]
|
||||
}
|
||||
|
||||
loki.write "default" {
|
||||
endpoint {
|
||||
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
|
||||
}
|
||||
}
|
||||
|
||||
controller:
|
||||
type: daemonset
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
62
add-on/monitoring/files/05-alertmanager-config.yaml
Normal file
62
add-on/monitoring/files/05-alertmanager-config.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
# =====================================================================
|
||||
# AlertmanagerConfig — routing alert per severità
|
||||
# Applica con: kubectl apply -f 05-alertmanager-config.yaml
|
||||
#
|
||||
# Richiede i Secret con le credenziali (webhook Slack, integration key
|
||||
# PagerDuty) creati separatamente, es.:
|
||||
#
|
||||
# kubectl create secret generic alertmanager-slack \
|
||||
# -n monitoring --from-literal=url='https://hooks.slack.com/services/XXX/YYY/ZZZ'
|
||||
#
|
||||
# kubectl create secret generic alertmanager-pagerduty \
|
||||
# -n monitoring --from-literal=serviceKey='YOUR_PAGERDUTY_INTEGRATION_KEY'
|
||||
# =====================================================================
|
||||
|
||||
apiVersion: monitoring.coreos.com/v1alpha1
|
||||
kind: AlertmanagerConfig
|
||||
metadata:
|
||||
name: routing-config
|
||||
namespace: monitoring
|
||||
labels:
|
||||
# deve matchare alertmanagerConfigSelector del chart
|
||||
# (di default kube-prometheus-stack seleziona per release label)
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
route:
|
||||
groupBy: ["alertname", "namespace"]
|
||||
groupWait: 30s
|
||||
groupInterval: 5m
|
||||
repeatInterval: 4h
|
||||
receiver: "slack-default"
|
||||
routes:
|
||||
- matchers:
|
||||
- name: severity
|
||||
value: critical
|
||||
matchType: "="
|
||||
receiver: "pagerduty-critical"
|
||||
continue: true # invia ANCHE a slack-default per visibilità
|
||||
- matchers:
|
||||
- name: severity
|
||||
value: warning
|
||||
matchType: "="
|
||||
receiver: "slack-default"
|
||||
|
||||
receivers:
|
||||
- name: "slack-default"
|
||||
slackConfigs:
|
||||
- apiURL:
|
||||
name: alertmanager-slack
|
||||
key: url
|
||||
channel: "#alerts-k8s"
|
||||
sendResolved: true
|
||||
title: '{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}'
|
||||
text: '{{ "{{" }} .CommonAnnotations.description {{ "}}" }}'
|
||||
|
||||
- name: "pagerduty-critical"
|
||||
pagerdutyConfigs:
|
||||
- serviceKey:
|
||||
name: alertmanager-pagerduty
|
||||
key: serviceKey
|
||||
severity: "critical"
|
||||
description: '{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}'
|
||||
sendResolved: true
|
||||
121
add-on/monitoring/files/README.md
Normal file
121
add-on/monitoring/files/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Stack di monitoring/alerting per RKE2 — CNPG come verticale iniziale
|
||||
|
||||
Stack generico, installato senza passare da Rancher Manager: kube-prometheus-stack
|
||||
(Prometheus Operator + Prometheus + Alertmanager + Grafana + node-exporter +
|
||||
kube-state-metrics) + Loki/Alloy per i log + monitoring dedicato per CloudNativePG.
|
||||
|
||||
## Ordine di installazione
|
||||
|
||||
1. **Namespace + kube-prometheus-stack**
|
||||
```bash
|
||||
kubectl create namespace monitoring
|
||||
|
||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
helm repo add grafana https://grafana.github.io/helm-charts
|
||||
helm repo update
|
||||
|
||||
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
|
||||
-n monitoring \
|
||||
-f 01-kube-prometheus-stack-values.yaml
|
||||
```
|
||||
|
||||
kube-prometheus-stack has been installed. Check its status by running:
|
||||
kubectl --namespace monitoring get pods -l "release=kube-prometheus-stack"
|
||||
|
||||
Get Grafana 'admin' user password by running:
|
||||
|
||||
kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
|
||||
|
||||
Access Grafana local instance:
|
||||
|
||||
export POD_NAME=$(kubectl --namespace monitoring get pod -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=kube-prometheus-stack" -oname)
|
||||
kubectl --namespace monitoring port-forward $POD_NAME 3000
|
||||
|
||||
Get your grafana admin user password by running:
|
||||
|
||||
kubectl get secret --namespace monitoring -l app.kubernetes.io/component=admin-secret -o jsonpath="{.items[0].data.admin-password}" | base64 --decode ; echo
|
||||
|
||||
|
||||
Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
|
||||
|
||||
kubectl run -it --rm debug --image=curlimages/curl -n monitoring --restart=Never -- curl -v http://kube-prometheus-stack-grafana.monitoring.svc.cluster.local:80/
|
||||
|
||||
|
||||
|
||||
|
||||
2. **Monitoring CNPG** (dopo che l'operatore CNPG è già installato)
|
||||
```bash
|
||||
kubectl apply -f 02-cnpg-monitoring.yaml
|
||||
```
|
||||
|
||||
3. **Alert rules** (infra + CNPG)
|
||||
```bash
|
||||
kubectl apply -f 03-alert-rules.yaml
|
||||
```
|
||||
|
||||
4. **Loki + Alloy** (log aggregation) — dividi il file in due se preferisci
|
||||
comandi helm separati (`loki-values.yaml` / `alloy-values.yaml`)
|
||||
```bash
|
||||
helm install loki grafana/loki -n monitoring -f 04-loki-alloy-values.yaml
|
||||
helm install alloy grafana/alloy -n monitoring -f 04-loki-alloy-values.yaml
|
||||
```
|
||||
|
||||
5. **Alertmanager routing** — crea prima i Secret con le credenziali reali,
|
||||
poi applica la config
|
||||
```bash
|
||||
kubectl create secret generic alertmanager-slack \
|
||||
-n monitoring --from-literal=url='<SLACK_WEBHOOK_URL>'
|
||||
kubectl create secret generic alertmanager-pagerduty \
|
||||
-n monitoring --from-literal=serviceKey='<PAGERDUTY_KEY>'
|
||||
|
||||
kubectl apply -f 05-alertmanager-config.yaml
|
||||
```
|
||||
|
||||
## Cose da personalizzare prima di applicare in un cluster reale
|
||||
|
||||
- **storageClassName** in tutti i file (`longhorn` è solo un placeholder —
|
||||
sostituisci con la storageClass effettivamente disponibile sul tuo RKE2,
|
||||
verificabile con `kubectl get storageclass`).
|
||||
- **adminPassword** di Grafana nel file 01 — spostalo in un Secret,
|
||||
non lasciarlo in chiaro nei values in produzione.
|
||||
- **Nomi metriche CNPG** — verifica che corrispondano alla versione esatta
|
||||
dell'operatore CNPG installata (vedi nota in fondo al file 03).
|
||||
- **Dashboard CNPG** — il ConfigMap nel file 02 è un placeholder; scarica
|
||||
il JSON reale da https://grafana.com/grafana/dashboards/20417 e
|
||||
sostituiscilo, oppure importa manualmente da Grafana UI (Dashboards →
|
||||
Import → ID 20417).
|
||||
- **Control-plane RKE2** (kubeEtcd/kubeControllerManager/kubeScheduler nel
|
||||
file 01) — porte ed endpoint possono variare in base a come RKE2 è
|
||||
configurato (hardening CIS, profili custom). Verifica con
|
||||
`kubectl get pods -n kube-system` quali pod statici girano e su quali
|
||||
porte, e correggi se necessario.
|
||||
- **AlertmanagerConfig selector** — deve combaciare con
|
||||
`alertmanagerConfigSelector` impostato dal chart (di default seleziona
|
||||
per label `release: <helm-release-name>`); se hai chiamato la release
|
||||
diversamente da `kube-prometheus-stack`, aggiorna la label.
|
||||
|
||||
## Verifiche post-installazione
|
||||
|
||||
```bash
|
||||
# Prometheus targets (verifica che CNPG, node-exporter, ecc. siano UP)
|
||||
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090
|
||||
# -> http://localhost:9090/targets
|
||||
|
||||
# Grafana
|
||||
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
|
||||
# -> http://localhost:3000 (utente: admin, password: quella impostata)
|
||||
|
||||
# Alertmanager
|
||||
kubectl port-forward -n monitoring svc/kube-prometheus-stack-alertmanager 9093
|
||||
# -> http://localhost:9093
|
||||
```
|
||||
|
||||
## Estensioni future (non incluse in questa prima fase)
|
||||
|
||||
- **blackbox_exporter** — probe HTTP/TCP/TLS per endpoint applicativi
|
||||
esterni e monitoraggio scadenza certificati (le regole `TLSCert*` nel
|
||||
file 03 presuppongono questo exporter già installato).
|
||||
- **Tempo + OpenTelemetry Collector** — tracing distribuito quando ci
|
||||
saranno più verticali applicative da correlare.
|
||||
- **Thanos/Mimir** — retention lunga e vista multi-cluster, se in futuro
|
||||
servirà storicizzare oltre i 15gg locali o aggregare più cluster RKE2.
|
||||
38
add-on/monitoring/grafana-service.yaml
Normal file
38
add-on/monitoring/grafana-service.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kube-prometheus-stack-grafana
|
||||
namespace: monitoring
|
||||
labels:
|
||||
app.kubernetes.io/instance: kube-prometheus-stack
|
||||
app.kubernetes.io/name: grafana
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/instance: kube-prometheus-stack
|
||||
app.kubernetes.io/name: grafana
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: grafana
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: monitoring
|
||||
spec:
|
||||
hostnames:
|
||||
- grafana.internal
|
||||
parentRefs:
|
||||
- name: main-gateway
|
||||
namespace: nginx-gateway
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: kube-prometheus-stack-grafana
|
||||
port: 80
|
||||
0
add-on/monitoring/grafana.yaml
Normal file
0
add-on/monitoring/grafana.yaml
Normal file
438
exportManofest.sh
Normal file
438
exportManofest.sh
Normal file
@@ -0,0 +1,438 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# export-k8s-manifests.sh
|
||||
#
|
||||
# Ricostruisce i manifest YAML dichiarativi delle risorse installate
|
||||
# su un cluster Kubernetes, per una lista di namespace data in input.
|
||||
#
|
||||
# Rimuove i campi "runtime" (status, managedFields, resourceVersion, uid,
|
||||
# creationTimestamp, ownerReferences generate da controller, ecc.) in modo
|
||||
# da produrre YAML riapplicabili con `kubectl apply -f`.
|
||||
#
|
||||
# Uso:
|
||||
# ./export-k8s-manifests.sh -n ns1,ns2,ns3 [-o output_dir] [-c kubeconfig] [-k context]
|
||||
# ./export-k8s-manifests.sh -f namespaces.txt [-o output_dir]
|
||||
#
|
||||
# Dipendenze:
|
||||
# - kubectl (obbligatorio)
|
||||
# - yq (v4+, https://github.com/mikefarah/yq) — consigliato per pulizia campi
|
||||
# Se assente, viene usato un fallback con python3.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Default
|
||||
# ------------------------------------------------------------------
|
||||
OUTPUT_DIR="./k8s-export-$(date +%Y%m%d-%H%M%S)"
|
||||
NAMESPACES=()
|
||||
NAMESPACE_FILE=""
|
||||
KUBECONFIG_OPT=()
|
||||
CONTEXT_OPT=()
|
||||
INCLUDE_SECRETS=false
|
||||
|
||||
# Tipi di risorsa namespaced da esportare.
|
||||
# Personalizza questa lista secondo le tue esigenze.
|
||||
RESOURCE_TYPES=(
|
||||
configmap
|
||||
secret
|
||||
service
|
||||
serviceaccount
|
||||
deployment
|
||||
statefulset
|
||||
daemonset
|
||||
replicaset
|
||||
job
|
||||
cronjob
|
||||
ingress
|
||||
networkpolicy
|
||||
poddisruptionbudget
|
||||
horizontalpodautoscaler
|
||||
persistentvolumeclaim
|
||||
role
|
||||
rolebinding
|
||||
|
||||
# --- API Gateway (namespaced) -------------------------------------
|
||||
# Gateway API standard (gateway.networking.k8s.io)
|
||||
gateway
|
||||
httproute
|
||||
grpcroute
|
||||
tcproute
|
||||
tlsroute
|
||||
referencegrant
|
||||
# Istio
|
||||
virtualservice
|
||||
destinationrule
|
||||
# Traefik
|
||||
ingressroute
|
||||
middleware
|
||||
# Kong
|
||||
kongingress
|
||||
kongplugin
|
||||
kongconsumer
|
||||
# APISIX
|
||||
apisixroute
|
||||
apisixpluginconfig
|
||||
# NGINX Gateway Fabric — CRD di estensione (gateway.nginx.org)
|
||||
# Le risorse Gateway API standard (gateway, httproute, gatewayclass, ecc.)
|
||||
# sono già coperte sopra; queste sono le estensioni specifiche NGF per
|
||||
# configurazioni non esprimibili nello standard Gateway API.
|
||||
nginxproxy
|
||||
clientsettingspolicy
|
||||
observabilitypolicy
|
||||
upstreamsettingspolicy
|
||||
snippetsfilter
|
||||
|
||||
# CRD comuni — decommenta/aggiungi secondo gli operator installati
|
||||
cluster.postgresql.cnpg.io # CloudNativePG Cluster
|
||||
scheduledbackup.postgresql.cnpg.io
|
||||
certificate.cert-manager.io
|
||||
)
|
||||
|
||||
# Tipi di risorsa CLUSTER-SCOPED da esportare.
|
||||
# Vengono filtrate per pertinenza ai namespace indicati dove possibile
|
||||
# (es. ClusterRoleBinding con subject in uno dei namespace, PV con claim
|
||||
# in uno dei namespace). Le risorse "globali" (StorageClass, GatewayClass,
|
||||
# IngressClass, PriorityClass) vengono esportate sempre, essendo poche
|
||||
# e prive di legame diretto con un namespace.
|
||||
CLUSTER_RESOURCE_TYPES_FILTERED=(
|
||||
clusterrolebinding
|
||||
persistentvolume
|
||||
)
|
||||
CLUSTER_RESOURCE_TYPES_GLOBAL=(
|
||||
storageclass
|
||||
priorityclass
|
||||
# API Gateway (cluster-scoped)
|
||||
gatewayclass
|
||||
ingressclass
|
||||
# Da valutare se esportare sempre (potenzialmente numerose/rumorose):
|
||||
# clusterrole
|
||||
# customresourcedefinition
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Parsing argomenti
|
||||
# ------------------------------------------------------------------
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Uso: $0 -n ns1,ns2,ns3 [opzioni]
|
||||
$0 -f namespaces.txt [opzioni]
|
||||
|
||||
Opzioni:
|
||||
-n <ns1,ns2,...> Lista namespace separati da virgola
|
||||
-f <file> File con un namespace per riga (alternativo a -n)
|
||||
-o <dir> Directory di output (default: ${OUTPUT_DIR})
|
||||
-c <kubeconfig> Path al kubeconfig da usare
|
||||
-k <context> Nome del context kubectl da usare
|
||||
-s Includi anche i Secret (default: esclusi per sicurezza)
|
||||
-h Mostra questo help
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "n:f:o:c:k:sh" opt; do
|
||||
case "$opt" in
|
||||
n) IFS=',' read -r -a NAMESPACES <<< "$OPTARG" ;;
|
||||
f) NAMESPACE_FILE="$OPTARG" ;;
|
||||
o) OUTPUT_DIR="$OPTARG" ;;
|
||||
c) KUBECONFIG_OPT=(--kubeconfig "$OPTARG") ;;
|
||||
k) CONTEXT_OPT=(--context "$OPTARG") ;;
|
||||
s) INCLUDE_SECRETS=true ;;
|
||||
h) usage ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$NAMESPACE_FILE" ]]; then
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
NAMESPACES+=("$line")
|
||||
done < "$NAMESPACE_FILE"
|
||||
fi
|
||||
|
||||
if [[ ${#NAMESPACES[@]} -eq 0 ]]; then
|
||||
echo "Errore: nessun namespace specificato (usa -n o -f)" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ "$INCLUDE_SECRETS" == false ]]; then
|
||||
RESOURCE_TYPES=("${RESOURCE_TYPES[@]/secret}")
|
||||
fi
|
||||
|
||||
KCTL=(kubectl "${KUBECONFIG_OPT[@]}" "${CONTEXT_OPT[@]}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verifica dipendenze
|
||||
# ------------------------------------------------------------------
|
||||
command -v kubectl >/dev/null 2>&1 || { echo "Errore: kubectl non trovato" >&2; exit 1; }
|
||||
|
||||
USE_YQ=false
|
||||
if command -v yq >/dev/null 2>&1; then
|
||||
USE_YQ=true
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
USE_YQ=false
|
||||
else
|
||||
echo "Errore: serve 'yq' oppure 'python3' per la pulizia dei manifest" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verifica connessione al cluster..."
|
||||
"${KCTL[@]}" cluster-info >/dev/null 2>&1 || { echo "Errore: impossibile contattare il cluster" >&2; exit 1; }
|
||||
CLUSTER_NAME=$("${KCTL[@]}" config current-context 2>/dev/null || echo "unknown-cluster")
|
||||
echo "Cluster context: ${CLUSTER_NAME}"
|
||||
echo "Output dir: ${OUTPUT_DIR}"
|
||||
echo "Namespace da esportare: ${NAMESPACES[*]}"
|
||||
echo "Secret inclusi: ${INCLUDE_SECRETS}"
|
||||
echo
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Gli script Python vengono scritti su file temporanei perché in bash
|
||||
# `python3 - <<'HEREDOC'` sovrascrive lo stdin del processo con l'heredoc,
|
||||
# rendendo impossibile leggere i dati dalla pipeline (il fd 0 è già consumato
|
||||
# prima che il codice Python arrivi a sys.stdin.read()).
|
||||
_PY_CLEAN=$(mktemp)
|
||||
_PY_FILTER=$(mktemp)
|
||||
trap 'rm -f "$_PY_CLEAN" "$_PY_FILTER"' EXIT
|
||||
|
||||
if [[ "$USE_YQ" == false ]]; then
|
||||
cat > "$_PY_CLEAN" << 'PYCLEAN'
|
||||
import sys, yaml
|
||||
doc = yaml.safe_load(sys.stdin)
|
||||
if doc is None:
|
||||
sys.exit(0)
|
||||
doc.pop("status", None)
|
||||
md = doc.get("metadata", {})
|
||||
for f in ("uid", "resourceVersion", "generation", "creationTimestamp",
|
||||
"selfLink", "managedFields", "ownerReferences"):
|
||||
md.pop(f, None)
|
||||
ann = md.get("annotations")
|
||||
if ann:
|
||||
ann.pop("kubectl.kubernetes.io/last-applied-configuration", None)
|
||||
ann.pop("deployment.kubernetes.io/revision", None)
|
||||
if not ann:
|
||||
md.pop("annotations", None)
|
||||
spec = doc.get("spec", {})
|
||||
spec.pop("clusterIP", None)
|
||||
spec.pop("clusterIPs", None)
|
||||
tmpl = spec.get("template", {})
|
||||
if isinstance(tmpl, dict):
|
||||
tmpl.get("metadata", {}).pop("creationTimestamp", None)
|
||||
print(yaml.dump(doc, sort_keys=False, default_flow_style=False), end="")
|
||||
PYCLEAN
|
||||
|
||||
cat > "$_PY_FILTER" << 'PYFILTER'
|
||||
import json, os, sys
|
||||
mode = sys.argv[1]
|
||||
data = json.load(sys.stdin)
|
||||
ns_set = set(os.environ.get("NS_CSV", "").split())
|
||||
for item in data.get("items", []):
|
||||
if mode == "subjects":
|
||||
subs = item.get("subjects") or []
|
||||
match = any(s.get("namespace") in ns_set for s in subs)
|
||||
else:
|
||||
claim = (item.get("spec") or {}).get("claimRef") or {}
|
||||
match = claim.get("namespace") in ns_set
|
||||
if match:
|
||||
print(item["metadata"]["name"])
|
||||
PYFILTER
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Funzione di pulizia dei campi runtime
|
||||
# ------------------------------------------------------------------
|
||||
clean_manifest_yq() {
|
||||
# Rimuove campi generati dal cluster, mantenendo lo spec dichiarativo
|
||||
yq eval '
|
||||
del(.status) |
|
||||
del(.metadata.uid) |
|
||||
del(.metadata.resourceVersion) |
|
||||
del(.metadata.generation) |
|
||||
del(.metadata.creationTimestamp) |
|
||||
del(.metadata.selfLink) |
|
||||
del(.metadata.managedFields) |
|
||||
del(.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration") |
|
||||
del(.metadata.annotations."deployment.kubernetes.io/revision") |
|
||||
del(.metadata.ownerReferences) |
|
||||
del(.spec.clusterIP) |
|
||||
del(.spec.clusterIPs) |
|
||||
del(.spec.template.metadata.creationTimestamp)
|
||||
' -
|
||||
}
|
||||
|
||||
clean_manifest_python() {
|
||||
# Usa il file temporaneo creato all'avvio; l'heredoc inline non funziona
|
||||
# in pipeline perché sovrascrive lo stdin del processo.
|
||||
python3 "$_PY_CLEAN"
|
||||
}
|
||||
|
||||
clean_manifest() {
|
||||
if [[ "$USE_YQ" == true ]]; then
|
||||
clean_manifest_yq
|
||||
else
|
||||
clean_manifest_python
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Export per namespace / kind / risorsa
|
||||
# ------------------------------------------------------------------
|
||||
SUMMARY_FILE="${OUTPUT_DIR}/EXPORT_SUMMARY.md"
|
||||
{
|
||||
echo "# Export manifest Kubernetes"
|
||||
echo
|
||||
echo "- Cluster context: \`${CLUSTER_NAME}\`"
|
||||
echo "- Data export: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo "- Namespace: ${NAMESPACES[*]}"
|
||||
echo
|
||||
echo "| Namespace | Kind | Nome | File |"
|
||||
echo "|---|---|---|---|"
|
||||
} > "$SUMMARY_FILE"
|
||||
|
||||
for ns in "${NAMESPACES[@]}"; do
|
||||
echo "== Namespace: ${ns} =="
|
||||
|
||||
if ! "${KCTL[@]}" get namespace "$ns" >/dev/null 2>&1; then
|
||||
echo " ATTENZIONE: namespace '${ns}' non trovato, salto." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
NS_DIR="${OUTPUT_DIR}/${ns}"
|
||||
mkdir -p "$NS_DIR"
|
||||
|
||||
for kind in "${RESOURCE_TYPES[@]}"; do
|
||||
[[ -z "$kind" ]] && continue
|
||||
|
||||
# Il controllo api-resources è omesso: kubectl get <kind> 2>/dev/null || true
|
||||
# gestisce già i resource type non esistenti restituendo NAMES vuoto.
|
||||
NAMES=$("${KCTL[@]}" -n "$ns" get "$kind" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
|
||||
[[ -z "$NAMES" ]] && continue
|
||||
|
||||
KIND_DIR="${NS_DIR}/${kind}"
|
||||
mkdir -p "$KIND_DIR"
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
|
||||
# Salta le risorse generate automaticamente (es. default token secret,
|
||||
# ReplicaSet gestiti da Deployment, Pod gestiti da controller superiori)
|
||||
if [[ "$kind" == "secret" && "$name" =~ ^default-token- ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
OUT_FILE="${KIND_DIR}/${name}.yaml"
|
||||
echo " -> ${kind}/${name}"
|
||||
|
||||
if "${KCTL[@]}" -n "$ns" get "$kind" "$name" -o yaml 2>/dev/null | clean_manifest > "$OUT_FILE"; then
|
||||
echo "| ${ns} | ${kind} | ${name} | \`${ns}/${kind}/${name}.yaml\` |" >> "$SUMMARY_FILE"
|
||||
else
|
||||
echo " ATTENZIONE: export fallito per ${kind}/${name}" >&2
|
||||
rm -f "$OUT_FILE"
|
||||
fi
|
||||
done <<< "$NAMES"
|
||||
done
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Export risorse cluster-scoped
|
||||
# ------------------------------------------------------------------
|
||||
echo
|
||||
echo "== Risorse cluster-scoped =="
|
||||
|
||||
CLUSTER_DIR="${OUTPUT_DIR}/_cluster-scoped"
|
||||
|
||||
# Cache unica delle risorse disponibili (evita N chiamate a api-resources)
|
||||
_AVAIL_NAMES=$("${KCTL[@]}" api-resources --no-headers 2>/dev/null | awk '{print tolower($1)}' || true)
|
||||
_AVAIL_KINDS=$("${KCTL[@]}" api-resources --no-headers 2>/dev/null | awk '{print tolower($NF)}' || true)
|
||||
|
||||
resource_type_exists() {
|
||||
local kind="$1"
|
||||
# Controlla sia la colonna NAME (plurale) sia la colonna KIND (singolare) del
|
||||
# risultato di api-resources per gestire plurali irregolari (es. ingress→ingresses).
|
||||
echo "$_AVAIL_NAMES" | grep -qx "${kind}s" \
|
||||
|| echo "$_AVAIL_KINDS" | grep -qx "$kind"
|
||||
}
|
||||
|
||||
export_cluster_resource() {
|
||||
local kind="$1"
|
||||
local name="$2"
|
||||
local kind_dir="${CLUSTER_DIR}/${kind}"
|
||||
mkdir -p "$kind_dir"
|
||||
local out_file="${kind_dir}/${name}.yaml"
|
||||
|
||||
echo " -> ${kind}/${name}"
|
||||
if "${KCTL[@]}" get "$kind" "$name" -o yaml 2>/dev/null | clean_manifest > "$out_file"; then
|
||||
echo "| (cluster) | ${kind} | ${name} | \`_cluster-scoped/${kind}/${name}.yaml\` |" >> "$SUMMARY_FILE"
|
||||
else
|
||||
echo " ATTENZIONE: export fallito per ${kind}/${name}" >&2
|
||||
rm -f "$out_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Risorse globali: esportate sempre, senza filtro namespace ---
|
||||
for kind in "${CLUSTER_RESOURCE_TYPES_GLOBAL[@]}"; do
|
||||
[[ -z "$kind" ]] && continue
|
||||
resource_type_exists "$kind" || continue
|
||||
|
||||
NAMES=$("${KCTL[@]}" get "$kind" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
|
||||
[[ -z "$NAMES" ]] && continue
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
export_cluster_resource "$kind" "$name"
|
||||
done <<< "$NAMES"
|
||||
done
|
||||
|
||||
# Filtra gli item di un array JSON kubectl (-o json) tenendo solo quelli
|
||||
# per cui `jq_path` (letto tramite kubectl jsonpath-like con yq/python) ha
|
||||
# un valore presente nella lista NAMESPACES. Stampa i nomi corrispondenti.
|
||||
# $1 = json completo (stdin) $2 = "subjects" | "claimref"
|
||||
filter_by_namespace_field() {
|
||||
local mode="$1"
|
||||
if [[ "$USE_YQ" == true ]]; then
|
||||
if [[ "$mode" == "subjects" ]]; then
|
||||
yq eval '.items[] | select((.subjects // []) | any_c(.namespace == env(NS_MATCH))) | .metadata.name' -
|
||||
else
|
||||
yq eval '.items[] | select(.spec.claimRef.namespace == env(NS_MATCH)) | .metadata.name' -
|
||||
fi
|
||||
else
|
||||
python3 "$_PY_FILTER" "$mode"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- ClusterRoleBinding: solo quelli con subject in uno dei namespace indicati ---
|
||||
if resource_type_exists clusterrolebinding; then
|
||||
MATCHED_CRB=""
|
||||
for ns in "${NAMESPACES[@]}"; do
|
||||
RESULT=$("${KCTL[@]}" get clusterrolebinding -o json 2>/dev/null \
|
||||
| NS_CSV="${NAMESPACES[*]}" NS_MATCH="$ns" filter_by_namespace_field subjects || true)
|
||||
MATCHED_CRB="${MATCHED_CRB}
|
||||
${RESULT}"
|
||||
done
|
||||
MATCHED_CRB=$(echo "$MATCHED_CRB" | sort -u | sed '/^$/d')
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
export_cluster_resource clusterrolebinding "$name"
|
||||
done <<< "$MATCHED_CRB"
|
||||
fi
|
||||
|
||||
# --- PersistentVolume: solo quelli con claimRef in uno dei namespace indicati ---
|
||||
if resource_type_exists persistentvolume; then
|
||||
MATCHED_PV=""
|
||||
for ns in "${NAMESPACES[@]}"; do
|
||||
RESULT=$("${KCTL[@]}" get persistentvolume -o json 2>/dev/null \
|
||||
| NS_CSV="${NAMESPACES[*]}" NS_MATCH="$ns" filter_by_namespace_field claimref || true)
|
||||
MATCHED_PV="${MATCHED_PV}
|
||||
${RESULT}"
|
||||
done
|
||||
MATCHED_PV=$(echo "$MATCHED_PV" | sort -u | sed '/^$/d')
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
export_cluster_resource persistentvolume "$name"
|
||||
done <<< "$MATCHED_PV"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Export completato in: ${OUTPUT_DIR}"
|
||||
echo "Riepilogo: ${SUMMARY_FILE}"
|
||||
@@ -4,25 +4,31 @@ set -euo pipefail
|
||||
# Uso: ./addlistener.sh [env] [properties_file]
|
||||
# env e' ignorato: endpoint viene sempre letto dalla chiave "endpoint".
|
||||
ENVIRONMENT="${1:-dev}"
|
||||
PROPERTIES_FILE="${2:-env/${ENVIRONMENT}/values.env}"
|
||||
# Hardening per evitare errori con set -u in esecuzioni/sourcing anomali
|
||||
: "${PROPERTIES_FILE:=env/${ENVIRONMENT}/values.env}"
|
||||
ENV_DIR="env/${ENVIRONMENT}"
|
||||
|
||||
if [[ -n "$ENVIRONMENT" ]]; then
|
||||
echo "Ambiente richiesto: $ENVIRONMENT (chiave usata: endpoint)"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$ENV_DIR" ]]; then
|
||||
echo "Warning: directory non trovata: $ENV_DIR" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f "${PROPERTIES_FILE:-}" ]]; then
|
||||
# Estrae endpoint ignorando commenti e spazi, supportando anche endpoint = valore
|
||||
endpoint_raw="$({ grep -E '^[[:space:]]*endpoint[[:space:]]*=' "${PROPERTIES_FILE}" | tail -n1 || true; } | sed -E 's/^[[:space:]]*endpoint[[:space:]]*=[[:space:]]*//')"
|
||||
elif [[ -d "$ENV_DIR" ]]; then
|
||||
# Estrae endpoint da qualsiasi file .env nella directory dell'ambiente
|
||||
endpoint_raw="$({ grep -r '^[[:space:]]*endpoint[[:space:]]*=' "$ENV_DIR" 2>/dev/null | head -n1 || true; } | sed -E 's/^[^:]*:[[:space:]]*endpoint[[:space:]]*=[[:space:]]*//')"
|
||||
else
|
||||
echo "Warning: file non trovato: ${PROPERTIES_FILE:-<non impostato>} e directory non trovata: $ENV_DIR" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Rimuove eventuali virgolette e spazi ai bordi
|
||||
endpoint="$(echo "$endpoint_raw" | sed -E 's/^[[:space:]"\x27]+//; s/[[:space:]"\x27]+$//')"
|
||||
|
||||
if [[ -z "$endpoint" ]]; then
|
||||
echo "La chiave endpoint non e valorizzata in $PROPERTIES_FILE" >&2
|
||||
echo "La chiave endpoint non e valorizzata in ${PROPERTIES_FILE:-<non impostato>}" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user