Kontenery, część 1
Po tej lekcji: kursant rozumie różnicę między obrazem a kontenerem i umie w Dockerze albo Podmanie uruchomić usługę w kontenerze, wystawić jej port, podejrzeć logi, wejść do środka, podpiąć folder i połączyć kontenery własną siecią.
Wstęp
Znasz to zdanie. Program działa na laptopie programisty, a na serwerze wywala się, bo jest inna wersja PHP, brakuje jednej biblioteki, a plik konfiguracyjny leży gdzie indziej. Przez lata admini i programiści przerzucali się tym zdaniem jak gorącym ziemniakiem.
Kontenery skończyły tę zabawę. Programista nie wysyła już instrukcji „zainstaluj to, potem tamto, potem się pomódl”. Wysyła gotowe pudełko, w którym program ma wszystko, czego potrzebuje. Ty to pudełko stawiasz na serwerze jedną komendą.
Dziś zrobimy dokładnie to: serwer WWW w kontenerze, w kilka sekund, bez instalowania go w systemie. Podepniemy mu własną stronę, zajrzymy do jego logów i do środka, a na koniec dwa kontenery pogadają ze sobą po nazwie.
Kontener mieszkalny na budowie
Kontener to kontener mieszkalny na budowie. Przyjeżdża na ciężarówce, w środku ma już łóżko, prąd, kuchenkę i szafkę. Stawiasz go na placu, podłączasz do prądu i wody z sieci budowy i można mieszkać. Nie budujesz domu od fundamentów.
Ważne: kontener nie ma własnej elektrowni. Korzysta z prądu placu budowy. Tak samo kontener w Linuksie nie ma własnego jądra. Korzysta z jądra systemu, na którym stoi. Dlatego startuje w ułamek sekundy, a nie w minutę jak maszyna wirtualna.
Teraz trzy słowa, które musisz rozróżniać.
Obraz (image) to model z katalogu fabryki: „Kontener biurowy B-200, z biurkiem i klimatyzacją”. Jeden projekt, niezmienny, tylko do odczytu.
Kontener to konkretny egzemplarz postawiony na placu. Z jednego obrazu możesz postawić dziesięć kontenerów. Każdy ma własny bałagan w środku, ale wszystkie zaczynały tak samo.
Rejestr to hurtownia z katalogiem obrazów. Najbardziej znane: Docker Hub (docker.io) i Quay (quay.io). Red Hat ma swój: registry.access.redhat.com.
Docker czy Podman
Dwa narzędzia, prawie ten sam język. Komendy są tak podobne, że wiele osób robi sobie alias docker=podman i nie zauważa różnicy.
Docker to ten, od którego wszystko się zaczęło. Działa jak recepcja z jednym kierownikiem: w tle cały czas chodzi usługa dockerd, a komenda docker tylko do niej dzwoni i prosi o kontener.
Podman nie ma stałego kierownika w tle. Komenda podman sama uruchamia kontener i odchodzi. To narzędzie, które Red Hat wbudował w swój system.
[RHEL] Red Hat w ogóle nie dostarcza Dockera i go nie wspiera. Instalujesz komplet narzędzi do kontenerów:
sudo dnf install container-toolsJeśli bardzo chcesz pisać docker, jest paczka podman-docker, która podstawia Podmana pod tę nazwę.
[Ubuntu] Masz oba w repozytorium:
sudo apt install podman
# albo
sudo apt install docker.ioW Dockerze, żeby nie pisać ciągle sudo, dodaje się użytkownika do grupy docker. Uwaga: sama dokumentacja Dockera mówi, że ta grupa daje uprawnienia równe rootowi. Kto jest w grupie docker, ten ma klucz do całego budynku, nie tylko do pokoju z kontenerami.
Dalej piszę podman, bo tak będzie na egzaminie RHCSA. W Dockerze zamieniasz jedno słowo i reszta zostaje.
Pierwszy kontener
podman run -d --name web -p 8080:80 docker.io/library/nginxRozbieramy to na części:
run— pobierz obraz, jeśli go nie ma, i postaw z niego kontener,-d— w tle (detached), żeby nie blokował terminala,--name web— nazwa, żeby nie posługiwać się losowym numerem,-p 8080:80— numer okienka. Port 8080 na serwerze prowadzi do portu 80 w kontenerze. Najpierw host, potem kontener: „moje okienko : jego okienko”,docker.io/library/nginx— pełna nazwa obrazu: hurtownia, półka, model.
Czemu pełna nazwa? Bo Podman przy krótkiej nazwie nginx może zapytać, z której hurtowni go wziąć. Pełna nazwa to jak adres z kodem pocztowym: nikt nie zgaduje.
podman psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
891ebc2feb81 docker.io/library/nginx:latest nginx -g daemon o... 3 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp webps to lista kontenerów, które działają. Kolumna PORTS mówi, które okienko prowadzi do którego. Sprawdzamy, czy ktoś odpowiada:
curl -sI http://localhost:8080 | head -1HTTP/1.1 200 OKSerwer WWW działa, a Ty nie zainstalowałeś w systemie ani jednej paczki nginx.
Jak masz zaporę, a chcesz wejść z innego komputera, pamiętaj o otwarciu portu 8080: ufw na Ubuntu, firewall-cmd na RHEL-u.
Logi i wejście do środka
Kontener w tle nie pisze do Twojego terminala. Gdzie są jego komunikaty? Program w kontenerze pisze na swoje standardowe wyjście, czyli głośnik numer 1 i 2, a Podman to nagrywa:
podman logs web
podman logs -f --tail 20 web10.88.0.1 - - [03/Nov/2026:15:30:12 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0"To nasze zapytanie z curl. -f działa jak tail -f: patrzysz na żywo, Ctrl+C kończy patrzenie, a kontener działa dalej.
A jak chcesz wejść do kontenera i się rozejrzeć? exec, czyli „wykonaj w środku”:
podman exec web cat /etc/os-release
podman exec -it web bash-it to dwie flagi: -i trzyma otwarte wejście, -t daje terminal. Razem: możesz pisać w środku jak w zwykłej powłoce. exit wychodzi, a kontener zostaje. Jeśli obraz jest bardzo mały, na przykład na Alpine, Basha może nie być. Wtedy sh.
A kto w ogóle działa w środku?
podman top webUSER PID PPID %CPU ELAPSED TTY TIME COMMAND
root 1 0 0.000 23.925827228s ? 0s nginx: master process nginx -g daemon off;
www-data 6 1 0.000 23.925900082s ? 0s nginx: worker process
www-data 7 1 0.000 23.925944363s ? 0s nginx: worker processZobacz PID 1. W normalnym systemie PID 1 to kierownik zmiany, systemd. W kontenerze kierownikiem jest sam nginx. Nie ma tam systemd, crona ani SSH. Jeden kucharz, jedno danie. Jak kucharz wyjdzie, kontener się kończy.
Woluminy, czyli własne meble
Kontener jest jednorazowy. Usuniesz go i wszystko, co w nim zapisałeś, znika razem z nim, jak rzeczy zostawione w kontenerze, który dźwig zabrał z placu. Dane trzeba trzymać poza nim. Do tego służy -v.
Pierwszy sposób: podpięty folder (bind mount). Wstawiasz do kontenera drzwi do konkretnego folderu na serwerze:
mkdir strona
echo '<h1>Witaj z kontenera</h1>' > strona/index.html
podman run -d --name web2 -p 8081:80 -v ./strona:/usr/share/nginx/html:ro docker.io/library/nginx
curl -s localhost:8081
echo '<h1>Zmiana na żywo</h1>' > strona/index.html
curl -s localhost:8081<h1>Witaj z kontenera</h1>
<h1>Zmiana na żywo</h1>Zmieniłeś plik na serwerze, a kontener od razu podaje nową wersję. Bez restartu. :ro na końcu znaczy „tylko do odczytu”: kontener może czytać stronę, ale jej nie zmieni. Format zawsze ten sam: skąd:dokąd:opcje.
Drugi sposób: nazwany wolumin. Podman sam trzyma folder, a Ty posługujesz się nazwą. Dobre dla baz danych, gdzie nie obchodzi Cię, gdzie leżą pliki, byle przetrwały:
podman volume create dane
podman run --rm -v dane:/dane docker.io/library/ubuntu bash -c 'echo zapisane > /dane/plik.txt'
podman run --rm -v dane:/dane docker.io/library/ubuntu cat /dane/plik.txt
podman volume inspect dane --format '{{.Mountpoint}}'zapisane
/var/lib/containers/storage/volumes/dane/_dataPierwszy kontener zapisał i zniknął, bo --rm sprząta kontener po zakończeniu. Drugi, zupełnie nowy, przeczytał to samo. Dane przeżyły swoich właścicieli.
[RHEL] Na systemie z SELinux podpięty folder bez dodatkowej literki skończy się Permission denied w środku kontenera. Rozwiązanie to :Z, i temu poświęcimy kawałek następnej lekcji.
Sieci, czyli osiedle kontenerów
Prawdziwa aplikacja to zwykle kilka kontenerów: strona, baza, może cache. Muszą się jakoś znaleźć. Podman i Docker robią kontenerom wirtualną sieć, jak osiedlową sieć wewnętrzną na placu budowy. Kontenery dostają adresy, a serwer jest dla nich bramą.
podman network lsNETWORK ID NAME DRIVER
2f259bab93aa podman bridgeAle uwaga, haczyk. W domyślnej sieci kontenery nie znają się po imieniu. To jak osiedle bez tabliczek na drzwiach: wiesz, że sąsiad mieszka pod 10.88.0.5, ale nie wiesz, że to Kowalski. Tworzymy własną sieć, a w niej działa książka adresowa:
podman network create lab
podman run -d --name backend --network lab docker.io/library/nginx
podman run --rm --network lab docker.io/library/alpine wget -qO- http://backend | grep title
podman run --rm docker.io/library/alpine wget -qO- http://backend<title>Welcome to nginx!</title>
wget: bad address 'backend'W sieci lab nazwa backend działa jak adres. Kontener spoza tej sieci nie ma pojęcia, kto to. W Dockerze jest identycznie: domyślna sieć bridge nie rozwiązuje nazw, własna sieć tak.
Sprzątanie
podman ps -a
podman stop web2
podman rm web2
podman images
podman rmi docker.io/library/alpineps -a pokazuje też kontenery zatrzymane. Status Exited (0) znaczy, że skończył pracę z kciukiem w górę. stop to grzeczna prośba o zamknięcie lokalu, czyli SIGTERM, a po 10 sekundach bez reakcji wyłączenie prądu, SIGKILL. rm zabiera kontener z placu, rmi wyrzuca obraz z katalogu. Kiedy na placu zrobi się śmietnisko:
podman container prune
podman image pruneJak to działa pod spodem
Kontener to nie jest mała maszyna. To zwykły proces na Twoim serwerze, któremu jądro założyło klapki na oczy.
ps -eo pid,user,cmd | grep '[n]ginx: master'
podman inspect web --format '{{.State.Pid}}'13837 root nginx: master process nginx -g daemon off;
13837Ten sam nginx, który w kontenerze ma PID 1, na serwerze ma PID 13837. To ten sam proces, tylko widziany z dwóch stron.
Klapki na oczy nazywają się namespaces, przestrzenie nazw: osobna lista procesów (PID), osobna sieć (net), osobne drzewo folderów (mnt), osobna nazwa komputera (uts), osobni użytkownicy (user). Drugi mechanizm to cgroups, grupy kontrolne: limit, ile procesora i pamięci kontener może zjeść. Trzeci to system plików z warstw: obraz na dole, cienka warstwa do zapisu na górze.
Kto to wszystko ustawia? Niskopoziomowy program uruchomieniowy, runc albo crun. Docker woła go przez swój stały serwis dockerd i containerd. Podman woła go sam, a pilnowanie kontenera zleca małemu programowi conmon. Dlatego w Podmanie nie ma jednej usługi, której awaria kładzie wszystkie kontenery naraz.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
podman run | -d | uruchamia w tle |
podman run | --name NAZWA | nadaje nazwę kontenerowi |
podman run | -p HOST:KONTENER | wystawia port |
podman run | -v SKĄD:DOKĄD[:ro] | podpina folder albo wolumin |
podman run | --rm | usuwa kontener po zakończeniu |
podman run | -it | interaktywnie, z terminalem |
podman run | --network SIEĆ | podłącza do wybranej sieci |
podman run | -e ZMIENNA=wartość | ustawia zmienną środowiskową |
podman ps | -a | pokazuje też zatrzymane |
podman logs | -f, --tail N | logi na żywo, ostatnie N linii |
podman exec | -it | wejście do działającego kontenera |
podman stop | -t SEKUNDY | ile czekać przed SIGKILL |
podman rm | -f | usuwa także działający kontener |
podman network | create, ls, rm | zarządzanie sieciami |
podman volume | create, ls, inspect | zarządzanie woluminami |
⚠ Typowy błąd
Uruchamiasz drugi raz tę samą komendę, bo „coś nie zadziałało”:
Error: creating container storage: the container name "web" is already in use by 891ebc2feb81... You have to remove that container to be able to reuse that name: that name is already in use, or use --replace to instruct Podman to do so.Kontener web dalej stoi na placu, tylko może jest zatrzymany. Nazwa jest jak numer rejestracyjny: dwa auta nie mogą mieć tego samego. Sprawdź podman ps -a, potem podman rm -f web albo dopisz --replace.
A jak zmienisz nazwę, ale zostawisz port:
Error: cannot listen on the TCP port: listen tcp4 :8080: bind: address already in useOkienko 8080 jest już zajęte przez pierwszy kontener. ss -tlnp | grep 8080 pokaże, kto je trzyma. Zmień port po lewej stronie dwukropka, na przykład -p 8082:80. Prawej nie ruszaj, bo to port, na którym słucha program w środku.
I trzeci klasyk: podman run docker.io/library/ubuntu i… nic. W ps -a status Exited (0). Kontener żyje tak długo, jak jego główny proces. Ubuntu bez komendy uruchamia powłokę, która nie ma terminala, więc od razu kończy. To nie awaria, to kontener, który nie miał nic do roboty. Daj mu zadanie albo -it.
✎ Zadanie
- Uruchom kontener nginx o nazwie
stronana porcie 8080 z podpiętym folderem, w którym leży Twój własnyindex.html. Sprawdźcurl, zajrzyj do logów i wejdź do środka przezexec, żeby sprawdzić, jaki system jest w kontenerze. - Utwórz sieć
lab, uruchom w niej dwa kontenery i pokaż, że jeden widzi drugi po nazwie, a kontener spoza sieci nie. - Posprzątaj wszystko po sobie.
Pokaż rozwiązanie
Część pierwsza: własna strona w nginx.
mkdir -p ~/strona
echo '<h1>Moja strona</h1>' > ~/strona/index.html
podman run -d --name strona -p 8080:80 -v ~/strona:/usr/share/nginx/html:ro docker.io/library/nginx
curl -s localhost:8080
podman logs strona | tail -1
podman exec strona cat /etc/os-release | head -1<h1>Moja strona</h1>W logach zobaczysz swoje zapytanie z curl. A os-release w środku powie coś innego niż Twój system: oficjalny obraz nginx jest zbudowany na Debianie. Możesz mieć Rocky Linux na serwerze i Debiana w kontenerze. Jak ambasada: budynek stoi w Warszawie, ale w środku obowiązują inne przepisy.
Część druga: sieć z książką adresową.
podman network create lab
podman run -d --name a --network lab docker.io/library/nginx
podman run -d --name b --network lab docker.io/library/nginx
podman exec b curl -sI http://a | head -1
podman run --rm docker.io/library/alpine wget -qO- http://aHTTP/1.1 200 OK
wget: bad address 'a'Kontener b widzi a po nazwie, bo są na tym samym osiedlu z tabliczkami. Kontener spoza sieci lab błądzi. Sprzątanie:
podman rm -f strona a b
podman network rm labrm -f zatrzymuje i usuwa za jednym razem. Jak eksmisja z kontenera razem z dźwigiem.
Co dalej
Do tej pory braliśmy gotowe obrazy z hurtowni. W następnej lekcji zbudujemy własny z pliku Containerfile, postawimy całą aplikację jednym plikiem compose, uruchomimy kontenery bez roota i zrobimy z kontenera usługę systemd, która sama wstaje po restarcie.