Hai Kubernetes in produzione. Ogni deploy è un copia-incolla di YAML? I manifest sono sparsi tra cartelle, valori hardcoded, nessuna tracciabilità. Lo vediamo spesso nei progetti che analizziamo: cluster funzionanti ma gestiti come fossero prototipi. Un errore di sintassi in un Deployment e l'app resta in CrashLoopBackOff per ore. Il tempo perso a correggere è tempo che potevi dedicare al prodotto.
Noi, di Meteora Web, lavoriamo con Kubernetes da anni — da quando i container non erano ancora la norma. Abbiamo visto aziende risparmiare centinaia di ore semplicemente strutturando i deploy con Helm. E abbiamo anche visto il lato economico: un deploy manuale sbagliato su un cluster di produzione costa più di una giornata di sviluppo. Con Helm il deploy diventa riproducibile, versionato, testabile. E si paga una volta sola.
Questa guida è il secondo spoke del nostro pillar su Kubernetes e orchestrazione container. Ti portiamo dentro Helm: dalla struttura di un chart alle best practice per ambienti reali. Niente teoria astratta — tutto quello che serve per smettere di scrivere YAML a mano e iniziare a usare un package manager degno di questo nome.
Perché Helm non è solo un abbellimento
Helm è il package manager di Kubernetes. Ma non limitarti a pensarlo come un 'apt per cluster'. È molto di più: è un motore di templating, un sistema di gestione delle release, un repository di pacchetti pronti all'uso. Se non lo usi, ogni deploy è un assemblaggio artigianale: copia un YAML, incolla, cambia namespace, modifica il nome, incrocia le dita. Con Helm definisci una volta la struttura e poi la parametrizzi. Il risultato? Uno stesso chart deployato in development, staging e production con valori diversi, zero duplicazione di codice.
L'analogia che usiamo coi nostri clienti è quella del fatturino di un negozio: se devi calcolare ogni scontrino a mano, perdi tempo e sbagli. Se hai un ERP che lo fa per te con regole fisse, guadagni precisione e velocità. Helm è il tuo ERP per Kubernetes.
Sponsored Protocol
Il costo di non usare Helm
Un cliente ci ha portato un cluster con 15 microservizi, ognuno con la propria cartella di YAML. Ogni deploy richiedeva tra 20 e 40 minuti di attenzione umana. Un errore di typo in un nome di volume — e il pod non partiva. Helm ha ridotto il deploy a helm upgrade --install con un singolo comando. Il risparmio? Circa 15 ore al mese. Non è solo tecnologia: è economia.
Anatomia di un Helm Chart
Un chart è una directory strutturata. Se non la rispetti, Helm non lo interpreta. Ecco i file obbligatori e quelli tipici:
mio-chart/
├── Chart.yaml # metadati del chart (nome, versione, dipendenze)
├── values.yaml # valori predefiniti per i template
├── templates/ # file Go template che generano YAML
│ ├── deployment.yaml
│ ├── service.yaml
│ └── _helpers.tpl # funzioni riutilizzabili (underscore = non generare risorse)
├── charts/ # dipendenze (chart annidati)
└── .helmignore # file da escludere
Chart.yaml è il biglietto da visita. Esempio minimo:
apiVersion: v2
name: mia-webapp
description: Un semplice web server
version: 0.1.0
appVersion: "1.16.0"
values.yaml è dove metti i valori che cambi per ambiente: replicaCount, immagine, porta, risorse. È il cuore della parametrizzazione.
templates/ contiene file che Helm elabora con il motore Go template. Ogni file che inizia con _ non produce un oggetto Kubernetes, ma può essere incluso da altri template (es. helper per label comuni).
Errore comune: dimenticare di versionare i chart in git. Il chart deve essere sotto controllo versione, esattamente come il codice. Altrimenti perdi la riproducibilità.
Sponsored Protocol
Creare il tuo primo chart da zero
Basta un terminale. Usiamo helm create per generare una struttura di esempio, ma noi preferiamo partire da zero per capire tutto.
Passo 1: la directory
mkdir nginx-private
cd nginx-private
mkdir templates
Passo 2: Chart.yaml
apiVersion: v2
name: nginx-private
description: Il mio nginx custom con valori parametrizzati
version: 0.1.0
appVersion: "1.25.0"
Passo 3: values.yaml
replicaCount: 2
image:
repository: nginx
tag: "1.25.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources: {}
nodeSelector: {}
Passo 4: deployment.yaml in templates/
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "nginx-private.fullname" . }}
labels:
{{- include "nginx-private.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "nginx-private.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "nginx-private.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
Passo 5: service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "nginx-private.fullname" . }}
labels:
{{- include "nginx-private.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
name: http
selector:
{{- include "nginx-private.selectorLabels" . | nindent 4 }}
Passo 6: _helpers.tpl (nomi e label standard)
{{- define "nginx-private.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "nginx-private.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "nginx-private.labels" -}}
helm.sh/chart: {{ include "nginx-private.name" . }}-{{ .Chart.Version | replace "+" "_" }}
{{ include "nginx-private.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "nginx-private.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nginx-private.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Ora puoi installare il chart con:
Sponsored Protocol
helm install mio-nginx ./nginx-private
Go Template nel dettaglio: quello che ti serve davvero
I template Helm usano il linguaggio Go con funzioni di Sprig. Non serve essere esperti di Go, ma devi conoscere le funzioni chiave.
Accesso ai valori
.Values.replicaCount — il punto indica l'oggetto globale. .Chart, .Release, .Files sono altri oggetti built-in.
Condizioni
Utili per esporre servizi solo se necessari:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
...
{{- end }}
Range (cicli)
Per iterare su un elenco, ad esempio più variabili d'ambiente:
env:
{{- range $key, $val := .Values.envVars }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
Funzioni di manipolazione stringhe
upper, lower, trimSuffix, quote, default, required. required è fantastico per validare che un valore obbligatorio sia presente:
domain: {{ required "Un valore per domain è obbligatorio" .Values.domain }}
Spazi e indentazione
Usa {{- per rimuovere spazi bianchi prima e -}} per rimuovere dopo. nindent aggiunge newline e indentazione. Regola d'oro: per gli elementi di una lista, usa nindent per mantenere YAML valido.
Sponsored Protocol
Gestione delle dipendenze: chart che contengono chart
Una webapp ha bisogno di un database? Invece di scrivere un Deployment per MySQL a mano, puoi dichiarare la dipendenza da un chart ufficiale (es. bitnami/mysql).
File Chart.yaml con dipendenze
apiVersion: v2
name: webapp-con-db
dependencies:
- name: mysql
version: "9.10.2"
repository: "https://charts.bitnami.com/bitnami"
condition: mysql.enabled
tags:
- database
Poi esegui:
helm dependency update
Scarica il chart nella cartella charts/. Ora puoi passare valori di configurazione per MySQL nel tuo values.yaml sotto la chiave mysql.
Attenzione: le dipendenze non vengono aggiornate automaticamente. Se il chart padre cambia versione, devi esplicitamente rifare helm dep update.
Deploy e rollback: il comando che salva la giornata
Il ciclo di vita di Helm è gestito con helm install, helm upgrade, helm rollback. Ogni deploy è una release con un numero di revisione.
Installazione
helm install mia-webapp ./mio-chart -f valori-produzione.yaml --namespace produzione --create-namespace
Upgrade
helm upgrade mia-webapp ./mio-chart -f valori-produzione.yaml --atomic --timeout 10m
Flag --atomic ripristina la release precedente se fallisce. Non volare senza rete.
Rollback
helm rollback mia-webapp 2 # torna alla revisione 2
helm history mia-webapp # elenca revisioni
Best practice per chart personalizzate
- Usa i naming standard:
app.kubernetes.io/name,app.kubernetes.io/instance,app.kubernetes.io/version. I tool di monitoring e networking si aspettano queste label. - Valida i valori con JSON Schema: crea un file
values.schema.jsonnella root del chart. Helm lo usa per validare i valori prima di iniziare il rendering. Un semplice schema previene errori comereplicaCount: "tre". - Non hardcodare nomi: usa sempre gli helper per generare fullname. Eviti collisioni in namespace condivisi.
- Separa i valori per ambiente: file
values-dev.yaml,values-prod.yamlseparati dal chart. Il chart è l'unica fonte di verità per la struttura; i valori sono configurazione. - Aggiorna l'appVersion: quando il container cambia tag, aggiorna
appVersionin Chart.yaml e incrementaversion. Così il team di Operations sa subito se il chart è allineato.
Integrazione con CI/CD
Helm brilla in pipeline automatiche. Una volta costruita l'immagine del container, la pipeline può eseguire helm upgrade --install con i valori giusti. Questo tema lo approfondiamo nel nostro articolo dedicato alla CI/CD.
Sponsored Protocol
Inoltre, ti consigliamo di leggere la nostra guida pillar su Kubernetes per inquadrare Helm nel contesto completo dell'orchestrazione.
In sintesi — cosa fare adesso
- Converti un tuo deploy manuale in un chart Helm. Prendi il Deployment, Service e ConfigMap più semplici che hai e crea un chart seguendo i passi di questa guida.
- Parametrizza i valori: sposta in values.yaml tutto ciò che cambia per ambiente (repliche, immagine, variabili d'ambiente, risorse).
- Aggiungi gli helper per i nomi: non usare nomi fissi, usa
include "tuo-chart.fullname" . - Aggiungi un JSON Schema: un semplice file
values.schema.jsonnella root del chart. Riduce errori di configurazione. - Integra Helm nella tua pipeline CI/CD: se già usi GitLab CI, GitHub Actions o Jenkins, aggiungi uno step che esegue
helm upgrade --install --atomic.
E se hai dubbi sul tuo cluster, sui costi nascosti del deploy manuale o sulla sicurezza dei tuoi manifest, parliamone. Lavoriamo con Kubernetes da quando non era ancora moda.