Init container: cosa sono e come funzionano
L’inizializzazione è un problema diffuso in molti linguaggi di programmazione: ad esempio, in Java, per istanziare un oggetto che richieda una certa configurazione, si usa il concetto di costruttore (o blocchi statici per casi di utilizzo più sofisticati). I costruttori sono eseguiti come prima cosa all’interno dell’oggetto e una sola volta dall’ambiente di esecuzione.
Senza entrare nel dettaglio e rendendo volutamente semplice l’esempio, sappiamo di poter usare il costruttore per verificare delle precondizioni, come i parametri obbligatori, o per inizializzare i campi dell’istanza con con argomenti ricevuti in ingresso o con dei valori predefiniti.
Cosa sono
Gli init container consentono la separazione delle responsabilità, fornendo un ciclo di vita separato per attività di inizializzazione che sono distinte da quelle eseguire nei container principali dell’applicazione.
Gli init container sono simili in termini di funzionalità, ma a livello di Pod anziché di classe.
Quindi, se in un Pod sono presenti uno o più container, questi possono avere dei prerequisiti prima dell’avvio, includendo la configurazione di permessi speciali sul filesystem, l’inizializzazione dello schema del database o la copia dei dati di partenza dell’applicazione.
Inoltre, questa logica di inizializzazione può richiedere strumenti e librerie che non possono essere inclusi nell’immagine dell’applicazione, per diverse ragioni, come per motivi di sicurezza: l’immagine dell’applicazione potrebbe non avere i permessi per eseguire queste attività.
Per tutti questi casi d’uso, Kubernetes utilizza i container di init come implementazione di questo modello, che consente di separare le attività di inizializzazione dalle attività principali dell’applicazione.
I container di init in Kubernetes fanno parte della definizione di Pod e separano tutti i container di un Pod in due gruppi: i container di init e i container applicativi.
Tutti i container init vengono eseguiti in sequenza, uno per uno, e tutti devono terminare con successo prima di avviare i container applicativi.
In questo senso, i container di init sono come le istruzioni del costruttore di una classe Java che aiutano l’inizializzazione dell’oggetto. I container applicativi, invece, vengono eseguiti in parallelo e l’ordine di avvio è arbitrario.
In genere, ci si aspetta che i container di init siano piccoli, vengano eseguiti rapidamente e completati con successo, tranne quando un container di init è usato per ritardare l’avvio di un Pod in attesa di una dipendenza, nel qual caso potrebbe non terminare finché questo controllo non è soddisfatto.
Se un container di init fallisce, infatti, l’intero Pod viene riavviato (a meno che non sia stato configurato con la policy RestartNever), facendo sì che tutti i container di init vengano eseguiti di nuovo.
Pertanto, per evitare effetti collaterali indesiderati, è sempre buona norma rendere i container di init idempotenti.
Da un lato, i container di init hanno tutte le stesse capacità dei container di applicazioni: tutti i container fanno parte dello stesso Pod, quindi condividono limiti di risorse, volumi e impostazioni di sicurezza. Dall’altro lato, hanno una semantica leggermente diversa: non esistono infatti readiness o liveness probe, poiché tutti i container di init devono terminare con successo prima che i processi di avvio di Pod possano continuare con i container di applicazioni.
I container init influiscono anche sui requisiti delle risorse Pod per la per la schedulazione, l’autoscaling e la gestione delle quote.
Dato l’ordine di esecuzione di tutti i container in un Pod, i valori effettivi di request e limit a livello di Pod diventano i valori più alti dei due gruppi seguenti:
- Il più alto valore di request/limit dell’init container, oppure
- La somma di tutti i valori di request/limit dei container applicativi.
Una conseguenza di questo comportamento è che se si hanno container di init con richieste di risorse elevate e container applicative con richieste più modeste, i valori a livello di Pod che influenzano la schedulazione saranno basati su quelli che afferiscono al container init.
Questa configurazione non è efficiente dal punto di vista delle risorse: anche se i container di init vengono eseguiti per un breve periodo di tempo e c’è capacità disponibile sul nodo, nessun altro Pod potrà utilizzarla nel frattempo.
Esempio
Il seguente esempio mostra un init container che inserisce una stringa all’interno del file index.html del server Nginx prima che questo venga avviato:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-server
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx-server
template:
metadata:
labels:
app: nginx-server
spec:
volumes:
- name: common-disk
emptyDir: {}
initContainers:
- name: busybox
image: busybox
volumeMounts:
- name: common-disk
mountPath: /web-files
command: ["/bin/sh"]
args: ["-c", "echo '<h2>Init container added this line!</h2>' > /web-files/index.html && sleep 20"]
containers:
- image: nginx
name: nginx
volumeMounts:
- name: common-disk
mountPath: /usr/share/nginx/html
Sapevi che il 18 marzo inizierà il master Cloud & DevOps Master organizzato da Talent Garden? Il percorso è interamente online, dura 9 settimane e i partecipanti riceveranno un voucher per l’esame di Certified Cloud Practitioner grazie alla partnership con AWS. Per saperne di più sul mondo DevOps, sui principali cloud provider e su come usare Docker e Kubernetes, iscriviti qui!
Risorse utili
- Docker - per cominciare bene con Docker e Kubernetes
- Kubernetes - Guida per gestire e orchestrare i container