Dlaczego mój proces w tle w Pythonie kończy się po zakończeniu sesji SSH?

19

Mam skrypt bash, który uruchamia skrypt python3 (nazwijmy go startup.sh), z kluczową linią:

nohup python3 -u <script> &

Kiedy wchodzę sshbezpośrednio i wywołuję ten skrypt, skrypt Pythona kontynuuje działanie w tle po zakończeniu pracy. Jednak gdy uruchomię to:

ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"

Proces kończy się, gdy tylko go sshzakończy i zakończy sesję.

Jaka jest różnica między nimi?

EDYCJA: Skrypt Pythona uruchamia usługę internetową za pośrednictwem Bottle.

EDIT2: Próbowałem również utworzyć skrypt inicjujący, który wywołuje startup.shi ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>"działa, ale mam takie samo zachowanie.

EDIT3: Może to coś innego w skrypcie. Oto większość skryptu:

chmod 700 ${key_loc}

echo "INFO: Syncing files."
rsync -azP -e "ssh -i ${key_loc} -o StrictHostKeyChecking=no" ${source_client_loc} ${remote_user}@${remote_hostname}:${destination_client_loc}

echo "INFO: Running startup script."
ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart"

EDIT4: Kiedy uruchamiam ostatnią linię ze snem na końcu:

ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart; sleep 1"

echo "Finished"

Nigdy nie dociera echo "Finished"i widzę komunikat serwera butelki, którego nigdy wcześniej nie widziałem:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.

Widzę „Zakończone”, jeśli ręcznie SSH i sam zabiję proces.

EDIT5: Korzystając z EDIT4, jeśli poproszę o dowolny punkt końcowy, dostanę stronę z powrotem, ale Butelka popełni błąd:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.


----------------------------------------
Exception happened during processing of request from ('<IP>', 55104)
neverendingqs
źródło
Czy jest jakiś sposób, aby uzyskać więcej opisu tego, co robi skrypt Pythona? Prawdopodobnie nadal będziesz mieć domysły bez pełnego kodu źródłowego, ale wiedza o tym, co robi skrypt w języku Python, może pomóc nam lepiej zgadywać domysły.
Bratchley,
Tak - dodano do pytania.
neverendingqs
Skrypt może robić coś wcześnie, co w jakiś sposób zależy od podłączonego terminala lub coś takiego i może to być problem z czasem: jeśli sesja trwa dłużej niż kilka pierwszych sekund, działa, w przeciwnym razie nie. Najlepszą opcją może być uruchomienie go, stracejeśli używasz Linuksa lub trussSolaris i zobaczysz, jak / dlaczego się kończy. Jak na przykład ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada
Czy próbowałeś użyć &na końcu skryptu uruchamiania? Dodanie &zabiera zależność twojej sesji ssh od bycia identyfikatorem nadrzędnym (kiedy umierają identyfikatory nadrzędne, to także ich dzieci). Myślę też, że jest to duplikat pytania w oparciu o ten poprzedni post. Post, który przesłałem do ciebie w poprzednim zdaniu, jest duplikatem tego postu, który może zawierać więcej szczegółów.
Jacob Bryan
Próbowałem już nohup ./startup.sh &wcześniej, ale miało to to samo zachowanie. startup.shzawiera już widelec ( nohup python3 -u <script> &), więc jestem prawie pewien, że nie muszę ponownie rozwidlać.
neverendingqs

Odpowiedzi:

11

Odłączę polecenie od jego standardowych przepływów wejścia / wyjścia i błędów:

nohup python3 -u <script> </dev/null >/dev/null 2>&1 &  

sshpotrzebuje wskaźnika, który nie ma już żadnych wyników i że nie wymaga więcej danych wejściowych. Posiadanie czegoś innego jako wejście i przekierowanie środków wyjściowych sshmoże bezpiecznie wyjść, ponieważ wejście / wyjście nie pochodzi ani nie dociera do terminala. Oznacza to, że dane wejściowe muszą pochodzić z innego miejsca, a dane wyjściowe (zarówno STDOUT, jak i STDERR) powinny iść gdzie indziej.

</dev/nullCzęść określa /dev/nulljako wejście <script>. Dlaczego jest to przydatne tutaj:

Przekierowanie / dev / null do stdin da natychmiastowy EOF do każdego wywołania odczytu z tego procesu. Jest to zwykle przydatne do odłączenia procesu od tty (taki proces nazywa się demonem). Na przykład, gdy zdalnie uruchamiasz proces w tle za pośrednictwem ssh, musisz przekierować stdin, aby proces nie oczekiwał na lokalne dane wejściowe. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

Alternatywnie przekierowanie z innego źródła wejściowego powinno być względnie bezpieczne, o ile bieżąca sshsesja nie musi być otwarta.

Z tą >/dev/nullczęścią powłoka przekierowuje standardowe wyjście do / dev / null, zasadniczo je odrzucając. >/path/to/filebędzie również działać.

Ostatnia część 2>&1to przekierowanie STDERR do STDOUT.

Istnieją trzy standardowe źródła wejścia i wyjścia dla programu. Standardowe wejście zwykle pochodzi z klawiatury, jeśli jest to program interaktywny, lub z innego programu, jeśli przetwarza dane wyjściowe innego programu. Program zwykle drukuje na standardowe wyjście, a czasem drukuje na standardowy błąd. Te trzy deskryptory plików (można je traktować jako „potoki danych”) są często nazywane STDIN, STDOUT i STDERR.

Czasami nie są nazwane, są ponumerowane! Wbudowane dla nich numeracje to 0, 1 i 2, w tej kolejności. Domyślnie, jeśli nie wymieniasz ani nie numerujesz wprost, mówisz o STDOUT.

Biorąc pod uwagę ten kontekst, możesz zobaczyć, że powyższe polecenie przekierowuje standardowe wyjście do / dev / null, w którym możesz zrzucić wszystko, czego nie chcesz (często nazywane wiadrem bitów), a następnie przekierować standardowy błąd na standardowe wyjście ( gdy to zrobisz, musisz umieścić znak & przed miejscem docelowym).

Dlatego krótkie wyjaśnienie brzmi: „wszystkie dane wyjściowe z tego polecenia powinny być umieszczone w czarnej dziurze”. To jeden dobry sposób, aby program był naprawdę cichy!
Co oznacza> / dev / null 2> & 1? | Xaprb

jlliagre
źródło
nohup python3 -u <script> >/dev/null 2>&1 &i nohup python3 -u <script> > nohup.out 2>&1 &pracował. Myślałem, że nohup automatycznie przekierowuje wszystkie dane wyjściowe - jaka jest różnica?
neverendingqs
@neverendingqs, jaką wersję nohupposiadasz na zdalnym hoście? POSIX nohupnie jest wymagany do przekierowania stdin, czego mi brakowało, ale powinien nadal przekierowywać stdouti stderr.
Graeme
Wygląda na to, że pracuję nohup (GNU coreutils) 8.21.
neverendingqs
@neverendingqs, czy nohupdrukuje jakieś wiadomości, na przykład nohup: ignoring input and appending output to ‘nohup.out’?
Graeme
Tak - to jest dokładna wiadomość.
neverendingqs
3

Spójrz na man ssh:

 ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
     [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L [bind_address:]port:host:hostport]
     [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
     [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
     [user@]hostname [command]

Po uruchomieniu ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"uruchamiasz skrypt powłoki startup.sh jako polecenie ssh.

Z opisu:

Jeśli podano polecenie, jest ono wykonywane na zdalnym hoście zamiast powłoki logowania.

Na tej podstawie skrypt powinien być uruchamiany zdalnie.

Różnica między tym a działaniem nohup python3 -u <script> &w lokalnym terminalu polega na tym, że działa on jako lokalny proces w tle, podczas gdy komenda ssh próbuje uruchomić go jako zdalny proces w tle.

Jeśli zamierzasz uruchomić skrypt lokalnie, nie uruchamiaj startup.sh jako części polecenia ssh. Możesz spróbować czegoś takiegossh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Jeśli masz zamiar uruchomić skrypt zdalnie i chcesz, aby ten proces był kontynuowany po zakończeniu sesji ssh, musisz najpierw rozpocząć screensesję na hoście zdalnym. Następnie musisz uruchomić skrypt Pythona na ekranie i będzie on nadal działał po zakończeniu sesji ssh.

Patrz ekran Instrukcja użytkownika

Chociaż myślę, że screen jest najlepszą opcją, jeśli musisz użyć nohup, rozważ uruchomienie shopt -s huponexitzdalnego hosta przed uruchomieniem polecenia nohup. Alternatywnie możesz użyć disown -h [jobID]do zaznaczenia procesu, aby SIGHUP nie został do niego wysłany. 1

Jak kontynuować pracę po wyjściu z monitu powłoki w tle?

Sygnał SIGHUP (Hangup) jest używany przez system do sterowania terminalem lub śmierci procesu kontrolowania. Możesz użyć SIGHUP, aby ponownie załadować pliki konfiguracyjne oraz otworzyć / zamknąć pliki dziennika. Innymi słowy, jeśli wylogujesz się z terminala, wszystkie uruchomione zadania zostaną zakończone. Aby tego uniknąć, możesz przekazać opcję -h komendy disown. Ta opcja oznacza każde ID zadania, aby SIGHUP nie był wysyłany do zadania, jeśli powłoka otrzyma SIGHUP.

Zobacz także to podsumowanie, jak huponexitdziała, gdy powłoka jest opuszczana, zabijana lub upuszczana. Zgaduję, że twój obecny problem jest związany ze sposobem zakończenia sesji powłoki. 2)

  1. Wszystkie procesy potomne powłoki działającej w tle lub nie w powłoce otwartej przez połączenie ssh są zabijane przez SIGHUP, gdy połączenie ssh jest zamknięte tylko wtedy, gdy jest ustawiona opcja huponexit: uruchom shopt huponexit, aby sprawdzić, czy to prawda.

  2. Jeśli huponexit jest prawdziwy, możesz użyć nohup lub disown, aby oddzielić proces od powłoki, aby nie został zabity po wyjściu. Lub uruchamiaj rzeczy za pomocą ekranu.

  3. Jeśli huponexit ma wartość false, co jest obecnie domyślnym ustawieniem przynajmniej niektórych linuksów, zadania w tle nie zostaną zabite przy normalnym wylogowaniu.

  4. Ale nawet jeśli huponexit jest fałszywy, to jeśli połączenie ssh zostanie zabite lub spadnie (inaczej niż normalne wylogowanie), procesy w tle nadal będą zabijane. Można tego uniknąć przez disown lub nohup jak w (2).

Na koniec oto kilka przykładów korzystania z shopt huponexit. 3)

$ shopt -s huponexit; shopt | grep huponexit
huponexit       on
# Background jobs will be terminated with SIGHUP when shell exits

$ shopt -u huponexit; shopt | grep huponexit
huponexit       off
# Background jobs will NOT be terminated with SIGHUP when shell exits
iyrin
źródło
Według strony podręcznika bash, huponexitpowinno to dotyczyć tylko interaktywnych powłok, a nie skryptów - „Jeśli opcja powłoki huponexit została ustawiona w shopt, bash wysyła SIGHUP do wszystkich zadań, gdy kończy się interaktywna powłoka logowania”.
Graeme
2

Może warto wypróbować -nopcję przy rozpoczynaniu ssh? Zapobiegnie to zdalnej zależności procesu od lokalnego stdin, który oczywiście zamyka się natychmiast po ssh sessionzakończeniu. A to spowoduje zdalne zakończenie cen, ilekroć spróbuje uzyskać do nich dostęp stdin.

Georgiy
źródło
Próbowałem bez powodzenia = [.
neverendingqs
2

Podejrzewam, że masz stan wyścigowy. Poszłoby coś takiego:

  • Rozpocznie się połączenie SSH
  • SSH uruchamia startup.sh
  • startup.sh uruchamia proces w tle (nohup)
  • startup.sh kończy
  • ssh kończy, a to zabija procesy potomne (tj. nohup)

Gdyby ssh nie skrócił rzeczy, wydarzyłyby się następujące rzeczy (nie jestem pewien kolejności tych dwóch):

  • nohup uruchamia skrypt Pythona
  • nohup rozłącza się z procesem nadrzędnym i terminalem.

Tak więc ostatnie dwa krytyczne kroki się nie zdarzają, ponieważ startup.sh i ssh kończą się zanim nohup nie zdąży zrobić tego.

Spodziewam się, że Twój problem zniknie, jeśli położysz kilka sekund snu na końcu startup.sh. Nie jestem pewien, ile dokładnie czasu potrzebujesz. Jeśli ważne jest, aby ograniczyć go do minimum, być może możesz spojrzeć na coś w proc, aby zobaczyć, kiedy jest to bezpieczne.

Mc0e
źródło
Dobrze, nie myśl, że okno na to będzie bardzo długie - prawdopodobnie tylko kilka milisekund. Możesz sprawdzić, /proc/$!/commczy nie nohuplub bardziej przenośnie użyć danych wyjściowych ps -o comm= $!.
Graeme
To powinno działać przy normalnym wylogowaniu, ale co z momentem, gdy sesja zostanie przerwana lub zabita? Czy nadal nie musisz rezygnować z pracy, więc jest to całkowicie ignorowane przez westchnienie?
iyrin
@RyanLoremIpsum: Skrypt startowy musi tylko czekać wystarczająco długo, aby proces potomny został całkowicie odłączony. Po tym nie ma znaczenia, co stanie się z sesją ssh. Jeśli coś innego zabije twoją sesję ssh w krótkim oknie, gdy tak się dzieje, niewiele możesz z tym zrobić.
mc0e,
@Graeme tak, zakładam, że to bardzo szybko, ale po prostu nie wiem wystarczająco dokładnie o tym, co nie robi nic, aby się upewnić. Przydałby się wskaźnik do wiarygodnego (lub przynajmniej kompetentnego i szczegółowego) źródła na ten temat.
mc0e,
Co powiesz na ten - lingrok.org/xref/coreutils/src/nohup.c
Graeme
1

To brzmi bardziej jak problem z tym, co robi pythonskrypt lub pythonsam. Jedyne, co nohuptak naprawdę robi (upraszczanie przekierowań), to po prostu ustawienie obsługi HUPsygnału SIG_IGN(ignorowanie) przed uruchomieniem programu. Nic nie stoi na przeszkodzie, aby program przywrócił go SIG_DFLlub zainstalował własny program obsługi po uruchomieniu.

Jedną z rzeczy, które warto wypróbować, jest umieszczenie polecenia w nawiasach, aby uzyskać efekt podwójnego rozwidlenia, a pythonskrypt nie jest już dzieckiem procesu powłoki. Na przykład:

( nohup python3 -u <script> & )

Inną rzeczą, która może być również warta wypróbowania (jeśli używasz, basha nie innej powłoki) jest użycie disownwbudowanego zamiast nohup. Jeśli wszystko działa tak, jak zostało to udokumentowane, nie powinno to mieć żadnego znaczenia, ale w interaktywnej powłoce uniemożliwiłoby to HUPpropagację sygnału do pythonskryptu. Możesz dodać disown w następnym wierszu lub tym samym, co poniżej (uwaga, że ​​dodanie ;po a &jest błędem w bash):

python3 -u <script> </dev/null &>/dev/null & disown

Jeśli powyższe lub niektóre ich kombinacje nie działają, to z pewnością jedynym miejscem na rozwiązanie tego problemu jest pythonsam skrypt.

Graeme
źródło
Czy wystarczy efekt podwójnego widelca (na podstawie odpowiedzi @ RyanLoremIpsum)?
neverendingqs
Oba nie rozwiązały problemu = [. Jeśli jest to problem w Pythonie, czy masz pomysł, od czego zacząć badanie (nie możesz tutaj opublikować zbyt dużo skryptu w Pythonie)?
neverendingqs
@neverendingqs, jeśli masz na myśli huponexitrzeczy, uruchomienie w podpowłoce powinno mieć taki sam efekt, ponieważ disownproces nie zostanie dodany do listy zadań.
Graeme
@neverendingqs, zaktualizowałem moją odpowiedź. Zapomniałeś, że powinieneś używać przekierowań z disown. Nie spodziewaj się jednak, że to wiele zmieni. Myślę, że najlepiej jest zmienić pythonskrypt, aby informował cię, dlaczego się kończy.
Graeme
Przekierowanie wyjścia zadziałało ( unix.stackexchange.com/a/176610/52894 ), ale nie jestem pewien, jaka jest różnica między jawnym robieniem a nohupjego wykonaniem.
neverendingqs
0

Myślę, że to dlatego, że praca jest związana z sesją. Gdy to się skończy, wszystkie zadania użytkownika również zostaną zakończone.

użytkownik208145
źródło
2
Ale dlaczego różni się to od uzyskania terminala, wpisania i uruchomienia polecenia oraz wyjścia? Obie sesje są zamykane po zamknięciu.
neverendingqs
Zgadzam się, chciałbym zrozumieć, dlaczego nie różni się to od ręcznego zamykania własnego terminalu.
Avindra Goolcharan
0

Jeśli nohupmożesz otworzyć plik wyjściowy, możesz mieć o tym pojęcia nohup.out. Możliwe jest, że pythonnie jest na ścieżce po uruchomieniu skryptu przez ssh.

Spróbowałbym utworzyć plik dziennika dla polecenia. Spróbuj użyć:

nohup /usr/bin/python3 -u <script> &>logfile &
BillThor
źródło
Używam sshdo ręcznego uruchamiania skryptu, więc zakładam, że python3 jest na ścieżce.
neverendingqs
@neverendingqs Czy plik dziennika zawiera coś?
BillThor,
Nic niezwykłego - start-up wygląda normalnie.
neverendingqs