Przejdź do treści
Lekcja 116 z 117

Kurs › Moduł 13 › Lekcja 116

Egzamin próbny: 20 zadań, dwie godziny, zero podpowiedzi

⏱ ok. 24 min czytania· moduł 13: Projekty końcowe

Po tej lekcji: kursant robi na czas egzamin próbny w stylu RHCSA i LFCS na własnej maszynie wirtualnej, sprawdza się automatycznym skryptem i wie, jak rozwiązać każde z dwudziestu zadań.

Wstęp

Sala egzaminacyjna. Albo Twój pokój z kamerką i egzaminatorem po drugiej stronie internetu. Przed Tobą lista zadań i maszyna. Żadnych pytań typu „wybierz A, B, C albo D”. Masz po prostu zrobić: założyć użytkowników, podzielić dysk, postawić stronę, naprawić start systemu. Na koniec ktoś wyłącza Ci maszynę, uruchamia ją od nowa i sprawdza, co przetrwało. Nie to, co wpisałeś. To, co zostało.

Tak wyglądają RHCSA od Red Hata i LFCS od Linux Foundation. I tak wygląda dzisiejsza lekcja. Dwadzieścia zadań, dwie godziny, a na końcu skrypt, który sam Ci policzy punkty. Rozwiązania są w drugiej połowie lekcji. Jeśli obejrzysz je przed egzaminem, to tak, jakbyś na kartkówce przepisał odpowiedzi od siebie z przyszłości. Technicznie działa, ale niczego się nie dowiesz.

Jak wyglądają prawdziwe egzaminy

Zanim zaczniemy, krótko, jak to wygląda naprawdę. Sprawdziłem na oficjalnych stronach.

RHCSA, czyli egzamin EX200, Red Hat opisuje jako egzamin praktyczny, oparty na Red Hat Enterprise Linux 10. Na egzaminie nie ma internetu i nie wolno mieć żadnych notatek ani dokumentacji, ani papierowej, ani elektronicznej. Masz tylko to, co jest w systemie: man, --help, /usr/share/doc. Lista celów egzaminu jest publiczna. Są tam między innymi: repozytoria RPM i od RHEL 10 także repozytoria Flatpak, przerywanie startu systemu, żeby dostać się do maszyny, profile tuned, trwały dziennik, partycje GPT, LVM, swap, montowanie po UUID, NFS i autofs, zadania w cron, at i timerach systemd, sieć IPv4 i IPv6, firewalld, użytkownicy i hasła, klucze SSH, SELinux z etykietami portów i wartościami logicznymi.

LFCS od Linux Foundation według strony organizatora to egzamin online z egzaminatorem, oparty na zadaniach, dwie godziny. Instrukcja dla zdających mówi o 17 do 20 zadaniach. Każde zadanie ma podany host, na który wchodzisz przez ssh nazwa. Wolno korzystać z man i z dokumentacji zainstalowanej w systemie. LFCS ma szerszy zakres niż RHCSA: kontenery, Git, certyfikaty SSL, parametry jądra, routing, odwrotne proxy.

Nasz egzamin jest mieszanką: zadania od 1 do 16 w stylu RHCSA, od 17 do 20 w stylu LFCS.

Zasady i przygotowanie

  1. Maszyna wirtualna z Rocky Linux, AlmaLinux albo RHEL 10. Minimum 2 GB RAM. Dodaj do niej drugi, pusty dysk 2 GB. Jeśli masz tylko Ubuntu, też da się: przy różnicach podam obie wersje, a zadania z SELinuksem i Flatpakiem potraktuj jako teorię.
  2. Zrób migawkę maszyny, czyli snapshot. Egzamin robi z maszyną różne rzeczy, a po wszystkim chcesz wrócić do czystego stanu i zrobić go drugi raz.
  3. Wyłącz internet w przeglądarce. Serio. Na egzaminie nie ma Google'a, nie ma czatbotów, nie ma Stack Overflow. Jest man.
  4. Dwie godziny na zegarze. Nie zatrzymujesz go na kawę.
  5. Na koniec restart i skrypt sprawdzający. Ten restart to nie złośliwość, tylko istota egzaminu: liczy się tylko to, co przetrwa restart.

I jeszcze jeden krok przygotowania, mój ulubiony. Zapomnij hasło roota:

openssl rand -base64 18 | sed 's/^/root:/' | sudo chpasswd

To ustawia rootowi losowe hasło, którego nikt nigdy nie zobaczył. Nawet Ty. Rób to WYŁĄCZNIE na maszynie wirtualnej z migawką i z kontem, które ma sudo. Na prawdziwym serwerze to nie jest ćwiczenie, to wypowiedzenie.

Dane do zadań, w których potrzebny jest adres: maszyna ma kartę w sieci 192.168.56.0/24, brama i serwer DNS to 192.168.56.1. Jeśli Twoja sieć wygląda inaczej, podmień adresy na swoje.

Egzamin: zadania

Zatrzymaj teraz wideo. Serio. Wróć, kiedy skończysz albo kiedy skończy się czas.

Zadanie 1. Hasło roota. Nie znasz hasła roota. Uruchom maszynę ponownie, przerwij start systemu i ustaw rootowi hasło Egzamin2026. Po starcie SELinux ma działać w trybie Enforcing.

Zadanie 2. Sieć. Ustaw nazwę hosta serwer1.lab.local. Ustaw na stałe adres IPv4 192.168.56.50/24, bramę 192.168.56.1 i serwer DNS 192.168.56.1.

Zadanie 3. Oprogramowanie. Skonfiguruj dostęp do repozytoriów BaseOS i AppStream z płyty instalacyjnej podpiętej do maszyny. Zainstaluj z nich serwer Apache. Dodaj repozytorium Flatpak i zainstaluj z niego jedną aplikację.

Zadanie 4. Użytkownicy. Utwórz grupę sysadmins. Utwórz użytkowników natalia i harry, którzy należą do niej jako do grupy dodatkowej. Utwórz użytkownika sarah, który nie należy do sysadmins i nie ma dostępu do powłoki interaktywnej. Wszyscy troje mają hasło Trudne123!.

Zadanie 5. Hasła i sudo. Hasło natalii ma być ważne maksymalnie 90 dni. Harry ma móc uruchamiać przez sudo dowolne polecenie bez podawania hasła.

Zadanie 6. Folder wspólny. Utwórz folder /common/admin, którego grupą jest sysadmins. Członkowie grupy mogą w nim czytać i pisać, inni nie mają żadnego dostępu. Nowe pliki w tym folderze mają automatycznie dostawać grupę sysadmins. Dodatkowo sarah ma mieć prawo do czytania zawartości folderu, ale nie do pisania.

Zadanie 7. Szukanie. Znajdź wszystkie pliki należące do użytkownika harry i skopiuj je z zachowaniem uprawnień do folderu /root/znalezione. Wszystkie linijki z pliku /etc/services, w których występuje słowo ssh, zapisz posortowane do pliku /root/ssh-linie.

Zadanie 8. Archiwum. Zrób archiwum całego folderu /etc skompresowane bzip2 i zapisz je jako /root/etc-backup.tar.bz2.

Zadanie 9. Harmonogram. Użytkownik natalia ma codziennie o 14:23 wysyłać do dziennika systemowego wiadomość egzamin trwa.

Zadanie 10. LVM. Na drugim dysku utwórz partycję GPT o rozmiarze 1 GiB. Zrób na niej grupę woluminów dane_vg z rozmiarem fizycznego kawałka (PE) 8 MiB i wolumin logiczny dane_lv o rozmiarze 500 MiB. Załóż na nim system plików XFS i zamontuj na stałe w /dane, używając UUID.

Zadanie 11. Swap. Na tym samym dysku dodaj partycję wymiany 256 MiB i włącz ją na stałe. Istniejący swap ma zostać.

Zadanie 12. Powiększenie. Powiększ dane_lv do 800 MiB razem z systemem plików, bez odmontowywania.

Zadanie 13. Strona WWW. Apache ma serwować stronę z tekstem Egzamin OK na porcie 82. Strona ma być osiągalna z innych maszyn i ma wstawać po restarcie. SELinux zostaje w trybie Enforcing.

Zadanie 14. Start i dziennik. Ustaw system tak, żeby domyślnie startował w trybie tekstowym. Dziennik systemowy ma przetrwać restart.

Zadanie 15. Wydajność i czas. Ustaw profil tuned, który system sam poleca. Skonfiguruj klienta czasu, żeby synchronizował się z serwerem zegar.lab.local. Uruchom sleep 3600 w tle z priorytetem nice 10.

Zadanie 16. Skrypt. Napisz skrypt /usr/local/bin/kto.sh. Dla każdego podanego argumentu, nazwy użytkownika, wypisuje jego UID i powłokę, a jeśli taki użytkownik nie istnieje, wypisuje brak. Uruchomiony bez argumentów wypisuje na stderr, jak go używać, i kończy się kodem 2.

Zadanie 17. Jądro. Włącz na stałe przekazywanie pakietów IPv4 (net.ipv4.ip_forward).

Zadanie 18. Certyfikat. Wygeneruj certyfikat z podpisem własnym dla nazwy egzamin.lab.local, ważny 365 dni, z kluczem RSA 2048. Certyfikat zapisz w /root/egz.crt, klucz w /root/egz.key, dostępny tylko dla roota.

Zadanie 19. Git. Utwórz repozytorium Git w /root/konfig, skopiuj do niego /etc/hosts i zrób commit z opisem.

Zadanie 20. Kontener. Uruchom kontener z serwerem nginx o nazwie www, dostępny na porcie 8081 maszyny. Kontener ma się sam uruchamiać po restarcie systemu.

Koniec czasu. Teraz restart:

sudo systemctl reboot

Sprawdzamy: skrypt egzaminatora

Prawdziwy egzaminator też ma skrypt. Nie patrzy w Twoją historię komend. Patrzy na stan systemu. Napisałem podobny. Każdy test to mała funkcja, która zwraca kciuk w górę albo w dół. Uwaga na pierwszą linijkę: jest set -uo pipefail, ale celowo bez -e. Tu oblany test nie może zatrzymać całego skryptu, ma tylko odjąć punkt.

#!/usr/bin/env bash
# sprawdz_egzamin.sh — automatycznie sprawdza zadania z egzaminu próbnego
# Uruchom jako root PO RESTARCIE maszyny: sudo ./sprawdz_egzamin.sh
set -uo pipefail          # bez -e: oblany test ma odjąć punkt, a nie zatrzymać skrypt

punkty=0
razem=0

sprawdz() {               # sprawdz "opis" funkcja_testowa
    local opis=$1; shift
    razem=$((razem + 1))
    if "$@" >/dev/null 2>&1; then
        printf '[ OK ] %s\n' "$opis"
        punkty=$((punkty + 1))
    else
        printf '[ -- ] %s\n' "$opis"
    fi
}

# --- testy: każdy zwraca 0 (zaliczone) albo coś innego (oblane) ---
t_host()    { [[ $(hostname) == serwer1.lab.local ]]; }
t_grupa()   { id -nG natalia | grep -qw sysadmins && id -nG harry | grep -qw sysadmins; }
t_sarah()   { [[ $(getent passwd sarah | cut -d: -f7) == */nologin ]]; }
t_chage()   { [[ $(getent shadow natalia | cut -d: -f5) == 90 ]]; }
t_sudo()    { sudo -l -U harry | grep -q 'NOPASSWD: ALL'; }
t_folder()  { [[ $(stat -c '%G %a' /common/admin) == 'sysadmins 2770' ]]; }
t_acl()     { getfacl -p /common/admin | grep -qx 'user:sarah:r-x'; }
t_find()    { [[ -n $(find /root/znalezione -type f -user harry) ]]; }
t_grep()    { [[ -s /root/ssh-linie ]] && ! grep -qvw ssh /root/ssh-linie; }
t_tar()     { tar -tjf /root/etc-backup.tar.bz2; }
t_cron()    { crontab -l -u natalia | grep -Eq '^23[[:space:]]+14[[:space:]]'; }
t_fstab()   { grep -Eq '^UUID=[^[:space:]]+[[:space:]]+/dane[[:space:]]+xfs' /etc/fstab; }
t_mount()   { findmnt /dane; }
t_lv()      { local mb; mb=$(lvs --noheadings --units m --nosuffix -o lv_size dane_vg/dane_lv)
              (( ${mb%.*} >= 800 )); }
t_swap()    { [[ -n $(swapon --noheadings --show) ]] && grep -Eq '[[:space:]]swap[[:space:]]' /etc/fstab; }
t_www()     { curl -s http://localhost:82/ | grep -q 'Egzamin OK'; }
t_target()  { [[ $(systemctl get-default) == multi-user.target ]]; }
t_journal() { [[ -d /var/log/journal ]]; }
t_sysctl()  { grep -rqsE '^net\.ipv4\.ip_forward[[:space:]]*=[[:space:]]*1' /etc/sysctl.d/ &&
              [[ $(sysctl -n net.ipv4.ip_forward) == 1 ]]; }
t_cert()    { openssl x509 -in /root/egz.crt -noout -subject | grep -q 'egzamin.lab.local' &&
              openssl x509 -in /root/egz.crt -noout -checkend $((300 * 86400)); }
t_git()     { git -C /root/konfig log --oneline -1 -- hosts | grep -q .; }
t_skrypt()  { /usr/local/bin/kto.sh; [[ $? -eq 2 ]]; }

sprawdz "nazwa hosta serwer1.lab.local"                   t_host
sprawdz "natalia i harry w grupie sysadmins"              t_grupa
sprawdz "sarah bez powłoki interaktywnej"                 t_sarah
sprawdz "hasło natalii ważne maksymalnie 90 dni"          t_chage
sprawdz "harry ma sudo bez hasła"                         t_sudo
sprawdz "/common/admin: grupa sysadmins, prawa 2770"      t_folder
sprawdz "sarah ma ACL r-x na /common/admin"               t_acl
sprawdz "pliki harry'ego w /root/znalezione"              t_find
sprawdz "/root/ssh-linie tylko z linijkami o ssh"         t_grep
sprawdz "archiwum /root/etc-backup.tar.bz2 jest czytelne" t_tar
sprawdz "cron natalii o 14:23"                            t_cron
sprawdz "/dane w fstab po UUID, typ xfs"                  t_fstab
sprawdz "/dane zamontowane"                               t_mount
sprawdz "dane_lv ma co najmniej 800 MB"                   t_lv
sprawdz "swap aktywny i wpisany do fstab"                 t_swap
sprawdz "strona na porcie 82 odpowiada"                   t_www
sprawdz "domyślny cel multi-user.target"                  t_target
sprawdz "dziennik zapisywany na dysk"                     t_journal
sprawdz "ip_forward włączony na stałe"                    t_sysctl
sprawdz "certyfikat egzamin.lab.local ważny > 300 dni"    t_cert
sprawdz "repozytorium /root/konfig z commitem hosts"      t_git
sprawdz "kto.sh bez argumentu kończy się kodem 2"         t_skrypt

echo
echo "Wynik: ${punkty}/${razem}"
(( punkty * 10 >= razem * 7 ))     # kod 0, jeśli co najmniej 70%

Sprawdzanie warunku 70% na liczbach całkowitych, bo Bash nie umie ułamków: zamiast punkty/razem >= 0.7 mnożymy obie strony, punkty*10 >= razem*7. Matematyka z podstawówki ratuje skrypt.

Skrypt przechodzi ShellChecka bez uwag. Na maszynie, na której zrobiłem dopiero część zadań, wynik wyglądał tak (skrócone):

[ -- ] nazwa hosta serwer1.lab.local
[ OK ] natalia i harry w grupie sysadmins
[ OK ] sarah bez powłoki interaktywnej
...
[ -- ] /dane zamontowane
[ -- ] dane_lv ma co najmniej 800 MB
[ -- ] swap aktywny i wpisany do fstab
[ -- ] strona na porcie 82 odpowiada
[ -- ] domyślny cel multi-user.target
...
[ OK ] kto.sh bez argumentu kończy się kodem 2

Wynik: 15/22

Zadań jest dwadzieścia, a testów dwadzieścia dwa, bo niektóre zadania mają po kilka części. Zadań 1, 3, 15 i 20 skrypt nie sprawdza: hasło roota sprawdzisz, logując się, a resztę zobaczysz w rozwiązaniach. Próg zdania przyjmijmy 70%. Organizatorzy podają swoje progi na stronach egzaminów i mogą je zmieniać.

Rozwiązania krok po kroku

1. Hasło roota. To przerwanie startu systemu. Instrukcja z dokumentacji Red Hata wygląda tak. Restart, w menu GRUB-a strzałką na pierwszą pozycję i klawisz e. W linijce zaczynającej się od linux dopisujesz na końcu rd.break i startujesz przez Ctrl+X. Lądujesz w powłoce awaryjnej, zanim system wystartuje na dobre. Prawdziwy dysk jest podpięty w /sysroot i to tylko do odczytu.

mount -o remount,rw /sysroot
chroot /sysroot
passwd root                 # wpisujesz Egzamin2026 dwa razy
touch /.autorelabel
exit
exit

chroot to przeprowadzka: od tej chwili / to Twój prawdziwy system, a nie mini-system startowy. touch /.autorelabel to kartka dla SELinuksa: „przy następnym starcie przepisz wszystkie etykiety plików”. Bez tego plik /etc/shadow ma złą etykietę, bo zmieniłeś go w momencie, kiedy SELinux nie pilnował, i po starcie nikt nie może się zalogować. Nawet z nowym hasłem. Ponowne etykietowanie trwa chwilę i system sam się raz zrestartuje. Nie panikuj, to normalne.

[Ubuntu] Tu zamiast rd.break zwykle dopisuje się init=/bin/bash, potem mount -o remount,rw / i passwd. SELinuksa nie ma, więc kartki dla niego też nie.

2. Sieć.

hostnamectl set-hostname serwer1.lab.local
nmcli connection show                       # jak nazywa się Twoje połączenie?
nmcli connection modify enp0s8 ipv4.method manual \
    ipv4.addresses 192.168.56.50/24 ipv4.gateway 192.168.56.1 ipv4.dns 192.168.56.1
nmcli connection up enp0s8
ip -4 addr show enp0s8

Nazwa enp0s8 jest przykładowa. Weź swoją z nmcli connection show. Kolejność ma znaczenie: najpierw adres, potem method manual. Jeśli ustawisz manual bez adresu, nmcli odmówi. Na egzaminie robisz to przy konsoli maszyny, nie przez SSH. Zmiana adresu przez SSH to piłowanie gałęzi, na której siedzisz.

[Ubuntu] Sieć ustawiasz w pliku netplan w /etc/netplan/ i sudo netplan try, który sam cofnie zmiany po 120 sekundach, jeśli nie potwierdzisz. Najlepszy wynalazek dla ludzi, którzy pracują zdalnie.

3. Oprogramowanie. Płyta podpięta do maszyny, montujemy ją i opisujemy repozytoria:

mkdir -p /mnt/iso
mount -o ro /dev/sr0 /mnt/iso
echo '/dev/sr0  /mnt/iso  iso9660  ro,nofail  0 0' >> /etc/fstab
# /etc/yum.repos.d/lokalne.repo
[BaseOS]
name=BaseOS z płyty
baseurl=file:///mnt/iso/BaseOS
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release

[AppStream]
name=AppStream z płyty
baseurl=file:///mnt/iso/AppStream
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
dnf repolist
dnf install -y httpd

Na Rocky i Almie plik z kluczem nazywa się inaczej: podejrzyj ls /etc/pki/rpm-gpg/. Jeśli zadanie ma podany adres zamiast płyty, w baseurl wpisujesz http://... zamiast file://....

Flatpak, nowość na RHEL 10. Według dokumentacji Red Hata:

dnf install -y flatpak
flatpak remote-add --if-not-exists rhel https://flatpaks.redhat.io/rhel.flatpakrepo
flatpak remotes
flatpak search calculator
flatpak install rhel <identyfikator-aplikacji>

Repozytorium Red Hata wymaga zalogowania do rejestru (podman login flatpaks.registry.redhat.io). Na egzaminie dostaniesz adres repozytorium w treści zadania, a komendy są te same. Na Rocky albo Almie możesz poćwiczyć z Flathubem.

4. Użytkownicy.

groupadd sysadmins
useradd -G sysadmins natalia
useradd -G sysadmins harry
useradd -s /sbin/nologin sarah
for u in natalia harry sarah; do echo "$u:Trudne123!" | chpasswd; done
id natalia
getent passwd sarah
uid=1001(natalia) gid=1002(natalia) groups=1002(natalia),1001(sysadmins)
sarah:x:1003:1004::/home/sarah:/sbin/nologin

-G duże, czyli grupa dodatkowa. Małe -g zmieniłoby grupę główną, a o to zadanie nie prosiło. Jedna litera, a różnica jak między „dopisz do listy gości” i „przemelduj na stałe”. Numery UID i GID u Ciebie mogą być inne.

[Ubuntu] useradd nie zakłada folderu domowego bez -m, a nologin leży w /usr/sbin/nologin. Dlatego skrypt sprawdzający patrzy tylko na końcówkę */nologin.

5. Hasła i sudo.

chage -M 90 natalia
chage -l natalia | grep -i maximum
echo 'harry ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/harry
chmod 440 /etc/sudoers.d/harry
visudo -cf /etc/sudoers.d/harry
sudo -l -U harry
Maximum number of days between password change		: 90
/etc/sudoers.d/harry: parsed OK
User harry may run the following commands on serwer1:
    (ALL) NOPASSWD: ALL

Nigdy nie edytuj głównego /etc/sudoers echem. Osobny plik w sudoers.d i od razu visudo -c. Jeden plik, jeden człowiek, jedno sprawdzenie.

6. Folder wspólny.

mkdir -p /common/admin
chgrp sysadmins /common/admin
chmod 2770 /common/admin
setfacl -m u:sarah:rx /common/admin
ls -ld /common/admin
getfacl -p /common/admin
drwxrws---+ 2 root sysadmins 4096 Nov  4 08:24 /common/admin
# file: /common/admin
# owner: root
# group: sysadmins
# flags: -s-
user::rwx
user:sarah:r-x
group::rwx
mask::rwx
other::---

2770 rozbieramy na kawałki: 2 to SGID, czyli „wszystko, co tu powstanie, dostaje grupę folderu”. 7 dla właściciela, 7 dla grupy, 0 dla reszty. Literka s w miejscu x grupy to właśnie SGID, a plusik na końcu znaczy „uwaga, są tu jeszcze listy ACL”. Sprawdziłem w praktyce: plik założony przez natalię dostał grupę sysadmins, a sarah może zrobić ls, ale touch kończy się Permission denied. ACL to dorobiony klucz do jednego pokoju dla jednej osoby, bez wpisywania jej do całej grupy.

7. Szukanie.

mkdir -p /root/znalezione
find / -user harry -type f -exec cp -a {} /root/znalezione/ \; 2>/dev/null
ls -la /root/znalezione
grep -w ssh /etc/services | sort > /root/ssh-linie

2>/dev/null wycisza krzyki z /proc, gdzie pliki znikają w trakcie szukania. cp -a zachowuje właściciela, prawa i daty. Na świeżym koncie harry'ego find znajdzie pliki z /etc/skel w jego domu i jego skrzynkę pocztową w /var/spool/mail. grep -w szuka całego słowa, więc nie złapie na przykład sshell.

8. Archiwum.

tar -cjf /root/etc-backup.tar.bz2 /etc
tar -tjf /root/etc-backup.tar.bz2 | head -3
tar: Removing leading `/' from member names
etc/
etc/rc2.d/

j to bzip2, z to gzip, J to xz. Komunikat o usuwaniu / to nie błąd, to troska: tar nie pozwoli, żeby rozpakowanie archiwum nadpisało Ci prawdziwe /etc.

9. Harmonogram.

crontab -e -u natalia
23 14 * * * logger "egzamin trwa"

Minuta, godzina, dzień miesiąca, miesiąc, dzień tygodnia. Najpierw minuta. Jeśli napiszesz 14 23, natalia będzie pisać do dziennika o 23:14, jak nastolatka w social mediach.

10. LVM. Najpierw sprawdź nazwę dysku. Na maszynach wirtualnych to zwykle /dev/vdb albo /dev/sdb. Pomyłka dysku w tym zadaniu to formatowanie systemu, więc dwa razy patrzysz w lsblk, zanim naciśniesz Enter.

lsblk
parted -s /dev/vdb mklabel gpt mkpart dane 1MiB 1025MiB set 1 lvm on
pvcreate /dev/vdb1
vgcreate -s 8M dane_vg /dev/vdb1
lvcreate -n dane_lv -L 500M dane_vg
mkfs.xfs /dev/dane_vg/dane_lv
mkdir /dane
blkid /dev/dane_vg/dane_lv

Teraz /etc/fstab. UUID z blkid, a nie ręcznie przepisywany, bo jedna literówka w 36 znakach i system nie wstanie:

echo "UUID=$(blkid -s UUID -o value /dev/dane_vg/dane_lv)  /dane  xfs  defaults  0 0" >> /etc/fstab
systemctl daemon-reload
mount -a
findmnt --verify
findmnt /dane

Partycje sprawdzałem na dysku z pliku (loop device): parted dało dokładnie taki układ, a blkid taki wynik dla XFS:

Number  Start   End     Size    File system  Name    Flags
 1      1049kB  1075MB  1074MB               dane    lvm
/dev/loop0p1: UUID="87d1c373-727f-4ba9-8fe0-ac3f382ed89e" BLOCK_SIZE="512" TYPE="xfs" PARTLABEL="dane" ...

[Ubuntu] Wszystko tak samo, tylko mkfs.xfs może wymagać pakietu xfsprogs.

11. Swap.

parted -s /dev/vdb mkpart swapik linux-swap 1025MiB 1281MiB
mkswap /dev/vdb2
echo "UUID=$(blkid -s UUID -o value /dev/vdb2)  none  swap  defaults  0 0" >> /etc/fstab
swapon -a
swapon --show
NAME      TYPE      SIZE USED PRIO
/dev/vdb2 partition 256M   0B   -2

U Ciebie w wyniku będzie też stary swap. Ma zostać, zadanie mówi o tym wprost. „Dodaj” to nie „zamień”.

12. Powiększenie.

lvextend -r -L 800M /dev/dane_vg/dane_lv
df -h /dane

-r to „resize filesystem”: lvextend sam powiększy XFS-a, za Ciebie wywoła xfs_growfs. Bez -r regał na kółkach jest większy, ale półki w środku zostały stare. XFS można powiększać, ale nie można go zmniejszać. Zapamiętaj to, to ulubione pytanie rekrutacyjne.

13. Strona WWW na porcie 82. Tu spotykają się trzy ochroniarze: Apache, SELinux i zapora. Każdego trzeba przekonać osobno.

echo 'Egzamin OK' > /var/www/html/index.html
sed -i 's/^Listen 80$/Listen 82/' /etc/httpd/conf/httpd.conf
semanage port -l | grep '^http_port_t'
semanage port -a -t http_port_t -p tcp 82
restorecon -Rv /var/www/html
systemctl enable --now httpd
firewall-cmd --permanent --add-port=82/tcp
firewall-cmd --reload
curl http://localhost:82/

Bez semanage port Apache nie wstanie, a w dzienniku znajdziesz komunikat, który podaje dokumentacja Red Hata: (13)Permission denied: AH00072: make_sock: could not bind to address. Uprawnienia są w porządku, port wolny, a SELinux mówi „Apache ma prawo tylko do okienek z etykietą http_port_t”. restorecon to bonus: jeśli plik strony zrobiłeś w /root i przeniosłeś mv, zabrał ze sobą złą etykietę i Apache dostanie 403.

[Ubuntu] Pakiet apache2, port w /etc/apache2/ports.conf, zapora ufw allow 82/tcp, a SELinuksa nie ma. AppArmor domyślnie Apache'owi w tym nie przeszkadza.

14. Start i dziennik.

systemctl set-default multi-user.target
mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nStorage=persistent\n' > /etc/systemd/journald.conf.d/trwaly.conf
systemctl restart systemd-journald
journalctl --list-boots

Według dokumentacji systemd Storage=persistent każe trzymać dziennik w /var/log/journal, a przy ustawieniu auto o wszystkim decyduje to, czy ten folder istnieje. Dlatego samo mkdir /var/log/journal też często wystarcza. Po kolejnym restarcie journalctl --list-boots ma pokazać więcej niż jeden start.

15. Wydajność i czas.

dnf install -y tuned
systemctl enable --now tuned
tuned-adm recommend
tuned-adm profile virtual-guest      # to, co pokazało recommend
tuned-adm active

Na maszynie wirtualnej recommend zwykle odpowiada virtual-guest. Wpisz to, co Ci pokazało, a nie to, co ja powiedziałem.

Czas: w /etc/chrony.conf [RHEL] albo /etc/chrony/chrony.conf [Ubuntu] zakomentuj linijki pool, dopisz:

server zegar.lab.local iburst
systemctl restart chronyd          # [Ubuntu]: chrony
chronyc sources

I priorytet:

nice -n 10 sleep 3600 &
ps -o pid,ni,cmd -C sleep

16. Skrypt.

#!/bin/bash
# kto.sh — dla każdego podanego użytkownika wypisuje UID i powłokę
if [ $# -eq 0 ]; then
    echo "Użycie: $0 UŻYTKOWNIK..." >&2
    exit 2
fi
kod=0
for u in "$@"; do
    if wpis=$(getent passwd "$u"); then
        IFS=: read -r _ _ uid _ _ _ powloka <<< "$wpis"
        echo "$u: UID $uid, powłoka $powloka"
    else
        echo "$u: brak"
        kod=1
    fi
done
exit "$kod"
install -m 755 kto.sh /usr/local/bin/kto.sh
kto.sh; echo "kod=$?"
kto.sh root sarah duch; echo "kod=$?"
Użycie: /usr/local/bin/kto.sh UŻYTKOWNIK...
kod=2
root: UID 0, powłoka /bin/bash
sarah: UID 1003, powłoka /sbin/nologin
duch: brak
kod=1

IFS=: read z here-stringiem rozkłada linijkę z /etc/passwd na pola, jak sortowanie listów do przegródek. Podkreślniki to pola, które nas nie interesują. ShellCheck: zero uwag.

17. Jądro.

echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/90-egzamin.conf
sysctl --system
sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

Samo sysctl -w działa tylko do restartu. Plik w /etc/sysctl.d to wersja na stałe. Egzaminator zrestartuje maszynę, więc wersja „do restartu” to zero punktów.

18. Certyfikat.

openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
    -subj "/CN=egzamin.lab.local" -keyout /root/egz.key -out /root/egz.crt
chmod 600 /root/egz.key
openssl x509 -in /root/egz.crt -noout -subject -enddate
subject=CN = egzamin.lab.local
notAfter=Nov  4 07:26:29 2027 GMT

-x509 to „od razu certyfikat, a nie prośba o certyfikat”. -nodes to „no DES”, czyli klucz bez hasła, inaczej Apache przy każdym starcie pytałby Cię o hasło jak bankomat.

19. Git.

git init /root/konfig
cp /etc/hosts /root/konfig/
cd /root/konfig
git add hosts
git -c user.name="Admin" -c user.email="admin@lab.local" commit -m "Kopia /etc/hosts"
git log --oneline
ec253e9 Kopia /etc/hosts

Jeśli Git przy commicie zapyta „Who are you?”, nie panikuj. Albo -c jak wyżej, albo raz git config --global user.name i user.email.

20. Kontener.

[Ubuntu] z Dockerem:

docker run -d --name www --restart unless-stopped -p 8081:80 nginx
curl -sI http://localhost:8081 | head -1

[RHEL] z Podmanem. Podman nie ma demona, który pilnowałby kontenerów po restarcie, więc --restart sam nie wystarczy. Potrzebna jest usługa, która je podniesie:

podman run -d --name www --restart always -p 8081:80 docker.io/library/nginx
systemctl enable podman-restart.service

Jak to działa pod spodem

Czemu egzaminy praktyczne restartują maszynę? Bo w Linuksie są dwie warstwy prawdy. Pierwsza to stan w pamięci: działające procesy, adresy na kartach, zamontowane dyski, wartości w /proc/sys. Druga to pliki na dysku: /etc/fstab, /etc/sysctl.d, pliki połączeń NetworkManagera, linki w /etc/systemd/system tworzone przez enable. Przy starcie systemd, nasz kierownik zmiany, czyta tę drugą warstwę i z niej odbudowuje pierwszą.

Prawie każde zadanie ma więc dwie komendy: jedną „teraz” i jedną „na stałe”. mount i wpis w fstab. sysctl -w i plik w sysctl.d. systemctl start i enable. firewall-cmd i --permanent. swapon i fstab. setenforce i /etc/selinux/config. Najczęstsza przyczyna oblania nie jest taka, że ktoś czegoś nie umie. Ktoś zrobił tylko połowę „teraz”.

A skrypt sprawdzający? Działa jak egzaminator: nie czyta historii Basha, tylko odpytuje system. findmnt, swapon --show, systemctl get-default, sudo -l -U. Każdy test to kciuk w górę albo w dół, a sprawdz je liczy.

Ściąga: „teraz” kontra „na stałe”

CoTeraz (do restartu)Na stałe
system plikówmount /dev/... /danewpis w /etc/fstab + mount -a
swapswapon /dev/vdb2wpis swap w /etc/fstab
parametr jądrasysctl -w klucz=wartośćplik w /etc/sysctl.d/ + sysctl --system
usługasystemctl startsystemctl enable (albo enable --now)
zapora RHELfirewall-cmd --add-port=82/tcpto samo z --permanent + --reload
zapora Ubuntu—ufw allow 82/tcp (od razu na stałe)
SELinuxsetenforce 0/1SELINUX= w /etc/selinux/config
port dla SELinuksa—semanage port -a (od razu na stałe)
cel startusystemctl isolate multi-user.targetsystemctl set-default multi-user.target
nazwa hostahostname nazwahostnamectl set-hostname nazwa
adres IPip addr addnmcli connection modify [RHEL], netplan [Ubuntu]
dziennik—Storage=persistent albo folder /var/log/journal

⚠ Typowy błąd

Literówka w /etc/fstab. Klasyka egzaminu i klasyka życia. Wpisujesz UUID ręcznie, mylisz jedną literkę, nie sprawdzasz, restartujesz na koniec egzaminu. A system wita Cię tak:

You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, "systemctl default" or "exit"
to boot into default mode.
Give root password for maintenance
(or press Control-D to continue):

Tryb awaryjny. Systemd czekał na dysk, którego nie ma, i się poddał. A teraz najgorsze: jeśli wcześniej nie zrobiłeś zadania 1, czyli nie znasz hasła roota, nawet tu nie wejdziesz. Egzamin ma poczucie humoru.

Naprawa, zanim cokolwiek się stanie: po każdej zmianie w fstab robisz mount -a i findmnt --verify. Sprawdziłem, co mówi findmnt przy UUID z jedną złą literą na końcu:

/dane
   [E] unreachable on boot required source: UUID=87d1c373-727f-4ba9-8fe0-ac3f382ed89f
0 parse errors, 1 error, 0 warnings

A przy poprawnym:

Success, no errors or warnings detected

Jeśli już wylądowałeś w trybie awaryjnym: podajesz hasło roota, journalctl -xb | grep -i fail, poprawiasz /etc/fstab, systemctl daemon-reload, mount -a, exit. Trzy minuty, jeśli wiesz, co robić. Pół egzaminu, jeśli nie wiesz.

Drugi błąd, równie popularny: ktoś robi wszystko przez SSH, a w zadaniu 2 zmienia adres karty, przez którą jest połączony. Połączenie zrywa się w połowie komendy. Dlatego zadania sieciowe robisz z konsoli maszyny wirtualnej, a na prawdziwym serwerze zawsze z otwartą drugą sesją albo dostępem do konsoli.

✎ Zadanie

Zrób egzamin jeszcze raz za tydzień, od czystej migawki, z zegarem na dwie godziny i bez zaglądania do tej lekcji. Przy każdym zadaniu zanotuj czas, w którym je skończyłeś. Na koniec uruchom skrypt sprawdzający i porównaj wynik z pierwszym podejściem. Zadania, które zajęły Ci ponad 10 minut albo które oblałeś, przećwicz po trzy razy na czystej maszynie.

Pokaż rozwiązanie

Zadanie było proste do powiedzenia i trudne do zrobienia: egzamin drugi raz, od czystej migawki, z zegarem i z notowaniem czasu każdego zadania.

Notowanie czasu na kartce jest w porządku, ale jesteśmy adminami, więc zrobimy to tak, jak robią admini: jedną funkcją. Wklejasz ją do terminala przed startem egzaminu:

z() { printf '%s %s\n' "$(date +%T)" "$1" >> ~/egzamin-czasy.txt; }

Zaczynasz zadanie 1? Wpisujesz z 1. Zaczynasz zadanie 2? z 2. Na koniec z koniec. Funkcja to przycisk w windzie: jedno naciśnięcie, a w środku dzieje się wszystko, co trzeba. Potem mały skrypt, który policzy różnice i pokaże, gdzie utknąłeś:

#!/usr/bin/env bash
# czasy.sh — liczy, ile minut zajęło każde zadanie egzaminu
# Plik wejściowy: linijki "GG:MM:SS numer_zadania" (start) i na końcu "GG:MM:SS koniec"
set -euo pipefail
plik=${1:-$HOME/egzamin-czasy.txt}
[[ -r $plik ]] || { echo "Brak pliku: $plik" >&2; exit 1; }

awk '
function sek(t,  a) { split(t, a, ":"); return a[1]*3600 + a[2]*60 + a[3] }
NR > 1 { min = (sek($1) - sek(poprz_czas)) / 60
         printf "zadanie %-3s %5.1f min%s\n", poprz_zad, min, (min > 10 ? "   <-- do powtórki" : "") }
{ poprz_czas = $1; poprz_zad = $2 }
' "$plik"

awk czyta plik linijka po linijce i zawsze pamięta poprzednią. Czas zadania to różnica między jego startem a startem następnego. Funkcja sek zamienia godzinę na sekundy, bo odejmowanie „10:21:10 minus 10:07:30” to coś, czego nie lubi nikt, łącznie z awk. Na moim przykładowym pliku:

zadanie 1     7.5 min
zadanie 2    13.7 min   <-- do powtórki
zadanie 3     4.8 min
zadanie 4     3.7 min
zadanie 5    14.4 min   <-- do powtórki
zadanie 10    7.9 min

ShellCheck czysty, a bez pliku skrypt grzecznie mówi „Brak pliku” na stderr i kończy się kodem 1.

Co zwykle wychodzi przy drugim podejściu? Zadania z użytkownikami i uprawnieniami lecą dwa razy szybciej, bo to pamięć mięśniowa. Za to sieć, LVM i SELinux dalej bolą. To dobrze. To znaczy, że wiesz, co ćwiczyć. Trzy powtórki każdego czerwonego zadania na czystej maszynie i za trzecim razem robisz je, myśląc o obiedzie.

Co dalej

Ostatnia lekcja kursu. Co dalej: który certyfikat wybrać, jak zbudować domowe laboratorium, czego uczyć się po Bashu i jak szukać pierwszej pracy jako admin w Polsce. I szczera rozmowa o tym, co dał Ci ten kurs, a czego żaden kurs nie da.