Come eseguire più servizi in un contenitore Docker

Pubblicato: 2022-06-30

Illustrazione che mostra il logo Docker

Docker è una tecnologia per imballare i componenti della tua pila come contenitori isolati. È pratica comune eseguire ciascuno dei processi nel proprio contenitore, creando una netta divisione tra i componenti. Ciò migliora la modularità e consente di accedere ai vantaggi di scalabilità della containerizzazione.

Possono ancora verificarsi situazioni in cui desideri eseguire più servizi all'interno di un singolo contenitore. Sebbene ciò non sia naturale nell'ecosistema Docker, mostreremo alcuni approcci diversi che puoi utilizzare per creare contenitori con più di un processo di lunga durata.

Identificazione del problema

I contenitori Docker eseguono un unico processo in primo piano. Questo è definito dalle istruzioni ENTRYPOINT e CMD dell'immagine. ENTRYPOINT viene impostato all'interno del Dockerfile di un'immagine mentre CMD può essere sovrascritto durante la creazione di contenitori. I contenitori si arrestano automaticamente quando il processo in primo piano termina.

Puoi avviare altri processi dal CMD ma il contenitore rimarrà in esecuzione solo mentre il processo in primo piano originale è attivo. Mantenere il container operativo attraverso la durata combinata di due servizi indipendenti non è direttamente possibile utilizzando il meccanismo ENTRYPOINT/CMD .

Avvolgimento di più processi in un unico punto di ingresso

Gli script wrapper sono la soluzione più semplice al problema. Puoi scrivere uno script che avvii tutti i tuoi processi e attenda che finiscano. L'impostazione dello script come Docker ENTRYPOINT lo eseguirà come processo in primo piano del contenitore, mantenendo il contenitore in esecuzione fino all'uscita di uno degli script racchiusi.

 #!/bin/bash

/opt/primo processo &

/opt/secondo processo &

aspetta -n

uscita $?

Questo script avvia i file binari /opt/first-process e /opt/second-process all'interno del contenitore. L'uso di & consente allo script di continuare senza attendere l'uscita di ogni processo. wait viene utilizzato per sospendere lo script fino al termine di uno dei processi. Lo script esce quindi con il codice di stato emesso dallo script finito.

Questo modello fa sì che il contenitore esegua sia il first-process che second-process fino all'uscita di uno di essi. A quel punto, il contenitore si arresterà, anche se l'altro processo potrebbe essere ancora in esecuzione.

Per utilizzare questo script, modifica ENTRYPOINT e CMD dell'immagine Docker per renderla il processo in primo piano del contenitore:

 ENTRYPOINT ["/bin/sh"]
CMD [./percorso/di/script.sh"]

L'opzione del contenitore --init

Una sfida nella gestione dei processi dei container è la pulizia efficace quando escono. Docker esegue il tuo CMD come ID processo 1, rendendolo responsabile della gestione dei segnali e dell'eliminazione degli zombi. Se il tuo script non ha queste capacità, potresti ritrovarti con processi figlio orfani persistenti all'interno del tuo contenitore.

Il comando docker run ha un flag --init che modifica il punto di ingresso per utilizzare tini come PID 1. Questa è un'implementazione minima del processo init che esegue il tuo CMD , gestisce l'inoltro del segnale e raccoglie continuamente zombi.

Vale la pena usare --init se si prevede di generare molti processi e non si desidera gestire manualmente la pulizia. Tini è un gusto leggero init progettato per i contenitori. È molto più piccolo di alternative a tutti gli effetti come systemd e upstart .

Utilizzo di un Process Manager dedicato

Lo scripting manuale diventa rapidamente non ottimale quando hai molti processi da gestire. L'adozione di un process manager è un altro modo per eseguire diversi servizi all'interno dei contenitori Docker. Il responsabile del processo diventa il tuo ENTRYPOINT e ha la responsabilità di avviare, mantenere e pulire dopo i processi di lavoro.

Ci sono diverse opzioni disponibili quando si implementa questo approccio. supervisord è una scelta popolare che può essere facilmente configurata tramite un file /etc/supervisor/conf.d/supervisord.conf :

 [programma: apache2]
comando=/usr/sbin/apache2 -DFOREGROUND

[programma: mysqld]
comando=/usr/sbin/mysqld_safe

Questo file di configurazione configura supervisord per avviare Apache e MySQL. Per utilizzarlo in un contenitore Docker, aggiungi tutti i pacchetti richiesti all'immagine, quindi copia il file di configurazione del supervisord nella posizione corretta. Imposta supervisord come CMD dell'immagine per eseguirlo automaticamente all'avvio dei contenitori.

 DA ubuntu: più recente
ESEGUI apt-get install -y apache2 mysql-server supervisor
COPIA supervisord.conf /etc/supervisor/conf.d/supervisord.conf
ENTRYPOINT ["/bin/sh"]
CMD ["/usr/bin/supervisore"]

Poiché supervisord viene eseguito continuamente, non è possibile arrestare il contenitore quando uno dei processi monitorati si chiude. Un'opzione alternativa è s6-overlay che ha questa capacità. Utilizza un modello di servizio dichiarativo in cui inserisci gli script di servizio direttamente in /etc/services.d :

 # Aggiungi s6-overlay alla tua immagine
AGGIUNGI https://github.com/just-containers/s6-overlay/releases/download/v3.1.0.0/s6-overlay-noarch.tar.xz /tmp
ESEGUI tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz

ESEGUI printf "#!/bin/shn/usr/sbin/apache2 -DFOREGROUND" > /etc/services.d/first-service/run
ESEGUI chmod +x /etc/services.d/first-service/run

# Usa s6-overlay come punto di ingresso della tua immagine
ENTRYPOINT ["/init"]

È possibile aggiungere uno script di finish eseguibile all'interno delle directory del servizio per gestire l'arresto del contenitore con docker stop . s6-overlay eseguirà automaticamente questi script quando il suo processo riceve un segnale TERM dovuto al comando di stop .

Gli script Finish ricevono il codice di uscita del loro servizio come primo argomento. Il codice è impostato su 256 quando il servizio viene interrotto a causa di un segnale non rilevato. Lo script deve scrivere il codice di uscita finale in /run/s6-linux-init-container-results/exitcode ; s6-overlay legge questo file ed esce con il valore all'interno, facendo sì che quel codice venga utilizzato come codice di arresto del contenitore.

 #!/bin/sh
echo "$1" > /run/s6-linux-init-container-results/exitcode

Quando dovresti eseguire più processi in un container?

Questa tecnica viene utilizzata al meglio con processi strettamente accoppiati che non è possibile separare per essere eseguiti come contenitori indipendenti. Potresti avere un programma che si basa su un'utilità di supporto in background o un'applicazione monolitica che esegue la propria gestione dei singoli processi. Le tecniche mostrate sopra possono aiutarti a containerizzare questi tipi di software.

L'esecuzione di più processi in un contenitore dovrebbe comunque essere evitata ove possibile. Attenersi a un unico processo in primo piano massimizza l'isolamento, impedisce ai componenti di interferire tra loro e migliora la tua capacità di eseguire il debug e testare pezzi specifici. Puoi ridimensionare i componenti individualmente utilizzando gli agenti di orchestrazione dei contenitori, offrendoti la flessibilità per eseguire più istanze dei processi che richiedono più risorse.

Conclusione

I contenitori di solito hanno un processo in primo piano e funzionano per tutto il tempo in cui è attivo. Questo modello si allinea alle best practice di containerizzazione e ti consente di trarre il massimo vantaggio dalla tecnologia.

In alcune situazioni potrebbe essere necessario eseguire più processi in un contenitore. Poiché tutte le immagini hanno in definitiva un unico punto di ingresso, è necessario scrivere uno script wrapper o aggiungere un gestore di processi che si assuma la responsabilità di avviare i file binari di destinazione.

I gestori di processo ti danno tutto ciò di cui hai bisogno ma gonfiano le tue immagini con pacchetti e configurazioni extra. Gli script wrapper sono più semplici ma potrebbe essere necessario associarli al flag --init di Docker per prevenire la proliferazione del processo zombie.