Comment maintenir le conteneur Docker en cours d'exécution après le démarrage des services?

156

J'ai vu un tas de tutoriels qui semblent faire la même chose que j'essaie de faire, mais pour une raison quelconque, mes conteneurs Docker se terminent. En gros, je mets en place un serveur Web et quelques démons dans un conteneur Docker. Je fais les dernières parties de cela via un script bash appelé run-all.shque je lance via CMD dans mon Dockerfile. run-all.shressemble à ça:

service supervisor start
service nginx start

Et je le démarre dans mon Dockerfile comme suit:

CMD ["sh", "/root/credentialize_and_run.sh"]

Je peux voir que tous les services démarrent correctement lorsque j'exécute les choses manuellement (c'est-à-dire que je passe à l'image avec -i -t / bin / bash), et que tout semble fonctionner correctement lorsque j'exécute l'image, mais il se termine une fois il termine le démarrage de mes processus. J'aimerais que les processus s'exécutent indéfiniment, et pour autant que je sache, le conteneur doit continuer à fonctionner pour que cela se produise. Néanmoins, quand je cours docker ps -a, je vois:

➜  docker_test  docker ps -a
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS                      PORTS               NAMES
c7706edc4189        some_name/some_repo:blah   "sh /root/run-all.sh   8 minutes ago       Exited (0) 8 minutes ago                        grave_jones

Ce qui donne? Pourquoi sort-il? Je sais que je pourrais simplement mettre une boucle while à la fin de mon script bash pour le maintenir, mais quelle est la bonne façon de l'empêcher de se terminer?

Eli
la source
1
exposez-vous les ports des services à l'extérieur (option -p pour exécuter docker)? (bien sûr, cela ne les empêchera pas de sortir)
ribamar
1
J'utilisais ENTRYPOINT dans mon Dockerfile, et après l'exécution du script défini dans ENTRYPOINT (mon script d'initialisation), il est apparu dans les journaux mais mon conteneur semblait être exit. Ainsi, au lieu de ENTRYPOINT, j'ai utilisé la commande RUN pour exécuter le script et le conteneur est toujours en cours d'exécution en arrière-plan.
ypahalajani

Réponses:

51

Ce n'est pas vraiment ainsi que vous devez concevoir vos conteneurs Docker.

Lors de la conception d'un conteneur Docker, vous êtes censé le construire de telle sorte qu'il n'y ait qu'un seul processus en cours d'exécution (c'est-à-dire que vous devriez avoir un conteneur pour Nginx et un pour le superviseur ou l'application qu'il exécute); en outre, ce processus doit s'exécuter au premier plan.

Le conteneur "quittera" lorsque le processus lui-même se terminera (dans votre cas, ce processus est votre script bash).


Cependant, si vous avez vraiment besoin (ou souhaitez) d'exécuter plusieurs services dans votre conteneur Docker, envisagez de commencer à partir de "Docker Base Image" , qui utilise runitcomme processus pseudo-init ( runitrestera en ligne pendant que Nginx et Supervisor s'exécutent), qui restera au premier plan pendant que vos autres processus font leur travail.

Ils ont des documents importants, vous devriez donc être en mesure de réaliser ce que vous essayez de faire assez facilement.

Thomas Orozco
la source
1
Pouvez-vous expliquer pourquoi je ne devrais avoir qu'un seul service en cours d'exécution? Je pourrais ajouter nginx au superviseur si nécessaire, mais je ne sais pas pourquoi cela devrait être nécessaire.
Eli
3
@Eli La réponse courte est que c'est ainsi que fonctionne Docker. Docker n'exécutera qu'un seul processus (et ses enfants) par conteneur. Il est recommandé que ce processus soit un processus d'application réel (de sorte que s'il se termine, Docker le sache), mais vous pouvez en effet utiliser superviseur comme processus. Notez que vous devrez configurer le superviseur pour qu'il s'exécute au premier plan (c'est-à-dire sans démoniser), ce qui se fait via l' --nodaemonoption.
Thomas Orozco
1
@Eli Ce billet de blog Docker montre que l'exécution de plusieurs processus (et, de manière générale, la visualisation d'un conteneur comme un "petit VPS") est sous-optimale. Dans votre cas, le fil de commentaire sera probablement plus pertinent que le véritable article du blog.
Thomas Orozco
1
L'image de base Docker est une solution terrible pour de nombreux problèmes d'entreprise car peu d'entreprises sérieuses utilisent ubuntu, préférant à la place l'arborescence RHEL / Centos.
Ingénieur logiciel
9
«Peu d'entreprises sérieuses» semble indéfendable. Le choix du système d'exploitation semble reposer entièrement sur le cas d'utilisation. Toute entreprise donnée a beaucoup d'environnements différents, y compris l'utilisation interne des développeurs, l'utilisation des employés internes, le support des ventes, la mise en scène, les POC et enfin la production (et même ce terme est vague). Je ne crois pas que l'OP ait mentionné leur cas d'utilisation, (désolé d'être pinailleux) mais ce genre de commentaire semble être le type qui diffuse des informations hautement avisées sans aucun argument quant à la raison.
John Carrell
157

Si vous utilisez un Dockerfile, essayez:

ENTRYPOINT ["tail", "-f", "/dev/null"]

(Évidemment, c'est uniquement à des fins de développement, vous ne devriez pas avoir besoin de garder un conteneur en vie à moins qu'il n'exécute un processus, par exemple nginx ...)

Balles souples
la source
5
J'utilisais CMD["sleep", "1d"]mais votre solution semble meilleure
George Pligoropoulos
@GeorgiosPligoropoulos cela restera coincé dans cette ligne; peut-être courir en arrière-plan fonctionnera
Prashanth Sams
5
Peut également utiliser CMD["sleep", "infinity"].
Romain
6
ou «chat», mais les gens pourraient dire qu'il s'agit de maltraitance animale. xD
lawphotog
Vous pouvez terminer votre script de point d'entrée avec exec tail -f /dev/nullmais utiliser tailcomme point d'entrée est une mauvaise réponse.
Torsten Bronger
86

J'ai juste eu le même problème et j'ai découvert que si vous exécutez votre conteneur avec le drapeau -tet -d, il continue de fonctionner.

docker run -td <image>

Voici ce que font les drapeaux (selon docker run --help):

-d, --detach=false         Run container in background and print container ID
-t, --tty=false            Allocate a pseudo-TTY

Le plus important est le -tdrapeau. -dvous permet simplement d'exécuter le conteneur en arrière-plan.

arne.z
la source
3
Je ne peux pas reproduire cela. Pouvez-vous donner un exemple? Y a-t-il quelque chose de spécifique (par exemple: CMD) à propos de Dockerfile dont nous avons besoin pour que cela fonctionne?
Matheus Santana
2
Cela n'a pas fonctionné pour moi. J'ai utilisé la commande docker logs <image>pour m'assurer que c'était une erreur qui provoquait la fermeture de mon conteneur docker. L'état de sortie est 0et la dernière sortie est la confirmation que mon lighttpdserveur est en cours d'exécution:[ ok ] Starting web server: lighttpd.
ob1
Je ne travaille pas avec Docker depuis un moment maintenant. Il est donc possible que l'interface de ligne de commande ait changé et que cette commande ne fonctionne plus.
arne.z
4
Je peux confirmer que cela fonctionne bien avec la dernière version de docker. Si vous souhaitez vous joindre ultérieurement à cette session, l'utilisation de -dit fonctionnera également.
John Hamilton
1
@Long un script n'acceptera pas un tty, add exec bashou exec shsi bash n'est pas installé, jusqu'à la fin de start.sh. Ensuite, vous pouvez utiliser l'indicateur -t
123
43

La raison pour laquelle il se ferme est que le script shell est d'abord exécuté en tant que PID 1 et qu'une fois terminé, le PID 1 a disparu et le docker ne s'exécute que pendant le PID 1.

Vous pouvez utiliser le superviseur pour tout faire, s'il est exécuté avec l'indicateur "-n", il est dit de ne pas démoniser, il restera donc le premier processus:

CMD ["/usr/bin/supervisord", "-n"]

Et votre supervisord.conf:

[supervisord]
nodaemon=true

[program:startup]
priority=1
command=/root/credentialize_and_run.sh
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=false
startsecs=0

[program:nginx]
priority=10
command=nginx -g "daemon off;"
stdout_logfile=/var/log/supervisor/nginx.log
stderr_logfile=/var/log/supervisor/nginx.log
autorestart=true

Ensuite, vous pouvez avoir autant d'autres processus que vous le souhaitez et le superviseur se chargera de leur redémarrage si nécessaire.

De cette façon, vous pouvez utiliser supervisord dans les cas où vous pourriez avoir besoin de nginx et de php5-fpm et que cela n'a pas beaucoup de sens de les séparer.

phazei
la source
Où dans la documentation est-il indiqué si PID 1 se termine le conteneur Docker s'arrête?
8oh8
@ 8oh8 C'est essentiellement ainsi que fonctionnent les espaces de noms de processus; ce n'est pas tant spécifique à Docker que "l'élément sous-jacent à tous les conteneurs". De man7.org/linux/man-pages/man7/pid_namespaces.7.html :If the "init" process of a PID namespace terminates, the kernel terminates all of the processes in the namespace via a SIGKILL signal. This behavior reflects the fact that the "init" process is essential for the correct operation of a PID namespace.
dannysauer
40

vous pouvez exécuter plain catsans aucun argument comme mentionné par bro @ Sa'ad pour simplement garder le conteneur en état de fonctionnement [en fait, en ne faisant rien d'autre qu'attendre l'entrée de l'utilisateur] (le plugin Docker de Jenkins fait la même chose)

Serge Velikanov
la source
en plus de ma réponse: mais comprenez que docker-compose (non démonisé) est utilisé pour vous montrer le flux de travail de votre conteneur, il peut donc être pratique de suivre les fichiers journaux de vos services démarrés. cheers
Serge Velikanov
1
ou cat. le plugin docker de jenkin le fait.
Sa'ad
12

Assurez-vous que vous ajoutez daemon off;à vous nginx.conf ou exécutez-le avec CMD ["nginx", "-g", "daemon off;"]selon l'image officielle nginx

Ensuite, utilisez ce qui suit pour exécuter à la fois superviseur en tant que service et nginx en tant que processus de premier plan qui empêchera le conteneur de quitter

service supervisor start && nginx

Dans certains cas, vous devrez avoir plus d'un processus dans votre conteneur, donc forcer le conteneur à avoir exactement un processus ne fonctionnera pas et peut créer plus de problèmes de déploiement.

Vous devez donc comprendre les compromis et prendre votre décision en conséquence.

iTech
la source
7

Motivation:

Il n'y a rien de mal à exécuter plusieurs processus à l'intérieur d'un conteneur Docker . Si l'on aime utiliser docker comme machine virtuelle légère, qu'il en soit ainsi. D'autres aiment diviser leurs applications en micro-services. Je pense: Une pile de LAMP dans un conteneur? Tout simplement génial.

La réponse:

Tenez-vous en à une bonne image de base comme l' image de base de la phusion . Il peut y en avoir d'autres. Commentez s'il vous plaît.

Et ce n'est qu'un autre plaidoyer pour le superviseur. Parce que l'image de base de phusion fournit un superviseur en plus de certaines autres choses comme la configuration de cron et des paramètres régionaux. Les éléments que vous aimez configurer lors de l'exécution d'une machine virtuelle aussi légère. Pour ce que ça vaut, il fournit également des connexions ssh dans le conteneur.

L'image phusion elle-même démarrera et continuera à s'exécuter si vous émettez cette instruction d'exécution de base de docker:

moin@stretchDEV:~$ docker run -d phusion/baseimage
521e8a12f6ff844fb142d0e2587ed33cdc82b70aa64cce07ed6c0226d857b367
moin@stretchDEV:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
521e8a12f6ff        phusion/baseimage   "/sbin/my_init"     12 seconds ago      Up 11 seconds

Ou tout simplement:

Si une image de base n'est pas pour vous ... Pour que le CMD rapide le fasse fonctionner, je suppose que quelque chose comme ça pour bash:

CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"

Ou ceci pour busybox:

CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"

C'est bien, car il sortira immédiatement sur un fichierdocker stop . Tout simplement sleepou catprendra quelques secondes avant que le conteneur ne sorte.

itsafire
la source
J'ai personnalisé l'image de base centos7 pour charger PostgreSQL 11. Vous démarrez cela avec un appel à / usr / pgsql-11 / bin / pg_ctl mais pg_ctl se termine une fois que le serveur est en marche. Votre suggestion d'utiliser le piège a très bien fonctionné; c'est la dernière ligne de mon script pgstartwait.sh
Alchemistmatt
6

Capturez le PID du processus ngnix dans une variable (par exemple $ NGNIX_PID) et à la fin du fichier entrypoint faire

wait $NGNIX_PID 

De cette façon, votre conteneur doit s'exécuter jusqu'à ce que ngnix soit actif, lorsque ngnix s'arrête, le conteneur s'arrête également

user2825611
la source