SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

fstab na VPS: UUID, nofail i montowanie bind

Jak napisać wpis w /etc/fstab, który przetrwa reboot i zmianę nazwy dysku: UUID zamiast /dev/sdb1, opcje nofail i _netdev, montowanie bind oraz wyjście z trybu emergency.

Wpis w /etc/fstab, który przetrwa reboot

Poprawny wpis w /etc/fstab wskazuje system plików przez UUID, a nie przez nazwę urządzenia, i ma opcję nofail wszędzie poza partycją główną. Tyle wystarczy, żeby restart VPS-a nie skończył się zatrzymanym startem systemu. Reszta tego poradnika tłumaczy, dlaczego tak jest, co znaczy każde z sześciu pól wiersza i czym wpis bind różni się od zwykłego.

Awaria wygląda zawsze tak samo. Serwer się restartuje, po czym SSH przestaje odpowiadać. W konsoli u dostawcy widać prośbę o hasło roota i taki komunikat:

You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, or "exit"
to continue bootup.

Ten tekst drukuje systemd-sulogin-shell, czyli powłoka awaryjna systemd. Przyczyna jest prawie zawsze jedna: któryś wiersz w /etc/fstab opisuje urządzenie, którego w tej chwili nie ma, a start systemu został na nie wymagany. Na VPS-ie nie ma klawiatury i monitora, więc jedyną drogą do środka jest konsola w panelu dostawcy. Warto wiedzieć, jak z niej skorzystać, zanim będzie potrzebna.

Dlaczego /dev/sdb1 zmienia nazwę

Nazwy takie jak /dev/sda czy /dev/vdb przydziela jądro w kolejności wykrywania urządzeń. Ta kolejność zależy od liczby podłączonych dysków i od tego, jak szybko odpowie warstwa wirtualizacji. Dołóż drugi wolumen blokowy albo odtwórz serwer ze snapshotu, a przypisanie może wyjść inaczej niż poprzednio. Wpis, który mówi /dev/sdb1, montuje wtedy nie ten dysk co trzeba albo nie montuje nic.

Manual fstab(5) pisze o tym wprost:

This is the recommended method, as device names are often a coincidence of hardware detection order, and can change when other disks are added or removed.

UUID jest zapisany w superbloku systemu plików, więc wędruje razem z danymi. Dysk może dostać inną nazwę urządzenia, a wpis dalej trafi we właściwy system plików. Są dwa zastrzeżenia. Po pierwsze, mkfs nadaje nowy UUID, więc po przeformatowaniu wolumenu stary wpis wskazuje w próżnię. Po drugie, klon obrazu ma ten sam UUID co oryginał, więc podpięcie obu wolumenów do jednej maszyny daje wynik zależny od kolejności wykrywania, czyli dokładnie ten problem, który chcieliśmy usunąć.

Alternatywą jest etykieta. Ustawiasz ją raz (sudo e2label /dev/sdb1 data dla ext4, sudo xfs_admin -L data /dev/sdb1 dla XFS) i piszesz w pierwszym polu LABEL=data. Etykieta jest czytelna dla człowieka, ale nie jest unikalna: dwa wolumeny z etykietą data to ta sama loteria co dwa identyczne UUID. Jeśli nie masz powodu, żeby robić inaczej, używaj UUID.

Sześć pól jednego wiersza fstab

Każdy wiersz ma sześć pól rozdzielonych białymi znakami. Manual fstab(5) nazywa je tak:

  1. fs_spec: urządzenie, system plików sieciowy albo obraz do podmontowania. Tu wpisujesz UUID=... lub LABEL=....
  2. fs_file: punkt montowania, czyli katalog, w którym dane mają się pojawić.
  3. fs_vfstype: typ systemu plików, na przykład ext4, xfs, none dla wpisu bind.
  4. fs_mntops: opcje montowania rozdzielone przecinkami, bez spacji.
  5. fs_freq: pole dla programu dump(8). Dziś zostawiasz 0.
  6. fs_passno: kolejność sprawdzania przez fsck(8). Partycja główna ma 1, pozostałe lokalne systemy plików 2. Manual dodaje: "Defaults to zero (don't check the filesystem) if not present".

Gotowy wpis dla dodatkowego wolumenu wygląda tak:

UUID=9f2b0e6a-4a3c-4f0e-9a5d-6c1f2b7d8e31  /mnt/data  ext4  defaults,nofail,x-systemd.device-timeout=10s  0  2

Opcje bezpieczeństwa w czwartym polu to osobny temat i nie powtarzam go tutaj. Jeśli wolumen trzyma tylko dane użytkowników, warto go domontować z ograniczeniami opisanymi w opcjach nosuid, nodev i noexec dla montowanych wolumenów.

Jak odczytać UUID bez literówki

UUID ma 36 znaków i przepisywanie go ręcznie to proszenie się o kłopot. Trzy polecenia pokazują to samo z różnych stron:

lsblk -f
sudo blkid -s UUID -o value /dev/sdb1
ls -l /dev/disk/by-uuid/

lsblk -f wypisuje drzewo urządzeń blokowych razem z typem systemu plików, etykietą, UUID i aktualnym punktem montowania. blkid z tymi flagami drukuje sam identyfikator, bez ozdobników, więc nadaje się do podstawienia w skrypcie. Katalog /dev/disk/by-uuid/ zawiera dowiązania symboliczne z UUID na bieżącą nazwę urządzenia, więc pokazuje to samo powiązanie z drugiej strony. Jeśli któryś z nich nie widzi Twojego wolumenu, problem jest wcześniej, po stronie podłączenia dysku, a nie w fstab. Kolejność czynności od podpięcia wolumenu do gotowego wpisu opisuje dodawanie wolumenu blokowego do VPS-a, a to, czy urządzenie jest naprawdę tym, za które je bierzesz, sprawdzisz przez weryfikację dysku NVMe w Linuksie.

Najbezpieczniej jest w ogóle nie przepisywać identyfikatora, tylko dopisać wiersz poleceniem:

DEV=/dev/sdb1
UUID=$(sudo blkid -s UUID -o value "$DEV")
printf 'UUID=%s /mnt/data ext4 defaults,nofail,x-systemd.device-timeout=10s 0 2\n' "$UUID" | sudo tee -a /etc/fstab

Jeśli blkid nic nie zwróci, zmienna UUID będzie pusta i do pliku trafi wiersz UUID= /mnt/data ..., czyli uszkodzony. Dlatego po dopisaniu zawsze obejrzyj wynik (tail -n 3 /etc/fstab), zanim przejdziesz dalej.

Co robią nofail i _netdev

Domyślnie systemd traktuje każdy lokalny wpis jako wymagany do zakończenia startu. Opcja nofail to zmienia. Manual systemd.mount(5) mówi:

With nofail, this mount will be only wanted, not required, by local-fs.target or remote-fs.target. Moreover, the mount unit is not ordered before these target units.

Różnica między "wanted" a "required" jest tu całą treścią. Zależność wymagana, która się nie powiedzie, przewraca cel local-fs.target, a wtedy start systemu nie może być kontynuowany i systemd uruchamia powłokę awaryjną. Zależność chciana tylko zgłasza błąd w dzienniku, a maszyna wstaje dalej, razem z SSH. Dla wszystkiego poza partycją główną to właściwy wybór: wolisz zalogować się na serwer bez jednego wolumenu niż nie zalogować się wcale.

nofail ma cenę i trzeba ją znać. Jeśli wolumen się nie zamontuje, katalog /mnt/data nadal istnieje, tyle że leży na partycji głównej. Aplikacja, która o tym nie wie, zacznie zapisywać dane właśnie tam i wypełni root. Objaw jest mylący, bo po późniejszym zamontowaniu wolumenu te pliki znikają z widoku, choć dalej zajmują miejsce. To klasyczny rozjazd między df a du na pełnym dysku. Dlatego wpisowi z nofail powinno towarzyszyć sprawdzenie w monitoringu, że katalog naprawdę jest punktem montowania (mountpoint -q /mnt/data).

Opcja _netdev mówi o czymś innym: o kolejności. Manual systemd.mount(5) opisuje ją tak:

Normally the file system type is used to determine if a mount is a "network mount", i.e. if it should only be started after the network is available. Using this option overrides this detection and specifies that the mount requires network.

Dla nfs czy cifs systemd rozpozna to sam po typie. _netdev jest potrzebne tam, gdzie typ wygląda lokalnie, a urządzenie lokalne nie jest, na przykład przy iSCSI albo przy sieciowym storage montowanym jak zwykły system plików. Bez tej opcji systemd próbuje zamontować zasób, zanim sieć wstanie, i montowanie kończy się błędem. Ten podział ma znaczenie także przy wyborze samego storage, co porównuje zestawienie taniego miejsca: storage VPS, wolumen blokowy i obiektowy.

Jest jeszcze noauto:

With noauto, the mount unit will not be added as a dependency for local-fs.target or remote-fs.target. This means that it will not be mounted automatically during boot, unless it is pulled in by some other unit. The auto option has the opposite meaning and is the default.

To nie jest to samo co nofail. noauto znaczy "nie montuj przy starcie w ogóle", a wpis służy wtedy tylko jako skrót dla późniejszego mount /mnt/data.

Ile systemd czeka na urządzenie

Jeśli wolumen sieciowy albo blokowy pojawia się z opóźnieniem, samo nofail nie wystarczy, bo start i tak stoi, dopóki systemd czeka na urządzenie. Od tego jest osobna opcja. Manual systemd.mount(5):

Configure how long systemd should wait for a device to show up before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as s, min, h, ms. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file.

Zwróć uwagę na ostatnie zdanie: ta opcja działa wyłącznie w /etc/fstab. Przeniesiona do pliku jednostki w Options= jest po cichu ignorowana.

Nie zgaduj wartości domyślnej i nie przepisuj jej z cudzego poradnika. Manual mówi tylko tyle:

The default value is set from DefaultTimeoutStartSec= option in systemd-system.conf(5).

Czyli liczba zależy od konfiguracji Twojego systemu i od dystrybucji. Sprawdzisz ją u siebie jednym poleceniem:

systemctl show --property=DefaultTimeoutStartSec

Wynik to jeden wiersz w postaci DefaultTimeoutStartSec=.... Tyle sekund maszyna będzie stać na ekranie startu, czekając na urządzenie, którego może nigdy nie być. Wpisanie x-systemd.device-timeout=10s skraca to oczekiwanie do dziesięciu sekund dla tego jednego wpisu.

Dlaczego jedna literówka zatrzymuje start systemu

W systemie z systemd fstab nie jest czytany bezpośrednio przez proces montujący przy starcie. Manual systemd-fstab-generator(8) opisuje to tak:

systemd-fstab-generator is a generator that translates /etc/fstab into native systemd units early at boot and when configuration of the system manager is reloaded.

Każdy wiersz staje się więc jednostką typu .mount z nazwą wyprowadzoną z punktu montowania. Nazwę zobaczysz poleceniem systemd-escape -p --suffix=mount /mnt/data, a samą wygenerowaną jednostkę przez systemctl cat mnt-data.mount. Lista wszystkich montowań widzianych przez systemd to systemctl list-units --type=mount.

Stąd biorą się dwie rzeczy, które zaskakują ludzi przyzwyczajonych do starszych systemów. Po pierwsze, błędny wiersz nie jest ostrzeżeniem, tylko jednostką, która nie wstaje, i jeśli jest wymagana, przewraca cały start. Po drugie, zmiana w /etc/fstab nie działa od razu na poziomie jednostek, bo generator uruchamia się przy starcie i przy przeładowaniu konfiguracji menedżera. Dlatego po każdej edycji pliku uruchom:

sudo systemctl daemon-reload

Bez tego systemctl start mnt-data.mount może dotyczyć poprzedniej wersji wpisu, a Ty będziesz szukać błędu w miejscu, w którym go nie ma.

Wpis bind: dlaczego zachowuje się inaczej

Montowanie typu bind nie montuje żadnego urządzenia. Podpina istniejące poddrzewo katalogów w drugie miejsce, tak że ta sama zawartość jest dostępna pod dwiema ścieżkami. Manual mount(8) ujmuje to krótko: "After this call the same contents are accessible in two places". Używa się tego, gdy aplikacja ma sztywno zapisaną ścieżkę, a dane mają leżeć na innym wolumenie.

W fstab wygląda to tak:

/srv/data  /var/lib/app/data  none  bind  0 0

Pierwsze pole to katalog źródłowy, a nie urządzenie. Trzecie pole to none, bo żaden typ systemu plików nie jest tu montowany. Opcja bind w czwartym polu jest tym, co odróżnia ten wpis od zwykłego. Szóste pole musi być 0, ponieważ fsck nie ma czego sprawdzać na podpięciu.

Pierwsza różnica dotyczy punktu montowania. Uruchomione ręcznie mount(8) nie tworzy brakującego katalogu i kończy się błędem mount: /var/lib/app/data: mount point does not exist.. Jednostka .mount zachowuje się inaczej, bo manual systemd.mount(5) mówi o polu Where=: "If the mount point does not exist at the time of mounting, it is created as either a directory or a file". Efekt jest taki, że ten sam wpis potrafi działać po restarcie, a nie działać przy ręcznym mount -a. Najprościej ten temat zamknąć przez sudo mkdir -p /var/lib/app/data przed pierwszą próbą.

Druga różnica jest groźniejsza, bo nie daje żadnego błędu. Jeśli /srv leży na osobnym wolumenie, a podpięcie wykona się wcześniej niż montowanie tego wolumenu, bind podepnie pusty katalog /srv/data z partycji głównej. Aplikacja wystartuje, zobaczy zero plików i zacznie pisać do roota. Nic się nie zepsuje na tyle głośno, żeby to zauważyć tego samego dnia. Zależność zapisuje się wprost, w opcjach:

/srv/data  /var/lib/app/data  none  bind,x-systemd.requires-mounts-for=/srv  0 0

Manual systemd.mount(5) opisuje tę opcję jako: "Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units". Innymi słowy, podpięcie poczeka, aż /srv będzie naprawdę zamontowane.

Trzecia rzecz to tryb tylko do odczytu, którego ludzie często oczekują po bind,ro. Manual mount(8) tłumaczy, skąd bierze się zamieszanie:

Since util-linux 2.27 mount permits changing the mount options by passing the relevant options along with --bind. This feature is not supported by the Linux kernel; it is implemented in userspace by an additional mount(2) remounting system call. This solution is not atomic.

Klasyczna droga to dwa kroki: najpierw mount --bind olddir newdir, potem mount -o remount,bind,ro olddir newdir. Manual dodaje ważne zastrzeżenie: "a read-only bind will create a read-only mountpoint (VFS entry), but the original filesystem superblock will still be writable". Katalog źródłowy pozostaje więc zapisywalny. To ograniczenie widoczności, a nie ochrona danych.

Jeśli pod katalogiem źródłowym są kolejne montowania i mają być widoczne po drugiej stronie, potrzebny jest wariant rekurencyjny: mount --rbind, a w fstab opcja rbind zamiast bind.

Bezpieczna kolejność pracy przed restartem

Edycja /etc/fstab na zdalnej maszynie to jedyna czynność administracyjna, która potrafi odciąć Cię od serwera bez żadnego ostrzeżenia. Dlatego rób to zawsze w tej kolejności:

sudo cp /etc/fstab /etc/fstab.bak
sudoedit /etc/fstab
findmnt --verify --verbose
sudo systemctl daemon-reload
sudo mount -a
findmnt /mnt/data

Kopia zapasowa daje Ci plik, który da się przywrócić z konsoli jednym poleceniem, nawet jeśli w panelu dostawcy nie działa wklejanie tekstu. findmnt --verify sprawdza sam plik, co manual findmnt(8) opisuje jako "Check mount table content. The default is to verify /etc/fstab parsability and usability", a --verbose dopisuje szczegóły każdej kontroli. daemon-reload każe generatorowi przerobić plik na jednostki. mount -a montuje wszystko, co powinno być zamontowane, i robi to teraz, gdy masz jeszcze działające SSH i widzisz komunikat błędu na ekranie.

Typowe komunikaty z tego kroku czyta się dosłownie. mount: /mnt/data: special device /dev/sdb1 does not exist. znaczy, że pierwsze pole wskazuje na nieistniejące urządzenie, czyli zwykle przepisany z błędem UUID. mount: /mnt/data: mount point does not exist. znaczy, że brakuje katalogu z drugiego pola. Błąd o złym typie systemu plików albo złej opcji wskazuje na pole trzecie lub czwarte.

Na końcu findmnt /mnt/data wypisuje, co faktycznie jest zamontowane w tym miejscu, z jakiego źródła i z jakimi opcjami. Jeśli polecenie nic nie zwraca, montowania nie ma, niezależnie od tego, że katalog istnieje i da się do niego wejść.

Jedna uczciwa uwaga: mount -a sprawdza składnię i to, czy zasób da się zamontować w tej chwili. Nie odtwarza kolejności zdarzeń przy starcie ani czekania na urządzenie. Pełną pewność daje dopiero restart, więc pierwszy restart po zmianie fstab wykonuj wtedy, gdy masz czas i otwartą konsolę u dostawcy, a nie w piątek wieczorem. Przed takim restartem warto mieć też świeży punkt przywracania, a różnice między nim a kopią danych wyjaśnia porównanie snapshotów i backupów VPS.

Jak wrócić do systemu, który wpadł w tryb emergency

Powłoka awaryjna prosi o hasło roota. W obrazach chmurowych konto root bywa zablokowane i wtedy logowanie w tym miejscu po prostu się nie uda, bo sulogin odmówi i zgłosi, że konto jest zablokowane. Nie próbuj tego obchodzić w panice. Droga wyjścia prowadzi przez parametry jądra.

W konsoli dostawcy zrestartuj maszynę i w menu GRUB naciśnij e, żeby edytować wpis startowy. Do wiersza zaczynającego się od linux dopisz fstab=no i uruchom system przez Ctrl+X. Manual systemd-fstab-generator(8) opisuje ten parametr tak: "If 'no', causes the generator to ignore any mounts or swap devices configured in /etc/fstab". System wstanie wtedy bez żadnego z Twoich wpisów, a to znaczy: bez tego, który go zatrzymał. Zmiana obowiązuje tylko do najbliższego restartu, więc nie zostawia po sobie śladu.

Po zalogowaniu napraw plik i sprawdź go tak, jak przed chwilą:

sudo cp /etc/fstab.bak /etc/fstab
findmnt --verify --verbose
sudo systemctl daemon-reload
sudo mount -a
sudo systemctl reboot

Jeśli dostałeś się do powłoki awaryjnej, a nie przez GRUB, pamiętaj, że partycja główna bywa zamontowana tylko do odczytu. Zapis włączysz przez mount -o remount,rw /, inaczej edytor zgłosi, że nie może zapisać pliku. Dziennik z nieudanego startu przeczytasz poleceniem journalctl -xb, a listę jednostek, które się nie powiodły, pokaże systemctl --failed. Zwykle wystarczy jedna linia z tej listy, żeby wiedzieć, który wpis poprawić.

FAQ

Dlaczego mój VPS wpada w tryb emergency po edycji /etc/fstab?

Ponieważ systemd zamienia każdy wiersz /etc/fstab na jednostkę montowania, a jednostka bez opcji nofail jest wymagana przez local-fs.target. Jeśli takie montowanie się nie powiedzie, cel nie wstaje, start nie może być kontynuowany i uruchamia się powłoka awaryjna na konsoli. Najczęstsze przyczyny to błędnie przepisany UUID, nieistniejący katalog punktu montowania i wolumen, który nie był jeszcze gotowy. Dodanie nofail do wpisów innych niż partycja główna sprawia, że maszyna wstaje mimo błędu, a Ty wchodzisz po SSH i czytasz systemctl --failed.

Czy nofail wystarczy, żeby serwer zawsze wstał?

Nie zawsze, bo nofail zmienia tylko wymagalność montowania, a nie czas oczekiwania na urządzenie. Jeśli wolumen pojawia się z opóźnieniem albo nie pojawia się wcale, start stoi tyle, ile wynosi limit czasu na urządzenie. Dlatego razem z nofail dopisuj x-systemd.device-timeout= z krótką wartością, na przykład 10s. Pamiętaj też o skutku ubocznym: gdy montowanie się nie uda, aplikacja zapisze dane do katalogu na partycji głównej, więc warto sprawdzać mountpoint -q /mnt/data.

Jaki jest domyślny czas oczekiwania na urządzenie z fstab?

Manual systemd.mount(5) nie podaje liczby. Mówi: "The default value is set from DefaultTimeoutStartSec= option in systemd-system.conf(5)". Wartość zależy więc od konfiguracji Twojego systemu, a nie od uniwersalnej stałej. Sprawdź ją u siebie poleceniem systemctl show --property=DefaultTimeoutStartSec. Jeśli chcesz krótszego oczekiwania dla konkretnego wpisu, ustaw x-systemd.device-timeout= w opcjach w /etc/fstab. Ta opcja działa wyłącznie w fstab i jest ignorowana w polu Options= pliku jednostki.

Dlaczego katalog podpięty przez bind jest pusty?

Bo podpięcie wykonało się, zanim katalog źródłowy został zamontowany. Wpis bind nie montuje urządzenia, tylko pokazuje istniejące poddrzewo w drugim miejscu, więc jeśli /srv jest osobnym wolumenem i nie był jeszcze gotowy, bind podpiął pusty katalog z partycji głównej. Żaden błąd się przy tym nie pojawia. Rozwiązaniem jest zapisanie zależności wprost w opcjach: bind,x-systemd.requires-mounts-for=/srv. Sprawdź wynik przez findmnt /var/lib/app/data, które pokaże faktyczne źródło podpięcia.

#fstab#storage#linux#mounts#vps