Compare commits

...
10 Commits
Author SHA1 Message Date
alessandro 23f276417f Create IDP-AgentPlatform.pdf 2026-09-27 11:23:06 +02:00
alessandro ff808293aa ff 2026-09-27 11:02:10 +02:00
alessandro 9c0189e08e Merge branch 'main' of https://git.pigreco66.it/gitadmin/italiadatacenter 2026-09-21 10:00:14 +02:00
alessandro d190719c5e varie 2026-09-21 10:00:05 +02:00
alessandro barucci d41d7f40cc Create IDP-AgentPlatform-formattato.docx 2026-09-20 19:39:12 +02:00
alessandro barucci 3a0853dd4c Update IDP-AgentPlatform.docx 2026-09-20 19:34:03 +02:00
alessandro barucci 63649a642a varie 2026-09-20 19:26:05 +02:00
alessandro barucci 48f3f1ff22 varie 2026-09-16 09:25:31 +02:00
alessandro barucci f8ffa9e065 gitops 2026-09-12 19:21:41 +02:00
alessandro 58a443a076 varie 2026-09-11 15:01:40 +02:00
41 changed files with 4316 additions and 42 deletions
@@ -0,0 +1,21 @@
# Dockerfile di esempio — containerizza un flow esportato da Langflow
# come Python app standalone, da usare nel percorso BYO (Esempio B).
FROM python:3.11-slim
WORKDIR /app
# requirements.txt generato/estratto insieme all'export del flow
# (langflow, langgraph-checkpointer-kagent, e le dipendenze del tuo flow)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Codice esportato dal builder low-code + eventuali customizzazioni
# del developer (es. integrazione kagentCheckpointer se usi LangGraph)
COPY . .
# L'agente deve esporre un endpoint compatibile A2A sulla porta
# dichiarata in byo.deployment.port dell'Agent CR
EXPOSE 8080
CMD ["python", "agent_server.py"]
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
# crea secret con langflow-secrets-setup.sh
# Poi installa con i valori personalizzati
helm install langflow-ide langflow/langflow-ide \
-f langflow-values.yaml \
-n langflow-team-alpha --create-namespace
@@ -0,0 +1,61 @@
# HTTPRoute (Gateway API) per Langflow IDE
# Presuppone un Gateway nginx già esistente nel cluster (nginx Gateway
# Fabric), referenziato come parentRef qui sotto.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: langflow-team-alpha
namespace: langflow-team-alpha
spec:
parentRefs:
- name: nginx-gateway # nome del Gateway condiviso — verifica con:
namespace: nginx-gateway # kubectl get gateway -A
sectionName: https # nome del listener HTTPS sul Gateway
hostnames:
- "langflow-team-alpha.pigreco66.it"
rules:
# UI/editor visuale (frontend) — porta 8080 come da doc Langflow
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: langflow-ide-frontend # verifica nome esatto: kubectl get svc -n langflow-team-alpha
port: 8080
# API backend, se vuoi esporla separatamente (es. per invocazione
# programmatica di un flow senza passare dall'editor) — porta 7860
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: langflow-ide-backend # verifica nome esatto: kubectl get svc -n langflow-team-alpha
port: 7860
---
# TLS: con Gateway API il certificato si referenzia sul Gateway stesso
# (non sull'HTTPRoute). Se il Gateway condiviso non ha già un listener
# per questo hostname, serve aggiungerlo lì, es.:
#
# apiVersion: gateway.networking.k8s.io/v1
# kind: Gateway
# metadata:
# name: nginx-gateway
# namespace: nginx-gateway
# spec:
# gatewayClassName: nginx
# listeners:
# - name: https
# protocol: HTTPS
# port: 443
# hostname: "*.pigreco66.it"
# tls:
# mode: Terminate
# certificateRefs:
# - name: wildcard-pigreco66-it-tls
# allowedRoutes:
# namespaces:
# from: All
@@ -0,0 +1,35 @@
# Creazione dei Secret richiesti da langflow-values.yaml
# Namespace di esempio: langflow-team-alpha (adatta al tuo tenant)
# 1. Secret connessione database Postgres
kubectl create secret generic langflow-db-secret \
-n langflow-team-alpha \
--from-literal=connection-string="postgresql://user:pass@host:5432/langflow"
# 2. Secret credenziali admin (superuser Langflow)
kubectl create secret generic langflow-admin-secret \
-n langflow-team-alpha \
--from-literal=username="admin" \
--from-literal=password="<password-sicura>"
# 3. Secret credenziali LLM — STESSO Secret già usato per kagent nel
# golden path di onboarding-team: se lo hai già creato per kagent
# in questo namespace, questo passaggio è già fatto, verifica solo
# che contenga le chiavi coi nomi attesi (vedi sotto).
#
# Se non esiste ancora, crealo così (aggiungi/rimuovi provider a
# seconda di cosa serve al team):
kubectl create secret generic llm-credentials \
-n langflow-team-alpha \
--from-literal=openai-api-key="sk-..." \
--from-literal=anthropic-api-key="sk-ant-..."
# Se il Secret esiste già (es. creato per kagent) e vuoi solo
# aggiungere/aggiornare una chiave senza ricrearlo da zero:
kubectl patch secret llm-credentials -n langflow-team-alpha \
--type=json \
-p='[{"op":"add","path":"/data/openai-api-key","value":"'$(echo -n "sk-..." | base64)'"}]'
# 4. Verifica che tutti i Secret siano presenti prima di installare/
# aggiornare la release Helm
kubectl get secrets -n langflow-team-alpha
@@ -0,0 +1,87 @@
# values.yaml — Langflow IDE, personalizzato per un namespace-tenant
# Uso: helm install langflow-ide langflow/langflow-ide -f values.yaml -n <namespace>
langflow:
backend:
image:
repository: langflowai/langflow
tag: "1.10.0" # fissa una versione esplicita, evita 'latest' in produzione
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 2Gi
# Variabili LLM/DB — usa Secret, mai valori in chiaro qui
env:
- name: LANGFLOW_DATABASE_URL
valueFrom:
secretKeyRef:
name: langflow-db-secret
key: connection-string
- name: LANGFLOW_SUPERUSER
valueFrom:
secretKeyRef:
name: langflow-admin-secret
key: username
- name: LANGFLOW_SUPERUSER_PASSWORD
valueFrom:
secretKeyRef:
name: langflow-admin-secret
key: password
# Rimuove automaticamente eventuali API key salvate nei flow prima
# di persisterli a DB — best practice di sicurezza, seconda linea
# di difesa oltre alle Global Variable qui sotto
- name: LANGFLOW_REMOVE_API_KEYS
value: "true"
# --- Pre-caricamento credenziali LLM come Global Variable ---
# Riusa lo stesso Secret 'llm-credentials' già previsto per kagent
# in fase di onboarding del team (vedi golden path Backstage).
# I developer trovano le chiavi già pronte nel dropdown Global
# Variable, senza mai vederne il valore reale.
- name: LANGFLOW_STORE_ENVIRONMENT_VARIABLES
value: "true"
- name: LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT
value: "OPENAI_API_KEY,ANTHROPIC_API_KEY"
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: openai-api-key
optional: true # non tutti i team useranno tutti i provider
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: anthropic-api-key
optional: true
frontend:
image:
repository: langflowai/langflow
tag: "1.10.0"
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Ingress classico DISABILITATO — l'esposizione avviene via Gateway API
# (nginx Gateway Fabric), vedi manifest separato langflow-httproute.yaml
ingress:
enabled: false
# Il Service del frontend resta ClusterIP (default del chart), sarà
# l'HTTPRoute a instradare il traffico dal Gateway verso questo Service
# Persistenza dei flow salvati (altrimenti persi al riavvio del pod)
persistence:
enabled: true
size: 5Gi
storageClassName: standard # adatta alla tua StorageClass
@@ -0,0 +1,166 @@
# =====================================================================
# ESEMPIO A — Agent dichiarativo nativo (kagent esegue direttamente)
# Caso d'uso: flow Langflow/Flowise semplice — un LLM + un paio di tool,
# nessuna logica custom complessa. Tradotto 1:1 in prompt + tool MCP.
# =====================================================================
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: k8s-troubleshooter
namespace: team-alpha
spec:
description: Agente di troubleshooting Kubernetes
type: Declarative
declarative:
modelConfig: gpt4o-config
systemMessage: |
Sei un agente di troubleshooting per Kubernetes...
a2aConfig:
skills:
- id: diagnose-pod-issue
name: Diagnose Pod Issue
description: Investiga problemi su pod/servizi Kubernetes
inputModes: ["text"]
outputModes: ["text"]
tags: ["kubernetes"]
tools:
- type: McpServer
mcpServer:
name: kagent-tool-server
kind: RemoteMCPServer
toolNames:
- k8s_get_pods
- k8s_describe_pod
- k8s_get_logs
---
apiVersion: kagent.dev/v1alpha1
kind: ModelConfig
metadata:
name: gpt4o-config
namespace: team-alpha
spec:
provider: OpenAI
model: gpt-4o
apiKeySecretRef:
name: llm-credentials
key: openai-api-key
# invocazione
Le 4 modalità di invocazione
1. Dashboard kagent (per gli utenti umani, uso interattivo)
bash
kagent dashboard
Apre una UI web dove cerchi k8s-troubleshooter nella lista e chatti direttamente — il modo più immediato per un developer che vuole testare l'agente senza altri strumenti.
2. CLI kagent (per script, pipeline, debug da terminale)
bash
kagent invoke --agent k8s-troubleshooter -n team-alpha \
--task "Il pod payment-service-7d9f in default sta crashando, investiga"
3. Chiamata diretta via A2A REST (per integrazione da altri sistemi/servizi)
Ogni Agent espone un endpoint A2A standard, con un "agent card" che ne descrive le capability:
bash
kubectl port-forward -n kagent svc/kagent-service 8083:8083
# scopri le capability dell'agente
curl localhost:8083/api/a2a/team-alpha/k8s-troubleshooter/.well-known/agent.json
# invocalo
curl -X POST localhost:8083/api/a2a/team-alpha/k8s-troubleshooter/ \
-H "Content-Type: application/json" \
-d '{"task": "Perché il pod payment-service sta in CrashLoopBackOff?"}'
Questa è la via che useresti se, ad esempio, un tuo servizio interno (o un'automazione CI/CD) deve invocare l'agente programmaticamente.
4. Canali chat (Slack/Teams/Discord/Telegram) — via AgentHarness CRD
Se vuoi che i developer interroghino l'agente direttamente da Slack invece che aprire la dashboard, kagent ha una CRD dedicata (AgentHarness) che fa da ponte:
yaml
apiVersion: kagent.dev/v1alpha2
kind: AgentHarness
metadata:
name: k8s-troubleshooter-slack
namespace: kagent
spec:
backend: hermes
modelConfigRef: default-model-config
channels:
- name: platform
type: slack
slack:
botToken:
valueFrom: {type: Secret, name: slack-tokens, key: bot-token}
appToken:
valueFrom: {type: Secret, name: slack-tokens, key: app-token}
Poi in Slack: /mykagent perché il pod X sta crashando? — il bot inoltra la richiesta all'agente via A2A e posta la risposta nel canale. Interessante per un'IDP perché è il canale più naturale per i developer, senza dover imparare dashboard o CLI dedicate.
# =====================================================================
# ESEMPIO B — Agent BYO (Bring Your Own), container esportato da
# Langflow/Flowise dopo containerizzazione custom.
# Caso d'uso: flow complesso con logica di stato/loop che il developer
# ha personalizzato oltre quello che il builder low-code esporta,
# quindi ha bisogno di pieno controllo del codice.
# =====================================================================
apiVersion: kagent.dev/v1alpha1
kind: Agent
metadata:
name: currency-exchange-agent
namespace: team-alpha
spec:
type: BYO
byo:
deployment:
image: ghcr.io/team-alpha/langflow-currency-agent:latest
workingDir: /app
env:
- name: GOOGLE_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: gemini-api-key
port: 8080 # porta su cui l'agente espone l'endpoint A2A
# Nota: kagent invoca questo agente via protocollo A2A, trattandolo
# come black box — nessuna decomposizione in prompt/tool separati,
# perché la logica vive interamente dentro l'immagine.
## Esempio Agent di Orchestrazione
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: incident-commander
namespace: team-alpha
spec:
type: Declarative
declarative:
systemMessage: |
Sei il coordinatore per la gestione incidenti. Quando ricevi una
segnalazione, decidi quali specialisti coinvolgere: se riguarda
pod/servizi K8s, delega al k8s-troubleshooter; se servono i log,
delega al log-analyzer. Puoi chiamare entrambi in sequenza se
il problema lo richiede.
modelConfig: gpt4o-config
tools:
- type: McpServer
mcpServer:
name: kagent-tool-server
toolNames: ["k8s_get_events"]
# Sotto-agenti esposti come "tool" — la scelta di chiamarli
# è dell'LLM di incident-commander, non di kagent
- type: Agent
agent:
name: k8s-troubleshooter
namespace: team-alpha
- type: Agent
agent:
name: log-analyzer
namespace: team-alpha
+536
View File
@@ -0,0 +1,536 @@
BLACKBOX EXPORTER - GUIDA OPERATIVA KUBERNETES
==============================================
Stack di partenza
-----------------
Nel cluster e' gia' installato kube-prometheus-stack nel namespace monitoring:
```text
helm list -A | grep prometheus
kube-prometheus-stack monitoring 4 2026-08-08 23:55:14.193903052 +0000 UTC deployed kube-prometheus-stack-88.1.5
```
Obiettivo
---------
Installare prometheus-blackbox-exporter per eseguire test HTTP/HTTPS su servizi Kubernetes interni, Ingress o URL esterni, esportando le metriche verso Prometheus tramite Prometheus Operator.
Metriche principali:
```text
probe_success
probe_duration_seconds
probe_http_status_code
probe_http_ssl
probe_dns_lookup_time_seconds
```
Legenda
-------
- COMANDO: istruzione da eseguire nel terminale.
- FILE: file da creare o aggiornare.
- NOTA: informazione da verificare prima di procedere.
- OUTPUT ATTESO: risultato indicativo del comando.
Prerequisiti
------------
- Accesso kubectl al cluster.
- Helm installato.
- Namespace monitoring presente o creabile.
- kube-prometheus-stack installato con Prometheus Operator.
- CRD Probe disponibile: probes.monitoring.coreos.com.
1. VERIFICA PROMETHEUS E PROMETHEUS OPERATOR
============================================
COMANDO - verifica release Helm Prometheus
```bash
helm list -A | grep prometheus
```
COMANDO - verifica pod Prometheus
```bash
kubectl get pods -A | grep prometheus
```
COMANDO - verifica CRD Probe
```bash
kubectl get crd probes.monitoring.coreos.com
```
COMANDO - verifica label delle risorse Prometheus
```bash
kubectl get prometheus -A --show-labels
```
COMANDO - controlla selector della risorsa Prometheus
```bash
kubectl get prometheus -n monitoring -o yaml
```
NOTA - label dei manifest
Dal comando `kubectl get prometheus -A --show-labels` risulta che Prometheus seleziona le risorse con la label `release=kube-prometheus-stack`.
Negli esempi sotto viene quindi usata questa label:
```yaml
release: kube-prometheus-stack
```
Se in futuro il tuo Prometheus usa un selector diverso, sostituisci questa label nei manifest Probe e PrometheusRule.
Controlla soprattutto questi campi nella risorsa Prometheus:
```yaml
spec:
probeSelector:
probeNamespaceSelector:
ruleSelector:
ruleNamespaceSelector:
```
2. AGGIUNTA REPOSITORY HELM
===========================
COMANDO - aggiungi repository prometheus-community
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
```
COMANDO - aggiorna indice chart Helm
```bash
helm repo update
```
3. CREA FILE VALUES HELM
========================
FILE - blackbox-values.yaml
Descrizione:
- configura i moduli HTTP usati da blackbox-exporter;
- espone blackbox-exporter come Service ClusterIP sulla porta 9115;
- lascia disabilitato ServiceMonitor perche' lo scraping dei target viene configurato tramite Probe.
COMANDO - crea blackbox-values.yaml
```bash
cat > blackbox-values.yaml <<'EOF'
fullnameOverride: blackbox-exporter
config:
modules:
http_2xx:
prober: http
timeout: 10s
http:
method: GET
preferred_ip_protocol: ip4
valid_http_versions:
- HTTP/1.1
- HTTP/2.0
valid_status_codes:
- 200
- 204
- 301
- 302
http_post_2xx:
prober: http
timeout: 10s
http:
method: POST
preferred_ip_protocol: ip4
valid_status_codes:
- 200
- 201
- 202
- 204
http_k8s_health:
prober: http
timeout: 10s
http:
method: GET
preferred_ip_protocol: ip4
fail_if_ssl: false
fail_if_not_ssl: false
valid_status_codes:
- 200
service:
type: ClusterIP
port: 9115
serviceMonitor:
enabled: false
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
EOF
```
4. INSTALLA BLACKBOX EXPORTER
=============================
COMANDO - installa o aggiorna blackbox-exporter
```bash
helm upgrade --install blackbox-exporter prometheus-community/prometheus-blackbox-exporter \
--namespace monitoring \
--create-namespace \
-f blackbox-values.yaml
```
COMANDO - verifica pod blackbox-exporter
```bash
kubectl get pods -n monitoring | grep blackbox
```
COMANDO - verifica Service blackbox-exporter
```bash
kubectl get svc -n monitoring | grep blackbox
```
OUTPUT ATTESO
```text
blackbox-exporter ClusterIP <cluster-ip> <none> 9115/TCP
```
5. CREA PROBE HTTP PER SERVIZI KUBERNETES INTERNI
=================================================
FILE - blackbox-probes.yaml
Descrizione:
- testa endpoint HTTP interni tramite DNS Kubernetes;
- usa il modulo http_k8s_health definito in blackbox-values.yaml;
- invia le metriche a Prometheus tramite la risorsa Probe del Prometheus Operator.
Da personalizzare:
- my-service
- my-namespace
- porta del servizio
- path HTTP, ad esempio /health, /ready o /
- label release se diversa da prometheus
COMANDO - crea blackbox-probes.yaml
```bash
cat > blackbox-probes.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: http-services-probe
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: http-services-probe
interval: 30s
scrapeTimeout: 15s
module: http_k8s_health
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- http://frontend.idcidp-dev.svc.cluster.local
EOF
```
COMANDO - applica Probe servizi interni
```bash
kubectl apply -f blackbox-probes.yaml
```
COMANDO - verifica Probe
```bash
kubectl get probe -n monitoring
kubectl describe probe http-services-probe -n monitoring
```
NOTA - campi importanti della Probe
```yaml
spec:
module: http_k8s_health
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
targets:
staticConfig:
static:
- http://my-service.my-namespace.svc.cluster.local:8080/health
```
6. CREA PROBE HTTP/HTTPS PER INGRESS O URL ESTERNI
==================================================
FILE - blackbox-ingress-probes.yaml
Descrizione:
- testa URL esposti tramite Ingress, Gateway, reverse proxy o endpoint pubblici;
- usa il modulo generico http_2xx.
COMANDO - crea blackbox-ingress-probes.yaml
```bash
cat > blackbox-ingress-probes.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: ingress-http-probe
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: ingress-http-probe
interval: 30s
scrapeTimeout: 15s
module: http_2xx
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- https://app.example.com/health
- https://api.example.com/ready
EOF
```
COMANDO - applica Probe endpoint esterni
```bash
kubectl apply -f blackbox-ingress-probes.yaml
```
7. VERIFICA METRICHE IN PROMETHEUS
==================================
COMANDO - apri Prometheus in locale con port-forward
```bash
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090
```
Poi apri nel browser:
```text
http://localhost:9090
```
QUERY PROMQL - tutte le probe
```promql
probe_success
```
QUERY PROMQL - probe servizi interni
```promql
probe_success{job="http-services-probe"}
```
QUERY PROMQL - status code HTTP
```promql
probe_http_status_code{job="http-services-probe"}
```
QUERY PROMQL - durata probe
```promql
probe_duration_seconds{job="http-services-probe"}
```
QUERY PROMQL - target falliti
```promql
probe_success{job="http-services-probe"} == 0
```
Interpretazione:
- probe_success = 1: test riuscito.
- probe_success = 0: test fallito.
- probe_http_status_code: status HTTP ricevuto.
- probe_duration_seconds: durata della probe.
8. CREA ALERT PROMETHEUSRULE
============================
FILE - blackbox-http-alerts.yaml
Descrizione:
- genera alert quando un target HTTP non risponde correttamente;
- genera alert quando un target risponde troppo lentamente.
COMANDO - crea blackbox-http-alerts.yaml
```bash
cat > blackbox-http-alerts.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: blackbox-http-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: blackbox-http
rules:
- alert: BlackboxHttpProbeFailed
expr: probe_success == 0
for: 2m
labels:
severity: warning
annotations:
summary: "HTTP probe fallita"
description: "Il target {{ $labels.instance }} non risponde correttamente da almeno 2 minuti."
- alert: BlackboxHttpSlowResponse
expr: probe_duration_seconds > 2
for: 5m
labels:
severity: warning
annotations:
summary: "HTTP probe lenta"
description: "Il target {{ $labels.instance }} risponde in piu' di 2 secondi da almeno 5 minuti."
EOF
```
COMANDO - applica alert
```bash
kubectl apply -f blackbox-http-alerts.yaml
```
COMANDO - verifica PrometheusRule
```bash
kubectl get prometheusrule -n monitoring | grep blackbox
kubectl describe prometheusrule blackbox-http-alerts -n monitoring
```
9. TEST MANUALE BLACKBOX EXPORTER
=================================
COMANDO - port-forward blackbox-exporter
```bash
kubectl port-forward -n monitoring svc/blackbox-exporter 9115:9115
```
COMANDO - esegui probe manuale da un altro terminale
```bash
curl "http://localhost:9115/probe?target=http://my-service.my-namespace.svc.cluster.local:8080/health&module=http_k8s_health"
```
OUTPUT ATTESO - metriche indicative
```text
probe_success 1
probe_http_status_code 200
```
10. TROUBLESHOOTING
===================
CASO - Prometheus non vede la Probe
COMANDO
```bash
kubectl get probe -A
kubectl describe probe -n monitoring http-services-probe
kubectl get prometheus -n monitoring -o yaml
```
Controlla:
- spec.probeSelector
- spec.probeNamespaceSelector
- metadata.labels della Probe
CASO - blackbox-exporter non parte
COMANDO
```bash
kubectl get pods -n monitoring | grep blackbox
kubectl logs -n monitoring deploy/blackbox-exporter
```
CASO - servizio target non raggiungibile
COMANDO
```bash
kubectl get svc -n my-namespace
kubectl get endpoints -n my-namespace my-service
kubectl describe svc -n my-namespace my-service
```
CASO - DNS Kubernetes non risolve
COMANDO
```bash
kubectl run dns-test --rm -it --image=busybox:1.36 --restart=Never -- nslookup my-service.my-namespace.svc.cluster.local
```
CASO - endpoint HTTPS fallisce per certificati, redirect o header
Azioni consigliate:
- usare il modulo http_2xx per endpoint esterni generici;
- verificare se l'endpoint richiede SNI, autenticazione, header custom o path diverso;
- aggiungere un modulo dedicato in blackbox-values.yaml se serve una configurazione HTTP specifica.
11. ORDINE DI ESECUZIONE CONSIGLIATO
====================================
COMANDO - sequenza completa
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm upgrade --install blackbox-exporter prometheus-community/prometheus-blackbox-exporter \
--namespace monitoring \
--create-namespace \
-f blackbox-values.yaml
kubectl apply -f blackbox-probes.yaml
kubectl apply -f blackbox-ingress-probes.yaml
kubectl apply -f blackbox-http-alerts.yaml
```
FILE CONSIGLIATI DA VERSIONARE
```text
blackbox-values.yaml
blackbox-probes.yaml
blackbox-ingress-probes.yaml
blackbox-http-alerts.yaml
```
12. NOTE PER DEV, QA E PROD
===========================
Per separare gli ambienti e semplificare dashboard e alert, usa Probe distinte:
```text
dev-http-services-probe
qa-http-services-probe
prod-http-services-probe
```
QUERY PROMQL - ambiente dev
```promql
probe_success{job="dev-http-services-probe"}
```
QUERY PROMQL - target prod falliti
```promql
probe_success{job="prod-http-services-probe"} == 0
```
Per endpoint critici in produzione, valuta:
- interval piu' basso, ad esempio 15s o 30s;
- durata for degli alert tra 1m e 5m in base alla criticita';
- dashboard Grafana con stato, status code e latenza per target.
+100
View File
@@ -0,0 +1,100 @@
kubectl cnpg psql pg-devops -n devops
CREATE DATABASE giteadbtest;
CREATE USER giteatest WITH PASSWORD 'KAYQE1QA7uwUZ8uI';
GRANT ALL PRIVILEGES ON DATABASE giteadbtest TO giteatest;
ALTER DATABASE giteadbtest OWNER TO giteatest;
helm repo add gitea https://dl.gitea.io/charts/
helm repo update
kubectl create namespace gitea
cat <<EOF |cat >valuestest.yaml -
replicaCount: 1
image:
repository: gitea/gitea
tag: 1.25.4-rootless
pullPolicy: IfNotPresent
strategy:
type: Recreate
service:
http:
type: ClusterIP
port: 3000
ssh:
type: ClusterIP
port: 22
redis-cluster:
enabled: false
redis:
enabled: false
ingress:
enabled: false
persistence:
enabled: true
storageClass: csi-rbdfs-sc
size: 10Gi
postgresql:
enabled: false
postgresql-ha:
enabled: false
gitea:
admin:
username: gitadmin
password: KAYQE1QA7uwUZ8uI
email: gitadmin@italiadatacenter.com
config:
database:
DB_TYPE: postgres
HOST: pg-devops-rw.devops.svc:5432
NAME: giteadbtest
USER: giteatest
PASSWD: KAYQE1QA7uwUZ8uI
SSL_MODE: disable
server:
ROOT_URL: https://git2.pigreco66.it/
SSH_DOMAIN: git2.pigreco66.it
SSH_PORT: 22
security:
INSTALL_LOCK: true
EOF
helm upgrade --install gitea gitea-charts/gitea --namespace gitea -f values.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitea
namespace: gitea
spec:
hostnames:
- git.italiadatacenter.com
parentRefs:
- name: main-gateway
namespace: nginx-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: gitea-http
port: 3000
@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: namespace-b
namespace: argocd
spec:
project: gitops-3ns
source:
repoURL: "https://github.com/<tua-org>/gitops-repo.git"
targetRevision: main
path: gitops/clusters/idc/namespace-B/manifests
destination:
server: "https://kubernetes.default.svc"
namespace: namespace-B
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+28 -3
View File
@@ -1,7 +1,32 @@
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
kubectl create namespace cattle-system kubectl create namespace cattle-system
helm install rancher rancher-stable/rancher \ helm install rancher rancher-stable/rancher --namespace cattle-system --set hostname=idc.internal --set bootstrapPassword=.... --set ingress.tls.source=rancher
helm upgrade rancher rancher-stable/rancher \
--namespace cattle-system \ --namespace cattle-system \
--set hostname=k8s.italiadatacenter.com \ --reuse-values \
--set bootstrapPassword=admin --set hostname=idc.internal \
--set bootstrapPassword=KAYQE1QA7uwUZ8uI \
--set ingress.enabled=false \
--set ingress.tls.source=rancher
Poc-25_sts
POC-25_sts
vi sistemarancher.sh
# 1. recupera il ClusterIP del service "rancher" già creato dal chart
CLUSTERIP=$(kubectl get svc -n cattle-system rancher -o jsonpath='{.spec.clusterIP}')
# 2. patch del deployment per aggiungere hostAliases
kubectl patch deployment rancher -n cattle-system --type='json' \
-p="[{\"op\":\"add\",\"path\":\"/spec/template/spec/hostAliases\",\"value\":[{\"ip\":\"$CLUSTERIP\",\"hostnames\":[\"idc.internal\"]}]}]"
# 3. crea il service NodePort per l'accesso dal browser
kubectl expose deployment rancher -n cattle-system --type=NodePort --port=443 --target-port=443 --name=rancher-nodeport
# 4. riavvia e segui i log
kubectl rollout restart deployment rancher -n cattle-system
kubectl -n cattle-system logs -l app=rancher -f
+136
View File
@@ -0,0 +1,136 @@
apiVersion: v1
kind: Secret
metadata:
name: opens3-console-secrets
namespace: minio
type: Opaque
stringData:
# Endpoint S3 del server MinIO a cui la console si collega.
# Cambialo con l'URL reale del tuo MinIO (Service interno o esterno).
CONSOLE_MINIO_SERVER: "http://minio.minio.svc.cluster.local:9000"
# Passphrase e salt usati da opens3/console per cifrare le sessioni
# (PBKDF2). Generane di robusti, es.:
# openssl rand -base64 32
CONSOLE_PBKDF_PASSPHRASE: "OHB576kn9SS4JdD7qG4+hyjRpa353HQqxPQ22z2Do0w="
CONSOLE_PBKDF_SALT: "OBHOWF2INklmrtmyI+qNEb3dbV0yz9ZAxiJJCcC6GYw="
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: opens3-console
namespace: minio
labels:
app.kubernetes.io/name: opens3-console
app.kubernetes.io/instance: opens3-console
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app.kubernetes.io/name: opens3-console
app.kubernetes.io/instance: opens3-console
template:
metadata:
labels:
app.kubernetes.io/name: opens3-console
app.kubernetes.io/instance: opens3-console
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: console
image: opens3/console:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 9090
protocol: TCP
envFrom:
- secretRef:
name: opens3-console-secrets
# Se il MinIO target usa certificati self-signed, decommenta:
# env:
# - name: CONSOLE_MINIO_TLS_SKIP_VERIFICATION
# value: "on"
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 3
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
name: opens3-console
namespace: minio
labels:
app.kubernetes.io/name: opens3-console
app.kubernetes.io/instance: opens3-console
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: opens3-console
app.kubernetes.io/instance: opens3-console
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httproute-minio-direct
spec:
hostnames:
- idcidp.pigreco66.it
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: main-gateway
namespace: nginx-gateway
rules:
- backendRefs:
- group: ""
kind: Service
name: opens3-console
port: 80
weight: 1
filters:
- responseHeaderModifier:
remove:
- X-Frame-Options
- Content-Security-Policy
set:
- name: X-Frame-Options
value: SAMEORIGIN
- name: Content-Security-Policy
value: frame-ancestors 'self'
type: ResponseHeaderModifier
matches:
- path:
type: PathPrefix
value: /minio-console
+870
View File
@@ -0,0 +1,870 @@
BLACKBOX EXPORTER - GUIDA OPERATIVA KUBERNETES
==============================================
Stack di partenza
-----------------
Nel cluster e' gia' installato kube-prometheus-stack nel namespace monitoring:
```text
helm list -A | grep prometheus
kube-prometheus-stack monitoring 4 2026-08-08 23:55:14.193903052 +0000 UTC deployed kube-prometheus-stack-88.1.5
```
Obiettivo
---------
Installare prometheus-blackbox-exporter per eseguire test HTTP/HTTPS su servizi Kubernetes interni, Ingress o URL esterni, esportando le metriche verso Prometheus tramite Prometheus Operator.
Metriche principali:
```text
probe_success
probe_duration_seconds
probe_http_status_code
probe_http_ssl
probe_dns_lookup_time_seconds
```
Legenda
-------
- COMANDO: istruzione da eseguire nel terminale.
- FILE: file da creare o aggiornare.
- NOTA: informazione da verificare prima di procedere.
- OUTPUT ATTESO: risultato indicativo del comando.
Prerequisiti
------------
- Accesso kubectl al cluster.
- Helm installato.
- Namespace monitoring presente o creabile.
- kube-prometheus-stack installato con Prometheus Operator.
- CRD Probe disponibile: probes.monitoring.coreos.com.
1. VERIFICA PROMETHEUS E PROMETHEUS OPERATOR
============================================
COMANDO - verifica release Helm Prometheus
```bash
helm list -A | grep prometheus
```
COMANDO - verifica pod Prometheus
```bash
kubectl get pods -A | grep prometheus
```
COMANDO - verifica CRD Probe
```bash
kubectl get crd probes.monitoring.coreos.com
```
COMANDO - verifica label delle risorse Prometheus
```bash
kubectl get prometheus -A --show-labels
```
COMANDO - controlla selector della risorsa Prometheus
```bash
kubectl get prometheus -n monitoring -o yaml
```
NOTA - label dei manifest
Dal comando `kubectl get prometheus -A --show-labels` risulta che Prometheus seleziona le risorse con la label `release=kube-prometheus-stack`.
Negli esempi sotto viene quindi usata questa label:
```yaml
release: kube-prometheus-stack
```
Se in futuro il tuo Prometheus usa un selector diverso, sostituisci questa label nei manifest Probe e PrometheusRule.
Controlla soprattutto questi campi nella risorsa Prometheus:
```yaml
spec:
probeSelector:
probeNamespaceSelector:
ruleSelector:
ruleNamespaceSelector:
```
2. AGGIUNTA REPOSITORY HELM
===========================
COMANDO - aggiungi repository prometheus-community
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
```
COMANDO - aggiorna indice chart Helm
```bash
helm repo update
```
3. CREA FILE VALUES HELM
========================
FILE - blackbox-values.yaml
Descrizione:
- configura i moduli HTTP usati da blackbox-exporter;
- espone blackbox-exporter come Service ClusterIP sulla porta 9115;
- lascia disabilitato ServiceMonitor perche' lo scraping dei target viene configurato tramite Probe.
COMANDO - crea blackbox-values.yaml
```bash
cat > blackbox-values.yaml <<'EOF'
fullnameOverride: blackbox-exporter
config:
modules:
http_2xx:
prober: http
timeout: 10s
http:
method: GET
preferred_ip_protocol: ip4
valid_http_versions:
- HTTP/1.1
- HTTP/2.0
valid_status_codes:
- 200
- 204
- 301
- 302
http_post_2xx:
prober: http
timeout: 10s
http:
method: POST
preferred_ip_protocol: ip4
valid_status_codes:
- 200
- 201
- 202
- 204
http_k8s_health:
prober: http
timeout: 10s
http:
method: GET
preferred_ip_protocol: ip4
fail_if_ssl: false
fail_if_not_ssl: false
valid_status_codes:
- 200
service:
type: ClusterIP
port: 9115
serviceMonitor:
enabled: false
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
EOF
```
4. INSTALLA BLACKBOX EXPORTER
=============================
COMANDO - installa o aggiorna blackbox-exporter
```bash
helm upgrade --install blackbox-exporter prometheus-community/prometheus-blackbox-exporter \
--namespace monitoring \
--create-namespace \
-f blackbox-values.yaml
```
COMANDO - verifica pod blackbox-exporter
```bash
kubectl get pods -n monitoring | grep blackbox
```
COMANDO - verifica Service blackbox-exporter
```bash
kubectl get svc -n monitoring | grep blackbox
```
OUTPUT ATTESO
```text
blackbox-exporter ClusterIP <cluster-ip> <none> 9115/TCP
```
5. CREA PROBE HTTP PER SERVIZI KUBERNETES INTERNI
=================================================
FILE - blackbox-probes.yaml
Descrizione:
- testa endpoint HTTP interni tramite DNS Kubernetes;
- usa il modulo http_k8s_health definito in blackbox-values.yaml;
- invia le metriche a Prometheus tramite la risorsa Probe del Prometheus Operator.
Da personalizzare:
- my-service
- my-namespace
- porta del servizio
- path HTTP, ad esempio /health, /ready o /
- label release se diversa da prometheus
COMANDO - crea blackbox-probes.yaml
```bash
cat > blackbox-probes.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: http-services-probe
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: http-services-probe
interval: 30s
scrapeTimeout: 15s
module: http_k8s_health
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- http://my-service.my-namespace.svc.cluster.local:8080/health
- http://another-service.default.svc.cluster.local:80/
EOF
```
COMANDO - applica Probe servizi interni
```bash
kubectl apply -f blackbox-probes.yaml
```
COMANDO - verifica Probe
```bash
kubectl get probe -n monitoring
kubectl describe probe http-services-probe -n monitoring
```
NOTA - campi importanti della Probe
```yaml
spec:
module: http_k8s_health
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
targets:
staticConfig:
static:
- http://my-service.my-namespace.svc.cluster.local:8080/health
```
6. CREA PROBE HTTP/HTTPS PER INGRESS O URL ESTERNI
==================================================
FILE - blackbox-ingress-probes.yaml
Descrizione:
- testa URL esposti tramite Ingress, Gateway, reverse proxy o endpoint pubblici;
- usa il modulo generico http_2xx.
COMANDO - crea blackbox-ingress-probes.yaml
```bash
cat > blackbox-ingress-probes.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: ingress-http-probe
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: ingress-http-probe
interval: 30s
scrapeTimeout: 15s
module: http_2xx
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- https://app.example.com/health
- https://api.example.com/ready
EOF
```
COMANDO - applica Probe endpoint esterni
```bash
kubectl apply -f blackbox-ingress-probes.yaml
```
7. VERIFICA METRICHE IN PROMETHEUS
==================================
COMANDO - apri Prometheus in locale con port-forward
```bash
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090
```
Poi apri nel browser:
```text
http://localhost:9090
```
QUERY PROMQL - tutte le probe
```promql
probe_success
```
QUERY PROMQL - probe servizi interni
```promql
probe_success{job="http-services-probe"}
```
QUERY PROMQL - status code HTTP
```promql
probe_http_status_code{job="http-services-probe"}
```
QUERY PROMQL - durata probe
```promql
probe_duration_seconds{job="http-services-probe"}
```
QUERY PROMQL - target falliti
```promql
probe_success{job="http-services-probe"} == 0
```
Interpretazione:
- probe_success = 1: test riuscito.
- probe_success = 0: test fallito.
- probe_http_status_code: status HTTP ricevuto.
- probe_duration_seconds: durata della probe.
8. CREA DASHBOARD GRAFANA PER LE PROBE
======================================
Obiettivo:
- visualizzare lo stato delle probe HTTP;
- vedere quali target sono UP o DOWN;
- controllare status code e latenza per ogni endpoint;
- filtrare la dashboard per job e target.
COMANDO - apri Grafana in locale con port-forward
```bash
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
```
Poi apri nel browser:
```text
http://localhost:3000
```
COMANDO - recupera password admin Grafana se non la conosci
```bash
kubectl get secret -n monitoring kube-prometheus-stack-grafana \
-o jsonpath="{.data.admin-password}" | base64 -d
```
Credenziali predefinite tipiche:
```text
utente: admin
password: valore recuperato dal secret
```
NOTA - datasource Prometheus
Con kube-prometheus-stack il datasource Prometheus di solito e' gia' configurato in Grafana.
Verifica da Grafana:
```text
Connections -> Data sources -> Prometheus
```
CREAZIONE DASHBOARD DA INTERFACCIA
1. Vai su Dashboards -> New -> New dashboard.
2. Clicca Add visualization.
3. Seleziona il datasource Prometheus.
4. Crea i pannelli usando le query sotto.
5. Salva la dashboard con nome, ad esempio:
```text
Blackbox HTTP Probes
```
VARIABILI CONSIGLIATE
Variabile job:
```text
Name: job
Type: Query
Data source: Prometheus
Query: label_values(probe_success, job)
Multi-value: enabled
Include All option: enabled
```
Variabile instance:
```text
Name: instance
Type: Query
Data source: Prometheus
Query: label_values(probe_success{job=~"$job"}, instance)
Multi-value: enabled
Include All option: enabled
```
PANNELLO - stato generale probe
Tipo pannello: Stat
Query:
```promql
min(probe_success{job=~"$job", instance=~"$instance"})
```
Configurazione consigliata:
```text
Unit: none
Thresholds:
0 = red
1 = green
Value mappings:
0 -> DOWN
1 -> UP
```
PANNELLO - stato per target
Tipo pannello: State timeline oppure Table
Query:
```promql
probe_success{job=~"$job", instance=~"$instance"}
```
Configurazione consigliata:
```text
Legend: {{ instance }}
Value mappings:
0 -> DOWN
1 -> UP
```
PANNELLO - target attualmente falliti
Tipo pannello: Table
Query:
```promql
probe_success{job=~"$job", instance=~"$instance"} == 0
```
Configurazione consigliata:
```text
Legend: {{ instance }}
Mostra colonne: instance, job, value
```
PANNELLO - durata probe per target
Tipo pannello: Time series
Query:
```promql
probe_duration_seconds{job=~"$job", instance=~"$instance"}
```
Configurazione consigliata:
```text
Unit: seconds
Legend: {{ instance }}
Thresholds:
1 = yellow
2 = red
```
PANNELLO - status code HTTP
Tipo pannello: Time series oppure Table
Query:
```promql
probe_http_status_code{job=~"$job", instance=~"$instance"}
```
Configurazione consigliata:
```text
Unit: none
Legend: {{ instance }}
```
PANNELLO - percentuale disponibilita' per target
Tipo pannello: Bar gauge oppure Table
Query:
```promql
avg_over_time(probe_success{job=~"$job", instance=~"$instance"}[24h]) * 100
```
Configurazione consigliata:
```text
Unit: percent
Min: 0
Max: 100
Legend: {{ instance }}
Thresholds:
95 = yellow
99 = green
```
PANNELLO - durata media nelle ultime 24 ore
Tipo pannello: Bar gauge oppure Table
Query:
```promql
avg_over_time(probe_duration_seconds{job=~"$job", instance=~"$instance"}[24h])
```
Configurazione consigliata:
```text
Unit: seconds
Legend: {{ instance }}
```
QUERY RAPIDE SENZA VARIABILI
Se vuoi creare una dashboard solo per la Probe di esempio dei servizi interni:
```promql
probe_success{job="http-services-probe"}
probe_http_status_code{job="http-services-probe"}
probe_duration_seconds{job="http-services-probe"}
probe_success{job="http-services-probe"} == 0
avg_over_time(probe_success{job="http-services-probe"}[24h]) * 100
```
Per la Probe di esempio degli Ingress o URL esterni:
```promql
probe_success{job="ingress-http-probe"}
probe_http_status_code{job="ingress-http-probe"}
probe_duration_seconds{job="ingress-http-probe"}
probe_success{job="ingress-http-probe"} == 0
avg_over_time(probe_success{job="ingress-http-probe"}[24h]) * 100
```
9. CREA ALERT PROMETHEUSRULE
============================
FILE - blackbox-http-alerts.yaml
Descrizione:
- genera alert quando un target HTTP non risponde correttamente;
- genera alert quando un target risponde troppo lentamente.
COMANDO - crea blackbox-http-alerts.yaml
```bash
cat > blackbox-http-alerts.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: blackbox-http-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: blackbox-http
rules:
- alert: BlackboxHttpProbeFailed
expr: probe_success == 0
for: 2m
labels:
severity: warning
annotations:
summary: "HTTP probe fallita"
description: "Il target {{ $labels.instance }} non risponde correttamente da almeno 2 minuti."
- alert: BlackboxHttpSlowResponse
expr: probe_duration_seconds > 2
for: 5m
labels:
severity: warning
annotations:
summary: "HTTP probe lenta"
description: "Il target {{ $labels.instance }} risponde in piu' di 2 secondi da almeno 5 minuti."
EOF
```
COMANDO - applica alert
```bash
kubectl apply -f blackbox-http-alerts.yaml
```
COMANDO - verifica PrometheusRule
```bash
kubectl get prometheusrule -n monitoring | grep blackbox
kubectl describe prometheusrule blackbox-http-alerts -n monitoring
```
10. CONFIGURA ALERTMANAGER - INVIO EMAIL SU FAIL DI UN SERVIZIO
================================================================
Obiettivo:
- inviare una email a un indirizzo specifico quando la probe di un
servizio specifico (serviceX) fallisce (alert BlackboxHttpProbeFailed).
FILE - blackbox-email-alert.yaml
Descrizione:
- crea un receiver email dedicato al servizio da monitorare;
- instrada verso quel receiver solo gli alert che riguardano il
target specifico, identificato tramite il label instance.
NOTA - credenziali SMTP
Se il server SMTP richiede autenticazione, crea prima un Secret con
la password:
COMANDO - crea secret con password SMTP
```bash
kubectl create secret generic alertmanager-smtp \
-n monitoring --from-literal=password='<SMTP_PASSWORD>'
```
COMANDO - crea blackbox-email-alert.yaml
```bash
cat > blackbox-email-alert.yaml <<'EOF'
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
name: blackbox-email-routing
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
route:
groupBy: ["alertname", "instance"]
groupWait: 30s
groupInterval: 5m
repeatInterval: 1h
receiver: "email-default"
routes:
- matchers:
- name: alertname
value: BlackboxHttpProbeFailed
matchType: "="
- name: instance
value: "<SERVIZIO_X_URL_O_HOST>"
matchType: "="
receiver: "email-servizioX"
receivers:
- name: "email-default"
emailConfigs: []
- name: "email-servizioX"
emailConfigs:
- to: "<INDIRIZZO_X>"
from: "<SMTP_FROM>"
smarthost: "<SMTP_HOST>:<SMTP_PORT>"
authUsername: "<SMTP_USER>"
authPassword:
name: alertmanager-smtp
key: password
requireTLS: true
sendResolved: true
headers:
subject: "[ALERT] Servizio X non raggiungibile"
html: |
<p>Il servizio <b>{{ "{{" }} .CommonLabels.instance {{ "}}" }}</b> non risponde.</p>
<p>{{ "{{" }} .CommonAnnotations.description {{ "}}" }}</p>
EOF
```
Da personalizzare:
- <SERVIZIO_X_URL_O_HOST>: valore del label instance della probe da
monitorare (es. http://my-service.my-namespace.svc.cluster.local:8080/health);
- <INDIRIZZO_X>: indirizzo email destinatario;
- <SMTP_HOST>, <SMTP_PORT>: server SMTP (es. smtp.gmail.com:587);
- <SMTP_FROM>: mittente email;
- <SMTP_USER>: utente SMTP, se richiesta autenticazione.
COMANDO - applica la configurazione
```bash
kubectl apply -f blackbox-email-alert.yaml
```
COMANDO - verifica AlertmanagerConfig
```bash
kubectl get alertmanagerconfig -n monitoring
kubectl describe alertmanagerconfig blackbox-email-routing -n monitoring
```
NOTA - matcher su instance vs job
Se vuoi far scattare l'email per TUTTI i target di una Probe (job)
invece che per un singolo servizio, sostituisci il matcher su
`instance` con:
```yaml
- name: job
value: "http-services-probe"
matchType: "="
```
COMANDO - verifica alert in Alertmanager
```bash
kubectl port-forward -n monitoring svc/kube-prometheus-stack-alertmanager 9093
# -> http://localhost:9093, controlla che l'alert BlackboxHttpProbeFailed
# per il servizio X sia instradato sul receiver email-servizioX
```
11. TEST MANUALE BLACKBOX EXPORTER
==================================
COMANDO - port-forward blackbox-exporter
```bash
kubectl port-forward -n monitoring svc/blackbox-exporter 9115:9115
```
COMANDO - esegui probe manuale da un altro terminale
```bash
curl "http://localhost:9115/probe?target=http://my-service.my-namespace.svc.cluster.local:8080/health&module=http_k8s_health"
```
OUTPUT ATTESO - metriche indicative
```text
probe_success 1
probe_http_status_code 200
```
12. TROUBLESHOOTING
===================
CASO - Prometheus non vede la Probe
COMANDO
```bash
kubectl get probe -A
kubectl describe probe -n monitoring http-services-probe
kubectl get prometheus -n monitoring -o yaml
```
Controlla:
- spec.probeSelector
- spec.probeNamespaceSelector
- metadata.labels della Probe
CASO - blackbox-exporter non parte
COMANDO
```bash
kubectl get pods -n monitoring | grep blackbox
kubectl logs -n monitoring deploy/blackbox-exporter
```
CASO - servizio target non raggiungibile
COMANDO
```bash
kubectl get svc -n my-namespace
kubectl get endpoints -n my-namespace my-service
kubectl describe svc -n my-namespace my-service
```
CASO - DNS Kubernetes non risolve
COMANDO
```bash
kubectl run dns-test --rm -it --image=busybox:1.36 --restart=Never -- nslookup my-service.my-namespace.svc.cluster.local
```
CASO - endpoint HTTPS fallisce per certificati, redirect o header
Azioni consigliate:
- usare il modulo http_2xx per endpoint esterni generici;
- verificare se l'endpoint richiede SNI, autenticazione, header custom o path diverso;
- aggiungere un modulo dedicato in blackbox-values.yaml se serve una configurazione HTTP specifica.
13. ORDINE DI ESECUZIONE CONSIGLIATO
====================================
COMANDO - sequenza completa
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm upgrade --install blackbox-exporter prometheus-community/prometheus-blackbox-exporter \
--namespace monitoring \
--create-namespace \
-f blackbox-values.yaml
kubectl apply -f blackbox-probes.yaml
kubectl apply -f blackbox-ingress-probes.yaml
kubectl apply -f blackbox-http-alerts.yaml
```
FILE CONSIGLIATI DA VERSIONARE
```text
blackbox-values.yaml
blackbox-probes.yaml
blackbox-ingress-probes.yaml
blackbox-http-alerts.yaml
```
14. NOTE PER DEV, QA E PROD
===========================
Per separare gli ambienti e semplificare dashboard e alert, usa Probe distinte:
```text
dev-http-services-probe
qa-http-services-probe
prod-http-services-probe
```
QUERY PROMQL - ambiente dev
```promql
probe_success{job="dev-http-services-probe"}
```
QUERY PROMQL - target prod falliti
```promql
probe_success{job="prod-http-services-probe"} == 0
```
Per endpoint critici in produzione, valuta:
- interval piu' basso, ad esempio 15s o 30s;
- durata for degli alert tra 1m e 5m in base alla criticita';
- dashboard Grafana con stato, status code e latenza per target.
@@ -0,0 +1,553 @@
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: <namespace>-<env>-<endpoint>-probe
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: <namespace>-<env>-<endpoint>-probe
interval: 30s
scrapeTimeout: 15s
module: http_2xx
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- https://<endpoint>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: <namespace>-<env>-<endpoint>-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
<namespace>-<env>-<endpoint>-dashboard.json: |
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"links": [],
"panels": [
{
"type": "stat",
"title": "Services UP",
"id": 1,
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "sum(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "stat",
"title": "Services DOWN",
"id": 2,
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "count(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}) - sum(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "stat",
"title": "Availability 24h",
"id": 3,
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg(avg_over_time(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}[24h])) * 100"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 99
},
{
"color": "green",
"value": 99.9
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "area"
}
},
{
"type": "stat",
"title": "Avg Latency",
"id": 4,
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg(probe_duration_seconds{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}) * 1000"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "table",
"title": "HTTP Services Status",
"id": 5,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 5
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "table",
"title": "HTTP Status Code",
"id": 6,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 5
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_http_status_code{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"custom": {
"align": "auto",
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "HTTP Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "timeseries",
"title": "HTTP Response Time",
"id": 7,
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 13
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_duration_seconds{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"} * 1000",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "table",
"title": "Currently DOWN",
"id": 8,
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 22
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"} == 0",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "timeseries",
"title": "Availability 24h",
"id": 9,
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 22
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg_over_time(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}[24h]) * 100",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"decimals": 2
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "timeseries",
"title": "P95 Latency - 1h",
"id": 10,
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 31
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "quantile_over_time(0.95, probe_duration_seconds{job=\"<namespace>-<env>-<endpoint>-probe\",instance=~\"$instance\"}[1h]) * 1000",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
}
],
"refresh": "30s",
"schemaVersion": 41,
"tags": [
"blackbox",
"http",
"monitoring"
],
"templating": {
"list": [
{
"name": "instance",
"label": "Service",
"type": "query",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"definition": "label_values(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\"}, instance)",
"query": {
"query": "label_values(probe_success{job=\"<namespace>-<env>-<endpoint>-probe\"}, instance)",
"refId": "StandardVariableQuery"
},
"includeAll": true,
"multi": true,
"allValue": ".*",
"refresh": 1,
"sort": 1
}
]
},
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "<namespace>-<env>-<endpoint>-dashboard",
"uid": "<namespace>-<env>-<endpoint>-dashboard",
"version": 1,
"weekStart": ""
}
@@ -0,0 +1,553 @@
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: <my-http-services-probe>
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobName: <my-http-services-probe>
interval: 30s
scrapeTimeout: 15s
module: http_k8s_health
prober:
url: blackbox-exporter.monitoring.svc.cluster.local:9115
scheme: http
path: /probe
targets:
staticConfig:
static:
- http://<my-service>.<my-namespace>.svc.cluster.local:<my-port>/health
---
apiVersion: v1
kind: ConfigMap
metadata:
name: <my-http-services-dashboard>
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
<my-http-services-dashboard>.json: |
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"links": [],
"panels": [
{
"type": "stat",
"title": "Services UP",
"id": 1,
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "sum(probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "stat",
"title": "Services DOWN",
"id": 2,
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "count(probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"}) - sum(probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "stat",
"title": "Availability 24h",
"id": 3,
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg(avg_over_time(probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"}[24h])) * 100"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 99
},
{
"color": "green",
"value": 99.9
}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "area"
}
},
{
"type": "stat",
"title": "Avg Latency",
"id": 4,
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 0
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg(probe_duration_seconds{job=\"<my-http-services-probe>\",instance=~\"$instance\"}) * 1000"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none"
}
},
{
"type": "table",
"title": "HTTP Services Status",
"id": 5,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 5
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"}",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "table",
"title": "HTTP Status Code",
"id": 6,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 5
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_http_status_code{job=\"<my-http-services-probe>\",instance=~\"$instance\"}",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"custom": {
"align": "auto",
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "HTTP Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "timeseries",
"title": "HTTP Response Time",
"id": 7,
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 13
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_duration_seconds{job=\"<my-http-services-probe>\",instance=~\"$instance\"} * 1000",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "table",
"title": "Currently DOWN",
"id": 8,
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 22
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"} == 0",
"instant": true,
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "color-text"
}
}
}
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"Value": "Status",
"instance": "Service"
}
}
}
],
"options": {
"showHeader": true,
"cellHeight": "sm"
}
},
{
"type": "timeseries",
"title": "Availability 24h",
"id": 9,
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 22
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "avg_over_time(probe_success{job=\"<my-http-services-probe>\",instance=~\"$instance\"}[24h]) * 100",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"decimals": 2
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "timeseries",
"title": "P95 Latency - 1h",
"id": 10,
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 31
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"targets": [
{
"refId": "A",
"expr": "quantile_over_time(0.95, probe_duration_seconds{job=\"<my-http-services-probe>\",instance=~\"$instance\"}[1h]) * 1000",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0
}
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
}
],
"refresh": "30s",
"schemaVersion": 41,
"tags": [
"blackbox",
"http",
"monitoring"
],
"templating": {
"list": [
{
"name": "instance",
"label": "Service",
"type": "query",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"definition": "label_values(probe_success{job=\"<my-http-services-probe>\"}, instance)",
"query": {
"query": "label_values(probe_success{job=\"<my-http-services-probe>\"}, instance)",
"refId": "StandardVariableQuery"
},
"includeAll": true,
"multi": true,
"allValue": ".*",
"refresh": 1,
"sort": 1
}
]
},
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "<my-http-services-dashboard>",
"uid": "<my-http-services-dashboard>",
"version": 1,
"weekStart": ""
}
+7
View File
@@ -2,6 +2,13 @@
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube
helm repo update helm repo update
helm pull sonarqube/sonarqube --version 2026.1.0
helm registry login harbor.italiadatacenter.com -u admin
(pwd: KAYQE1QA7uwUZ8uI)
helm push sonarqube-2026.1.0.tgz oci://harbor.italiadatacenter.com/italiadatacenter
########### creazione ns e secret db ################ ########### creazione ns e secret db ################
kubectl create namespace sonarqube kubectl create namespace sonarqube
+85
View File
@@ -0,0 +1,85 @@
# gitops-repo - esempio completo (3 namespace, Fleet su RKE2)
Repo di esempio pronto da usare come base: GitOps limitato a
namespace-A, namespace-B, namespace-C, con Fleet come motore di sync.
## Struttura
```
.
├── bootstrap/ <- file legacy Argo CD (non usati con Fleet)
│ ├── 00-namespace.yaml
│ ├── 01-appproject.yaml
│ └── 02-root.yaml
│
└── clusters/
└── idc/ <- nome cluster/repository scope attuale
├── namespace-A/
│ ├── application.yaml <- da migrare a Fleet GitRepo
│ └── manifests/
│ └── deployment.yaml
├── namespace-B/
│ ├── application.yaml <- da migrare a Fleet GitRepo
│ └── manifests/
│ └── deployment.yaml
└── namespace-C/
├── application.yaml <- gia convertito a Fleet GitRepo
└── manifests/
└── deployment.yaml
```
## Cosa applichi a mano con Fleet
- Applichi a mano i manifest Fleet di tipo GitRepo (uno per namespace o uno aggregato).
- I file in clusters/idc/namespace-*/manifests vengono sincronizzati automaticamente da Fleet.
- I file in bootstrap sono legacy Argo CD: con Fleet installato, non sono necessari.
## Bootstrap Fleet - passo per passo
### 1. Verifica Fleet
```bash
kubectl -n cattle-fleet-system get pods
kubectl get crd gitrepos.fleet.cattle.io
```
### 2. Applica il GitRepo
Esempio con namespace-C:
```bash
kubectl apply -f clusters/idc/namespace-C/application.yaml
```
### 3. Verifica stato Fleet
```bash
kubectl -n fleet-local get gitrepo
kubectl -n cattle-fleet-system get bundles
kubectl -n cattle-fleet-system get bundledeployments
```
## Repo privato GitHub
Se il repo e privato, configura le credenziali per Fleet (Secret nel namespace Fleet usato, tipicamente fleet-local o fleet-default).
## Prima di usare questo repo
Sostituisci ovunque compare:
- <tua-org> -> org/utente reale del tuo repo Git
- i deployment di esempio -> i tuoi manifest reali
## Migrazione consigliata (A e B)
Attualmente solo namespace-C e gia in formato Fleet.
Per allineare tutto:
1. Converti clusters/idc/namespace-A/application.yaml in GitRepo Fleet.
2. Converti clusters/idc/namespace-B/application.yaml in GitRepo Fleet.
3. Applica i due manifest e verifica bundle/bundledeployments.
## Estendere a un quarto namespace in futuro
1. Crea clusters/idc/namespace-D/manifests con i tuoi YAML.
2. Crea clusters/idc/namespace-D/application.yaml in formato Fleet GitRepo.
3. Applica il manifest e verifica la generazione dei bundle.
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: argocd
labels:
app.kubernetes.io/name: argocd
+37
View File
@@ -0,0 +1,37 @@
# =====================================================================
# AppProject con perimetro ristretto a namespace-A, namespace-B,
# namespace-C. Qualunque Application che referenzi questo project e
# provi a scrivere in un namespace diverso da questi (+ argocd, per le
# risorse interne) viene rifiutata da Argo CD stesso.
# =====================================================================
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: gitops-3ns
namespace: argocd
spec:
description: "GitOps limitato a namespace-A, namespace-B, namespace-C"
sourceRepos:
- "https://github.com/<tua-org>/gitops-repo.git"
destinations:
- namespace: argocd
server: "https://kubernetes.default.svc"
- namespace: namespace-A
server: "https://kubernetes.default.svc"
- namespace: namespace-B
server: "https://kubernetes.default.svc"
- namespace: namespace-C
server: "https://kubernetes.default.svc"
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: "*"
kind: "*"
orphanedResources:
warn: true
+40
View File
@@ -0,0 +1,40 @@
# =====================================================================
# Root "app of apps": punta a gitops/clusters/idc/ nel repo. Grazie a
# directory.recurse=true, scopre automaticamente ogni application.yaml
# trovato dentro namespace-A/, namespace-B/, namespace-C/ e li applica
# come proprie Application figlie.
# =====================================================================
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-3ns
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: gitops-3ns
source:
repoURL: "https://github.com/<tua-org>/gitops-repo.git"
targetRevision: main
path: gitops/clusters/idc
directory:
recurse: true
destination:
server: "https://kubernetes.default.svc"
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 10s
factor: 2
maxDuration: 3m
@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: namespace-a
namespace: argocd
spec:
project: gitops-3ns
source:
repoURL: "https://github.com/<tua-org>/gitops-repo.git"
targetRevision: main
path: gitops/clusters/idc/namespace-A/manifests
destination:
server: "https://kubernetes.default.svc"
namespace: namespace-A
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,37 @@
# =====================================================================
# Esempio di manifest reale — sostituiscilo con le tue risorse
# effettive. Ogni file .yaml in questa cartella viene applicato
# automaticamente da Argo CD (Application "namespace-a").
# =====================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: esempio-app-a
namespace: namespace-A
spec:
replicas: 1
selector:
matchLabels:
app: esempio-app-a
template:
metadata:
labels:
app: esempio-app-a
spec:
containers:
- name: esempio-app-a
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: esempio-app-a
namespace: namespace-A
spec:
selector:
app: esempio-app-a
ports:
- port: 80
targetPort: 80
@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: namespace-b
namespace: argocd
spec:
project: gitops-3ns
source:
repoURL: "https://github.com/<tua-org>/gitops-repo.git"
targetRevision: main
path: gitops/clusters/idc/namespace-B/manifests
destination:
server: "https://kubernetes.default.svc"
namespace: namespace-B
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: esempio-app-b
namespace: namespace-B
spec:
replicas: 1
selector:
matchLabels:
app: esempio-app-b
template:
metadata:
labels:
app: esempio-app-b
spec:
containers:
- name: esempio-app-b
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: esempio-app-b
namespace: namespace-B
spec:
selector:
app: esempio-app-b
ports:
- port: 80
targetPort: 80
@@ -0,0 +1,13 @@
apiVersion: fleet.cattle.io/v1alpha1
kind: GitRepo
metadata:
name: namespace-c
namespace: fleet-local
spec:
repo: "https://github.com/<tua-org>/gitops-repo.git"
branch: main
paths:
- gitops/clusters/idc/namespace-C/manifests
targets:
- clusterName: local
namespace: namespace-C
@@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: esempio-app-c
namespace: namespace-C
spec:
replicas: 1
selector:
matchLabels:
app: esempio-app-c
template:
metadata:
labels:
app: esempio-app-c
spec:
containers:
- name: esempio-app-c
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: esempio-app-c
namespace: namespace-C
spec:
selector:
app: esempio-app-c
ports:
- port: 80
targetPort: 80
+256
View File
@@ -0,0 +1,256 @@
#!/usr/bin/env bash
# =====================================================================
# extract-manifests.sh
#
# Estrae tutte le risorse namespaced dai namespace indicati, le ripulisce
# con kubectl-neat (rimuove resourceVersion, uid, status, managedFields,
# creationTimestamp, ecc.) e le salva organizzate per namespace/kind,
# pronte per essere committate in un repo GitOps.
#
# Uso:
# ./extract-manifests.sh [opzioni] <namespace1> <namespace2> ...
# ./extract-manifests.sh [opzioni] --namespaces-file namespaces.txt
#
# Opzioni:
# -o, --output <dir> Directory di output (default: ./export)
# -s, --include-secrets Include anche i Secret (in CHIARO, base64 non
# cifrato — vedi warning qui sotto). Di default
# i Secret vengono SALTATI per sicurezza.
# -f, --namespaces-file File con un namespace per riga. Righe vuote e
# righe che iniziano con # vengono ignorate.
# -k, --kinds <lista> Lista custom di kind separati da virgola,
# al posto della discovery automatica
# (es. "deployment,service,httproute")
# -h, --help Mostra questo help
#
# Esempi:
# ./extract-manifests.sh monitoring minio idcidp-dev
# ./extract-manifests.sh -o ./gitops-repo/imported -s monitoring
# ./extract-manifests.sh -k "httproute,gateway" nginx-gateway
# ./extract-manifests.sh -f ./namespaces.txt -o ./gitops-repo/imported
#
# Prerequisiti:
# - kubectl configurato e puntato al cluster corretto
# - kubectl-neat installato (kubectl krew install neat)
# https://github.com/itaysk/kubectl-neat
# =====================================================================
set -euo pipefail
# ---------------------------------------------------------------------
# Default
# ---------------------------------------------------------------------
OUTPUT_DIR="./export"
INCLUDE_SECRETS="false"
CUSTOM_KINDS=""
NAMESPACES_FILE=""
# Kind che non ha senso portare in un repo GitOps: generati/gestiti
# automaticamente da controller, non sono mai "desired state" da
# dichiarare a mano.
EXCLUDED_KINDS="events pods replicasets endpoints endpointslices controllerrevisions"
usage() {
grep '^#' "$0" | sed -e 's/^#//' -e 's/^ //'
exit 0
}
load_namespaces_from_file() {
local file_path="$1"
if [[ ! -f "$file_path" ]]; then
echo "Errore: file namespace non trovato: $file_path" >&2
exit 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
[[ -z "$line" ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" ]] && continue
NAMESPACES+=("$line")
done < "$file_path"
}
# ---------------------------------------------------------------------
# Parsing argomenti
# ---------------------------------------------------------------------
NAMESPACES=()
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
OUTPUT_DIR="$2"
shift 2
;;
-s|--include-secrets)
INCLUDE_SECRETS="true"
shift
;;
-f|--namespaces-file)
NAMESPACES_FILE="$2"
shift 2
;;
-k|--kinds)
CUSTOM_KINDS="$2"
shift 2
;;
-h|--help)
usage
;;
-*)
echo "Opzione sconosciuta: $1" >&2
exit 1
;;
*)
NAMESPACES+=("$1")
shift
;;
esac
done
if [[ -n "$NAMESPACES_FILE" ]]; then
load_namespaces_from_file "$NAMESPACES_FILE"
fi
if [[ ${#NAMESPACES[@]} -eq 0 ]]; then
echo "Errore: specifica almeno un namespace o usa --namespaces-file." >&2
echo "Uso: $0 [opzioni] <namespace1> <namespace2> ..." >&2
echo " o: $0 [opzioni] --namespaces-file namespaces.txt" >&2
exit 1
fi
# ---------------------------------------------------------------------
# Prerequisiti
# ---------------------------------------------------------------------
if ! command -v kubectl >/dev/null 2>&1; then
echo "Errore: kubectl non trovato nel PATH." >&2
exit 1
fi
if ! kubectl neat --help >/dev/null 2>&1; then
echo "Errore: plugin kubectl-neat non trovato." >&2
echo "Installa con: kubectl krew install neat" >&2
echo "(krew: https://krew.sigs.k8s.io/docs/user-guide/setup/install/)" >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR"
echo "==> Output directory: $OUTPUT_DIR"
echo "==> Namespace da processare: ${NAMESPACES[*]}"
echo "==> Include Secret: $INCLUDE_SECRETS"
echo ""
if [[ "$INCLUDE_SECRETS" == "true" ]]; then
echo "########################################################"
echo "# ATTENZIONE: i Secret verranno esportati in CHIARO #"
echo "# (base64, NON cifrato). Non committarli in Git così #"
echo "# come sono. Usa Sealed Secrets, SOPS o External Secrets #"
echo "# Operator prima di aggiungerli al repo. #"
echo "########################################################"
echo ""
fi
# ---------------------------------------------------------------------
# Determina i kind namespaced da processare
# ---------------------------------------------------------------------
get_kinds() {
if [[ -n "$CUSTOM_KINDS" ]]; then
echo "$CUSTOM_KINDS" | tr ',' '\n'
return
fi
# Discovery automatica: tutti i kind namespaced supportati dal cluster
# (copre anche le CRD installate, es. httproute, podmonitor, cluster
# CNPG, ecc.), escludendo quelli in EXCLUDED_KINDS.
kubectl api-resources --namespaced=true --verbs=list -o name 2>/dev/null \
| cut -d. -f1 \
| sort -u \
| while read -r kind; do
skip="false"
for excl in $EXCLUDED_KINDS; do
[[ "$kind" == "$excl" ]] && skip="true" && break
done
[[ "$skip" == "false" ]] && echo "$kind"
done
}
KINDS=$(get_kinds)
# ---------------------------------------------------------------------
# Estrazione
# ---------------------------------------------------------------------
TOTAL_EXPORTED=0
TOTAL_EMPTY=0
TOTAL_ERRORS=0
for ns in "${NAMESPACES[@]}"; do
echo "=== Namespace: $ns ==="
if ! kubectl get namespace "$ns" >/dev/null 2>&1; then
echo " ! Namespace '$ns' non trovato, salto." >&2
continue
fi
ns_dir="${OUTPUT_DIR}/${ns}"
mkdir -p "$ns_dir"
while IFS= read -r kind; do
[[ -z "$kind" ]] && continue
# Salta i secret a meno che non richiesti esplicitamente
if [[ "$kind" == "secrets" || "$kind" == "secret" ]] && [[ "$INCLUDE_SECRETS" != "true" ]]; then
continue
fi
# Conta quante risorse di questo kind esistono nel namespace, per
# evitare di scrivere file vuoti/inutili
count=$(kubectl get "$kind" -n "$ns" --no-headers 2>/dev/null | wc -l | tr -d ' ')
if [[ "$count" -eq 0 ]]; then
TOTAL_EMPTY=$((TOTAL_EMPTY + 1))
continue
fi
out_file="${ns_dir}/${kind}.yaml"
if kubectl get "$kind" -n "$ns" -o yaml 2>/dev/null | kubectl neat > "$out_file" 2>/dev/null; then
# kubectl neat su una List vuota/malformata può comunque produrre
# un file quasi-vuoto: verifichiamo che contenga davvero "kind:"
if grep -q "^kind:" "$out_file" 2>/dev/null || grep -q "^items:" "$out_file" 2>/dev/null; then
echo " + ${kind} (${count})"
TOTAL_EXPORTED=$((TOTAL_EXPORTED + 1))
else
rm -f "$out_file"
fi
else
echo " ! Errore esportando ${kind}" >&2
rm -f "$out_file"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
fi
done <<< "$KINDS"
echo ""
done
echo "=== Riepilogo ==="
echo "Risorse esportate: $TOTAL_EXPORTED"
echo "Kind vuoti/saltati: $TOTAL_EMPTY"
echo "Errori: $TOTAL_ERRORS"
echo ""
echo "Output in: $OUTPUT_DIR"
echo ""
echo "Prossimi passi consigliati:"
echo " 1. Rivedi manualmente ogni file: alcuni campi (es. clusterIP,"
echo " nodePort, annotazioni iniettate da controller/webhook) vanno"
echo " rimossi a mano perché non fanno parte del 'desired state'."
echo " 2. Se hai esportato Secret, cifrali (Sealed Secrets / SOPS) prima"
echo " di committarli."
echo " 3. Riorganizza i file nella struttura del repo GitOps"
echo " (infrastructure/ vs apps/, vedi README del repo)."
+305
View File
@@ -0,0 +1,305 @@
#!/usr/bin/env bash
set -euo pipefail
OUTPUT_DIR="./generated-helmops"
API_VERSION="fleet.cattle.io/v1alpha1"
RESOURCE_KIND="HelmOp"
VALUES_ARGS=()
WORKSPACE_NAMESPACE="fleet-local"
usage() {
cat <<'EOF'
Usage:
./generate-fleet-helmops-from-helm.sh [options]
Description:
Legge le release presenti con `helm list -A`, recupera metadata e values
per ogni release e genera un file YAML per release in formato HelmOp-style.
Options:
-o, --output-dir <dir> Directory di output (default: ./generated-helmops)
-w, --workspace-namespace <ns>
Namespace dove creare la risorsa HelmOp
(default: fleet-local)
--api-version <value> apiVersion del manifest generato
(default: fleet.cattle.io/v1alpha1)
--kind <value> kind del manifest generato (default: HelmOp)
-a, --all-values Usa `helm get values -a -o yaml`
-h, --help Mostra questo help
Examples:
./generate-fleet-helmops-from-helm.sh
./generate-fleet-helmops-from-helm.sh -o ./clusters/idc/imported
./generate-fleet-helmops-from-helm.sh -w fleet-default
./generate-fleet-helmops-from-helm.sh --kind HelmChart --api-version helm.cattle.io/v1
EOF
}
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Errore: comando richiesto non trovato: $cmd" >&2
exit 1
fi
}
trim() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
safe_file_name() {
printf '%s' "$1" | tr '/\\:' '---'
}
extract_yaml_scalar() {
local key="$1"
local content="$2"
printf '%s\n' "$content" \
| sed -n "s/^[[:space:]]*${key}:[[:space:]]*//p" \
| head -n 1 \
| sed 's/^"//; s/"$//; s/^\x27//; s/\x27$//'
}
extract_first_source() {
local content="$1"
awk '
/^sources:/ { in_sources=1; next }
in_sources && /^[^[:space:]-]/ { exit }
in_sources && /^[[:space:]]*-[[:space:]]*/ {
sub(/^[[:space:]]*-[[:space:]]*/, "")
print
exit
}
' <<< "$content"
}
extract_chart_name_from_ref() {
local chart_ref="$1"
if [[ "$chart_ref" =~ ^(.+)-([0-9][A-Za-z0-9.+_-]*)$ ]]; then
printf '%s' "${BASH_REMATCH[1]}"
else
printf '%s' "$chart_ref"
fi
}
extract_chart_version_from_ref() {
local chart_ref="$1"
if [[ "$chart_ref" =~ ^(.+)-([0-9][A-Za-z0-9.+_-]*)$ ]]; then
printf '%s' "${BASH_REMATCH[2]}"
fi
}
find_repo_url_from_local_cache() {
local chart_name="$1"
local chart_version="$2"
local search_json=""
local repo_alias=""
[[ -z "$chart_name" ]] && return 0
search_json="$(helm search repo "$chart_name" --versions -o json 2>/dev/null || true)"
[[ -z "$search_json" || "$search_json" == "[]" ]] && return 0
if [[ -n "$chart_version" ]]; then
repo_alias="$(jq -r --arg chart "$chart_name" --arg version "$chart_version" '
map(select((.name | split("/") | last) == $chart and .version == $version))
| .[0].name // empty
' <<< "$search_json")"
fi
if [[ -z "$repo_alias" ]]; then
repo_alias="$(jq -r --arg chart "$chart_name" '
map(select((.name | split("/") | last) == $chart))
| .[0].name // empty
' <<< "$search_json")"
fi
[[ -z "$repo_alias" ]] && return 0
repo_alias="${repo_alias%%/*}"
helm repo list -o json 2>/dev/null \
| jq -r --arg alias "$repo_alias" 'map(select(.name == $alias)) | .[0].url // empty'
}
indent_file() {
local file_path="$1"
sed 's/^/ /' "$file_path"
}
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
-w|--workspace-namespace)
WORKSPACE_NAMESPACE="$2"
shift 2
;;
--api-version)
API_VERSION="$2"
shift 2
;;
--kind)
RESOURCE_KIND="$2"
shift 2
;;
-a|--all-values)
VALUES_ARGS=(-a)
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Opzione sconosciuta: $1" >&2
usage >&2
exit 1
;;
esac
done
require_cmd helm
require_cmd jq
mkdir -p "$OUTPUT_DIR"
releases_json="$(helm list -A -o json)"
release_count="$(jq 'length' <<< "$releases_json")"
if [[ "$release_count" -eq 0 ]]; then
echo "Nessuna release Helm trovata." >&2
exit 0
fi
echo "==> Release trovate: $release_count"
echo "==> Directory output: $OUTPUT_DIR"
generated_count=0
warning_count=0
while IFS= read -r release; do
name="$(jq -r '.name' <<< "$release")"
namespace="$(jq -r '.namespace' <<< "$release")"
chart_ref="$(jq -r '.chart // ""' <<< "$release")"
metadata_yaml="$(helm get metadata "$name" -n "$namespace" -o yaml 2>/dev/null || true)"
if [[ -z "$metadata_yaml" ]]; then
echo "! Impossibile leggere metadata per ${namespace}/${name}, salto." >&2
warning_count=$((warning_count + 1))
continue
fi
repo_url="$(trim "$(extract_yaml_scalar repo "$metadata_yaml")")"
if [[ -z "$repo_url" ]]; then
repo_url="$(trim "$(extract_yaml_scalar repository "$metadata_yaml")")"
fi
if [[ -z "$repo_url" ]]; then
repo_url="$(trim "$(extract_yaml_scalar repoURL "$metadata_yaml")")"
fi
if [[ -z "$repo_url" ]]; then
repo_url="$(trim "$(extract_first_source "$metadata_yaml")")"
fi
chart_name="$(trim "$(extract_yaml_scalar chart "$metadata_yaml")")"
chart_version="$(trim "$(extract_yaml_scalar version "$metadata_yaml")")"
if [[ -z "$chart_name" ]]; then
chart_name="$(extract_chart_name_from_ref "$chart_ref")"
fi
if [[ -z "$chart_version" ]]; then
chart_version="$(extract_chart_version_from_ref "$chart_ref")"
fi
if [[ -z "$repo_url" ]]; then
repo_url="$(trim "$(find_repo_url_from_local_cache "$chart_name" "$chart_version")")"
fi
if [[ -z "$repo_url" ]]; then
repo_url="REPO_URL_NOT_FOUND"
echo "! Repo URL non trovato in metadata o cache locale per ${namespace}/${name}" >&2
warning_count=$((warning_count + 1))
fi
if [[ -z "$chart_name" ]]; then
chart_name="CHART_NAME_NOT_FOUND"
echo "! Chart name non trovato per ${namespace}/${name}" >&2
warning_count=$((warning_count + 1))
fi
values_file="$(mktemp)"
if ! helm get values "$name" -n "$namespace" "${VALUES_ARGS[@]}" -o yaml > "$values_file" 2>/dev/null; then
printf '{}\n' > "$values_file"
echo "! Values non disponibili per ${namespace}/${name}, uso {}" >&2
warning_count=$((warning_count + 1))
fi
if [[ ! -s "$values_file" ]]; then
printf '{}\n' > "$values_file"
fi
values_compact="$(tr -d '[:space:]' < "$values_file")"
ns_dir="${OUTPUT_DIR}/${namespace}"
mkdir -p "$ns_dir"
safe_name="$(safe_file_name "$name")"
out_file="${ns_dir}/${safe_name}-helmop.yaml"
cat > "$out_file" <<EOF
apiVersion: ${API_VERSION}
kind: ${RESOURCE_KIND}
metadata:
name: ${safe_name}
namespace: ${WORKSPACE_NAMESPACE}
spec:
namespace: ${namespace}
helm:
releaseName: ${name}
repo: ${repo_url}
chart: ${chart_name}
EOF
if [[ -n "$chart_version" ]]; then
cat >> "$out_file" <<EOF
version: ${chart_version}
EOF
fi
if [[ "$values_compact" == "{}" ]]; then
cat >> "$out_file" <<EOF
values: {}
EOF
else
cat >> "$out_file" <<EOF
values:
$(indent_file "$values_file")
EOF
fi
rm -f "$values_file"
echo "+ Generato ${out_file} da ${namespace}/${name}"
generated_count=$((generated_count + 1))
done < <(jq -c '.[]' <<< "$releases_json")
echo ""
echo "=== Riepilogo ==="
echo "File generati: $generated_count"
echo "Warning: $warning_count"
echo "Output: $OUTPUT_DIR"
echo "Workspace namespace: $WORKSPACE_NAMESPACE"
echo ""
echo "Nota: il repo URL viene letto da 'helm get metadata' e, se assente,"
echo " viene cercato nella cache locale di 'helm repo list/search repo'."
echo "Se vedi REPO_URL_NOT_FOUND, completa il campo repo manualmente."
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
# Aggiunge un hostname a spec.template.spec.hostAliases del Deployment cert-manager.
#
# Uso:
# ./add-cert-manager-hostalias.sh <hostname>
#
# Esempio:
# ./add-cert-manager-hostalias.sh main-gateway-nginx.nginx-gateway.svc.cluster.local
# ./add-cert-manager-hostalias.sh api.internal
DEPLOYMENT_NAME="cert-manager"
DEPLOYMENT_NS="cert-manager"
TARGET_IP="10.43.37.118"
HOSTNAME_VAL="${1:-}"
if [[ -z "$HOSTNAME_VAL" ]]; then
echo "Uso: $0 <hostname>" >&2
exit 1
fi
if ! command -v kubectl >/dev/null 2>&1; then
echo "Errore: kubectl non trovato nel PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "Errore: jq non trovato nel PATH" >&2
echo "Installa jq e riprova." >&2
exit 1
fi
# Idempotenza: controlla solo il blocco hostAliases associato a TARGET_IP.
EXISTING_HOSTNAMES="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o jsonpath="{.spec.template.spec.hostAliases[?(@.ip=='${TARGET_IP}')].hostnames[*]}" 2>/dev/null || true)"
if echo " $EXISTING_HOSTNAMES " | grep -Fq " $HOSTNAME_VAL "; then
echo "Hostname '$HOSTNAME_VAL' gia presente per IP ${TARGET_IP} in ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}. Nessuna modifica."
exit 0
fi
DEPLOY_JSON="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o json)"
UPDATED_HOST_ALIASES="$({
echo "$DEPLOY_JSON" | jq -c --arg ip "$TARGET_IP" --arg hostname "$HOSTNAME_VAL" '
(.spec.template.spec.hostAliases //= [])
| if any(.spec.template.spec.hostAliases[]?; .ip == $ip) then
.spec.template.spec.hostAliases |= map(
if .ip == $ip then
if ((.hostnames // []) | index($hostname)) then
.
else
.hostnames = ((.hostnames // []) + [$hostname])
end
else
.
end
)
else
.spec.template.spec.hostAliases += [{"ip": $ip, "hostnames": [$hostname]}]
end
| .spec.template.spec.hostAliases
'
} )"
PATCH_PAYLOAD="$(jq -cn --argjson hostAliases "$UPDATED_HOST_ALIASES" '{spec:{template:{spec:{hostAliases:$hostAliases}}}}')"
kubectl patch deployment "$DEPLOYMENT_NAME" \
-n "$DEPLOYMENT_NS" \
--type=merge \
-p "$PATCH_PAYLOAD" >/dev/null
echo "Hostname aggiunto con successo."
echo " Deployment: ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}"
echo " IP : ${TARGET_IP}"
echo " Hostname : ${HOSTNAME_VAL}"
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
# Aggiunge un hostname a spec.template.spec.hostAliases del Deployment blackbox.
#
# Uso:
# ./add-hostalias.sh <hostname> <deployment_name> <deployment_namespace>
#
# Esempio:
# ./add-hostalias.sh idcidcp.pigreco66.it cert-manager cert-manager
#
DEPLOYMENT_NAME="${2:-}"
DEPLOYMENT_NS="${3:-}"
TARGET_IP="10.43.37.118"
HOSTNAME_VAL="${1:-}"
if [[ -z "$HOSTNAME_VAL" ]]; then
echo "Uso: $0 <hostname>" >&2
exit 1
fi
if ! command -v kubectl >/dev/null 2>&1; then
echo "Errore: kubectl non trovato nel PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "Errore: jq non trovato nel PATH" >&2
echo "Installa jq e riprova." >&2
exit 1
fi
# Idempotenza: controlla solo il blocco hostAliases associato a TARGET_IP.
EXISTING_HOSTNAMES="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o jsonpath="{.spec.template.spec.hostAliases[?(@.ip=='${TARGET_IP}')].hostnames[*]}" 2>/dev/null || true)"
if echo " $EXISTING_HOSTNAMES " | grep -Fq " $HOSTNAME_VAL "; then
echo "Hostname '$HOSTNAME_VAL' gia presente per IP ${TARGET_IP} in ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}. Nessuna modifica."
exit 0
fi
DEPLOY_JSON="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o json)"
UPDATED_HOST_ALIASES="$({
echo "$DEPLOY_JSON" | jq -c --arg ip "$TARGET_IP" --arg hostname "$HOSTNAME_VAL" '
(.spec.template.spec.hostAliases //= [])
| if any(.spec.template.spec.hostAliases[]?; .ip == $ip) then
.spec.template.spec.hostAliases |= map(
if .ip == $ip then
if ((.hostnames // []) | index($hostname)) then
.
else
.hostnames = ((.hostnames // []) + [$hostname])
end
else
.
end
)
else
.spec.template.spec.hostAliases += [{"ip": $ip, "hostnames": [$hostname]}]
end
| .spec.template.spec.hostAliases
'
} )"
PATCH_PAYLOAD="$(jq -cn --argjson hostAliases "$UPDATED_HOST_ALIASES" '{spec:{template:{spec:{hostAliases:$hostAliases}}}}')"
kubectl patch deployment "$DEPLOYMENT_NAME" \
-n "$DEPLOYMENT_NS" \
--type=merge \
-p "$PATCH_PAYLOAD" >/dev/null
echo "Hostname aggiunto con successo."
echo " Deployment: ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}"
echo " IP : ${TARGET_IP}"
echo " Hostname : ${HOSTNAME_VAL}"
+16
View File
@@ -35,6 +35,7 @@ fi
# Per calcolare il token usa host pulito (senza schema e path) # Per calcolare il token usa host pulito (senza schema e path)
host_for_token="${endpoint#*://}" host_for_token="${endpoint#*://}"
host_for_token="${host_for_token%%/*}" host_for_token="${host_for_token%%/*}"
host_no_port="${host_for_token%%:*}"
token="${host_for_token%%.*}" token="${host_for_token%%.*}"
if [[ -z "$token" ]]; then if [[ -z "$token" ]]; then
@@ -42,5 +43,20 @@ if [[ -z "$token" ]]; then
exit 1 exit 1
fi fi
# Se l'hostname non e nel dominio *.italiadatacenter.com, aggiungilo anche a cert-manager hostAliases.
if [[ "$host_no_port" != *.italiadatacenter.com ]]; then
HOSTALIAS_SCRIPT="/root/work/pipeline/add-hostalias.sh"
if [[ -x "$HOSTALIAS_SCRIPT" ]]; then
"$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager"
"$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring"
elif [[ -f "$HOSTALIAS_SCRIPT" ]]; then
bash "$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager"
bash "$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring"
else
echo "Errore: script non trovato: $HOSTALIAS_SCRIPT" >&2
exit 1
fi
fi
# Output richiesto: <endpoint> https-<token> <token>-secret # Output richiesto: <endpoint> https-<token> <token>-secret
/root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret /root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret
+29
View File
@@ -6,6 +6,19 @@ VALUES_DIR="env/$ENV"
PROPERTIES_FILE="properties.env" PROPERTIES_FILE="properties.env"
YAML_DIR="kubernetes" YAML_DIR="kubernetes"
# Ricava il nome progetto dalle variabili esposte da Gitea/GitHub Actions.
# Fallback locale: nome della directory del repository.
PROJECT_NAME="${GITHUB_REPOSITORY_NAME:-${GITEA_REPOSITORY_NAME:-}}"
if [ -z "$PROJECT_NAME" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then
PROJECT_NAME="${GITHUB_REPOSITORY##*/}"
fi
if [ -z "$PROJECT_NAME" ] && [ -n "${GITEA_REPOSITORY:-}" ]; then
PROJECT_NAME="${GITEA_REPOSITORY##*/}"
fi
if [ -z "$PROJECT_NAME" ]; then
PROJECT_NAME=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
fi
# Estrai l'hash completo del commit e crea una variabile temporanea per la sostituzione # Estrai l'hash completo del commit e crea una variabile temporanea per la sostituzione
TAG=$(git rev-parse HEAD) TAG=$(git rev-parse HEAD)
TMP_TAG_FILE=$(mktemp) TMP_TAG_FILE=$(mktemp)
@@ -24,6 +37,9 @@ if [ ! -f "$PROPERTIES_FILE" ]; then
exit 1 exit 1
fi fi
# Costruisce il nome del namespace come <project_name>-<env>
NAMESPACE="${PROJECT_NAME}-${ENV}"
# Crea una lista key=value temporanea partendo da tutti i file *.env in env/$ENV e aggiunge env dinamico # Crea una lista key=value temporanea partendo da tutti i file *.env in env/$ENV e aggiunge env dinamico
> "$TMP_VALUES_FILE" > "$TMP_VALUES_FILE"
for env_file in "$VALUES_DIR"/*.env; do for env_file in "$VALUES_DIR"/*.env; do
@@ -31,6 +47,19 @@ for env_file in "$VALUES_DIR"/*.env; do
printf '\n' >> "$TMP_VALUES_FILE" printf '\n' >> "$TMP_VALUES_FILE"
done done
printf '\nenv=%s\n' "$ENV" >> "$TMP_VALUES_FILE" printf '\nenv=%s\n' "$ENV" >> "$TMP_VALUES_FILE"
printf 'namespace=%s\n' "$NAMESPACE" >> "$TMP_VALUES_FILE"
printf 'project=%s\n' "$PROJECT_NAME" >> "$TMP_VALUES_FILE"
# Ricava endpoint se presente e genera le variabili derivate per le sostituzioni YAML.
ENDPOINT_VAL=$(grep -E '^[[:space:]]*endpoint[[:space:]]*=' "$TMP_VALUES_FILE" "$PROPERTIES_FILE" 2>/dev/null | tail -n 1 | sed -E 's/.*endpoint[[:space:]]*=[[:space:]]*//' | tr -d '\r\n[:space:]"' || true)
if [ -n "$ENDPOINT_VAL" ]; then
ENDPOINT_NODOT=$(echo "$ENDPOINT_VAL" | tr '.' '-')
ENDPOINT_NOSPACE=$(echo "$ENDPOINT_VAL" | tr -d '.')
printf 'endpoint-nodot=%s\n' "$ENDPOINT_NODOT" >> "$TMP_VALUES_FILE"
printf 'endpoint-nospace=%s\n' "$ENDPOINT_NOSPACE" >> "$TMP_VALUES_FILE"
fi
# Trova tutti i file .yaml nella directory kubernetes e sottodirectory # Trova tutti i file .yaml nella directory kubernetes e sottodirectory
find "$YAML_DIR" -type f -name "*.yaml" | while read YAML_FILE; do find "$YAML_DIR" -type f -name "*.yaml" | while read YAML_FILE; do
+7 -39
View File
@@ -7,52 +7,20 @@ ENVIRONMENT="${1:-dev}"
YAML_DIR="kubernetes" YAML_DIR="kubernetes"
# ---------------------------------------------------------------------------
# Cerca risorse postgresql.cnpg.io/v1 nei manifest e deploya una ConfigMap
# ---------------------------------------------------------------------------
CNPG_FILE=$(grep -rl "postgresql.cnpg.io/v1" "$YAML_DIR" 2>/dev/null | head -1 || true)
if [ -n "$CNPG_FILE" ]; then
echo "Trovata risorsa postgresql.cnpg.io/v1 in: $CNPG_FILE"
# Estrae metadata.name dal manifest CNPG preferendo yq, altrimenti awk
if command -v yq >/dev/null 2>&1; then
PG_NAME=$(yq eval 'select(.apiVersion == "postgresql.cnpg.io/v1") | .metadata.name' "$CNPG_FILE")
else
PG_NAME=$(awk '/postgresql\.cnpg\.io\/v1/{found=1} found && /^metadata:/{meta=1} meta && /^\s+name:/{print $2; exit}' "$CNPG_FILE")
fi
# Ricava il namespace dal Role namespace-deployer presente nel cluster
PG_NS=$(kubectl --kubeconfig=./kubeconfig get role namespace-deployer \
--no-headers \
-o custom-columns='NS:.metadata.namespace' 2>/dev/null | head -1 || true)
#PG_NS="${PG_NS:-default}"
if [ -z "$PG_NAME" ]; then
echo "⚠️ Impossibile estrarre metadata.name dal cluster CNPG, skip ConfigMap." >&2
else
echo " → cluster: $PG_NAME namespace: $PG_NS"
kubectl --kubeconfig=./kubeconfig apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: service-config
namespace: ${PG_NS}
data:
tipodb: "postgres"
urldb: "${PG_NAME}.${PG_NS}.svc.cluster.local"
EOF
echo "ConfigMap db-config deployata in namespace ${PG_NS}."
fi
fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Deploy di tutti i manifest Kubernetes # Deploy di tutti i manifest Kubernetes
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
find "$YAML_DIR" -type d | while read DIR; do find "$YAML_DIR" -type d | while read DIR; do
if ls "$DIR"/*.yaml 1> /dev/null 2>&1; then if ls "$DIR"/*.yaml 1> /dev/null 2>&1; then
echo "Deploy delle risorse nella directory $DIR..." if [ "$(basename "$DIR")" = "monitoring" ]; then
kubectl --kubeconfig=./kubeconfig apply -f "$DIR" echo "Deploy delle risorse di monitoring nella directory $DIR con kubeconfig dedicato..."
kubectl --kubeconfig=/root/work/pipeline/kc apply -f "$DIR"
else
echo "Deploy delle risorse nella directory $DIR..."
kubectl --kubeconfig=./kubeconfig apply -f "$DIR"
fi
fi fi
done done
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
. ./.profile
cd /project/inbound_figc
rm -f *.csv
rm -f *.csv_int*
./getftp.sh
./loadpgare.sh csexc17f.csv ana
./loadpgare.sh cseri17f.csv imp
./loadpgare.sh cserc17f.csv cal
./loadpgare.sh csezc17f.csv cla
#./loadpgare.sh cseyc17f.csv
/usr/local/bin/kubectl cp cserc17f.csv_int_utf8_nobom.csv db/postgresql-1-postgresql-0:/etc/cserc17f_2load_utf8_nobom.csv
/usr/local/bin/kubectl cp cseri17f.csv_int_utf8_nobom.csv db/postgresql-1-postgresql-0:/etc/cseri17f_2load_utf8_nobom.csv
/usr/local/bin/kubectl cp csexc17f.csv_int_utf8_nobom.csv db/postgresql-1-postgresql-0:/etc/csexc17f_2load_utf8_nobom.csv
/usr/local/bin/kubectl cp csezc17f.csv_int_utf8_nobom.csv db/postgresql-1-postgresql-0:/etc/csezc17f_2load_utf8_nobom.csv
#k cp csezc17f.csv_int_utf8_nobom.csv db/postgresql-1-postgresql-0:/etc/csezc17f_2load_utf8_nobom.csv
name=$(date '+%Y-%m-%d')
tar -zcvf "$name.tar.gz" *.csv
mv *.gz ./arch
/usr/local/bin/kubectl cp pgareload.sql db/postgresql-1-postgresql-0:/etc/pgareload.sql
/usr/local/bin/kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d pgare -f /etc/pgareload.sql
/usr/local/bin/kubectl cp loadclub.sql db/postgresql-1-postgresql-0:/etc/loadclub.sql
/usr/local/bin/kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d segdigi -f /etc/loadclub.sql
sleep 120
/usr/local/bin/kubectl cp partite_ftp.sql db/postgresql-1-postgresql-0:/etc/partite_ftp.sql
/usr/local/bin/kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d gare -f /etc/partite_ftp.sql
/usr/local/bin/kubectl cp campionati_ftp.sql db/postgresql-1-postgresql-0:/etc/campionati_ftp.sql
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d gare -f /etc/campionati_ftp.sql
/usr/local/bin/kubectl cp companies_ftp.sql db/postgresql-1-postgresql-0:/etc/companies_ftp.sql
/usr/local/bin/kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d companies -f /etc/companies_ftp.sql