Compare commits
10
Commits
5bca0da31f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23f276417f | ||
|
|
ff808293aa | ||
|
|
9c0189e08e | ||
|
|
d190719c5e | ||
|
|
d41d7f40cc | ||
|
|
3a0853dd4c | ||
|
|
63649a642a | ||
|
|
48f3f1ff22 | ||
|
|
f8ffa9e065 | ||
|
|
58a443a076 |
@@ -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.
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
|
||||
Binary file not shown.
@@ -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.
|
||||
@@ -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
|
||||
@@ -1,7 +1,32 @@
|
||||
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
|
||||
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 \
|
||||
--set hostname=k8s.italiadatacenter.com \
|
||||
--set bootstrapPassword=admin
|
||||
--reuse-values \
|
||||
--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
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube
|
||||
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 ################
|
||||
kubectl create namespace sonarqube
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)."
|
||||
@@ -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."
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -35,6 +35,7 @@ fi
|
||||
# Per calcolare il token usa host pulito (senza schema e path)
|
||||
host_for_token="${endpoint#*://}"
|
||||
host_for_token="${host_for_token%%/*}"
|
||||
host_no_port="${host_for_token%%:*}"
|
||||
token="${host_for_token%%.*}"
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
@@ -42,5 +43,20 @@ if [[ -z "$token" ]]; then
|
||||
exit 1
|
||||
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
|
||||
/root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret
|
||||
|
||||
@@ -6,6 +6,19 @@ VALUES_DIR="env/$ENV"
|
||||
PROPERTIES_FILE="properties.env"
|
||||
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
|
||||
TAG=$(git rev-parse HEAD)
|
||||
TMP_TAG_FILE=$(mktemp)
|
||||
@@ -24,6 +37,9 @@ if [ ! -f "$PROPERTIES_FILE" ]; then
|
||||
exit 1
|
||||
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
|
||||
> "$TMP_VALUES_FILE"
|
||||
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"
|
||||
done
|
||||
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
|
||||
find "$YAML_DIR" -type f -name "*.yaml" | while read YAML_FILE; do
|
||||
|
||||
+5
-37
@@ -7,53 +7,21 @@ ENVIRONMENT="${1:-dev}"
|
||||
|
||||
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
|
||||
# ---------------------------------------------------------------------------
|
||||
find "$YAML_DIR" -type d | while read DIR; do
|
||||
if ls "$DIR"/*.yaml 1> /dev/null 2>&1; then
|
||||
if [ "$(basename "$DIR")" = "monitoring" ]; then
|
||||
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
|
||||
done
|
||||
|
||||
echo "Deploy completato di tutte le directory YAML."
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user