Compare commits
11
Commits
5bca0da31f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34213918fe | ||
|
|
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"]
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
| | **Langflow** | **Kagent** |
|
||||||
|
| --------------- | -------------------------------------------- | --------------------------------------- |
|
||||||
|
| **Fase** | **Build time** | **Run time** |
|
||||||
|
| **Ruolo** | AI Agent Builder | AI Agent Runtime / Deployment |
|
||||||
|
| **Funzione** | Creare, configurare e testare agenti AI | Distribuire ed eseguire agenti AI |
|
||||||
|
| **Interfaccia** | Visuale / low-code | Kubernetes-native / YAML / CRD |
|
||||||
|
| **Deployment** | Può essere installato come servizio dell'IDP | È parte del runtime Kubernetes dell'IDP |
|
||||||
|
| **Output** | Flow/agent e relativo artefatto | Agent in esecuzione su Kubernetes |
|
||||||
|
|
||||||
|
Quindi:
|
||||||
|
|
||||||
|
### Langflow — Build Time
|
||||||
|
|
||||||
|
> **Software per progettare, costruire e testare AI Agent.**
|
||||||
|
|
||||||
|
Può essere installato **centralmente come servizio dell'IDP**, accessibile tramite browser dai developer.
|
||||||
|
|
||||||
|
Il developer può costruire, ad esempio:
|
||||||
|
|
||||||
|
```text
|
||||||
|
LLM
|
||||||
|
│
|
||||||
|
├── Knowledge Base
|
||||||
|
├── MCP Tool
|
||||||
|
├── API
|
||||||
|
└── Business Logic
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Agent
|
||||||
|
```
|
||||||
|
|
||||||
|
Il risultato può poi diventare un **artefatto software versionabile e deployabile**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Kagent — Run Time
|
||||||
|
|
||||||
|
> **Piattaforma Kubernetes-native per il deployment e l'esecuzione degli AI Agent.**
|
||||||
|
|
||||||
|
E qui la tua osservazione è importante: ci sono **due modalità**.
|
||||||
|
|
||||||
|
#### 1. Agent semplice definito direttamente
|
||||||
|
|
||||||
|
Per agenti relativamente semplici, la definizione può essere direttamente nella configurazione Kubernetes/Kagent:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
kind: Agent
|
||||||
|
metadata:
|
||||||
|
name: my-agent
|
||||||
|
spec:
|
||||||
|
model: ...
|
||||||
|
tools:
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Quindi:
|
||||||
|
|
||||||
|
```text
|
||||||
|
YAML
|
||||||
|
↓
|
||||||
|
Kagent
|
||||||
|
↓
|
||||||
|
Agent Runtime
|
||||||
|
↓
|
||||||
|
Kubernetes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Agent sviluppato con Langflow
|
||||||
|
|
||||||
|
Per agenti più complessi:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Langflow
|
||||||
|
│
|
||||||
|
│ Build
|
||||||
|
▼
|
||||||
|
Agent Artifact
|
||||||
|
│
|
||||||
|
│ CI/CD
|
||||||
|
▼
|
||||||
|
Registry / Git
|
||||||
|
│
|
||||||
|
│ Deploy
|
||||||
|
▼
|
||||||
|
Kagent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Kubernetes
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Running Agent
|
||||||
|
```
|
||||||
|
|
||||||
|
### La precisazione più importante
|
||||||
|
|
||||||
|
Direi però **"artefatti costruiti con Langflow"** piuttosto che:
|
||||||
|
|
||||||
|
> "Kagent esegue Langflow"
|
||||||
|
|
||||||
|
perché sono due cose diverse.
|
||||||
|
|
||||||
|
Kagent **non è il runtime di Langflow**. Il concetto corretto è:
|
||||||
|
|
||||||
|
> **Langflow è uno strumento di authoring/build; Kagent è uno strumento di deployment/runtime.**
|
||||||
|
|
||||||
|
L'artefatto prodotto da Langflow deve essere compatibile con il modello di deployment/runtime che hai scelto.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Quindi il modello del tuo IDP diventa molto chiaro
|
||||||
|
|
||||||
|
```text
|
||||||
|
IDP
|
||||||
|
│
|
||||||
|
┌──────────┴──────────┐
|
||||||
|
│ │
|
||||||
|
BUILD TIME RUN TIME
|
||||||
|
│ │
|
||||||
|
┌─────────┐ ┌─────────┐
|
||||||
|
│Langflow │ │ Kagent │
|
||||||
|
│ │ │ │
|
||||||
|
│ Build │──────────►│ Deploy │
|
||||||
|
│ Test │ Artifact │ Execute │
|
||||||
|
└─────────┘ └────┬────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Kubernetes
|
||||||
|
```
|
||||||
|
|
||||||
|
E aggiungerei un **terzo livello trasversale**:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IDP GOVERNANCE
|
||||||
|
┌────────────────────────────┐
|
||||||
|
│ Security │
|
||||||
|
│ RBAC │
|
||||||
|
│ Secrets │
|
||||||
|
│ Network Policy │
|
||||||
|
│ Resource Quotas │
|
||||||
|
│ Approved Models │
|
||||||
|
│ Approved MCP │
|
||||||
|
│ Versioning │
|
||||||
|
│ CI/CD │
|
||||||
|
│ Observability │
|
||||||
|
│ Compliance │
|
||||||
|
└────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────┴─────────┐
|
||||||
|
▼ ▼
|
||||||
|
Langflow Kagent
|
||||||
|
Build Time Run Time
|
||||||
|
```
|
||||||
|
|
||||||
|
**Questa secondo me è la rappresentazione più corretta per il tuo progetto IDP:** Langflow e Kagent non sono concorrenti, ma **componenti di due fasi diverse del lifecycle dell'Agent**.
|
||||||
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,146 @@
|
|||||||
|
# =====================================================================
|
||||||
|
# ToolServer — Gitea MCP (ufficiale), token con permessi SOLO lettura
|
||||||
|
# =====================================================================
|
||||||
|
apiVersion: kagent.dev/v1alpha2
|
||||||
|
kind: ToolServer
|
||||||
|
metadata:
|
||||||
|
name: gitea-mcp
|
||||||
|
namespace: platform-architect
|
||||||
|
spec:
|
||||||
|
type: RemoteMCPServer
|
||||||
|
remoteMCPServer:
|
||||||
|
url: "http://gitea-mcp.platform-tools.svc.cluster.local:8080"
|
||||||
|
# Il token Gitea sottostante (configurato nel deployment di
|
||||||
|
# gitea-mcp, non qui) deve avere SOLO permesso di lettura
|
||||||
|
# (repo:read) sull'organizzazione idp-templates — l'agente non
|
||||||
|
# deve mai poter scrivere/modificare i template
|
||||||
|
|
||||||
|
---
|
||||||
|
# =====================================================================
|
||||||
|
# Agent dichiarativo — l'architetto esperto della IDP
|
||||||
|
# =====================================================================
|
||||||
|
apiVersion: kagent.dev/v1alpha2
|
||||||
|
kind: Agent
|
||||||
|
metadata:
|
||||||
|
name: idp-architect
|
||||||
|
namespace: platform-architect
|
||||||
|
spec:
|
||||||
|
description: >
|
||||||
|
Architetto esperto della Internal Developer Platform. Analizza le
|
||||||
|
specifiche applicative di un dev team e propone lo scenario
|
||||||
|
architetturale compliant, basandosi sul catalogo ufficiale dei
|
||||||
|
template della piattaforma.
|
||||||
|
type: Declarative
|
||||||
|
declarative:
|
||||||
|
modelConfig: gpt4o-config
|
||||||
|
|
||||||
|
a2aConfig:
|
||||||
|
skills:
|
||||||
|
- id: propose-architecture
|
||||||
|
name: Propose Architecture
|
||||||
|
description: >
|
||||||
|
Data una specifica applicativa, propone il template IDP
|
||||||
|
più adatto con motivazione e piano di customizzazione.
|
||||||
|
inputModes: ["text"]
|
||||||
|
outputModes: ["text"]
|
||||||
|
tags: ["architecture", "idp", "platform-engineering"]
|
||||||
|
|
||||||
|
systemMessage: |
|
||||||
|
# Ruolo
|
||||||
|
|
||||||
|
Sei l'architetto esperto della Internal Developer Platform (IDP)
|
||||||
|
di pigreco66.it. Il tuo compito è aiutare i dev team a scegliere
|
||||||
|
e adottare il template architetturale più corretto tra quelli
|
||||||
|
ufficialmente supportati dalla piattaforma, a fronte delle loro
|
||||||
|
specifiche applicative.
|
||||||
|
|
||||||
|
Il tuo obiettivo non è solo tecnico: sei anche lo strumento
|
||||||
|
principale di ADOPTION della piattaforma. Un dev team che parla
|
||||||
|
con te deve percepire che usare l'IDP è PIÙ VELOCE e PIÙ SICURO
|
||||||
|
che costruire da zero, non un vincolo burocratico.
|
||||||
|
|
||||||
|
# Fonte di verità — NON INVENTARE TEMPLATE
|
||||||
|
|
||||||
|
L'unica fonte di verità sui template disponibili è il catalogo
|
||||||
|
ufficiale, accessibile tramite i tool Gitea MCP:
|
||||||
|
|
||||||
|
1. Leggi SEMPRE prima l'indice aggregato:
|
||||||
|
repo "idp-templates-catalog", file "catalog-index.yaml"
|
||||||
|
2. Per i template candidati migliori (massimo 2-3), leggi il
|
||||||
|
dettaglio completo nel rispettivo repo, file
|
||||||
|
"idp-template.yaml", per verificare compliance, parametri e
|
||||||
|
vincoli (not_suitable_for) prima di confermare la proposta.
|
||||||
|
|
||||||
|
Non proporre MAI un'architettura o un componente che non esiste
|
||||||
|
nel catalogo. Se nessun template è adeguato, dillo esplicitamente
|
||||||
|
(vedi sezione "Quando non c'è un template adatto").
|
||||||
|
|
||||||
|
# Processo da seguire
|
||||||
|
|
||||||
|
1. **Raccogli le specifiche**: se l'input del dev team è
|
||||||
|
incompleto (manca almeno una di: tipo di workload,
|
||||||
|
persistenza dati, pattern di comunicazione, volumetria
|
||||||
|
attesa, classificazione dati/PII), fai domande di
|
||||||
|
chiarimento mirate PRIMA di proporre. Non indovinare requisiti
|
||||||
|
critici per sicurezza/compliance.
|
||||||
|
|
||||||
|
2. **Fai matching contro il catalogo** usando i tool Gitea MCP
|
||||||
|
come descritto sopra.
|
||||||
|
|
||||||
|
3. **Verifica i servizi cross della piattaforma**: controlla il
|
||||||
|
campo "cross_platform_services" del template candidato — se
|
||||||
|
il dev team descrive un bisogno già coperto da un servizio
|
||||||
|
condiviso (es. "mi serve una cache" quando esiste
|
||||||
|
shared-redis-cache), segnalalo esplicitamente: NON serve
|
||||||
|
che lo costruiscano, è già disponibile.
|
||||||
|
|
||||||
|
4. **Proponi**, con questa struttura:
|
||||||
|
- Template consigliato (id + nome + link al repo)
|
||||||
|
- Perché è compliant con le specifiche (motivazione tecnica)
|
||||||
|
- Eventuali servizi cross della piattaforma da riusare
|
||||||
|
- Parametri da personalizzare (dai file in env/)
|
||||||
|
- Eventuali gap o customizzazioni necessarie oltre al
|
||||||
|
template base
|
||||||
|
- Se pertinente, alternative scartate e perché
|
||||||
|
|
||||||
|
5. Se richiesto, genera il comando/istruzioni operative per
|
||||||
|
clonare/istanziare il template (basati sulla struttura reale
|
||||||
|
del repo: kubernetes/, containers/, src/, env/).
|
||||||
|
|
||||||
|
# Quando NON c'è un template adatto
|
||||||
|
|
||||||
|
Se nessun template del catalogo copre adeguatamente le
|
||||||
|
specifiche (es. gap reale, non solo mancanza di dettagli),
|
||||||
|
dillo chiaramente al dev team e segnala che si tratta di un
|
||||||
|
caso da portare al platform engineering team per valutare
|
||||||
|
l'introduzione di un nuovo template. NON improvvisare
|
||||||
|
un'architettura custom spacciandola per "compliant" — altrimenti
|
||||||
|
perdi la fiducia che è il tuo scopo costruire.
|
||||||
|
|
||||||
|
# Perimetro di azione — SOLO CONSULTIVO
|
||||||
|
|
||||||
|
In questa fase operi in modalità puramente consultiva: la tua
|
||||||
|
proposta è testuale e informativa, non esegui mai direttamente
|
||||||
|
azioni che modificano repository, pipeline o infrastruttura
|
||||||
|
(non crei repo, non apri PR, non modifichi file). Se il dev team
|
||||||
|
ti chiede di "farlo per loro" (es. "creamelo tu il repo"),
|
||||||
|
rispondi spiegando i passi operativi che devono seguire loro
|
||||||
|
stessi (clonare il template, personalizzare env/), senza
|
||||||
|
eseguirli al posto loro.
|
||||||
|
|
||||||
|
# Tono
|
||||||
|
|
||||||
|
Sei un collega esperto, non un gatekeeper burocratico. Spiega
|
||||||
|
il "perché" delle scelte, non solo il "cosa" — un dev team che
|
||||||
|
capisce il ragionamento adotta la piattaforma più volentieri di
|
||||||
|
uno a cui viene solo imposto un verdetto.
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- type: McpServer
|
||||||
|
mcpServer:
|
||||||
|
name: gitea-mcp
|
||||||
|
kind: RemoteMCPServer
|
||||||
|
toolNames:
|
||||||
|
- get_file_content
|
||||||
|
- get_dir_content
|
||||||
|
- list_repo_commits
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: kagent.dev/v1alpha2
|
||||||
|
kind: AgentHarness
|
||||||
|
metadata:
|
||||||
|
name: idp-architect-slack
|
||||||
|
namespace: platform-architect
|
||||||
|
spec:
|
||||||
|
backend: hermes
|
||||||
|
agentRef:
|
||||||
|
name: idp-architect
|
||||||
|
namespace: platform-architect
|
||||||
|
channels:
|
||||||
|
- name: idp-architect-chat
|
||||||
|
type: slack
|
||||||
|
slack:
|
||||||
|
botToken:
|
||||||
|
valueFrom: { type: Secret, name: slack-tokens, key: bot-token }
|
||||||
|
appToken:
|
||||||
|
valueFrom: { type: Secret, name: slack-tokens, key: app-token }
|
||||||
|
# Canale dedicato consigliato: #idp-architect-support
|
||||||
|
# così tutti i dev team sanno dove chiedere, e le conversazioni
|
||||||
|
# restano visibili/searchable per il resto del team (effetto
|
||||||
|
# knowledge-sharing, non solo 1:1 privato)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# idp-templates-catalog/catalog-index.yaml
|
||||||
|
templates:
|
||||||
|
- id: tpl-java-springboot-rest-api
|
||||||
|
name: "Java Spring Boot — REST API sincrona"
|
||||||
|
workload_type: [sync-api, rest]
|
||||||
|
tags: [java, spring-boot, rest, sync, stateless]
|
||||||
|
status: active
|
||||||
|
repo_url: "https://gitea.pigreco66.it/idp-templates/java-springboot-rest-api"
|
||||||
|
- id: tpl-node-kafka-consumer
|
||||||
|
name: "Node.js Kafka Consumer"
|
||||||
|
workload_type: [async-consumer, event-driven]
|
||||||
|
tags: [nodejs, kafka, async]
|
||||||
|
status: active
|
||||||
|
repo_url: "..."
|
||||||
|
# ... altri 18
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# idp-template.yaml — da posizionare nella root di ogni repo Gitea
|
||||||
|
# di tipo "template". Questo è l'esempio compilato per un template
|
||||||
|
# "REST API sincrona Java/Spring Boot".
|
||||||
|
|
||||||
|
id: tpl-java-springboot-rest-api
|
||||||
|
name: "Java Spring Boot — REST API sincrona"
|
||||||
|
version: "2.1.0"
|
||||||
|
status: active # active | beta | deprecated
|
||||||
|
last_updated: "2026-09-01"
|
||||||
|
maintainer: "platform-engineering@pigreco66.it"
|
||||||
|
repo_url: "https://gitea.pigreco66.it/idp-templates/java-springboot-rest-api"
|
||||||
|
|
||||||
|
# --- Classificazione, usata dall'agente per il primo filtro ---
|
||||||
|
category: api-service
|
||||||
|
workload_type:
|
||||||
|
- sync-api
|
||||||
|
- rest
|
||||||
|
|
||||||
|
tech_stack:
|
||||||
|
language: Java 21
|
||||||
|
framework: Spring Boot 3.3
|
||||||
|
build_tool: Maven
|
||||||
|
|
||||||
|
tags: ["java", "spring-boot", "rest", "sync", "stateless"]
|
||||||
|
|
||||||
|
description: >
|
||||||
|
Template per esporre una REST API sincrona stateless. Include
|
||||||
|
healthcheck, metrics endpoint Prometheus-ready, struttura a layer
|
||||||
|
controller/service/repository, logging strutturato JSON.
|
||||||
|
|
||||||
|
# --- Cosa contiene esattamente la struttura del repo ---
|
||||||
|
components_included:
|
||||||
|
kubernetes:
|
||||||
|
- Deployment (con probe readiness/liveness preconfigurate)
|
||||||
|
- Service (ClusterIP)
|
||||||
|
- HorizontalPodAutoscaler
|
||||||
|
- NetworkPolicy (default-deny + regole minime)
|
||||||
|
containers:
|
||||||
|
- Dockerfile multi-stage (build + runtime minimale distroless)
|
||||||
|
src:
|
||||||
|
- hello-world Spring Boot funzionante, con un endpoint /health e
|
||||||
|
un endpoint di esempio /api/v1/ping
|
||||||
|
env:
|
||||||
|
- application-dev.properties
|
||||||
|
- application-staging.properties
|
||||||
|
- application-prod.properties
|
||||||
|
|
||||||
|
# --- Integrazione con servizi cross della piattaforma ---
|
||||||
|
# Fondamentale: dice all'agente COSA NON riproporre perché già
|
||||||
|
# disponibile centralmente (evita duplicazione di servizi)
|
||||||
|
cross_platform_services:
|
||||||
|
- service: shared-postgres
|
||||||
|
how: "via env PLATFORM_DB_CONNECTION_STRING, iniettata dalla pipeline"
|
||||||
|
- service: shared-redis-cache
|
||||||
|
how: "opzionale, abilitabile via env ENABLE_CACHE=true"
|
||||||
|
- service: centralized-logging
|
||||||
|
how: "automatico, nessuna configurazione richiesta"
|
||||||
|
|
||||||
|
# --- Vincoli di compliance soddisfatti dal template ---
|
||||||
|
compliance:
|
||||||
|
data_classification_supported: ["public", "internal", "confidential"]
|
||||||
|
# NON supporta dati PII senza customizzazioni aggiuntive
|
||||||
|
pii_ready: false
|
||||||
|
network_policy_baseline: "default-deny"
|
||||||
|
security_scanning: "incluso in pipeline (SAST + container scan)"
|
||||||
|
|
||||||
|
scaling:
|
||||||
|
default_replicas: 2
|
||||||
|
autoscaling: hpa
|
||||||
|
min_replicas: 2
|
||||||
|
max_replicas: 10
|
||||||
|
|
||||||
|
# --- Parametri personalizzabili (quelli nei file in env/) ---
|
||||||
|
parameters:
|
||||||
|
- name: PLATFORM_DB_CONNECTION_STRING
|
||||||
|
required: false
|
||||||
|
description: "Richiesta solo se il servizio necessita persistenza"
|
||||||
|
- name: ENABLE_CACHE
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
# --- Quando NON usare questo template (altrettanto utile del 'quando sì') ---
|
||||||
|
not_suitable_for:
|
||||||
|
- "Workload asincroni basati su eventi (usa tpl-java-kafka-consumer)"
|
||||||
|
- "Batch job schedulati (usa tpl-batch-job)"
|
||||||
|
- "Dati classificati come PII senza data masking aggiuntivo"
|
||||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="210mm" height="297mm">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="b">
|
||||||
|
<stop offset="0" stop-color="#fff"/>
|
||||||
|
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="e" x1="49.136964" x2="747.09593" y1="467.2588" y2="467.2588" gradientTransform="scale(.90134 1.10946)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#729fcf"/>
|
||||||
|
<stop offset="1" stop-color="#204a87"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="a">
|
||||||
|
<stop offset="0" stop-color="#fff"/>
|
||||||
|
<stop offset="1" stop-color="#729fcf"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="f">
|
||||||
|
<stop offset="0"/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="c">
|
||||||
|
<stop offset="0" stop-color="#3977c3"/>
|
||||||
|
<stop offset="1" stop-color="#89aedc" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="d">
|
||||||
|
<stop offset="0" stop-color="#729fcf"/>
|
||||||
|
<stop offset=".31578946" stop-color="#babdb6"/>
|
||||||
|
<stop offset="1" stop-color="#3465a4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient xlink:href="#a" id="k" x1="24" x2="24" y1="16.525082" y2="13.284962" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient xlink:href="#b" id="l" x1="16.07143" x2="15.785715" y1="19.5" y2="30" gradientTransform="translate(-152.46168 8.8865747) scale(21.1429)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient xlink:href="#c" id="j" x1="17.160095" x2="37.969398" y1="14.004482" y2="56.575912" gradientTransform="translate(-149.44117 -46.991167) scale(21.1429)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient xlink:href="#d" id="h" x1="9.3030529" x2="38.826412" y1="26.71875" y2="26.71875" gradientTransform="translate(-149.44117 -46.991167) scale(21.1429)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<linearGradient xlink:href="#e" id="i" x1="49.136964" x2="747.09593" y1="467.2588" y2="467.2588" gradientTransform="matrix(.90134 0 0 1.10946 .52026458 -.48501452)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
<radialGradient xlink:href="#f" id="g" cx="23.857143" cy="40" r="17.142857" fx="23.857143" fy="40" gradientTransform="matrix(1 0 0 .5 0 20)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#g)" d="M41 40a17.142857 8.5714283 0 1 1-34.2857132 0A17.142857 8.5714283 0 1 1 41 40z" display="block" overflow="visible" style="marker:none" transform="matrix(22.3851 0 0 24.34096 -173.03587 -228.02997)"/>
|
||||||
|
<path fill="url(#h)" stroke="url(#i)" stroke-miterlimit="10" stroke-width="21.14290619" d="M356.66648 141.31284c-166.17052 0-301.285983 59.253-301.285983 132.14314v488.92965c0 72.89018 135.115463 132.14315 301.285983 132.14315 166.17053 0 306.66725-59.25297 306.66725-132.14315V273.45598c0-72.89014-140.49672-132.14314-306.66725-132.14314z" display="block" overflow="visible" style="marker:none"/>
|
||||||
|
<path fill="none" stroke="url(#j)" stroke-miterlimit="10" stroke-width="21.14290619" d="M356.84833 157.78994c-154.92885 0-280.904411 55.24468-280.904411 123.20413v467.81359c0 67.95922 125.975561 123.20393 280.904411 123.20393 154.93094 0 285.92181-55.24471 285.92181-123.20393V280.99407c0-67.95945-130.99087-123.20413-285.92181-123.20413z" display="block" opacity=".5443038" overflow="visible" style="marker:none"/>
|
||||||
|
<path fill="#3465a4" stroke="url(#k)" stroke-miterlimit="10" stroke-width="1.00492704" d="M36.857142 14.071428a12.857142 5.5 0 1 1-25.714284 0 12.857142 5.5 0 1 1 25.714284 0z" display="block" overflow="visible" style="marker:none" transform="matrix(21.93135 0 0 20.18342 -168.36368 -12.347239)"/>
|
||||||
|
<path fill="url(#l)" d="m139.00874 369.82596 7.5501 354.89887 96.65272 27.18366-3.0192-353.38841s43.7954 0 114.77637-4.53074c-110.27718-4.92776-236.44943-52.3219-279.389384-83.06136 29.958254 44.23476 63.429394 58.89798 63.429394 58.89798z" display="block" opacity=".49367083" overflow="visible" style="marker:none"/>
|
||||||
|
<path fill="#204a87" d="M114.15586 701.5918h16.09033v-63.74234l25.1253 55.573c2.84764 6.6832 6.93184 9.15937 14.8527 9.15937 7.92167 0 11.7583-2.47543 14.72914-9.15866l25.00197-55.57371v63.74234h16.21373v-63.74234c0-6.18856-2.47543-9.15937-7.67397-10.7683-12.25289-3.71313-20.42233-.4946-24.13546 7.79743l-24.63007 54.95407-23.7643-54.95407c-3.58957-8.29206-11.88261-11.51056-24.25961-7.79743-5.07443 1.6089-7.54976 4.5797-7.54976 10.7683v63.74234zm125.38108-51.86037h16.21373v35.0272c-.12343 1.98003.61887 6.43708 9.40633 6.56054 4.5797.12276 34.90377 0 35.15144 0v-41.83541h16.21454v57.30688c0 14.1095-17.45224 17.20454-25.62087 17.328h-50.99404v-10.7683h51.1175c10.39707-1.11347 9.15937-6.31203 9.15937-8.0452v-4.20854h-34.28487c-15.96684-.12343-26.2397-7.1786-26.3632-15.22377 0-.7431.37113-35.89373 0-36.14147l.00007.00007zm347.79917-40.59788c-9.90174-.24686-17.57571.74388-24.01204 3.46624-1.85657.7423-4.8274.74307-5.07511 3.09427.99081.9908 1.11427 2.59967 1.98084 3.96087 1.48621 2.47462 4.0843 5.8174 6.43627 7.5505 2.5989 1.98003 5.19857 3.96006 7.92093 5.6932 4.8274 2.97084 10.27364 4.70313 14.97681 7.67397 2.72394 1.73152 5.44549 3.96083 8.16863 5.8174 1.36197.98926 2.22773 2.5989 3.96083 3.21776v-.37116c-.86653-1.11424-1.11423-2.72314-1.98003-3.96084-1.23692-1.23773-2.4754-2.35197-3.71313-3.5897-3.58967-4.82663-8.04517-9.03513-12.87251-12.5006-3.96006-2.72313-12.62483-6.43627-14.23376-11.01597.00077 0-.12411-.12343-.24754-.24686 2.72314-.24787 5.94087-1.2377 8.54057-1.9808 4.20854-1.11427 8.04517-.86657 12.37714-1.98003 1.98003-.49541 3.96083-1.11427 5.94086-1.73314v-1.11346c-2.22692-2.22851-3.83659-5.19854-6.18856-7.30284-6.31203-5.44704-13.24374-10.76827-20.42234-15.22377-3.83737-2.47543-8.78824-4.0851-12.8725-6.1886-1.48463-.74387-3.96087-1.11423-4.82663-2.35197-2.22851-2.7231-3.46624-6.31203-5.07511-9.53056-3.58812-6.80821-7.05513-14.3572-10.1494-21.5358-2.22692-4.82741-3.58889-9.65484-6.31203-14.11024-12.74826-21.04113-26.6109-33.7895-47.89974-46.29087-4.5797-2.5989-10.02516-3.71314-15.8426-5.07433-3.09349-.12411-6.18856-.37114-9.28286-.49537-1.98004-.86657-3.96087-3.21777-5.69398-4.33204-7.05355-4.45623-25.24893-14.10949-30.4475-1.36116-3.34196 8.04517 4.95087 15.96606 7.79744 20.05114 2.10504 2.84659 4.8274 6.06433 6.3128 9.2829.86657 2.10268 1.1135 4.332 1.98003 6.55973 1.98084 5.44469 3.83663 11.51057 6.4363 16.5857 1.36194 2.5989 2.84657 5.32204 4.57971 7.6732.98999 1.36116 2.72232 1.9808 3.09426 4.20853-1.73233 2.47544-1.85656 6.18861-2.84737 9.28291-4.45546 13.98599-2.72233 31.31406 3.5897 41.5869 1.98003 3.09352 6.68394 9.9025 12.99604 7.30287 5.5697-2.22777 4.332-9.28291 5.94086-15.47151.37194-1.48617.12411-2.4754.86657-3.46543.00078.12343 0 .24787 0 .24787 1.7331 3.46543 3.46543 6.80747 5.0743 10.27283 3.8374 6.06437 10.52063 12.37717 16.0903 16.58574 2.97161 2.22696 5.32281 6.06436 9.03594 7.4263v-.37194h-.24787c-.7431-1.11427-1.85657-1.60886-2.8466-2.47543-2.22773-2.22773-4.70394-4.95087-6.43627-7.42627-5.19776-6.9317-9.77827-14.6049-13.86253-22.52657-1.98003-3.83659-3.71317-8.04516-5.32204-11.88176-.7431-1.48621-.7431-3.71314-1.9808-4.45547-1.85657 2.72237-4.57893 5.07434-5.9409 8.41634-2.35039 5.322-2.59886 11.88176-3.46543 18.68913-.49463.1241-.24787 0-.4954.24787-3.96006-.99081-5.32204-5.07433-6.80747-8.54057-3.71313-8.78743-4.332-22.89774-1.11346-33.04637.86656-2.59967 4.5797-10.7683 3.09426-13.24373-.74229-2.35197-3.2185-3.71314-4.5797-5.5697-1.60886-2.35197-3.34197-5.32203-4.45543-7.92167-2.97084-6.9309-4.45627-14.6049-7.674-21.53661-1.4854-3.2185-4.0843-6.55973-6.18857-9.52976-2.35119-3.342-4.95087-5.69397-6.80747-9.65484-.61806-1.36116-1.4854-3.58889-.4954-5.07433.24787-.99.74233-1.36116 1.7331-1.60886 1.6089-1.36194 6.18861.37116 7.79744 1.11349 4.5797 1.85576 8.41633 3.58967 12.2537 6.18857 1.73233 1.2377 3.5889 3.5897 5.81663 4.20853h2.59967c3.96087.8658 8.41633.24754 12.12947 1.3612 6.5605 2.10346 12.50059 5.19854 17.82343 8.54054 16.21454 10.27287 29.5809 24.87854 38.61691 42.33004 1.48543 2.84582 2.10349 5.44627 3.46546 8.41633 2.59967 6.0651 5.81741 12.2537 8.4163 18.19457 2.5989 5.81663 5.07433 11.75833 8.78747 16.58574 1.85734 2.59812 9.28286 3.96006 12.62483 5.32203 2.4754 1.11346 6.31281 2.10427 8.54054 3.46543 4.20776 2.59809 8.4163 5.56974 12.37713 8.4163 1.98084 1.48466 8.16863 4.57974 8.54057 7.05436h.00004z"/>
|
||||||
|
<path fill="#00546b" d="M461.08775 501.57571c-2.10427 0-3.58889.24854-5.07429.61964v.24787h.24786c.99081 1.98003 2.72233 3.342 3.96007 5.0743.9908 1.98003 1.85656 3.96086 2.84737 5.94167.12343-.12411.24686-.24787.24686-.24787 1.73391-1.23851 2.59967-3.21854 2.59967-6.18857-.7431-.86657-.86657-1.7331-1.4854-2.59967-.74233-1.23851-2.35197-1.85657-3.342-2.84737z"/>
|
||||||
|
<path fill="#f57900" d="M328.90008 701.5918h46.53854c5.4455 0 10.64406-1.11346 14.85179-3.0943 7.05595-3.21773 10.39711-7.54973 10.39711-13.2437v-11.88257c0-4.57893-3.83741-8.9109-11.51057-11.88177-3.96006-1.48463-8.9117-2.35119-13.7391-2.35119h-19.55496c-6.56051 0-9.65481-1.98081-10.52134-6.31281-.12411-.49463-.12411-.99003-.12411-1.48463v-7.30287c0-.37113 0-.86653.12411-1.36116.86656-3.34197 2.59889-4.20854 8.29286-4.8274h47.65281v-10.76831h-45.30164c-6.55974 0-10.02517.37117-13.1195 1.36197-9.53057 2.96926-13.73911 7.67317-13.73911 15.96603v9.40714c0 7.30203 8.16944 13.49063 22.03198 14.97603 1.48543.12343 3.0943.12343 4.70316.12343h16.70917c.61887 0 1.23773 0 1.73314.12411 5.07429.49459 7.30203 1.36116 8.78743 3.21773.99003.98922 1.23773 1.85656 1.23773 2.97083l.00078 9.40556c0 1.11424-.74311 2.60045-2.22851 3.83815-1.48466 1.23773-3.8374 2.1043-6.93174 2.22773-.61806 0-.99.12343-1.60886.12343l-44.68124-.00077v10.76907l.00007.00007zm172.53802-18.68916c0 11.01438 8.29287 17.20376 24.87773 18.44146 1.60886.1241 3.09426.24787 4.70316.24787h42.08389v-10.76908h-42.45431c-9.40633 0-12.99674-2.35042-12.99674-8.04439l.00077-55.69721h-16.21454v55.82148l.00004.00003zm-90.47782.49442v-38.36913c0-9.77747 6.9309-15.71914 20.42234-17.57567 1.48621-.1241 2.97083-.24686 4.332-.24686l30.69517-.00078c1.61044 0 2.97083.12411 4.5797.24754 13.4914 1.85576 20.29887 7.7982 20.29887 17.57567l.00081 38.36913c0 7.92097-2.84741 12.13028-9.53141 14.97684l15.8434 14.3572H478.912l-12.87251-11.6348-12.99603.74229h-17.32881c-2.84582 0-6.06514-.37036-9.40633-1.23692-10.27367-2.8466-15.34797-8.29287-15.34797-17.20457l-.00007-.00004zm17.45147-.86653c0 .49459.24753.9908.37194 1.60967.86576 4.45543 5.07429 6.9309 11.51056 6.9309h14.60487l-13.36717-12.13027h18.68997l11.75753 10.64487c2.22931-1.23851 3.58967-2.97007 4.08507-5.1978.12343-.49537.12343-1.11423.12343-1.60964v-36.7603c0-.49463 0-.99003-.12343-1.48463-.86653-4.20857-5.0751-6.56054-11.3871-6.56054h-24.38317c-7.1786 0-11.88257 3.0943-11.88257 8.04517v36.51264l.00007-.00007zm152.11655 19.18436h2.35197v-9.03513h3.0943v-1.85734h-8.6648v1.85734h3.21853v9.03513zm17.94771 0h2.22693v-10.8925h-3.3412l-2.72394 7.42627-2.96925-7.42627h-3.21851v10.8925h2.10427v-8.29286h.12343l3.09427 8.29286h1.60967l3.0943-8.29286v8.29286z"/>
|
||||||
|
<path fill="none" d="M-64.879845 115.85568h850v850h-850z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -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
|
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
|
||||||
kubectl create namespace cattle-system
|
kubectl create namespace cattle-system
|
||||||
|
|
||||||
helm install rancher rancher-stable/rancher \
|
helm install rancher rancher-stable/rancher --namespace cattle-system --set hostname=idc.internal --set bootstrapPassword=.... --set ingress.tls.source=rancher
|
||||||
|
|
||||||
|
|
||||||
|
helm upgrade rancher rancher-stable/rancher \
|
||||||
--namespace cattle-system \
|
--namespace cattle-system \
|
||||||
--set hostname=k8s.italiadatacenter.com \
|
--reuse-values \
|
||||||
--set bootstrapPassword=admin
|
--set hostname=idc.internal \
|
||||||
|
--set bootstrapPassword=KAYQE1QA7uwUZ8uI \
|
||||||
|
--set ingress.enabled=false \
|
||||||
|
--set ingress.tls.source=rancher
|
||||||
|
|
||||||
|
Poc-25_sts
|
||||||
|
|
||||||
|
POC-25_sts
|
||||||
|
|
||||||
|
vi sistemarancher.sh
|
||||||
|
# 1. recupera il ClusterIP del service "rancher" già creato dal chart
|
||||||
|
CLUSTERIP=$(kubectl get svc -n cattle-system rancher -o jsonpath='{.spec.clusterIP}')
|
||||||
|
|
||||||
|
# 2. patch del deployment per aggiungere hostAliases
|
||||||
|
kubectl patch deployment rancher -n cattle-system --type='json' \
|
||||||
|
-p="[{\"op\":\"add\",\"path\":\"/spec/template/spec/hostAliases\",\"value\":[{\"ip\":\"$CLUSTERIP\",\"hostnames\":[\"idc.internal\"]}]}]"
|
||||||
|
|
||||||
|
# 3. crea il service NodePort per l'accesso dal browser
|
||||||
|
kubectl expose deployment rancher -n cattle-system --type=NodePort --port=443 --target-port=443 --name=rancher-nodeport
|
||||||
|
|
||||||
|
# 4. riavvia e segui i log
|
||||||
|
kubectl rollout restart deployment rancher -n cattle-system
|
||||||
|
kubectl -n cattle-system logs -l app=rancher -f
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -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,52 @@
|
|||||||
|
kubectl create secret generic alertmanager-smtp -n monitoring --from-literal=password='pX8XpxrNT8'
|
||||||
|
|
||||||
|
|
||||||
|
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: "https://idcidp.pigreco66.it"
|
||||||
|
matchType: "="
|
||||||
|
receiver: "email-servizio-idcidp"
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: "email-default"
|
||||||
|
emailConfigs: []
|
||||||
|
|
||||||
|
- name: "email-servizio-idcidp"
|
||||||
|
emailConfigs:
|
||||||
|
- to: "alessandro.barucci66@gmail.com"
|
||||||
|
from: "alert@italiadatacenter.com"
|
||||||
|
smarthost: "mail.tophost.it:587"
|
||||||
|
authUsername: "italiadatacenter.com62374"
|
||||||
|
authPassword:
|
||||||
|
name: alertmanager-smtp
|
||||||
|
key: password
|
||||||
|
requireTLS: true
|
||||||
|
sendResolved: true
|
||||||
|
headers:
|
||||||
|
- key: subject
|
||||||
|
value: "[ALERT] Servizio IDCIDP non raggiungibile"
|
||||||
|
html: |
|
||||||
|
<p>Il servizio <b>{{ .CommonLabels.instance }}</b> non risponde.</p>
|
||||||
|
<p>{{ .CommonAnnotations.description }}</p>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
kubectl apply -f blackbox-email-alert.yaml
|
||||||
@@ -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": ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# MySQL Database Operator
|
||||||
|
|
||||||
|
A small Kubernetes controller that provides a declarative `MySQLDatabase` CRD on top of an existing MySQL service, including Oracle MySQL Operator / InnoDB Cluster deployments.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This MVP manages:
|
||||||
|
|
||||||
|
- `CREATE DATABASE IF NOT EXISTS`
|
||||||
|
- optional application user creation
|
||||||
|
- application credentials Secret generation
|
||||||
|
- database-scoped grants
|
||||||
|
- `Retain` or `Delete` deletion policy
|
||||||
|
- status/conditions
|
||||||
|
|
||||||
|
It does **not** create or manage the MySQL/InnoDBCluster itself. The Oracle MySQL Operator remains responsible for the MySQL infrastructure.
|
||||||
|
|
||||||
|
## Important production notes
|
||||||
|
|
||||||
|
1. `adminSecretRef` must contain `username` and `password`.
|
||||||
|
2. The controller currently connects to the host in `spec.connection.host`; if omitted it uses `spec.clusterRef.name`.
|
||||||
|
3. The generated application Secret contains `username`, `password`, and `database`.
|
||||||
|
4. The user is created as `'user'@'%'` because Kubernetes workloads normally connect over the cluster network. Tighten this later if your network model supports it.
|
||||||
|
5. The controller intentionally supports a small allow-list of privileges; do not accept arbitrary SQL from the CRD.
|
||||||
|
6. For production, add TLS support, NetworkPolicy, Secret rotation policy, metrics, admission validation, and tests before broad multi-team rollout.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod tidy
|
||||||
|
go build ./cmd
|
||||||
|
|
||||||
|
docker build -t harbor.example.com/platform/mysql-database-operator:0.1.0 .
|
||||||
|
docker push harbor.example.com/platform/mysql-database-operator:0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy on RKE2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create namespace mysql-database-operator-system
|
||||||
|
kubectl apply -f config/crd/mysqldatabase-crd.yaml
|
||||||
|
kubectl apply -f deploy/rbac.yaml
|
||||||
|
sed 's#ghcr.io/example/mysql-database-operator:0.1.0#harbor.example.com/platform/mysql-database-operator:0.1.0#' deploy/deployment.yaml | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
Or Helm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade --install mysql-database-operator ./helm/mysql-database-operator \
|
||||||
|
--namespace mysql-database-operator-system \
|
||||||
|
--create-namespace \
|
||||||
|
--set image.repository=harbor.example.com/platform/mysql-database-operator \
|
||||||
|
--set image.tag=0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the admin Secret and database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create namespace team-a
|
||||||
|
kubectl apply -f examples/admin-secret.yaml
|
||||||
|
kubectl apply -f examples/mysql-database.yaml
|
||||||
|
kubectl get mysqldatabase -n team-a
|
||||||
|
kubectl describe mysqldatabase orders -n team-a
|
||||||
|
kubectl get secret orders-db-credentials -n team-a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Oracle MySQL Operator integration
|
||||||
|
|
||||||
|
Example architecture:
|
||||||
|
|
||||||
|
```text
|
||||||
|
InnoDBCluster mysql-prod
|
||||||
|
|
|
||||||
|
| Service mysql-prod:3306
|
||||||
|
v
|
||||||
|
MySQLDatabase orders
|
||||||
|
|
|
||||||
|
+-- CREATE DATABASE orders
|
||||||
|
+-- CREATE USER orders-app
|
||||||
|
+-- GRANT SELECT, INSERT, UPDATE, DELETE
|
||||||
|
+-- Secret orders-db-credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the actual Router/primary Service exposed by your Oracle MySQL Operator installation in `spec.connection.host` if it is not the same as `clusterRef.name`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
The source tree was formatted with `gofmt`. Full `go test ./...` could not be executed in this environment because external Go module downloads are disabled; run `go mod tidy && go test ./...` in a network-enabled build environment before publishing the image.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
bcdb6dd502904deb1982acff76dbcecef61fe64c1edce33dbc535fe714809d0f ./Dockerfile
|
||||||
|
811616c4bfe6df79795d4cfe845926b9e1af47501421d092f67bf511f4cdd673 ./README.md
|
||||||
|
ffe83a66f937391f9c6b79450f5161095bdba5c794838b2bc8965c715c5849ca ./api/v1alpha1/groupversion_info.go
|
||||||
|
12f4d95346545e4dd390dcd5718ae0e5104ffd3e6153b9ead6bfaf78e7293d09 ./api/v1alpha1/mysqldatabase_types.go
|
||||||
|
89e9b851001dcb3a3bf88de546fbde273d9cab9951b389d9a07bf106adc570f5 ./api/v1alpha1/zz_generated.deepcopy.go
|
||||||
|
08be703122880eb86372acdbe9d560280f9c2428cf2279cc05061bbf79cb8836 ./cmd/main.go
|
||||||
|
78813f202aa5a135a25f1c1524fdc8e09174f13f601985923a00cf6d9fcc6f28 ./config/crd/mysqldatabase-crd.yaml
|
||||||
|
c9f3d6c121d19bf780f6a35b69eb242202164c38fc201ad67f6789e363620bf4 ./controllers/mysqldatabase_controller.go
|
||||||
|
b164fdebc25270c033e6b5608a6b7eb005007757913ff3f7dea692f2ff306777 ./controllers/setup.go
|
||||||
|
75d21caeb9f41976855fa8421e07f31cee39877844ca30d9f0a3104863090458 ./deploy/deployment.yaml
|
||||||
|
18f1896539466ce1d0707ca42877d94e97b6e50af72924ecb236cbc276dd4690 ./deploy/namespace.yaml
|
||||||
|
0de7361b72bcd016c49d44e1bc1a292d6ceba4ab2836218732bd133380151472 ./deploy/rbac.yaml
|
||||||
|
f31d3c24dc3f666ddd62af0bc8dde2b2623edfa370099e4b759e3cfc87a40cf5 ./examples/admin-secret.yaml
|
||||||
|
9f954df207eebfa0f1551bdeb05ec97f33ea7184a8c69aad7526b25ffb98cb99 ./examples/mysql-database.yaml
|
||||||
|
a17f89658ffb2d16a6c64e6bae3a99f8b3fc5130a6c00fdeb6bbd231cde0488f ./go.mod
|
||||||
|
3266515bc31fd4465da3eacc34d98ddbb18e67444d855438c683a8875f7bce03 ./helm/mysql-database-operator/Chart.yaml
|
||||||
|
dcb57bf001e957e3abad642ca56c76ba2b65b6c6419dc9148b95be3293f29a4b ./helm/mysql-database-operator/templates/_helpers.tpl
|
||||||
|
78813f202aa5a135a25f1c1524fdc8e09174f13f601985923a00cf6d9fcc6f28 ./helm/mysql-database-operator/templates/crd.yaml
|
||||||
|
55159600ff11ee14ab542d6ad647129fb1321fc591e17693340d481af4467ab2 ./helm/mysql-database-operator/templates/deployment.yaml
|
||||||
|
dd8a29aa027dfc40a77f295fd4d70ab950c14bcc8a3c8bf7efc98f2ff77ee75d ./helm/mysql-database-operator/templates/namespace.yaml
|
||||||
|
e06e41e699589b14609bb5f32555c01851ba4a7e514d647f3191623832e09e01 ./helm/mysql-database-operator/templates/rbac.yaml
|
||||||
|
107ddf20ea52d35fbd2dfbbbcadc86e8848b6013566e4fd4229326d9087cce7e ./helm/mysql-database-operator/templates/serviceaccount.yaml
|
||||||
|
8d6267287c8b41e1c30b11d13c73273aaf48d2a69f717ea0de1f7dda72a26c78 ./helm/mysql-database-operator/values.yaml
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
var GroupVersion = schema.GroupVersion{Group: "platform.example.com", Version: "v1alpha1"}
|
||||||
|
var SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||||
|
var AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
|
||||||
|
func init() { SchemeBuilder.Register(&MySQLDatabase{}, &MySQLDatabaseList{}) }
|
||||||
|
|
||||||
|
var _ = metav1.Now
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
// MySQLDatabaseSpec defines the desired state of a logical MySQL database.
|
||||||
|
type MySQLDatabaseSpec struct {
|
||||||
|
ClusterRef ClusterReference `json:"clusterRef"`
|
||||||
|
DatabaseName string `json:"databaseName"`
|
||||||
|
AdminSecretRef SecretReference `json:"adminSecretRef"`
|
||||||
|
Connection ConnectionSpec `json:"connection,omitempty"`
|
||||||
|
User *UserSpec `json:"user,omitempty"`
|
||||||
|
Credentials *CredentialsSpec `json:"credentials,omitempty"`
|
||||||
|
Privileges []string `json:"privileges,omitempty"`
|
||||||
|
Charset string `json:"charset,omitempty"`
|
||||||
|
Collation string `json:"collation,omitempty"`
|
||||||
|
DeletionPolicy string `json:"deletionPolicy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterReference struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
type SecretReference struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionSpec struct {
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
Port int32 `json:"port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSpec struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Create bool `json:"create,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CredentialsSpec struct {
|
||||||
|
SecretName string `json:"secretName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MySQLDatabaseStatus struct {
|
||||||
|
Phase string `json:"phase,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
DatabaseName string `json:"databaseName,omitempty"`
|
||||||
|
UserName string `json:"userName,omitempty"`
|
||||||
|
SecretName string `json:"secretName,omitempty"`
|
||||||
|
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||||
|
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:printcolumn:name="Database",type="string",JSONPath=".status.databaseName"
|
||||||
|
// +kubebuilder:printcolumn:name="User",type="string",JSONPath=".status.userName"
|
||||||
|
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
|
||||||
|
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||||
|
type MySQLDatabase struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
Spec MySQLDatabaseSpec `json:"spec"`
|
||||||
|
Status MySQLDatabaseStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
type MySQLDatabaseList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []MySQLDatabase `json:"items"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Code generated by controller-gen style deepcopy implementation. DO NOT EDIT.
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
func (in *MySQLDatabase) DeepCopyInto(out *MySQLDatabase) {
|
||||||
|
*out = *in
|
||||||
|
out.ObjectMeta = *in.ObjectMeta.DeepCopy()
|
||||||
|
out.Status = in.Status
|
||||||
|
if in.Status.Conditions != nil {
|
||||||
|
out.Status.Conditions = make([]metav1.Condition, len(in.Status.Conditions))
|
||||||
|
copy(out.Status.Conditions, in.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (in *MySQLDatabase) DeepCopy() *MySQLDatabase {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(MySQLDatabase)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (in *MySQLDatabaseList) DeepCopyInto(out *MySQLDatabaseList) {
|
||||||
|
*out = *in
|
||||||
|
if in.Items != nil {
|
||||||
|
out.Items = make([]MySQLDatabase, len(in.Items))
|
||||||
|
for i := range in.Items {
|
||||||
|
in.Items[i].DeepCopyInto(&out.Items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (in *MySQLDatabaseList) DeepCopy() *MySQLDatabaseList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(MySQLDatabaseList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/example/mysql-database-operator/api/v1alpha1"
|
||||||
|
"github.com/example/mysql-database-operator/controllers"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"os"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var metricsAddr, probeAddr string
|
||||||
|
var leader bool
|
||||||
|
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "metrics address")
|
||||||
|
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "health probe address")
|
||||||
|
flag.BoolVar(&leader, "leader-elect", true, "enable leader election")
|
||||||
|
var zo zap.Options
|
||||||
|
zo.BindFlags(flag.CommandLine)
|
||||||
|
flag.Parse()
|
||||||
|
controller.SetLogger(zap.New(zap.UseFlagOptions(&zo)))
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
_ = v1alpha1.AddToScheme(scheme)
|
||||||
|
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{Scheme: scheme, Metrics: server.Options{BindAddress: metricsAddr}, HealthProbeBindAddress: probeAddr, LeaderElection: leader, LeaderElectionID: "mysql-database-operator"})
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := (&controllers.MySQLDatabaseReconciler{Client: mgr.GetClient(), Scheme: scheme}).SetupWithManager(mgr); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := mgr.AddReadyzCheck("ready", healthz.Ping); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := mgr.Start(controller.SetupSignalHandler()); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: mysqldatabases.platform.example.com
|
||||||
|
spec:
|
||||||
|
group: platform.example.com
|
||||||
|
scope: Namespaced
|
||||||
|
names:
|
||||||
|
plural: mysqldatabases
|
||||||
|
singular: mysqldatabase
|
||||||
|
kind: MySQLDatabase
|
||||||
|
shortNames: [mysqldb]
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
additionalPrinterColumns:
|
||||||
|
- name: Database
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.databaseName
|
||||||
|
- name: User
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.userName
|
||||||
|
- name: Phase
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.phase
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
required: [spec]
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
required: [clusterRef,databaseName,adminSecretRef]
|
||||||
|
properties:
|
||||||
|
clusterRef:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties: {name: {type: string}}
|
||||||
|
databaseName:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 64
|
||||||
|
pattern: '^[A-Za-z0-9_$-]+$'
|
||||||
|
adminSecretRef:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties: {name: {type: string}}
|
||||||
|
connection:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
host: {type: string}
|
||||||
|
port: {type: integer, minimum: 1, maximum: 65535}
|
||||||
|
user:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: {type: string, maxLength: 32}
|
||||||
|
create: {type: boolean}
|
||||||
|
credentials:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
secretName: {type: string}
|
||||||
|
privileges:
|
||||||
|
type: array
|
||||||
|
items: {type: string}
|
||||||
|
charset: {type: string}
|
||||||
|
collation: {type: string}
|
||||||
|
deletionPolicy:
|
||||||
|
type: string
|
||||||
|
enum: [Retain, Delete]
|
||||||
|
default: Retain
|
||||||
|
status:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
phase: {type: string}
|
||||||
|
message: {type: string}
|
||||||
|
databaseName: {type: string}
|
||||||
|
userName: {type: string}
|
||||||
|
secretName: {type: string}
|
||||||
|
observedGeneration: {type: integer, format: int64}
|
||||||
|
conditions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required: [type,status,reason,message,lastTransitionTime]
|
||||||
|
properties:
|
||||||
|
type: {type: string}
|
||||||
|
status: {type: string}
|
||||||
|
reason: {type: string}
|
||||||
|
message: {type: string}
|
||||||
|
lastTransitionTime: {type: string, format: date-time}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
platformv1alpha1 "github.com/example/mysql-database-operator/api/v1alpha1"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
)
|
||||||
|
|
||||||
|
const finalizer = "platform.example.com/mysqldatabase-finalizer"
|
||||||
|
|
||||||
|
type MySQLDatabaseReconciler struct{ client.Client }
|
||||||
|
|
||||||
|
func (r *MySQLDatabaseReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
var obj platformv1alpha1.MySQLDatabase
|
||||||
|
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return reconcile.Result{}, nil
|
||||||
|
}
|
||||||
|
return reconcile.Result{}, err
|
||||||
|
}
|
||||||
|
if obj.Spec.DatabaseName == "" {
|
||||||
|
return r.setError(ctx, &obj, "InvalidSpec", "databaseName is required")
|
||||||
|
}
|
||||||
|
if obj.Spec.AdminSecretRef.Name == "" {
|
||||||
|
return r.setError(ctx, &obj, "InvalidSpec", "adminSecretRef.name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !obj.DeletionTimestamp.IsZero() {
|
||||||
|
if !controllerutil.ContainsFinalizer(&obj, finalizer) {
|
||||||
|
return reconcile.Result{}, nil
|
||||||
|
}
|
||||||
|
if strings.EqualFold(obj.Spec.DeletionPolicy, "Delete") {
|
||||||
|
if err := r.withDB(ctx, &obj, func(db *sql.DB) error {
|
||||||
|
_, err := db.ExecContext(ctx, "DROP DATABASE IF EXISTS `"+quoteIdent(obj.Spec.DatabaseName)+"`")
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return reconcile.Result{RequeueAfter: 10 * time.Second}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controllerutil.RemoveFinalizer(&obj, finalizer)
|
||||||
|
return reconcile.Result{}, r.Update(ctx, &obj)
|
||||||
|
}
|
||||||
|
if !controllerutil.ContainsFinalizer(&obj, finalizer) {
|
||||||
|
controllerutil.AddFinalizer(&obj, finalizer)
|
||||||
|
return reconcile.Result{}, r.Update(ctx, &obj)
|
||||||
|
}
|
||||||
|
if err := r.withDB(ctx, &obj, func(db *sql.DB) error { return ensureDatabase(ctx, db, &obj) }); err != nil {
|
||||||
|
return r.setError(ctx, &obj, "ConnectionError", err.Error())
|
||||||
|
}
|
||||||
|
if obj.Spec.User != nil && obj.Spec.User.Create {
|
||||||
|
username := obj.Spec.User.Name
|
||||||
|
if username == "" {
|
||||||
|
return r.setError(ctx, &obj, "InvalidSpec", "user.name is required when user.create is true")
|
||||||
|
}
|
||||||
|
password, err := r.ensureCredentials(ctx, &obj, username)
|
||||||
|
if err != nil {
|
||||||
|
return r.setError(ctx, &obj, "CredentialsError", err.Error())
|
||||||
|
}
|
||||||
|
if err := r.withDB(ctx, &obj, func(db *sql.DB) error { return ensureUserAndGrants(ctx, db, &obj, password) }); err != nil {
|
||||||
|
return r.setError(ctx, &obj, "UserError", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.Status.Phase = "Ready"
|
||||||
|
obj.Status.Message = "Database reconciled"
|
||||||
|
obj.Status.DatabaseName = obj.Spec.DatabaseName
|
||||||
|
if obj.Spec.User != nil {
|
||||||
|
obj.Status.UserName = obj.Spec.User.Name
|
||||||
|
}
|
||||||
|
if obj.Spec.Credentials != nil {
|
||||||
|
obj.Status.SecretName = obj.Spec.Credentials.SecretName
|
||||||
|
}
|
||||||
|
obj.Status.ObservedGeneration = obj.Generation
|
||||||
|
obj.Status.Conditions = []metav1.Condition{{Type: "Ready", Status: metav1.ConditionTrue, Reason: "Reconciled", Message: "MySQL database is ready", LastTransitionTime: metav1.Now()}}
|
||||||
|
if err := r.Status().Update(ctx, &obj); err != nil {
|
||||||
|
logger.Error(err, "status update failed")
|
||||||
|
return reconcile.Result{}, err
|
||||||
|
}
|
||||||
|
return reconcile.Result{RequeueAfter: 5 * time.Minute}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MySQLDatabaseReconciler) setError(ctx context.Context, obj *platformv1alpha1.MySQLDatabase, reason, msg string) (reconcile.Result, error) {
|
||||||
|
obj.Status.Phase = "Error"
|
||||||
|
obj.Status.Message = msg
|
||||||
|
obj.Status.ObservedGeneration = obj.Generation
|
||||||
|
obj.Status.Conditions = []metav1.Condition{{Type: "Ready", Status: metav1.ConditionFalse, Reason: reason, Message: msg, LastTransitionTime: metav1.Now()}}
|
||||||
|
_ = r.Status().Update(ctx, obj)
|
||||||
|
return reconcile.Result{RequeueAfter: 15 * time.Second}, fmt.Errorf("%s: %s", reason, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MySQLDatabaseReconciler) withDB(ctx context.Context, obj *platformv1alpha1.MySQLDatabase, fn func(*sql.DB) error) error {
|
||||||
|
var admin corev1.Secret
|
||||||
|
if err := r.Get(ctx, types.NamespacedName{Name: obj.Spec.AdminSecretRef.Name, Namespace: obj.Namespace}, &admin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
username, password := string(admin.Data["username"]), string(admin.Data["password"])
|
||||||
|
if username == "" || password == "" {
|
||||||
|
return fmt.Errorf("admin secret %q must contain username and password", obj.Spec.AdminSecretRef.Name)
|
||||||
|
}
|
||||||
|
host := obj.Spec.Connection.Host
|
||||||
|
if host == "" {
|
||||||
|
host = obj.Spec.ClusterRef.Name
|
||||||
|
}
|
||||||
|
port := obj.Spec.Connection.Port
|
||||||
|
if port == 0 {
|
||||||
|
port = 3306
|
||||||
|
}
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?parseTime=true&timeout=5s&readTimeout=10s&writeTimeout=10s", username, password, host, port)
|
||||||
|
db, err := sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
pingCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := db.PingContext(pingCtx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDatabase(ctx context.Context, db *sql.DB, obj *platformv1alpha1.MySQLDatabase) error {
|
||||||
|
q := "CREATE DATABASE IF NOT EXISTS `" + quoteIdent(obj.Spec.DatabaseName) + "`"
|
||||||
|
if obj.Spec.Charset != "" {
|
||||||
|
q += " CHARACTER SET " + safeSQLWord(obj.Spec.Charset)
|
||||||
|
}
|
||||||
|
if obj.Spec.Collation != "" {
|
||||||
|
q += " COLLATE " + safeSQLWord(obj.Spec.Collation)
|
||||||
|
}
|
||||||
|
_, err := db.ExecContext(ctx, q)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureUserAndGrants(ctx context.Context, db *sql.DB, obj *platformv1alpha1.MySQLDatabase, password string) error {
|
||||||
|
u := quoteIdent(obj.Spec.User.Name)
|
||||||
|
// User is intentionally scoped to localhost wildcard host. For networked K8s apps, '%' is needed.
|
||||||
|
if _, err := db.ExecContext(ctx, "CREATE USER IF NOT EXISTS '"+quoteString(obj.Spec.User.Name)+"'@'%' IDENTIFIED BY '"+quoteString(password)+"'"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.ExecContext(ctx, "ALTER USER '"+quoteString(obj.Spec.User.Name)+"'@'%' IDENTIFIED BY '"+quoteString(password)+"'"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
grants := obj.Spec.Privileges
|
||||||
|
if len(grants) == 0 {
|
||||||
|
grants = []string{"SELECT", "INSERT", "UPDATE", "DELETE"}
|
||||||
|
}
|
||||||
|
for _, g := range grants {
|
||||||
|
if !isAllowedPrivilege(g) {
|
||||||
|
return fmt.Errorf("unsupported privilege %q", g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grant := strings.Join(grants, ", ")
|
||||||
|
_, err := db.ExecContext(ctx, "GRANT "+grant+" ON `"+quoteIdent(obj.Spec.DatabaseName)+"`.* TO '"+quoteString(obj.Spec.User.Name)+"'@'%'")
|
||||||
|
_ = u
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MySQLDatabaseReconciler) ensureCredentials(ctx context.Context, obj *platformv1alpha1.MySQLDatabase, username string) (string, error) {
|
||||||
|
if obj.Spec.Credentials == nil || obj.Spec.Credentials.SecretName == "" {
|
||||||
|
return "", fmt.Errorf("credentials.secretName is required when user.create is true")
|
||||||
|
}
|
||||||
|
key := types.NamespacedName{Name: obj.Spec.Credentials.SecretName, Namespace: obj.Namespace}
|
||||||
|
var s corev1.Secret
|
||||||
|
err := r.Get(ctx, key, &s)
|
||||||
|
if err == nil {
|
||||||
|
p := string(s.Data["password"])
|
||||||
|
if p == "" {
|
||||||
|
return "", fmt.Errorf("credentials secret must contain password")
|
||||||
|
}
|
||||||
|
if string(s.Data["username"]) == "" {
|
||||||
|
s.Data["username"] = []byte(username)
|
||||||
|
if err := r.Update(ctx, &s); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
if !apierrors.IsNotFound(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p, err := randomPassword(32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s = corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: key.Name, Namespace: key.Namespace}, Type: corev1.SecretTypeOpaque, StringData: map[string]string{"username": username, "password": p, "database": obj.Spec.DatabaseName}}
|
||||||
|
if err := controllerutil.SetControllerReference(obj, &s, r.Scheme); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := r.Create(ctx, &s); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomPassword(n int) (string, error) {
|
||||||
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*_-"
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
x, e := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
b[i] = chars[x.Int64()]
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
func quoteIdent(s string) string { return strings.ReplaceAll(s, "`", "``") }
|
||||||
|
func quoteString(s string) string {
|
||||||
|
return strings.ReplaceAll(strings.ReplaceAll(s, "\\", "\\\\"), "'", "\\'")
|
||||||
|
}
|
||||||
|
func safeSQLWord(s string) string {
|
||||||
|
for _, r := range s {
|
||||||
|
if !(r == '_' || r == '-' || r == '.' || r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9') {
|
||||||
|
return "utf8mb4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func isAllowedPrivilege(s string) bool {
|
||||||
|
switch strings.ToUpper(strings.TrimSpace(s)) {
|
||||||
|
case "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "INDEX", "DROP", "REFERENCES", "EXECUTE", "SHOW VIEW", "TRIGGER":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
platformv1alpha1 "github.com/example/mysql-database-operator/api/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *MySQLDatabaseReconciler) SetupWithManager(mgr manager.Manager) error {
|
||||||
|
r.Client = mgr.GetClient()
|
||||||
|
return builder.ControllerManagedBy(mgr).For(&platformv1alpha1.MySQLDatabase{}).Owns(&corev1.Secret{}).Complete(r)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: mysql-prod-admin
|
||||||
|
namespace: team-a
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
username: root
|
||||||
|
password: CHANGE-ME
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: platform.example.com/v1alpha1
|
||||||
|
kind: MySQLDatabase
|
||||||
|
metadata:
|
||||||
|
name: orders
|
||||||
|
namespace: team-a
|
||||||
|
spec:
|
||||||
|
clusterRef:
|
||||||
|
name: mysql-prod
|
||||||
|
# If omitted, the controller uses clusterRef.name as the Service DNS name.
|
||||||
|
connection:
|
||||||
|
host: mysql-prod
|
||||||
|
port: 3306
|
||||||
|
databaseName: orders
|
||||||
|
adminSecretRef:
|
||||||
|
name: mysql-prod-admin
|
||||||
|
user:
|
||||||
|
name: orders-app
|
||||||
|
create: true
|
||||||
|
credentials:
|
||||||
|
secretName: orders-db-credentials
|
||||||
|
privileges:
|
||||||
|
- SELECT
|
||||||
|
- INSERT
|
||||||
|
- UPDATE
|
||||||
|
- DELETE
|
||||||
|
charset: utf8mb4
|
||||||
|
collation: utf8mb4_0900_ai_ci
|
||||||
|
deletionPolicy: Retain
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: mysql-database-operator
|
||||||
|
description: Declarative MySQL database/user controller for Kubernetes
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "0.1.0"
|
||||||
|
type: application
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{{- define "mysql-database-operator.name" -}}mysql-database-operator{{- end }}
|
||||||
|
{{- define "mysql-database-operator.fullname" -}}{{ include "mysql-database-operator.name" . }}{{- end }}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: mysqldatabases.platform.example.com
|
||||||
|
spec:
|
||||||
|
group: platform.example.com
|
||||||
|
scope: Namespaced
|
||||||
|
names:
|
||||||
|
plural: mysqldatabases
|
||||||
|
singular: mysqldatabase
|
||||||
|
kind: MySQLDatabase
|
||||||
|
shortNames: [mysqldb]
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
additionalPrinterColumns:
|
||||||
|
- name: Database
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.databaseName
|
||||||
|
- name: User
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.userName
|
||||||
|
- name: Phase
|
||||||
|
type: string
|
||||||
|
jsonPath: .status.phase
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
required: [spec]
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
required: [clusterRef,databaseName,adminSecretRef]
|
||||||
|
properties:
|
||||||
|
clusterRef:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties: {name: {type: string}}
|
||||||
|
databaseName:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 64
|
||||||
|
pattern: '^[A-Za-z0-9_$-]+$'
|
||||||
|
adminSecretRef:
|
||||||
|
type: object
|
||||||
|
required: [name]
|
||||||
|
properties: {name: {type: string}}
|
||||||
|
connection:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
host: {type: string}
|
||||||
|
port: {type: integer, minimum: 1, maximum: 65535}
|
||||||
|
user:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: {type: string, maxLength: 32}
|
||||||
|
create: {type: boolean}
|
||||||
|
credentials:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
secretName: {type: string}
|
||||||
|
privileges:
|
||||||
|
type: array
|
||||||
|
items: {type: string}
|
||||||
|
charset: {type: string}
|
||||||
|
collation: {type: string}
|
||||||
|
deletionPolicy:
|
||||||
|
type: string
|
||||||
|
enum: [Retain, Delete]
|
||||||
|
default: Retain
|
||||||
|
status:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
phase: {type: string}
|
||||||
|
message: {type: string}
|
||||||
|
databaseName: {type: string}
|
||||||
|
userName: {type: string}
|
||||||
|
secretName: {type: string}
|
||||||
|
observedGeneration: {type: integer, format: int64}
|
||||||
|
conditions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required: [type,status,reason,message,lastTransitionTime]
|
||||||
|
properties:
|
||||||
|
type: {type: string}
|
||||||
|
status: {type: string}
|
||||||
|
reason: {type: string}
|
||||||
|
message: {type: string}
|
||||||
|
lastTransitionTime: {type: string, format: date-time}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mysql-database-operator
|
||||||
|
namespace: mysql-database-operator-system
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector: {matchLabels: {app: mysql-database-operator}}
|
||||||
|
template:
|
||||||
|
metadata: {labels: {app: mysql-database-operator}}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: {{ .Values.serviceAccount.name }}
|
||||||
|
securityContext: {runAsNonRoot: true}
|
||||||
|
containers:
|
||||||
|
- name: manager
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
args: ["--leader-elect={{ .Values.leaderElect }}"]
|
||||||
|
ports:
|
||||||
|
- {name: metrics, containerPort: 8080}
|
||||||
|
- {name: health, containerPort: 8081}
|
||||||
|
resources: {{- toYaml .Values.resources | nindent 10 }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities: {drop: [ALL]}
|
||||||
|
seccompProfile: {type: RuntimeDefault}
|
||||||
|
readinessProbe: {httpGet: {path: /readyz, port: health}}
|
||||||
|
livenessProbe: {httpGet: {path: /healthz, port: health}}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: mysql-database-operator-system
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{{- if .Values.rbac.create }}
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata: {name: mysql-database-operator}
|
||||||
|
rules:
|
||||||
|
- apiGroups: [platform.example.com]
|
||||||
|
resources: [mysqldatabases]
|
||||||
|
verbs: [get,list,watch,create,update,patch,delete]
|
||||||
|
- apiGroups: [platform.example.com]
|
||||||
|
resources: [mysqldatabases/status]
|
||||||
|
verbs: [get,update,patch]
|
||||||
|
- apiGroups: [platform.example.com]
|
||||||
|
resources: [mysqldatabases/finalizers]
|
||||||
|
verbs: [update]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [secrets]
|
||||||
|
verbs: [get,list,watch,create,update,patch,delete]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [events]
|
||||||
|
verbs: [create,patch]
|
||||||
|
- apiGroups: [coordination.k8s.io]
|
||||||
|
resources: [leases]
|
||||||
|
verbs: [get,list,watch,create,update,patch,delete]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata: {name: mysql-database-operator}
|
||||||
|
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: mysql-database-operator}
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: {{ .Values.serviceAccount.name }}
|
||||||
|
namespace: mysql-database-operator-system
|
||||||
|
{{- end }}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.serviceAccount.name }}
|
||||||
|
namespace: mysql-database-operator-system
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
image:
|
||||||
|
repository: ghcr.io/example/mysql-database-operator
|
||||||
|
tag: 0.1.0
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
replicaCount: 1
|
||||||
|
leaderElect: true
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 50m, memory: 64Mi}
|
||||||
|
limits: {cpu: 500m, memory: 256Mi}
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: mysql-database-operator
|
||||||
|
rbac:
|
||||||
|
create: true
|
||||||
|
crd:
|
||||||
|
install: true
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -2,6 +2,13 @@
|
|||||||
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube
|
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube
|
||||||
helm repo update
|
helm repo update
|
||||||
|
|
||||||
|
helm pull sonarqube/sonarqube --version 2026.1.0
|
||||||
|
|
||||||
|
helm registry login harbor.italiadatacenter.com -u admin
|
||||||
|
(pwd: KAYQE1QA7uwUZ8uI)
|
||||||
|
|
||||||
|
helm push sonarqube-2026.1.0.tgz oci://harbor.italiadatacenter.com/italiadatacenter
|
||||||
|
|
||||||
########### creazione ns e secret db ################
|
########### creazione ns e secret db ################
|
||||||
kubectl create namespace sonarqube
|
kubectl create namespace sonarqube
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
# Per calcolare il token usa host pulito (senza schema e path)
|
||||||
host_for_token="${endpoint#*://}"
|
host_for_token="${endpoint#*://}"
|
||||||
host_for_token="${host_for_token%%/*}"
|
host_for_token="${host_for_token%%/*}"
|
||||||
|
host_no_port="${host_for_token%%:*}"
|
||||||
token="${host_for_token%%.*}"
|
token="${host_for_token%%.*}"
|
||||||
|
|
||||||
if [[ -z "$token" ]]; then
|
if [[ -z "$token" ]]; then
|
||||||
@@ -42,5 +43,20 @@ if [[ -z "$token" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Se l'hostname non e nel dominio *.italiadatacenter.com, aggiungilo anche a cert-manager hostAliases.
|
||||||
|
if [[ "$host_no_port" != *.italiadatacenter.com ]]; then
|
||||||
|
HOSTALIAS_SCRIPT="/root/work/pipeline/add-hostalias.sh"
|
||||||
|
if [[ -x "$HOSTALIAS_SCRIPT" ]]; then
|
||||||
|
"$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager"
|
||||||
|
"$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring"
|
||||||
|
elif [[ -f "$HOSTALIAS_SCRIPT" ]]; then
|
||||||
|
bash "$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager"
|
||||||
|
bash "$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring"
|
||||||
|
else
|
||||||
|
echo "Errore: script non trovato: $HOSTALIAS_SCRIPT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Output richiesto: <endpoint> https-<token> <token>-secret
|
# Output richiesto: <endpoint> https-<token> <token>-secret
|
||||||
/root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret
|
/root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ for dir in containers/*/; do
|
|||||||
|
|
||||||
PLATFORM_CONF="$dir/platform.conf"
|
PLATFORM_CONF="$dir/platform.conf"
|
||||||
BUILD_PLATFORM=""
|
BUILD_PLATFORM=""
|
||||||
|
MULTISTAGE=""
|
||||||
if [ -f "$PLATFORM_CONF" ]; then
|
if [ -f "$PLATFORM_CONF" ]; then
|
||||||
BUILD_PLATFORM=$(grep -E '^[[:space:]]*platform[[:space:]]*=' "$PLATFORM_CONF" | tail -n 1 | cut -d '=' -f 2- | tr -d '[:space:]')
|
BUILD_PLATFORM=$(grep -E '^[[:space:]]*platform[[:space:]]*=' "$PLATFORM_CONF" | tail -n 1 | cut -d '=' -f 2- | tr -d '[:space:]')
|
||||||
|
MULTISTAGE=$(grep -E '^[[:space:]]*multistage[[:space:]]*=' "$PLATFORM_CONF" | tail -n 1 | cut -d '=' -f 2- | tr -d '[:space:]')
|
||||||
if [ -n "$BUILD_PLATFORM" ]; then
|
if [ -n "$BUILD_PLATFORM" ]; then
|
||||||
echo "platform.conf trovato in $dir: uso platform=$BUILD_PLATFORM"
|
echo "platform.conf trovato in $dir: uso platform=$BUILD_PLATFORM"
|
||||||
else
|
else
|
||||||
@@ -57,15 +59,39 @@ for dir in containers/*/; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$DOCKERFILE" ]; then
|
if [ -f "$DOCKERFILE" ]; then
|
||||||
|
if [ -n "$MULTISTAGE" ] && ! [[ "$MULTISTAGE" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Valore multistage non valido in $PLATFORM_CONF: $MULTISTAGE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD_COUNT="${MULTISTAGE:-1}"
|
||||||
|
for ((target_index = 1; target_index <= BUILD_COUNT; target_index++)); do
|
||||||
|
BUILD_TARGET=""
|
||||||
|
if [ -n "$MULTISTAGE" ]; then
|
||||||
|
BUILD_TARGET=$(grep -E "^[[:space:]]*target${target_index}[[:space:]]*=" "$PLATFORM_CONF" | tail -n 1 | cut -d '=' -f 2- | tr -d '[:space:]')
|
||||||
|
if [ -z "$BUILD_TARGET" ]; then
|
||||||
|
echo "target${target_index} non valorizzato in $PLATFORM_CONF"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build $target_index/$BUILD_COUNT${BUILD_TARGET:+ target=$BUILD_TARGET}"
|
||||||
|
BUILD_TARGET_ARGS=()
|
||||||
|
if [ -n "$BUILD_TARGET" ]; then
|
||||||
|
BUILD_TARGET_ARGS=(--target "$BUILD_TARGET")
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$BUILD_PLATFORM" ]; then
|
if [ -n "$BUILD_PLATFORM" ]; then
|
||||||
docker buildx build --platform "$BUILD_PLATFORM" -t "$IMAGE_TAG" -t "$IMAGE_TAG_LATEST" -f "$DOCKERFILE" "$dir" --push
|
docker buildx build --platform "$BUILD_PLATFORM" "${BUILD_TARGET_ARGS[@]}" -t "$IMAGE_TAG" -t "$IMAGE_TAG_LATEST" -f "$DOCKERFILE" "$dir" --push
|
||||||
else
|
else
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--cache-from=type=registry,ref="${REGISTRY_URL}/${REPO_NAME}/${CONTAINER_NAME}:cache" \
|
--cache-from=type=registry,ref="${REGISTRY_URL}/${REPO_NAME}/${CONTAINER_NAME}:cache" \
|
||||||
--cache-to=type=registry,ref="${REGISTRY_URL}/${REPO_NAME}/${CONTAINER_NAME}:cache",mode=max \
|
--cache-to=type=registry,ref="${REGISTRY_URL}/${REPO_NAME}/${CONTAINER_NAME}:cache",mode=max \
|
||||||
|
"${BUILD_TARGET_ARGS[@]}" \
|
||||||
-t "$IMAGE_TAG" -t "$IMAGE_TAG_LATEST" \
|
-t "$IMAGE_TAG" -t "$IMAGE_TAG_LATEST" \
|
||||||
-f "$DOCKERFILE" "$dir" --push
|
-f "$DOCKERFILE" "$dir" --push
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
echo "Build e push completate: $IMAGE_TAG"
|
echo "Build e push completate: $IMAGE_TAG"
|
||||||
|
|
||||||
# Cleanup immagini locali dopo il push (buildx --push potrebbe non averle in locale)
|
# Cleanup immagini locali dopo il push (buildx --push potrebbe non averle in locale)
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ VALUES_DIR="env/$ENV"
|
|||||||
PROPERTIES_FILE="properties.env"
|
PROPERTIES_FILE="properties.env"
|
||||||
YAML_DIR="kubernetes"
|
YAML_DIR="kubernetes"
|
||||||
|
|
||||||
|
# Ricava il nome progetto dalle variabili esposte da Gitea/GitHub Actions.
|
||||||
|
# Fallback locale: nome della directory del repository.
|
||||||
|
PROJECT_NAME="${GITHUB_REPOSITORY_NAME:-${GITEA_REPOSITORY_NAME:-}}"
|
||||||
|
if [ -z "$PROJECT_NAME" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then
|
||||||
|
PROJECT_NAME="${GITHUB_REPOSITORY##*/}"
|
||||||
|
fi
|
||||||
|
if [ -z "$PROJECT_NAME" ] && [ -n "${GITEA_REPOSITORY:-}" ]; then
|
||||||
|
PROJECT_NAME="${GITEA_REPOSITORY##*/}"
|
||||||
|
fi
|
||||||
|
if [ -z "$PROJECT_NAME" ]; then
|
||||||
|
PROJECT_NAME=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
|
||||||
|
fi
|
||||||
|
|
||||||
# Estrai l'hash completo del commit e crea una variabile temporanea per la sostituzione
|
# Estrai l'hash completo del commit e crea una variabile temporanea per la sostituzione
|
||||||
TAG=$(git rev-parse HEAD)
|
TAG=$(git rev-parse HEAD)
|
||||||
TMP_TAG_FILE=$(mktemp)
|
TMP_TAG_FILE=$(mktemp)
|
||||||
@@ -24,6 +37,9 @@ if [ ! -f "$PROPERTIES_FILE" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Costruisce il nome del namespace come <project_name>-<env>
|
||||||
|
NAMESPACE="${PROJECT_NAME}-${ENV}"
|
||||||
|
|
||||||
# Crea una lista key=value temporanea partendo da tutti i file *.env in env/$ENV e aggiunge env dinamico
|
# Crea una lista key=value temporanea partendo da tutti i file *.env in env/$ENV e aggiunge env dinamico
|
||||||
> "$TMP_VALUES_FILE"
|
> "$TMP_VALUES_FILE"
|
||||||
for env_file in "$VALUES_DIR"/*.env; do
|
for env_file in "$VALUES_DIR"/*.env; do
|
||||||
@@ -31,6 +47,19 @@ for env_file in "$VALUES_DIR"/*.env; do
|
|||||||
printf '\n' >> "$TMP_VALUES_FILE"
|
printf '\n' >> "$TMP_VALUES_FILE"
|
||||||
done
|
done
|
||||||
printf '\nenv=%s\n' "$ENV" >> "$TMP_VALUES_FILE"
|
printf '\nenv=%s\n' "$ENV" >> "$TMP_VALUES_FILE"
|
||||||
|
printf 'namespace=%s\n' "$NAMESPACE" >> "$TMP_VALUES_FILE"
|
||||||
|
printf 'project=%s\n' "$PROJECT_NAME" >> "$TMP_VALUES_FILE"
|
||||||
|
|
||||||
|
# Ricava endpoint se presente e genera le variabili derivate per le sostituzioni YAML.
|
||||||
|
ENDPOINT_VAL=$(grep -E '^[[:space:]]*endpoint[[:space:]]*=' "$TMP_VALUES_FILE" "$PROPERTIES_FILE" 2>/dev/null | tail -n 1 | sed -E 's/.*endpoint[[:space:]]*=[[:space:]]*//' | tr -d '\r\n[:space:]"' || true)
|
||||||
|
if [ -n "$ENDPOINT_VAL" ]; then
|
||||||
|
ENDPOINT_NODOT=$(echo "$ENDPOINT_VAL" | tr '.' '-')
|
||||||
|
ENDPOINT_NOSPACE=$(echo "$ENDPOINT_VAL" | tr -d '.')
|
||||||
|
|
||||||
|
printf 'endpoint-nodot=%s\n' "$ENDPOINT_NODOT" >> "$TMP_VALUES_FILE"
|
||||||
|
printf 'endpoint-nospace=%s\n' "$ENDPOINT_NOSPACE" >> "$TMP_VALUES_FILE"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
# Trova tutti i file .yaml nella directory kubernetes e sottodirectory
|
# Trova tutti i file .yaml nella directory kubernetes e sottodirectory
|
||||||
find "$YAML_DIR" -type f -name "*.yaml" | while read YAML_FILE; do
|
find "$YAML_DIR" -type f -name "*.yaml" | while read YAML_FILE; do
|
||||||
|
|||||||
+11
-37
@@ -7,53 +7,27 @@ ENVIRONMENT="${1:-dev}"
|
|||||||
|
|
||||||
YAML_DIR="kubernetes"
|
YAML_DIR="kubernetes"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# Valore di alert_notification_email per l'ambiente (ultima occorrenza in env/<env>/*.env)
|
||||||
# Cerca risorse postgresql.cnpg.io/v1 nei manifest e deploya una ConfigMap
|
ALERT_EMAIL=$(cat "env/$ENVIRONMENT"/*.env 2>/dev/null | grep -E '^[[:space:]]*alert_notification_email[[:space:]]*=' | tail -n 1 | sed -E 's/^[^=]*=//' | tr -d '\r"'"'"'[:space:]' || true)
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
CNPG_FILE=$(grep -rl "postgresql.cnpg.io/v1" "$YAML_DIR" 2>/dev/null | head -1 || true)
|
|
||||||
|
|
||||||
if [ -n "$CNPG_FILE" ]; then
|
|
||||||
echo "Trovata risorsa postgresql.cnpg.io/v1 in: $CNPG_FILE"
|
|
||||||
|
|
||||||
# Estrae metadata.name dal manifest CNPG preferendo yq, altrimenti awk
|
|
||||||
if command -v yq >/dev/null 2>&1; then
|
|
||||||
PG_NAME=$(yq eval 'select(.apiVersion == "postgresql.cnpg.io/v1") | .metadata.name' "$CNPG_FILE")
|
|
||||||
else
|
|
||||||
PG_NAME=$(awk '/postgresql\.cnpg\.io\/v1/{found=1} found && /^metadata:/{meta=1} meta && /^\s+name:/{print $2; exit}' "$CNPG_FILE")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ricava il namespace dal Role namespace-deployer presente nel cluster
|
|
||||||
PG_NS=$(kubectl --kubeconfig=./kubeconfig get role namespace-deployer \
|
|
||||||
--no-headers \
|
|
||||||
-o custom-columns='NS:.metadata.namespace' 2>/dev/null | head -1 || true)
|
|
||||||
#PG_NS="${PG_NS:-default}"
|
|
||||||
|
|
||||||
if [ -z "$PG_NAME" ]; then
|
|
||||||
echo "⚠️ Impossibile estrarre metadata.name dal cluster CNPG, skip ConfigMap." >&2
|
|
||||||
else
|
|
||||||
echo " → cluster: $PG_NAME namespace: $PG_NS"
|
|
||||||
kubectl --kubeconfig=./kubeconfig apply -f - <<EOF
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: service-config
|
|
||||||
namespace: ${PG_NS}
|
|
||||||
data:
|
|
||||||
tipodb: "postgres"
|
|
||||||
urldb: "${PG_NAME}.${PG_NS}.svc.cluster.local"
|
|
||||||
EOF
|
|
||||||
echo "ConfigMap db-config deployata in namespace ${PG_NS}."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Deploy di tutti i manifest Kubernetes
|
# Deploy di tutti i manifest Kubernetes
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
find "$YAML_DIR" -type d | while read DIR; do
|
find "$YAML_DIR" -type d | while read DIR; do
|
||||||
if ls "$DIR"/*.yaml 1> /dev/null 2>&1; then
|
if ls "$DIR"/*.yaml 1> /dev/null 2>&1; then
|
||||||
|
if [ "$(basename "$DIR")" = "monitoring" ]; then
|
||||||
|
if [ -z "$ALERT_EMAIL" ]; then
|
||||||
|
echo "alert_notification_email non valorizzata per l'ambiente $ENVIRONMENT: deploy di $DIR saltato."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
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..."
|
echo "Deploy delle risorse nella directory $DIR..."
|
||||||
kubectl --kubeconfig=./kubeconfig apply -f "$DIR"
|
kubectl --kubeconfig=./kubeconfig apply -f "$DIR"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Deploy completato di tutte le directory YAML."
|
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