Przejdź do treści
Lekcja 94 z 117

Kurs › Moduł 10 › Lekcja 94

Kontenery, część 1

⏱ ok. 11 min czytania· moduł 10: Usługi i serwery

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-tools

Jeś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.io

W 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/nginx

Rozbieramy 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 ps
CONTAINER 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  web

ps 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 -1
HTTP/1.1 200 OK

Serwer 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 web
10.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 web
USER        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 process

Zobacz 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/_data

Pierwszy 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 ls
NETWORK ID    NAME        DRIVER
2f259bab93aa  podman      bridge

Ale 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/alpine

ps -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 prune

Jak 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;
13837

Ten 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

KomendaFlagaCo robi
podman run-duruchamia w tle
podman run--name NAZWAnadaje nazwę kontenerowi
podman run-p HOST:KONTENERwystawia port
podman run-v SKĄD:DOKĄD[:ro]podpina folder albo wolumin
podman run--rmusuwa kontener po zakończeniu
podman run-itinteraktywnie, z terminalem
podman run--network SIEĆpodłącza do wybranej sieci
podman run-e ZMIENNA=wartośćustawia zmienną środowiskową
podman ps-apokazuje też zatrzymane
podman logs-f, --tail Nlogi na żywo, ostatnie N linii
podman exec-itwejście do działającego kontenera
podman stop-t SEKUNDYile czekać przed SIGKILL
podman rm-fusuwa także działający kontener
podman networkcreate, ls, rmzarządzanie sieciami
podman volumecreate, ls, inspectzarzą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 use

Okienko 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

  1. Uruchom kontener nginx o nazwie strona na porcie 8080 z podpiętym folderem, w którym leży Twój własny index.html. Sprawdź curl, zajrzyj do logów i wejdź do środka przez exec, żeby sprawdzić, jaki system jest w kontenerze.
  2. Utwórz sieć lab, uruchom w niej dwa kontenery i pokaż, że jeden widzi drugi po nazwie, a kontener spoza sieci nie.
  3. 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://a
HTTP/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 lab

rm -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.