Wie kann der Docker-Container nach dem Start der Dienste ausgeführt werden?

152

Ich habe eine Reihe von Tutorials gesehen, die anscheinend dasselbe tun, was ich versuche, aber aus irgendeinem Grund werden meine Docker-Container beendet. Grundsätzlich richte ich einen Webserver und einige Daemons in einem Docker-Container ein. Ich mache die letzten Teile davon durch ein Bash-Skript run-all.sh, das CMD in meiner Docker-Datei durchläuft. run-all.shsieht aus wie das:

service supervisor start
service nginx start

Und ich starte es in meinem Dockerfile wie folgt:

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

Ich kann sehen, dass alle Dienste korrekt gestartet werden, wenn ich Dinge manuell ausführe (dh mit -i -t / bin / bash zum Image komme), und alles sieht so aus, als würde es korrekt ausgeführt, wenn ich das Image ausführe, aber es wird einmal beendet Damit ist der Start meiner Prozesse abgeschlossen. Ich möchte, dass die Prozesse unbegrenzt ausgeführt werden, und soweit ich weiß, muss der Container weiter ausgeführt werden, damit dies geschieht. Trotzdem sehe docker ps -aich beim Laufen :

➜  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

Was gibt? Warum ist es aufregend? Ich weiß, ich könnte einfach eine while-Schleife am Ende meines Bash-Skripts einfügen, um es aufrechtzuerhalten, aber wie kann ich verhindern, dass es beendet wird?

Eli
quelle
1
Setzen Sie die Ports der Dienste nach außen aus (Option -p für Docker-Ausführung)? (Natürlich wird dies sie nicht daran hindern zu verlassen)
Ribamar
1
Ich habe ENTRYPOINT in meiner Docker-Datei verwendet und nachdem das in ENTRYPOINT definierte Skript (mein Init-Skript) ausgeführt wurde, wurde es in den Protokollen angezeigt, aber mein Container schien beendet zu sein. Anstelle von ENTRYPOINT habe ich das Skript mit dem Befehl RUN ausgeführt, und der Container wird weiterhin im Hintergrund ausgeführt.
Ypahalajani

Antworten:

49

So sollten Sie Ihre Docker-Container nicht wirklich entwerfen.

Wenn Sie einen Docker-Container entwerfen, sollten Sie ihn so erstellen, dass nur ein Prozess ausgeführt wird (dh Sie sollten einen Container für Nginx und einen für Supervisord oder die App haben, die er ausführt). Außerdem sollte dieser Prozess im Vordergrund ausgeführt werden.

Der Container wird "beendet", wenn der Prozess selbst beendet wird (in Ihrem Fall ist dieser Prozess Ihr Bash-Skript).


Wenn Sie jedoch wirklich mehrere Dienste in Ihrem Docker-Container ausführen müssen (oder möchten), sollten Sie von "Docker Base Image" ausgehen , das runitals Pseudo-Init-Prozess verwendet wird ( runitbleibt online, während Nginx und Supervisor ausgeführt werden) im Vordergrund, während Ihre anderen Prozesse ihr Ding machen.

Sie verfügen über umfangreiche Dokumente, sodass Sie in der Lage sein sollten, das, was Sie versuchen, relativ einfach zu erreichen.

Thomas Orozco
quelle
1
Können Sie erklären, warum nur ein Dienst ausgeführt werden sollte? Ich könnte dem Supervisor bei Bedarf Nginx hinzufügen, bin mir aber nicht sicher, warum dies notwendig sein sollte.
Eli
3
@Eli Die kurze Antwort lautet: So funktioniert Docker. Docker führt nur einen Prozess (und seine untergeordneten Elemente) pro Container aus. Es wird empfohlen, dass dieser Prozess ein tatsächlicher Anwendungsprozess ist (damit Docker weiß, dass er beim Beenden weiß), aber Sie können tatsächlich Supervisor als diesen Prozess verwenden. Beachten Sie, dass Sie den Supervisor so konfigurieren müssen, dass er im Vordergrund ausgeführt wird (dh nicht dämonisiert). Dies erfolgt über die --nodaemonOption.
Thomas Orozco
1
@Eli In diesem Docker-Blogbeitrag wird der Fall ausgeführt, dass das Ausführen mehrerer Prozesse (und im Großen und Ganzen das Anzeigen eines Containers als "kleines VPS") nicht optimal ist. In Ihrem Fall ist der Kommentarthread wahrscheinlich relevanter als der eigentliche Blogbeitrag.
Thomas Orozco
1
Das Docker-Basisimage ist eine schreckliche Lösung für viele Unternehmensprobleme, da nur wenige seriöse Unternehmen Ubuntu verwenden und stattdessen den RHEL / Centos-Baum bevorzugen.
Software Engineer
9
"Nur wenige seriöse Unternehmen" scheinen nicht zu rechtfertigen. Die Wahl des Betriebssystems scheint vollständig auf dem Anwendungsfall zu beruhen. Jedes Unternehmen verfügt über viele verschiedene Umgebungen, einschließlich interner Entwicklernutzung, interner Mitarbeiternutzung, Vertriebsunterstützung, Staging, POCs und schließlich Produktion (und selbst das ist ein vager Begriff). Ich glaube nicht, dass das OP ihren Anwendungsfall so erwähnt hat (sorry, um pingelig zu sein), aber diese Art von Kommentar scheint der Typ zu sein, der hochmeinende Informationen verbreitet, ohne zu argumentieren, warum.
John Carrell
151

Wenn Sie eine Docker-Datei verwenden, versuchen Sie:

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

(Dies ist natürlich nur für Entwicklungszwecke gedacht. Sie sollten einen Container nicht am Leben erhalten müssen, es sei denn, er führt einen Prozess aus, z. B. Nginx ...)

Weiche Kugeln
quelle
5
Ich habe verwendet, CMD["sleep", "1d"]aber Ihre Lösung scheint besser zu sein
George Pligoropoulos
@GeorgiosPligoropoulos dies wird in dieser Zeile stecken; Vielleicht funktioniert das Laufen im Hintergrund
Prashanth Sams
5
Kann auch verwenden CMD["sleep", "infinity"].
Romain
5
oder "Katze", aber die Leute könnten sagen, es ist Tiermissbrauch. xD
lawphotog
Sie können Ihr Einstiegspunktskript mit beenden, exec tail -f /dev/nullaber die Verwendung tailals Einstiegspunkt ist eine falsche Antwort.
Torsten Bronger
83

Ich hatte gerade das gleiche Problem und ich fand heraus , dass , wenn Sie Ihren Behälter mit dem laufen -tund -dFlagge, es läuft weiter .

docker run -td <image>

Folgendes tun die Flags (laut docker run --help):

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

Das wichtigste ist die -tFlagge. -dSie können den Container einfach im Hintergrund ausführen.

arne.z
quelle
3
Ich kann das nicht reproduzieren. Würden Sie bitte ein Beispiel geben? Gibt es etwas Spezielles (z. B. CMD) an Dockerfile, das wir benötigen, damit dies funktioniert?
Matheus Santana
2
Das hat bei mir nicht funktioniert. Ich habe den Befehl verwendet docker logs <image>, um sicherzustellen, dass es sich um einen Fehler handelt, der dazu führt, dass mein Docker-Container beendet wird. Der Exit-Status ist 0und die letzte Ausgabe ist die Bestätigung, dass mein lighttpdServer läuft:[ ok ] Starting web server: lighttpd.
ob1
Ich habe jetzt schon eine Weile nicht mehr mit Docker gearbeitet. Es ist also möglich, dass sich die Befehlszeilenschnittstelle geändert hat und dieser Befehl nicht mehr funktioniert.
Arne.z
4
Ich kann bestätigen, dass dies tatsächlich mit der neuesten Docker-Version funktioniert. Wenn Sie später eine Verbindung zu dieser Sitzung herstellen möchten, funktioniert auch die Verwendung von -dit.
John Hamilton
1
@Long ein Skript akzeptiert keine tty, add exec bashoder exec shwenn bash nicht installiert ist, bis zum Ende von start.sh. Dann können Sie das Flag -t
123
42

Der Grund dafür ist, dass das Shell-Skript zuerst als PID 1 ausgeführt wird. Wenn dies abgeschlossen ist, ist PID 1 nicht mehr vorhanden und Docker wird nur ausgeführt, solange PID 1 vorhanden ist.

Sie können Supervisor verwenden, um alles zu erledigen. Wenn Sie mit dem "-n" -Flag ausgeführt werden, wird angewiesen, nicht zu dämonisieren, sodass dies der erste Prozess bleibt:

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

Und deine 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

Dann können Sie so viele andere Prozesse haben, wie Sie möchten, und der Supervisor übernimmt bei Bedarf den Neustart.

Auf diese Weise können Sie Supervisord in Fällen verwenden, in denen Sie möglicherweise Nginx und PHP5-Fpm benötigen, und es macht wenig Sinn, sie getrennt zu haben.

Phazei
quelle
Wo in den Dokumenten steht, ob der Docker-Container mit PID 1 nicht mehr ausgeführt wird?
8oh8
@ 8oh8 So funktionieren Prozess-Namespaces im Wesentlichen. Es ist nicht Docker-spezifisch, sondern "das Ding, das allen Containern zugrunde liegt". Von 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

Sie können catohne Argumente, wie von bro @ Sa'ad erwähnt, einfach ausgeführt werden, um den Container einfach am Laufen zu halten [eigentlich nur auf Benutzereingaben zu warten] (Jenkins 'Docker-Plugin macht dasselbe)

Serge Velikanov
quelle
Zusätzlich zu meiner Antwort: Verstehen Sie jedoch, dass Docker-Compose (nicht daemonisiert) verwendet wird, um Ihnen den Workflow Ihres Containers anzuzeigen. Daher kann es nützlich sein, die Protokolldateien Ihrer gestarteten Dienste zu verfolgen. Prost
Serge Velikanov
1
oder cat. Jenkins Docker-Plugin tut dies.
Sa'ad
12

daemon off;Stellen Sie sicher, dass Sie nginx.conf hinzufügen oder es CMD ["nginx", "-g", "daemon off;"]gemäß dem offiziellen nginx-Image ausführen

Verwenden Sie dann Folgendes, um sowohl Supervisor als Service als auch Nginx als Vordergrundprozess auszuführen, um zu verhindern, dass der Container beendet wird

service supervisor start && nginx

In einigen Fällen müssen Sie mehr als einen Prozess in Ihrem Container haben, sodass das Erzwingen, dass der Container genau einen Prozess hat, nicht funktioniert und weitere Probleme bei der Bereitstellung verursachen kann.

Sie müssen also die Kompromisse verstehen und Ihre Entscheidung entsprechend treffen.

iTech
quelle
6

Erfassen Sie die PID des ngnix-Prozesses in einer Variablen (z. B. $ NGNIX_PID) und am Ende der Einstiegspunktdatei

wait $NGNIX_PID 

Auf diese Weise sollte Ihr Container ausgeführt werden, bis ngnix aktiv ist. Wenn ngnix stoppt, stoppt auch der Container

user2825611
quelle
6

Motivation:

Es ist nichts Falsches daran, mehrere Prozesse in einem Docker-Container auszuführen . Wenn man Docker gerne als leichte VM verwendet, dann soll es so sein. Andere teilen ihre Anwendungen gerne in Mikrodienste auf. Ich denke: Ein LAMPENstapel in einem Container? Einfach toll.

Die Antwort:

Halten Sie sich an ein gutes Basisbild wie das Phusionsbasisbild . Es kann andere geben. Bitte kommentieren.

Und dies ist nur ein weiteres Plädoyer für den Vorgesetzten. Weil das Phusionsbasis-Image neben einigen anderen Dingen wie der Einrichtung von Cron und Gebietsschema einen Supervisor bereitstellt. Dinge, die Sie gerne einrichten möchten, wenn Sie eine so leichte VM ausführen. Für das, was es wert ist, bietet es auch SSH-Verbindungen in den Container.

Das Phusions-Image selbst wird nur gestartet und läuft weiter, wenn Sie diese grundlegende Docker-Run-Anweisung ausgeben:

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

Oder ganz einfach:

Wenn ein Basis-Image nicht für Sie ist ... Damit das schnelle CMD es am Laufen hält, würde ich für bash Folgendes annehmen:

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

Oder dies für Busybox:

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

Das ist schön, weil es sofort auf einem beendet wird docker stop. Einfach sleepoder catdauert einige Sekunden, bis der Container austritt.

itsafire
quelle
Ich habe das Centos7-Basis-Image so angepasst, dass es PostgreSQL 11 lädt. Sie beginnen dies mit einem Aufruf von / usr / pgsql-11 / bin / pg_ctl, aber pg_ctl wird beendet, sobald der Server ausgeführt wird. Ihr Vorschlag, Trap zu verwenden, hat großartig funktioniert. Es ist die letzte Zeile meines Skripts pgstartwait.sh
Alchemistmatt