Docker Compose: automatyczny start po restarcie systemu
Sprawdź, jak uruchamiać usługi Docker Compose po restarcie: zasady restartu, ograniczenie on-failure oraz przypadki wymagające jednostki systemd.
Krótka odpowiedź
Usługi Docker Compose są uruchamiane podczas startu systemu, gdy jednocześnie są spełnione dwa warunki. Demon Docker musi być włączony jako usługa systemowa, a każda usługa w pliku musi mieć zasadę ponownego uruchamiania unless-stopped lub always. Należy dodać restart: unless-stopped do każdej usługi i jednorazowo uruchomić docker compose up -d. Po ponownym uruchomieniu systemu kontenery uruchomią się automatycznie. W typowym przypadku nie jest wymagane nic więcej.
Jednostka systemd jest potrzebna tylko wtedy, gdy kolejność uruchamiania ma znaczenie: na przykład gdy stos zależy od zamontowanego dysku, interfejsu VPN lub udziału sieciowego, który nie jest gotowy w chwili uruchamiania demona Docker. Jest to rzeczywisty przypadek, omówiony w drugiej części tego poradnika. Jeśli nie są jeszcze znane definicje usług i woluminy, należy rozpocząć od podstaw Docker Compose na VPS i następnie wrócić do tego poradnika.
Ustawianie zasad ponownego uruchamiania w compose.yaml
Zasady określa się osobno dla każdej usługi. Nie ma globalnego przełącznika. Dlatego usługa, dla której pominięto konfigurację, pozostanie wyłączona po ponownym uruchomieniu systemu, podczas gdy pozostała część stosu zostanie uruchomiona.
services:
app:
image: nginx:1.27
restart: unless-stopped
ports:
- "8080:80"
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:Należy zastosować konfigurację, a następnie odczytać zasady z działającego kontenera:
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)Zostanie wyświetlona wartość unless-stopped. Jeśli zostanie wyświetlona wartość no, plik został zmodyfikowany, ale kontener nie został ponownie utworzony.
Jest to najczęstsza przyczyna problemów. Zasady ponownego uruchamiania są przechowywane w kontenerze, a nie w pliku YAML. Modyfikacja compose.yaml nie zmienia istniejącego kontenera. docker compose restart również nie pomaga, ponieważ zatrzymuje i uruchamia ten sam obiekt kontenera bez zmiany jego konfiguracji. Tylko docker compose up -d porównuje plik z działającymi kontenerami, wykrywa zmianę zasad i ponownie je tworzy.
Jeśli kontener nie powinien być teraz ponownie tworzony, zasady można zmienić bezpośrednio:
docker update --restart unless-stopped my-containerPlik YAML również należy zmodyfikować. docker update zmienia działający kontener, a następne docker compose up -d odczyta plik i przywróci poprzednią wartość.
Co faktycznie powoduje każda wartość restartu
Docker definiuje 4 wartości. Różnica między nimi jest widoczna tylko podczas ponownego uruchamiania komputera lub demona.
nojest wartością domyślną. Kontener nigdy nie jest automatycznie uruchamiany ponownie, niezależnie od okoliczności.alwaysuruchamia ponownie kontener za każdym razem, gdy ten zostanie zatrzymany. Jeśli kontener zatrzymano ręcznie, zostanie on ponownie uruchomiony przy następnym uruchomieniu demona Docker. Często jest to zaskakujące: kontener celowo zatrzymany w zeszłym tygodniu ponownie działa po restarcie.unless-stoppeddziała tak jakalways, z wyjątkiem sytuacji, w której kontener zatrzymany ręcznie pozostaje zatrzymany po ponownym uruchomieniu demona. Jest to właściwa wartość dla usługi, która jest okresowo zatrzymywana na czas konserwacji.on-failureuruchamia ponownie kontener tylko wtedy, gdy kończy działanie z niezerowym kodem wyjścia. Liczbę prób można ograniczyć, jak wrestart: on-failure:3.
W przypadku stosu, który powinien działać zawsze, gdy działa serwer, właściwą wartością domyślną jest unless-stopped. Wartość always należy wybrać tylko wtedy, gdy kontener nie powinien pozostawać zatrzymany.
Dlaczego restart: on-failure nie działa po ponownym uruchomieniu
Wiele osób wybiera on-failure, ponieważ ta opcja wydaje się ostrożna, a następnie stwierdza, że po pierwszym ponownym uruchomieniu wszystkie kontenery są zatrzymane. Przyczyna wynika z definicji. on-failure reaguje tylko na jedno zdarzenie: zakończenie procesu kontenera z kodem błędu.
Ponowne uruchomienie nie jest błędem. Podczas wyłączania hosta systemd zatrzymuje docker.service, a demon celowo zatrzymuje każdy kontener. Kontener nie uległ awarii, więc zasada restartu nie ma na co zareagować. Po ponownym uruchomieniu demon sprawdza kontenery, które powinien wznowić. Kontener on-failure, który został prawidłowo zatrzymany, nie znajduje się na tej liście. Pozostaje w stanie exited.
Można to bezpośrednio sprawdzić. Ustaw restart: on-failure dla usługi, uruchom docker compose up -d, ponownie uruchom system, a następnie wykonaj:
docker compose ps -aUsługa jest wyświetlana ze stanem Exited i statusem takim jak Exited (0) 2 minutes ago. Nic nie jest uszkodzone i żaden błąd nie jest rejestrowany, co utrudnia diagnozowanie problemu. Zasada restartu działa dokładnie zgodnie z definicją.
on-failure jest nadal przydatne. Sprawdza się w przypadku kontenera uruchamiającego zadanie, który może ulec awarii, gdy wymagana jest ograniczona liczba ponowień bez tworzenia pętli restartów. Nie jest to właściwe rozwiązanie do utrzymywania długotrwałej usługi po ponownym uruchomieniu systemu.
Zasady restartu działają tylko wtedy, gdy usługa Docker uruchamia się podczas rozruchu
Zasady restartu są egzekwowane przez demona Docker. Jeśli demon się nie uruchomi, nic ich nie egzekwuje. Należy to sprawdzić:
systemctl is-enabled docker
systemctl is-enabled containerdW obu przypadkach powinien zostać wyświetlony komunikat enabled. Pakiety z oficjalnego repozytorium Docker włączają te ustawienia podczas instalacji, dlatego na nowym serwerze test zwykle kończy się powodzeniem. Jeśli w którymkolwiek przypadku zostanie wyświetlony komunikat disabled, należy to naprawić:
sudo systemctl enable --now docker containerdW tym miejscu występuje pułapka, którą należy zrozumieć. Ubuntu dostarcza również docker.socket, który uruchamia demona na żądanie, gdy coś po raz pierwszy komunikuje się z API Docker. Wyświetlenie informacji, że docker.socket jest włączona, może prowadzić do założenia, że demon jest objęty konfiguracją, i do wyłączenia docker.service w celu oszczędzania pamięci. Podczas rozruchu nic nie wywołuje API, dlatego gniazdo nie jest używane, demon się nie uruchamia i żaden kontener nie zostaje uruchomiony do czasu wpisania pierwszego polecenia docker. Aktywacja gniazda nie zastępuje włączenia docker.service.
Kiedy jednostka systemd jest lepszym rozwiązaniem
Zasady ponownego uruchamiania nie uwzględniają kolejności uruchamiania względem pozostałych elementów systemu. Demon uruchamia się i podnosi kontenery, gdy tylko może. Jeśli stos używa bind-mount do katalogu znajdującego się na osobnym woluminie, udziale NFS (network file system) lub zaszyfrowanym dysku, kontenery mogą uruchomić się, zanim ta ścieżka będzie dostępna. Docker bez problemu utworzy pusty katalog w punkcie montowania i uruchomi kontener z użyciem tego katalogu, a baza danych uruchomi się bez danych.
Jednostkę systemd należy utworzyć, jeśli występuje którykolwiek z poniższych przypadków. Stos wymaga, aby najpierw był gotowy punkt montowania, interfejs VPN lub inna jednostka. Wymagane jest, aby systemctl stop myapp i systemctl start myapp działały tak jak w przypadku każdej innej usługi w systemie. Stos ma być również poprawnie zatrzymywany podczas zamykania systemu, zamiast kończyć działanie razem z demonem. Jeśli jednostki systemd są nowym zagadnieniem, tworzenie usługi i zegara systemd zawiera szczegółowe informacje o formacie pliku.
Tworzenie jednostki systemd
Umieść stos w stałej ścieżce poza katalogiem domowym. /srv/myapp to dobry wybór, ponieważ jednostka uruchamiana przed zalogowaniem się użytkownika nie powinna odczytywać /home.
Utwórz /etc/systemd/system/myapp.service:
[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetWłącz jednostkę i uruchom ją:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.servicePrawidłowo działająca jednostka wyświetla Active: active (exited). Przy pierwszym kontakcie może to wyglądać nieprawidłowo. Jest to poprawne: Type=oneshot wraz z RemainAfterExit=yes oznacza, że jednostka wykonała swoje polecenie, polecenie zakończyło działanie, a systemd pozostawia jednostkę oznaczoną jako aktywną, aby ExecStop zostało uruchomione podczas zamykania systemu.
Każdy wiersz ma określone zastosowanie. Requires=docker.service oznacza, że jednostka kończy działanie natychmiast, zamiast wykonywać docker compose wobec niedostępnego gniazda. After= ustawia kolejność, ponieważ samo Requires= tego nie robi. RequiresMountsFor= powoduje włączenie jednostki montowania dla tej ścieżki i oczekiwanie na jej gotowość. Jest to główny powód użycia jednostki zamiast zasady ponownego uruchamiania. TimeoutStartSec=0 zapobiega zakończeniu zadania uruchamiania przez systemd podczas pobierania dużego obrazu.
Uwaga dotycząca łączenia obu mechanizmów. Dokumentacja Docker odradza łączenie zasad ponownego uruchamiania z menedżerem procesów hosta. Ostrzeżenie dotyczy menedżera procesów, który nadzoruje sam proces kontenera i uruchamia go ponownie, gdy daemon próbuje wykonywać tę samą czynność. Jednostka Type=oneshot niczego nie nadzoruje, dlatego pozostawienie restart: unless-stopped w pliku compose obok tej jednostki jest prawidłowe i zalecane. systemd obsługuje kolejność podczas uruchamiania systemu, a daemon obsługuje kontener, który ulegnie awarii o trzeciej nad ranem.
Weryfikacja po rzeczywistym ponownym uruchomieniu
Rzeczywisty test jest niezbędny. systemctl restart docker nie sprawdza kolejności montowania, a docker compose down wykonane po docker compose up -d nie sprawdza żadnego aspektu uruchamiania systemu.
sudo rebootNależy zaczekać, ponownie nawiązać połączenie i wykonać sprawdzenie w następującej kolejności:
uptime
systemctl is-active docker
docker compose psuptime potwierdza, że używana maszyna została rzeczywiście ponownie uruchomiona. docker compose ps, wykonane z katalogu stosu, powinno wyświetlić każdą usługę ze stanem running i czasem działania zbliżonym do czasu działania maszyny. Usługa wyświetlana jako Exited wymaga sprawdzenia.
Jeśli któryś składnik nie został uruchomiony, dziennik demona obejmuje okres uruchamiania systemu:
journalctl -u docker.service -b --no-pager | tail -50W przypadku stosu zarządzanego przez unit polecenie journalctl -u myapp.service -b --no-pager wyświetla dokładny wynik docker compose z uruchamiania systemu, w tym informacje o nieudanym pobraniu obrazu lub braku pliku .env.
Elementy, które niepostrzeżenie uniemożliwiają automatyczny start
Kontenery utworzone za pomocą docker compose run nigdy nie otrzymują zasad restartu z pliku. Compose traktuje je jako kontenery uruchamiane jednorazowo. Jeśli usługa wydaje się ignorować swoją zasadę, należy sprawdzić, czy została uruchomiona za pomocą run zamiast up.
Ścieżka względna w wolumenie lub we wpisie env_file jest rozwiązywana względem katalogu pliku Compose. Działa to z poziomu powłoki oraz z poziomu jednostki, która ustawia WorkingDirectory. Nie działa z poziomu jednostki bez tej wartości, ponieważ katalogiem roboczym jest wtedy /.
Docker działający bez uprawnień root to osobny przypadek. Demon działa jako usługa użytkownika, a usługa użytkownika zatrzymuje się po zakończeniu ostatniej sesji tego użytkownika. Należy włączyć ją dla użytkownika i zezwolić jej na działanie bez zalogowanego użytkownika:
systemctl --user enable docker
sudo loginctl enable-linger $USERBez enable-linger demon działający bez uprawnień root zostaje wyłączony po wylogowaniu, a kontenery są wraz z nim zatrzymywane. Wygląda to dokładnie tak, jak uszkodzona zasada restartu.
Pozostaje jeszcze jedna kwestia. Automatyczne aktualizacje zabezpieczeń mogą ponownie uruchamiać serwer o ustalonej godzinie. Jest to korzystne tylko wtedy, gdy stos uruchamia się samoczynnie. Konfiguracja tej funkcji na nowej maszynie należy do pozostałych zadań wykonywanych w pierwszej godzinie pracy, opisanych w pierwszych dziesięciu minutach na nowym VPS.
FAQ
Jaka jest różnica między restart: always a restart: unless-stopped?
Obie opcje uruchamiają ponownie kontener, gdy zatrzyma się samoczynnie. Różnica występuje po ręcznym zatrzymaniu kontenera. W przypadku always kontener uruchamia się ponownie przy następnym uruchomieniu demona Docker, więc ponowne uruchomienie systemu anuluje ręczne zatrzymanie. W przypadku unless-stopped demon pamięta, że kontener został zatrzymany celowo, i pozostawia go zatrzymanym. Należy użyć unless-stopped, chyba że wymagany jest kontener, który po zatrzymaniu ma pozostać wyłączony.
Dla opcji restart: unless-stopped kontener nadal nie uruchamia się po ponownym uruchomieniu systemu. Dlaczego?
Zasada jest przypisana do kontenera, a nie do pliku. Edycja YAML nie aktualizuje istniejącego kontenera. Należy uruchomić docker compose up -d, aby Compose utworzył kontener ponownie, a następnie potwierdzić wynik za pomocą docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Jeśli polecenie wyświetli no, kontener został utworzony przed wprowadzeniem zmiany. Inną częstą przyczyną jest wyłączona opcja docker.service. Można to sprawdzić za pomocą systemctl is-enabled docker.
Czy potrzebna jest jednostka systemd, jeśli używane są zasady restartu?
Zwykle nie. Zasada restartu wystarcza w przypadku stosu, który wymaga tylko sieci. Dotyczy to większości stosów. Jednostkę należy dodać, gdy kontenery zależą od zasobu, który nie jest gotowy podczas uruchamiania demona Docker, na przykład zewnętrznego dysku, zaszyfrowanego woluminu, udziału NFS lub interfejsu VPN. Jednostka zapewnia kolejność uruchamiania za pomocą After= i RequiresMountsFor=, czego nie można wyrazić za pomocą zasady restartu.
Jak trwale zatrzymać stos, aby nie uruchomił się ponownie przy następnym ponownym uruchomieniu systemu?
W przypadku unless-stopped wystarczy docker compose stop, ponieważ kontener zatrzymany ręcznie nie jest wznawiany po ponownym uruchomieniu demona. W przypadku always samo zatrzymanie nie wystarcza i kontener zostanie uruchomiony ponownie po ponownym uruchomieniu systemu. Należy uruchomić docker compose down, które usuwa kontenery, albo najpierw zmienić zasadę za pomocą docker update --restart no my-container. Jeśli stosem zarządza jednostka systemd, należy uruchomić również sudo systemctl disable myapp.service. W przeciwnym razie jednostka uruchomi stos ponownie.