Aplikacja w Pythonie nie drukuje niczego, gdy jest uruchomiona odłączona w dockerze

160

Mam aplikację w Pythonie (2.7), która jest uruchamiana w moim pliku dockerfile:

CMD ["python","main.py"]

main.py wypisuje niektóre łańcuchy po uruchomieniu, a następnie przechodzi w pętlę:

print "App started"
while True:
    time.sleep(1)

Dopóki zacznę kontener z flagą -it, wszystko działa zgodnie z oczekiwaniami:

$ docker run --name=myapp -it myappimage
> App started

I widzę ten sam wynik w dziennikach później:

$ docker logs myapp
> App started

Jeśli spróbuję uruchomić ten sam kontener z flagą -d, kontener wydaje się uruchamiać normalnie, ale nie widzę żadnych danych wyjściowych:

$ docker run --name=myapp -d myappimage
> b82db1120fee5f92c80000f30f6bdc84e068bafa32738ab7adb47e641b19b4d1
$ docker logs myapp
$ (empty)

Ale kontener nadal wydaje się działać;

$ docker ps
Container Status ...
myapp     up 4 minutes ... 

Załącz również nie wyświetla niczego:

$ docker attach --sig-proxy=false myapp
(working, no output)

Jakieś pomysły, co się dzieje? Czy „print” zachowuje się inaczej, gdy działa w tle?

Wersja Dockera:

Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.2
Git commit (client): a8a31ef
OS/Arch (client): linux/arm
Server version: 1.5.0
Server API version: 1.17
Go version (server): go1.4.2
Git commit (server): a8a31ef
jpdus
źródło

Odpowiedzi:

265

W końcu znalazłem rozwiązanie, aby zobaczyć wyjście Pythona podczas uruchamiania zdemonizowanego w Dockerze, dzięki @ahmetalpbalkan na GitHub . Odpowiadam na to tutaj w celu uzyskania dalszych informacji:

Używanie niebuforowanych danych wyjściowych z

CMD ["python","-u","main.py"]

zamiast

CMD ["python","main.py"]

rozwiązuje problem; możesz zobaczyć wyjście (zarówno stderr, jak i stdout) przez

docker logs myapp

teraz!

jpdus
źródło
2
-u wydaje się działać dla mnie, ale czy jest gdzieś dokumentacja z opisem tego, co właściwie robi?
Little geek
7
Jak sugerują inne odpowiedzi, możesz spróbować ustawić zmienną środowiskową ENV PYTHONUNBUFFERED=0na wypadek, gdyby -uflaga nie działała.
Farshid T
1
To też był mój problem. Bardziej szczegółowe wyjaśnienie można znaleźć na stronie stackoverflow.com/a/24183941/562883
Jonathan Stray
1
Działa jak sen na pythonie3, a ustawienie PYTHONUNBUFFERED = 0 nie pomogło.
Lech Migdal
71

W moim przypadku uruchomienie Pythona z systemem -uniczego nie zmieniło. Jednak sztuczka polegała na ustawieniu PYTHONUNBUFFERED=0jako zmiennej środowiskowej:

docker run --name=myapp -e PYTHONUNBUFFERED=0 -d myappimage
Zwycięzca
źródło
6
W moim przypadku dodanie -e PYTHONUNBUFFERED=0pomaga.
David Ng
1
Dziękuję Ci! Godzinami uderzałem głową o ścianę i nie mogłem nawet zmusić kłód do pracy -u. Twoje rozwiązanie naprawiło to dla mnie na Docker for Mac z Django
Someguy123
2
Myślę, że jest to lepsze rozwiązanie, że nie musimy odbudowywać obrazu
dockera,
2
To wielkie dzięki. Warto wspomnieć, że musi to być po prostu niepusty charakter, aby działać zgodnie z dokumentami PYTHONUNBUFFERED
A Star
Pracował dla interfejsu Docker-Compose. Nigdy bym nie zgadł
deepelement
24

Dla mnie to funkcja, a nie błąd. Bez pseudo-TTY nie ma do czego wyjść. Zatem prostym rozwiązaniem jest przydzielenie pseudo-TTY do uruchomionego kontenera za pomocą:

$ docker run -t ...
Peter Senna
źródło
To nie daje odpowiedzi na pytanie. Aby skrytykować lub poprosić autora o wyjaśnienie, zostaw komentarz pod jego postem.
Prezydent James K. Polk
@JamesKPolk, czy teraz jest lepiej?
Peter Senna
docker nie wymaga przydzielenia pseudo tty dla stdout i stderr
Matt
3
tty: truein compose land
deepelement
15

Zapoznaj się z tym artykułem, w którym wyjaśniono szczegółowo przyczynę tego zachowania:

Zwykle są trzy tryby buforowania:

  • Jeśli deskryptor pliku nie jest buforowany, wówczas buforowanie w ogóle nie występuje, a wywołania funkcji, które odczytują lub zapisują dane, występują natychmiast (i będą blokowane).
  • Jeśli deskryptor pliku jest w pełni buforowany, używany jest bufor o stałym rozmiarze, a wywołania odczytu lub zapisu po prostu odczytują lub zapisują z bufora. Bufor nie jest opróżniany, dopóki się nie zapełni.
  • Jeśli deskryptor pliku jest buforowany wierszowo, wówczas buforowanie czeka, aż zobaczy znak nowego wiersza. Tak więc dane będą buforowane i buforowane, aż pojawi się \ n, a następnie wszystkie zbuforowane dane zostaną opróżnione w tym momencie. W rzeczywistości zwykle istnieje maksymalny rozmiar bufora (tak jak w przypadku w pełni buforowanego), więc reguła bardziej przypomina „buforowanie do momentu wyświetlenia znaku nowego wiersza lub napotkania 4096 bajtów danych, w zależności od tego, co nastąpi wcześniej”.

A GNU libc (glibc) używa następujących reguł do buforowania:

Stream               Type          Behavior
stdin                input         line-buffered
stdout (TTY)         output        line-buffered
stdout (not a TTY)   output        fully-buffered
stderr               output        unbuffered

Tak więc, jeśli zostanie użyty -t, z dokumentu docker , przydzieli pseudo-tty, a następnie stanie stdoutsię line-buffered, dzięki czemu będzie docker run --name=myapp -it myappimagemógł zobaczyć jednowierszowe wyjście.

A jeśli tylko użyjemy -d, nie przydzielono żadnego terminala tty, stdoutto fully-bufferedjedna linia z App startedpewnością nie jest w stanie opróżnić bufora.

Następnie użyj -dtdo make stdout line bufferedlub dodaj -uw pythonie, aby flush the bufferto naprawić.

na linii
źródło
6

Możesz zobaczyć logi na odłączonym obrazie, jeśli zmienisz printna logging.

main.py:

import time
import logging
print "App started"
logging.warning("Log app started")
while True:
    time.sleep(1)

Dockerfile:

FROM python:2.7-stretch
ADD . /app
WORKDIR /app
CMD ["python","main.py"]
Wieprz
źródło
1
miły. wskazówka: użyj Pythona 3.
adhg
pytanie jest w Pythonie 2 (wypisz instrukcję bez nawiasów), dlatego używam tutaj 2. Chociaż jest to dokładnie to samo zachowanie na Pythonie 3.6, więc dzięki za wskazówkę;)
The Hog
6

Ponieważ nie widziałem jeszcze tej odpowiedzi:

Możesz również opróżnić stdout po wydrukowaniu:

import time

if __name__ == '__main__':
    while True:
        print('cleaner is up', flush=True)
        time.sleep(5)
tycl
źródło
1
to działało idealnie dla mnie, głupi, że to musi tam być, ale teraz działa świetnie.
jamescampbell
5

Spróbuj dodać te dwie zmienne środowiskowe do swojego rozwiązania PYTHONUNBUFFERED=1iPYTHONIOENCODING=UTF-8

Łukasz Dynowski
źródło
3

Aby szybko rozwiązać problem, spróbuj tego:

from __future__ import print_function
# some code
print("App started", file=sys.stderr)

To działa dla mnie, gdy napotykam te same problemy. Ale szczerze mówiąc, nie wiem, dlaczego tak się dzieje.

Vitaly Isaev
źródło
Dzięki za wskazówkę! Próbowałem zamienić wszystkie wydruki na twoją wersję, niestety nie zadziałało to dla mnie, nadal nie mogę uzyskać żadnych danych wyjściowych z dzienników dockera (zmiana między sys.stderr / sys.stdout nie ma widocznego wyniku). Czy to błąd Dockera?
jpdus
Zobacz moją odpowiedź , powód jest następujący: stderr nie był buforowany, więc możesz to naprawić za pomocą swojego rozwiązania.
atline
1

Musiałem użyć PYTHONUNBUFFERED=1w moim pliku docker-compose.yml, aby zobaczyć dane wyjściowe z serwera uruchomieniowego django.

Adam Radestock
źródło
0

Zwykle przekierowujemy go do określonego pliku (montując wolumin z hosta i zapisując go w tym pliku).

Dodanie tty za pomocą -t również jest w porządku. Musisz go odebrać w dziennikach dockera.

Używając dużych danych wyjściowych dziennika, nie miałem żadnego problemu z przechowywaniem wszystkiego w buforze bez umieszczania go w dzienniku Dockera.

Edward Aung
źródło
-1

Jeśli nie używasz, docker-composea dockerzamiast tego normalnie , możesz dodać to do swojej Dockerfileaplikacji hostującej kolbę

ARG FLASK_ENV="production"
ENV FLASK_ENV="${FLASK_ENV}" \
    PYTHONUNBUFFERED="true"

CMD [ "flask", "run" ]
G Wayne
źródło