Guida pratica a Helm hooks, tests e dependencies per deploy Kubernetes affidabili. Pattern concreti per orchestrare microservizi in produzione.
Helm Chart Avanzato: Padroneggiare Hooks, Tests e Dependencies per Deploy Kubernetes Affidabili
Hai un chart Helm che funziona in sviluppo. Poi arriva il momento di andare in produzione: devi eseguire migrazioni database prima del deploy, validare che i servizi dipendenti siano raggiungibili, gestire rollback quando qualcosa va storto. Il tuo values.yaml diventa un mostro ingestibile e i deploy falliscono alle 3 di notte.
Questo articolo ti mostra come superare questi problemi reali. Non teoria astratta, ma pattern concreti che uso quotidianamente per orchestrare deploy di architetture microservizi con decine di componenti interdipendenti.
Prerequisiti
- Kubernetes cluster funzionante (minikube, kind o cloud provider)
- Helm 3.12+ installato
- FamiliaritĂ con la sintassi base dei chart Helm
- Conoscenza di base di Go templates
- kubectl configurato e funzionante
1
2
3
4
5
6
| # Verifica versioni
helm version --short
# v3.14.0+g3fc9f4b
kubectl version --client
# Client Version: v1.29.0
|
Architettura e Concetti Chiave
Prima di scrivere codice, capiamo come Helm orchestra il ciclo di vita di un release e dove si inseriscono hooks, test e dependencies.
flowchart TD
A[helm install / upgrade] --> B[Resolve Dependencies]
B --> PG[postgresql]
B --> RD[redis]
B --> RMQ[rabbitmq]
PG & RD & RMQ --> C["pre-install / pre-upgrade hooks"]
C --> H1["weight: -10 → DB Migration"]
C --> H2["weight: 0 → Cache Warmup"]
C --> H3["weight: 10 → Notification"]
H1 & H2 & H3 --> D[Deploy Resources]
D --> E["post-install / post-upgrade hooks"]
E --> F{helm test}
F -->|pass| G[âś… Release Complete]
F -->|fail| H[❌ Rollback Decision]
Hooks: Job Kubernetes eseguiti in momenti specifici del ciclo di vita. Perfetti per migrazioni, seed data, cleanup.
Tests: Pod che validano lo stato del release dopo il deploy. Helm li esegue con helm test e riporta successo/fallimento.
Dependencies: Chart esterni che il tuo chart richiede. Helm li scarica, li configura e li installa nell’ordine corretto.
Implementazione Passo-Passo
Hooks Pre-Install e Pre-Upgrade per Migrazioni Database
Il caso d’uso piĂą comune: eseguire migrazioni database prima che l’applicazione parta. Vediamo un’implementazione robusta con gestione errori e retry.
Struttura del chart:
myapp/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── hooks/
│ │ ├── db-migration.yaml
│ │ ├── seed-data.yaml
│ │ └── pre-rollback-backup.yaml
│ └── tests/
│ ├── test-connection.yaml
│ └── test-api-health.yaml
└── charts/
Hook di migrazione database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
| # templates/hooks/db-migration.yaml
{{- if .Values.migrations.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-db-migrate
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
# Hook pre-install e pre-upgrade
"helm.sh/hook": pre-install,pre-upgrade
# Weight negativo = esegue prima di altri hook
"helm.sh/hook-weight": "-5"
# Elimina il job precedente prima di crearne uno nuovo,
# poi elimina dopo successo (mantieni in caso di fallimento per debug)
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
# Timeout massimo per la migrazione
activeDeadlineSeconds: {{ .Values.migrations.timeout | default 600 }}
# Numero di retry in caso di fallimento
backoffLimit: {{ .Values.migrations.retries | default 3 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: migration
spec:
restartPolicy: Never
# Init container che aspetta il database
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
echo "Attendo che PostgreSQL sia raggiungibile..."
until nc -z {{ .Values.database.host }} {{ .Values.database.port }}; do
echo "Database non ancora pronto, riprovo in 5 secondi..."
sleep 5
done
echo "Database raggiungibile!"
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/sh
- -c
- |
set -e
echo "=== Inizio migrazione database ==="
echo "Versione applicazione: {{ .Values.image.tag }}"
echo "Database: {{ .Values.database.host }}:{{ .Values.database.port }}/{{ .Values.database.name }}"
# Esegui le migrazioni con output dettagliato
./migrate -path /migrations -database "$DATABASE_URL" up
RESULT=$?
if [ $RESULT -eq 0 ]; then
echo "=== Migrazione completata con successo ==="
else
echo "=== ERRORE: Migrazione fallita con codice $RESULT ==="
exit $RESULT
fi
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db-credentials
key: url
{{- if .Values.migrations.dryRun }}
- name: DRY_RUN
value: "true"
{{- end }}
resources:
{{- toYaml .Values.migrations.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
|
đź’ˇ Tip: Usa hook-delete-policy: before-hook-creation,hook-succeeded per mantenere i job falliti. Ti permette di ispezionare i log con kubectl logs job/myapp-db-migrate quando qualcosa va storto.
Hook per seed data con dipendenza dalla migrazione:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # templates/hooks/seed-data.yaml
{{- if and .Values.seed.enabled (not .Values.global.production) }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-seed-data
annotations:
"helm.sh/hook": post-install
# Weight maggiore = esegue DOPO la migrazione (weight -5)
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
activeDeadlineSeconds: 300
backoffLimit: 2
template:
spec:
restartPolicy: Never
containers:
- name: seed
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command:
- /bin/sh
- -c
- |
set -e
echo "Caricamento dati di seed per ambiente {{ .Release.Namespace }}..."
# Verifica che le migrazioni siano state eseguite
./cli db:check-migrations
# Carica i dati appropriati per l'ambiente
{{- if eq .Release.Namespace "staging" }}
./cli db:seed --dataset=staging
{{- else }}
./cli db:seed --dataset=development
{{- end }}
echo "Seed completato!"
envFrom:
- secretRef:
name: {{ include "myapp.fullname" . }}-db-credentials
{{- end }}
|
⚠️ Warning: Non eseguire mai seed data in produzione! Nota il controllo (not .Values.global.production) che disabilita questo hook negli ambienti di produzione.
Hook pre-rollback per backup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| # templates/hooks/pre-rollback-backup.yaml
{{- if .Values.backup.beforeRollback }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-pre-rollback-backup
annotations:
"helm.sh/hook": pre-rollback
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
activeDeadlineSeconds: 900
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: postgres:15-alpine
command:
- /bin/sh
- -c
- |
set -e
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="pre_rollback_${TIMESTAMP}.sql.gz"
echo "Creazione backup pre-rollback: $BACKUP_NAME"
pg_dump -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" | gzip > /backup/$BACKUP_NAME
# Upload su S3 (opzionale)
{{- if .Values.backup.s3.enabled }}
aws s3 cp /backup/$BACKUP_NAME s3://{{ .Values.backup.s3.bucket }}/rollbacks/$BACKUP_NAME
{{- end }}
echo "Backup completato: $BACKUP_NAME"
env:
- name: DB_HOST
value: {{ .Values.database.host | quote }}
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db-credentials
key: username
- name: DB_NAME
value: {{ .Values.database.name | quote }}
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db-credentials
key: password
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
emptyDir:
sizeLimit: 5Gi
{{- end }}
|
Test Suite Helm per Validazione Post-Deploy
I test Helm sono Pod che verificano lo stato del release. A differenza degli hook, non bloccano il deploy ma validano che tutto funzioni correttamente dopo l’installazione.
Test di connettivitĂ database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| # templates/tests/test-db-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: {{ include "myapp.fullname" . }}-test-db
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: test-db
image: postgres:15-alpine
command:
- /bin/sh
- -c
- |
set -e
echo "=== Test connessione database ==="
# Test 1: Connessione base
echo "Test 1: Verifica connessione..."
pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME"
# Test 2: Query di verifica
echo "Test 2: Esecuzione query di test..."
RESULT=$(psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM schema_migrations;")
echo "Migrazioni applicate: $RESULT"
# Test 3: Verifica tabelle critiche
echo "Test 3: Verifica schema..."
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "\dt" | grep -q "users" || {
echo "ERRORE: Tabella 'users' non trovata!"
exit 1
}
echo "=== Tutti i test database superati ==="
env:
- name: DB_HOST
value: {{ .Values.database.host | quote }}
- name: DB_PORT
value: {{ .Values.database.port | quote }}
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db-credentials
key: username
- name: DB_NAME
value: {{ .Values.database.name | quote }}
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ include "myapp.fullname" . }}-db-credentials
key: password
|
Test API health check completo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
| # templates/tests/test-api-health.yaml
apiVersion: v1
kind: Pod
metadata:
name: {{ include "myapp.fullname" . }}-test-api
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: test-api
image: curlimages/curl:8.5.0
command:
- /bin/sh
- -c
- |
set -e
API_URL="http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
echo "=== Test API Health ==="
echo "Endpoint: $API_URL"
# Attendi che il servizio sia pronto
echo "Attendo che il servizio sia raggiungibile..."
for i in $(seq 1 30); do
if curl -sf "$API_URL/health" > /dev/null 2>&1; then
break
fi
echo "Tentativo $i/30..."
sleep 2
done
# Test 1: Health endpoint
echo "Test 1: Health check..."
HEALTH=$(curl -sf "$API_URL/health")
echo "Response: $HEALTH"
echo "$HEALTH" | grep -q '"status":"healthy"' || {
echo "ERRORE: Health check fallito!"
exit 1
}
# Test 2: Ready endpoint (include check dipendenze)
echo "Test 2: Readiness check..."
READY=$(curl -sf "$API_URL/ready")
echo "Response: $READY"
# Verifica che tutte le dipendenze siano OK
echo "$READY" | grep -q '"database":"connected"' || {
echo "ERRORE: Database non connesso!"
exit 1
}
{{- if .Values.redis.enabled }}
echo "$READY" | grep -q '"redis":"connected"' || {
echo "ERRORE: Redis non connesso!"
exit 1
}
{{- end }}
# Test 3: Verifica configurazione (solo staging/dev)
{{- if not .Values.global.production }}
echo "Test 3: Verifica configurazione..."
CONFIG=$(curl -sf "$API_URL/debug/config")
echo "$CONFIG" | grep -q '"environment":"{{ .Release.Namespace }}"' || {
echo "WARNING: Environment mismatch"
}
{{- end }}
# Test 4: Metriche Prometheus disponibili
{{- if .Values.metrics.enabled }}
echo "Test 4: Metriche Prometheus..."
curl -sf "$API_URL/metrics" | grep -q "http_requests_total" || {
echo "ERRORE: Metriche non disponibili!"
exit 1
}
{{- end }}
echo "=== Tutti i test API superati ==="
|
Test di integrazione multi-servizio:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| # templates/tests/test-integration.yaml
{{- if .Values.tests.integration.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "myapp.fullname" . }}-test-integration
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: test-integration
image: "{{ .Values.tests.integration.image }}"
command:
- /bin/sh
- -c
- |
set -e
echo "=== Test di Integrazione ==="
# Esegui test suite con pytest/jest/go test
cd /tests
{{- if eq .Values.tests.integration.framework "pytest" }}
pytest integration/ \
--tb=short \
--junitxml=/results/integration.xml \
-v
{{- else if eq .Values.tests.integration.framework "jest" }}
npm test -- --ci --reporters=default --reporters=jest-junit
{{- end }}
echo "=== Test di integrazione completati ==="
env:
- name: API_BASE_URL
value: "http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
- name: TEST_TIMEOUT
value: "{{ .Values.tests.integration.timeout | default 30 }}"
volumeMounts:
- name: test-results
mountPath: /results
volumes:
- name: test-results
emptyDir: {}
{{- end }}
|
Gestione Dependencies Multi-Servizio
Per architetture microservizi, devi orchestrare multiple dipendenze. Ecco un Chart.yaml completo con gestione avanzata:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| # Chart.yaml
apiVersion: v2
name: myapp
description: Applicazione principale con dipendenze complete
type: application
version: 1.2.0
appVersion: "2.1.0"
dependencies:
# PostgreSQL - sempre richiesto
- name: postgresql
version: "13.2.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
# Alias permette di avere multiple istanze dello stesso chart
alias: db-primary
# PostgreSQL replica per read-only (opzionale)
- name: postgresql
version: "13.2.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.replica.enabled
alias: db-replica
# Redis per caching
- name: redis
version: "18.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
# RabbitMQ per message queue
- name: rabbitmq
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: rabbitmq.enabled
# Tags permettono di abilitare gruppi di dipendenze
tags:
- messaging
- async-processing
# Elasticsearch per search/logging
- name: elasticsearch
version: "19.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: elasticsearch.enabled
tags:
- search
- logging
# Chart interno per microservizio auth
- name: auth-service
version: "1.x.x"
repository: "file://../auth-service"
condition: authService.enabled
|
Values.yaml con override per le dipendenze:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| # values.yaml
global:
production: false
imageRegistry: ""
storageClass: "standard"
# Configurazione applicazione principale
image:
repository: mycompany/myapp
tag: "2.1.0"
pullPolicy: IfNotPresent
service:
port: 8080
type: ClusterIP
# === CONFIGURAZIONE DIPENDENZE ===
# PostgreSQL primario
postgresql:
enabled: true
db-primary:
# Override specifici per il chart postgresql con alias db-primary
auth:
postgresPassword: "" # SarĂ sovrascritto da secret esterno
username: "myapp"
password: ""
database: "myapp"
primary:
persistence:
enabled: true
size: 50Gi
resources:
requests: { memory: 512Mi, cpu: 250m }
limits: { memory: 2Gi, cpu: 1000m }
# Redis (aliasato di default, nessun alias qui)
redis:
enabled: true
architecture: replication
auth:
enabled: true
password: ""
# Gruppi di dipendenze abilitabili via tag
tags:
messaging: true
async-processing: true
search: false
logging: false
# Microservizio interno
authService:
enabled: true
replicaCount: 2
|
⚠️ helm dependency update riscrive Chart.lock. Committa il lockfile. Senza, due sviluppatori che lanciano helm dependency update a settimane di distanza risolvono postgresql: 13.2.x a patch diverse e mandano in produzione database sottilmente diversi.
Prima di ogni install (o build CI) scarica e “vendorizza” i subchart:
1
2
3
4
5
6
7
8
9
10
11
| # Scarica i chart dichiarati in Chart.yaml dentro charts/
helm dependency update ./myapp
# Ispeziona l'albero risolto e quali condizioni sono attive
helm dependency list ./myapp
# NAME VERSION REPOSITORY STATUS
# postgresql 13.2.x https://charts.bitnami.com/bitnami ok
# postgresql 13.2.x https://charts.bitnami.com/bitnami unpacked (condition è false)
# redis 18.x.x https://charts.bitnami.com/bitnami ok
# rabbitmq 12.x.x https://charts.bitnami.com/bitnami ok
# elasticsearch 19.x.x https://charts.bitnami.com/bitnami unpacked (tag search è false)
|
Condition vs. Tag
Usa condition quando un singolo booleano attiva un subchart. Usa tags quando un interruttore deve attivare o disattivare un gruppo di chart insieme (vedi messaging e search sopra). Se condition e tag si applicano allo stesso chart, vince condition.
Passare valori dal subchart al parent
Il parent può leggere i valori che un subchart esporta, evitando di duplicare i dati di connessione:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # charts/postgresql/values.yaml (child)
exports:
dbConnection:
host: db-primary-postgresql
port: 5432
# Chart.yaml (parent) — importa il blocco esportato dal child
dependencies:
- name: postgresql
version: "13.2.x"
repository: "https://charts.bitnami.com/bitnami"
alias: db-primary
import-values:
- child: exports.dbConnection
parent: database
|
Dopo l’import, {{ .Values.database.host }} nel parent vale quello che il subchart ha esportato.
Configurazione per Produzione
Hooks e test ripagano solo se il comando di deploy tratta un hook fallito come un release fallito. In CI/CD deploya sempre con --atomic:
1
2
3
4
5
6
7
8
9
10
| helm upgrade --install myapp ./myapp \
--namespace myapp \
--create-namespace \
--atomic \
--timeout 10m \
--wait \
--set-string db-primary.auth.postgresPassword="$DB_ADMIN_PASSWORD" \
--set-string db-primary.auth.password="$DB_APP_PASSWORD" \
--values values.yaml \
--values values.production.yaml
|
Cosa ti dĂ ogni flag:
--atomic — se una risorsa o un hook pre-* fallisce, Helm fa il rollback dell’intero release alla revisione precedente. Senza, una migrazione fallita ti lascia su un release applicato a metĂ .--wait — Helm si blocca finchĂ© Deployment, StatefulSet e Job non sono ready. --atomic lo implica, ma esplicitarlo rende l’intento chiaro.--timeout 10m — il limite per ogni fase di attesa (hook, risorse, test). Le migrazioni su tabelle grandi hanno bisogno di margine.--set-string per i secret — tiene le password fuori da values.yaml e dalla history git. Meglio ancora: usa un Secret esistente via db-primary.auth.existingSecret.
Lancia la test suite come stage separato della pipeline, così un deploy “verde” che fallisce la validazione blocca comunque il rilascio:
1
| helm test myapp --namespace myapp --logs --timeout 5m
|
đź’ˇ --logs porta l’output di ogni Pod di test nel terminale (e nel log CI) anche quando il Pod viene cancellato subito dopo da hook-delete-policy: hook-succeeded. Senza, un test fallito in CI ti lascia solo un exit code.
Errori Comuni e Troubleshooting
Le risorse degli hook non vengono ripulite da helm uninstall. Helm non traccia le risorse create dagli hook come parte del release. Un Job hook senza hook-delete-policy sopravvive all’uninstall e va in collisione con l’install successivo. Metti sempre almeno before-hook-creation, e aggiungi hook-succeeded per tenere pulito il cluster preservando i job falliti da ispezionare.
Un hook di migrazione fallito non fa il rollback dello schema. --atomic fa il rollback delle risorse Kubernetes, non del database. Helm non sa cosa ha fatto il container di migrazione. Accoppia ogni migrazione forward con un hook pre-rollback (mostrato sopra) e rendi le migrazioni transazionali dove il tuo engine lo permette.
I test passano in locale ma si bloccano in CI. I Pod di test con restartPolicy: Never che vanno in errore restano in stato Error per sempre se helm test non riceve --timeout. Impostalo, e metti backoffLimit sui test basati su Job.
Le CRD non si aggiornano. I file sotto crds/ vengono installati una volta sola, prima degli hook, e non sono mai aggiornati o cancellati da Helm su upgrade o uninstall. Se un subchart alza la versione di una CRD, devi fare kubectl apply a mano.
helm dependency update fallisce dietro proxy o in cluster air-gapped. Vendorizza i charts/*.tgz in git, oppure pushali su un registry OCI (helm push) e referenzia repository oci:// così le build non escono su internet.
Diagnostica utile:
1
2
3
4
5
6
7
8
9
| # I manifest esatti che Helm applicherĂ , hook inclusi
helm template ./myapp --values values.yaml | less
# Cosa è successo nell'ultimo release
helm history myapp -n myapp
helm status myapp -n myapp --show-resources
# Log di un Job hook fallito e preservato
kubectl logs -n myapp job/myapp-db-migrate
|
- Gli hook girano in sequenza e bloccano il release. Dieci hook
pre-upgrade da dieci secondi l’uno aggiungono due minuti di finestra di downtime a ogni deploy. Accorpa gli step indipendenti in un unico Job hook, e tieni hook separati solo per ciò che deve davvero essere ordinato. - Raggruppa per weight, non per numero. Hook con lo stesso weight girano comunque un oggetto alla volta, ma eviti un nuovo ciclo di wait-for-ready per ogni bucket di weight.
- Tieni le test suite sotto il minuto.
helm test è nel percorso critico della pipeline. Sposta i test di integrazione esaustivi su un job separato contro il release deployato; in-chart lascia solo smoke test (connettività , versione schema, sanità config). - Grafi di dipendenze grandi rallentano ogni comando.
helm dependency update riscarica e ri-pacchettizza l’intero albero. In CI, cachea la cartella charts/ con chiave sull’hash di Chart.lock. - Preferisci i registry OCI per le dipendenze. Il pull di chart
oci:// è una singola richiesta autenticata per chart e sfrutta il layer caching, contro gli index file HTTP che Helm ri-scarica e ri-parsa. ttlSecondsAfterFinished sui Job hook evita che il namespace accumuli centinaia di Pod di migrazione completati lungo un ciclo di release intenso.
Conclusioni e Next Steps
Ora hai i tre pilastri di un chart Helm production-grade che lavorano insieme: hook che eseguono migrazioni e backup in modo atomico dentro il lifecycle del release, test suite che validano un deploy prima di dichiararlo riuscito, e un grafo di dipendenze che compone microservizi con condition, tag e import di valori.
Il pattern che li lega è trattare il release come una transazione: deploy con --atomic --wait, hook pre-* che fanno da gate, test che confermano, e uno step fallito che fa tornare tutto a una revisione nota-buona.
Prossimi passi:
- Aggiungi un hook
post-upgrade che scalda le cache o triggera un purge CDN, così la prima richiesta utente non è quella che paga il cold start. - Sposta i secret fuori da
--set-string con External Secrets Operator o Sealed Secrets. - Collega
helm test a un tool di progressive delivery (Argo Rollouts o Flagger) così la validazione fa da gate a una canary invece che a un cutover completo. - Pubblica parent chart e subchart su un registry OCI e consumali per digest per deploy completamente riproducibili.
Risorse Aggiuntive