Bereitstellen einer Minimal-Flask-App bei Docker-Server-Verbindungsproblemen

105

Ich habe eine App, deren einzige Abhängigkeit Flask ist, die außerhalb von Docker einwandfrei läuft und an den Standardport bindet 5000. Hier ist die vollständige Quelle:

from flask import Flask

app = Flask(__name__)
app.debug = True

@app.route('/')
def main():
    return 'hi'

if __name__ == '__main__':
    app.run()

Das Problem ist, dass bei der Bereitstellung im Docker der Server ausgeführt wird, aber von außerhalb des Containers nicht erreichbar ist.

Unten ist meine Docker-Datei. Das Bild ist Ubuntu mit installiertem Kolben. Der Teer enthält nur die index.pyoben aufgeführten;

# Dockerfile
FROM dreen/flask
MAINTAINER dreen
WORKDIR /srv

# Get source
RUN mkdir -p /srv
COPY perfektimprezy.tar.gz /srv/perfektimprezy.tar.gz
RUN tar x -f perfektimprezy.tar.gz
RUN rm perfektimprezy.tar.gz

# Run server
EXPOSE 5000
CMD ["python", "index.py"]

Hier sind die Schritte, die ich zum Bereitstellen mache

$> sudo docker build -t perfektimprezy .

Soweit ich weiß, läuft das oben genannte gut, das Bild enthält den Inhalt des Teers /srv. Starten wir nun den Server in einem Container:

$> sudo docker run -i -p 5000:5000 -d perfektimprezy
1c50b67d45b1a4feade72276394811c8399b1b95692e0914ee72b103ff54c769

Läuft es tatsächlich?

$> sudo docker ps
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS              PORTS                    NAMES
1c50b67d45b1        perfektimprezy:latest   "python index.py"   5 seconds ago       Up 5 seconds        0.0.0.0:5000->5000/tcp   loving_wozniak

$> sudo docker logs 1c50b67d45b1
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat

Ja, anscheinend läuft der Flaschenserver. Hier wird es komisch. Stellen wir eine Anfrage an den Server:

 $> curl 127.0.0.1:5000 -v
 * Rebuilt URL to: 127.0.0.1:5000/
 * Hostname was NOT found in DNS cache
 *   Trying 127.0.0.1...
 * Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
 > GET / HTTP/1.1
 > User-Agent: curl/7.35.0
 > Host: 127.0.0.1:5000
 > Accept: */*
 >
 * Empty reply from server
 * Connection #0 to host 127.0.0.1 left intact
 curl: (52) Empty reply from server

Leere Antwort ... Aber läuft der Prozess?

$> sudo docker top 1c50b67d45b1
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                2084                812                 0                   10:26               ?                   00:00:00            python index.py
root                2117                2084                0                   10:26               ?                   00:00:00            /usr/bin/python index.py

Lassen Sie uns jetzt in den Server ssh und überprüfen ...

$> sudo docker exec -it 1c50b67d45b1 bash
root@1c50b67d45b1:/srv# netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:47677         127.0.0.1:5000          TIME_WAIT
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node   Path
root@1c50b67d45b1:/srv# curl -I 127.0.0.1:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 5447
Server: Werkzeug/0.10.4 Python/2.7.6
Date: Tue, 19 May 2015 12:18:14 GMT

Es ist in Ordnung ... aber nicht von außen :( Was mache ich falsch?

Dreen
quelle
Die relevante Sache ist "Verursacht durch <Klasse 'httplib.BadStatusLine'>", siehe stackoverflow.com/questions/16592568/…
user2915097
Ich versuche jedoch nur einmal, eine Verbindung herzustellen, und ich bin mir ziemlich sicher, dass dies weder ein Fehler in httpie (ich habe das Beispiel jetzt in curl geändert) noch auf dem Server ist, da es außerhalb von Docker einwandfrei funktioniert. Ich habe das starke Gefühl, dass dies ein Docker-Konfigurations- / Bereitstellungsfehler ist
Dreen
Checken Sie den Container mit docker exec -it 1c50b67d45b1 bashund dann den üblichen netstat -anoder einen beliebigen Befehl ein, den Sie beim Debuggen einer Flasche (Schwanz, Katze ...)
ausführen würden
@ user2915097: Ich habe einige Ausgaben vom Server hinzugefügt
Dreen
'Kann keine Verbindung herstellen ...' @Dreen, Sie können eine Verbindung herstellen , Sie erhalten nur eine leere Antwort ( Connected to 127.0.0.1)
ForceBru

Antworten:

175

Das Problem ist, dass Sie nur an die localhost-Schnittstelle binden. Sie sollten sich an binden, 0.0.0.0wenn der Container von außen zugänglich sein soll. Wenn Sie ändern:

if __name__ == '__main__':
    app.run()

zu

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Es sollte funktionieren.

Adrian Mouat
quelle
Diese Lösung funktioniert. Das Ergebnis kann hier mit einem vollständigen Dockerfile- und Python-Skript angezeigt werden, wie in dieser Lösung beschrieben.
was ist der Unterschied?
Jwan622
1
@ Jwan622 Die localhost-Schnittstelle ist nur im Container verfügbar. 0.0.0.0 bindet an alle Schnittstellen. Schnittstellen stellen eine Verbindung zu verschiedenen Netzwerken her (so dass Sie eine für WLAN, LANs usw. haben können)
Adrian Mouat
Stellen Sie sicher, dass Sie daran denken, Port 5000 mit dem -p 5000:5000Flag mit Ihrem docker runBefehl an Ihren Container zu binden .
T Loch.
39

Wenn Sie den flaskBefehl anstelle von verwenden app.run, können Sie die --hostOption zum Ändern des Hosts übergeben. Die Zeile in Docker wäre:

CMD ["flask", "run", "--host", "0.0.0.0"]

oder

CMD flask run --host 0.0.0.0
Marku
quelle
2
Vielen Dank für diese Lösung. Ich habe das gleiche Problem. Wissen Sie, warum app.run(host="0.0.0.0") das nicht funktioniert? Ich habe auch einen Beitrag für diese Frage gemacht: stackoverflow.com/q/53133350/3279996
xirururu
1
Stellen Sie in diesem Zusammenhang sicher, dass Sie nicht verwenden python run.py --host=0.0.0.0 . Das bringt mich hin und wieder aufgrund meiner Namenskonventionen. Dieser Code scheint zu funktionieren, aber der Server wird auf dem lokalen Host ausgeführt.
Braden Holt
Läuft hier nicht der eingebaute Kolbenserver, der nicht für Produktionsumgebungen gedacht ist?
Code_dredd
Dies ist großartig, aus welchem ​​Grund auch immer, fast alle Flask-with-Docker-Informationen verwenden nicht die Flask-CLI. Dies ist die Flask 1.0-Methode zum Starten einer App.
Zach Valenta
3
Hier im Jahr 2020 mit Flask 1.1.1 mit app.run(host="0.0.0.0")Versagen und CMD ["flask", "run", "--host", "0.0.0.0" ]Arbeiten wie ein Champion.
Joe
1

Ihr Docker-Container verfügt über mehr als eine Netzwerkschnittstelle. Mein Container hat beispielsweise Folgendes:

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Wenn Sie ausführen docker network inspect bridge, können Sie sehen, dass Ihr Container mit der zweiten Schnittstelle in der obigen Ausgabe mit dieser Bridge verbunden ist. Diese Standardbrücke ist auch mit dem Docker-Prozess auf Ihrem Host verbunden.

Daher müssten Sie den folgenden Befehl ausführen:

CMD flask run --host 172.17.0.2

So greifen Sie von Ihrem Host-Computer aus auf Ihre Flask-App zu, die in einem Docker-Container ausgeführt wird. Ersetzen Sie diese 172.17.0.2durch die IP-Adresse Ihres Containers.

Bert
quelle
1

Um auf anderen Antworten aufzubauen:

Stellen Sie sich vor, Sie haben zwei Computer. Jeder Computer verfügt über eine Netzwerkschnittstelle (z. B. WLAN), bei der es sich um die öffentliche IP-Adresse handelt. Jeder Computer verfügt über eine Loopback / Localhost-Schnittstelle unter 127.0.0.1. Dies bedeutet "nur dieser Computer".

Wenn Sie auf Computer A unter 127.0.0.1 aufgeführt sind, würden Sie nicht erwarten, dass Sie auf Computer B über 127.0.0.1 eine Verbindung herstellen können. Schließlich haben Sie darum gebeten, die lokale , private Adresse von Computer A abzuhören .

Docker ist ähnlich eingerichtet; Technisch gesehen ist es derselbe Computer, aber der Linux-Kernel ermöglicht es jedem Container, mit seinem eigenen isolierten Netzwerkstapel ausgeführt zu werden. 127.0.0.1 in einem Container ist also dasselbe wie 127.0.0.1 auf einem anderen Computer als Ihrem Host - Sie können keine Verbindung dazu herstellen.

Längere Version mit Diagrammen: https://pythonspeed.com/articles/docker-connection-refused/

Itamar Turner-Trauring
quelle
0

Zunächst müssen Sie in Ihrem Python-Skript den Code von ändern

app.run()

zu

app.run(host="0.0.0.0")

Zweitens sollte in Ihrer Docker-Datei die letzte Zeile wie folgt sein

CMD ["flask", "run", "-h", "0.0.0.0", "-p", "5000"]

Und auf dem Host-Computer0.0.0.0:5000 sollten Sie es versuchen, wenn dies nicht funktioniertlocalhost:5000

Hinweis - Der CMD-Befehl muss korrekt sein. Weil der CMD-Befehl Standardeinstellungen für die Ausführung von Containern bereitstellt.

Nivedita Shah
quelle