Wprowadzenie
Docker jest narzędziem, o którym wielu już słyszało, wielu go też używało pośrednio, lub bezpośrednio. Mówiąc w skrócie jest to narzędzie tworzące niezależne wirtualne izolowane środowisko. Coś jak bardzo lekka maszyna wirtualna. Narzędzie to posiada bardzo dużą ilość zaawansowanych funkcji, których nawet nie tknę w tym artykule. Najczęściej używaną funkcjonalnością jest możliwość postawienia izolowanego systemu operacyjnego zawierającego wszystkie zależności do aplikacji, której planujemy użyć bez skomplikowanej instalacji i konfiguracji. Poza samymi funkcjonalnościami tworzenia środowiska pod naszą aplikacje Docker umożliwia nam w łatwy sposób migrować między wersjami, rozpowszechniać naszą aplikacje, tworzyć migawki itp. Kluczowe jest, że raz przygotowany obraz będzie działał na różnorakich systemach, dzięki temu nie musimy pisać naszej aplikacji pod różne systemy operacyjne w różnych wersjach i dystrybucjach. Kontenery stosujemy min. w sytuacji, gdy chcemy "zainstalować" aplikacje bez troszczenia się o zależności oraz bez obaw, że coś zostanie w systemie.
Podstawowe pojęcia
Obraz dockera - plik obrazu bez możliwości edycji. Obrazy mogą być pobierane z repozytorium obrazów lub tworzone własnoręcznie. W przeciwieństwie do maszyn wirtualnych każdy kontener dockera pochodzący powstały na bazie obrazu używa jądra maszyny hosta, a nie własnego.
Instancja kontenera - plik obrazu z możliwością zapisu i odczytu. Kontenery tworzone są na bazie obrazów. Przeznaczeniem instancji kontenera jest wykonywanie pojedynczego zadania (procesu), możliwe
stany kontenera: created, running, restarting, exited, paused. dead.
Aby dobrze zrozumieć czym w dockerze jest obraz wyobraźmy sobie, że mamy płytę z systemem operacyjnym (lub obraz *.iso), po zainstalowaniu mamy już instancje systemu operacyjnego, którą sobie zmieniamy a obraz zostaje bez zmian. Z jednego obrazu możemy tworzyć wiele instancji, a instancje dockera możemy zmienić na obraz. Obrazu mogą zawierać własne systemy operacyjne, zaimplementowane na pokładzie bazy danych, aplikacje, serwisy.
Instalacja Dockera
Zasadniczo możemy go zainstalować na dowolnym Linuxie (nawet Raspberry PI), możemy też na Windowsie od wersji 7 wzwyż. Instalacja na Linuxie jest dość prosta, przykładowo na debiano-pochodnych systemach wystarczy:
sudo apt-get install docker -y
W niniejszym artykule skupię się wyłącznie na używanie Dockera na Linuxach. Posiadaczy Windowsa odsyłam do
innego artykułu.
Zwiększenie wygody użytkowania Dockera - docker bez roota
Po zainstalowaniu dockera zauważymy, że aby cokolwiek na nim zrobić musimy użyć
sudo
, co jest konieczne nawet jak chcemy sprawdzić które instancje działają. Możemy to zmienić przez komendę:
sudo setfacl -m user:$USER:rw /var/run/docker.sock
# jakbysmy nie mieli powyzszego:
sudo apt-get install acl -y
Jest sposób aby nie musieć używać sudo ani nie zwiększać uprawnień - zastosować alternatywę do dockera:
Padmana, która używa innego mechanizmu, przez to nie potrzebuje
sudo
. Dodam, że Padman jest napisany przez firmę RedHat, co sugeruje, że jest to narzędzie do użytku produkcyjnego.
Pobieranie obrazów dockera
Obrazy możemy sobie pozyskać z m.in. z własnego archiwum (obraz utworzony wcześniej), z internetu (repozytoria kontenerów) lub wygenerować obraz własnoręcznie (uruchamiając plik z instrukcjami pozwalającego zbudować własny obraz dockera, czyli używając tzw. Dockerfile).
Strona
https://hub.docker.com/ jest oficjalnym repozytorium obrazów, tam możemy wyszukiwać interesujące obrazy dostępne. Istotnym aspektem jest możliwość filtrowania wyników po "Verified publisher" lub "Official image", również mamy możliwość wyboru kategorii, przykładowo:
Jeśli nie mamy "zweryfikowanego dostawcy" lub "Official image" możemy pokusić się o pobranie obrazu jeśli jest on Open Source - tą kwestie możemy sprawdzić jeśli albo znamy dostawcę obrazu, albo możemy też próbować wejść w szczegóły obrazu i tam poszukać jakiś odniesień do Githuba, gdzie powinien być Dockerfile. Można też na Githubie kliknąć zakładkę
Packages, gdzie będzie instrukcja jak pobrać konkretny obraz dla konkretnej wersji (wśród tych udostępnionych). Przykładowo dla projektu
Hyperledger Iroha mamy kilka
pakietów, a pobrać je możemy w następujący sposób - z
Dockerhuba robimy to komendę:
docker pull nazwa_obrazu
Przykładowe uruchomienie komendy w celu pobrania obrazu:
docker pull ghcr.io/hyperledger/iroha-burrow:91970160562d16211decda909e0b9629ecc2781c684d68655ec43377b21ce6ff
zrzucanie obrazu do/z pliku archiwum TAR:
docker save/load
Polecenie poniżej wypisuje wszystkie obraz opisując stan ich uruchomienia:
docker images # listowanie obrazów dockera
sudo docker image ls
Kasowanie obrazu Dockera:
docker image rm
docker rmi nazwa_obrazu/id_obrazu --force # wtedy też powstałe z nich instancje zostaną usunięte
Obraz dockera, nawet systemu jest minimalny, co widać przykładowo na poniższej liście:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres 9.5-alpine f6e71ff7ed6b 7 months ago 36.2MB
ubuntu 21.04 1fc773f9e714 10 months ago 80.7MB
System Ubuntu, który zajmuje poniżej 100 Mb, jakże minimalistyczny musi być ten system (przykładowo nie ma
vim
i dla równowagi również
emac
).
Zatrzymanie, uruchomienie i sprawdzenie instancji kontenerów dockera
Najprostrzym poleceniem uruchomienia kontenera jest polecenie
run
. W tym wypadku zostanie uruchomiony kontener, którego obraz zostanie automatycznie uruchomiony. W sytuacji, gdy obraz nie istnieje jest automatycznie pobierany.
Przykładowo jak chcemy zainstalować sobie bazę danych Postgres to po zainstalowaniu dockera możemy wywołać np.:
docker run --name przykladowy_postgres \
-e POSTGRES_USER=uzytkownik \
-e POSTGRES_PASSWORD=haslo \
--network=host \
-d \
postgres
W powyższym przykładzie uruchomiona zostanie instancja dockera z przygotowanego obrazu
postgres
, użytkownik i hasło będzie odpowiednio
uzytkownik
i
haslo
. Nazwa instancji
przykladowy_postgres
, co prawda możemy jej nie podawać, wtedy nazwa będzie losowa. Parametr
--network=host
oznacza, że do bazy danych możemy się dostać przed adres serwera
localhost
. Bez parametru
-d
po wywołaniu powyższej komendy byśmy byli podpięci do
stdout
bazy danych, zupełnie tak jakbyśmy ją uruchomili ręcznie. W ramach instancji dockera będzie uruchomiona komenda odpowiedzialna za uruchomienie procesu serwera bazy danych.
Dla nie-programistów przy pomocy Dockera można szybko wejść do internetu anonimowo przy pomocy protokołu Tor:
docker run --network=host --rm -it --shm-size=512m -p 6901:6901 -e VNC_PW=password kasmweb/tor-browser:1.10.0
Wtedy możemy wejść przez przeglądarkę na adres
https://localhost:6901 i zobaczymy jakby okno pulpitu wewnątrz przeglądarki z włączonym oknem programu Tor-Browser, loginem i hasłem są odpowiednio:
kasm_user/password
.
Można sterować tak uruchomionym kontenerem. Poniżej opisano listę komend, które można wykorzystać w tym zadaniu.
Uruchomienie instancji kontenera
docker start nazwa_kontenera
docker start id_instancji
Restart instancji kontenera:
docker restart nazwa_kontenera
docker restart id_instancji
Zatrzymanie instancji kontenera
docker stop nazwa_kontenera
# lub
docker stop id_instancji
Usunięcie instancji kontenera
docker rm nazwa_kontenera
# lub
docker rm id_instancji
# lub jeśli kotener jest wciąż uruchomiony:
docker rm ... --force
Ogólny przegląd stanu instancji kontenera
docker ps # wyświetlenie uruchomionych instancji
docker ps -a # wyświetlenie wszystkich instancji
docker ps --all # alternatywnie do powyższego
docker container ls # dokładnie to samo co docker ps
docker ps -a--format "{{.ID}} \t {{.Names}} \t {{.Status}}" # inna forma wyświetlania
Przegląd szczegółów instancji kontenera
Są to takie szczegóły jak m.in. adres IP, wolumeny (o tym może później będzie), argumenty uruchomienia, zmapowane ścieżki i wiele innych, można zwrócić przy pomocy komendy:
docker inspect # zwraca wszystkie szczegóły instancji kontenera
# można też zwrócić poszczególne składowe np.:
docker inspect --format='{{.LogPath}}' nazwa_kontenera/id_instancji # plik z logami instancji
docker inspect -f '{{ .Mounts }}' nazwa_kontenera/id_instancji # lista zmapowanych katalogów
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nazwa_kontenera/id_instancji # adresy IP kontenera
Aktualizacja szczegółów instancji kontenera
Zdarza się, że w uruchomionej instancji zmienić konfigurację kontenera. Do tego celu używamy:
docker update opcje_kontenera nazwa_kontenera
Np. jeśli chcemy ustawić aby kontener po awarii był automatycznie ponownie uruchamiany:
docker update --restart=always id_instancji/nazwa_kontenera
poza powyższym możliwe jest m.in. ograniczenie zasobów używanych przez dockera (np. ilość pamięci RAM itp)
Wyświetlenie statystyk (użycia zasobów) instancji kontenera
Robimy to komendą:
docker stats
I otrzymujemy podobne wyjście:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
918db77597be postgres_tmp 0.01% 51.92MiB / 31.24GiB 0.16% 0B / 0B 79.5MB / 41.8MB 7
Wywołanie komendy na instancji kontenera
Bardzo często na uruchomionej instancji dockera chcemy zawołać jakąś komendę, nr. doinstalować brakujące wtyczki. Robimy to przy pomocy polecenia:
docker exec -it nazwa_kontenera/id_instancji komenda
# powyższe inaczej:
docker exec --interactive --tty nazwa_kontenera/id_instancji komenda
Gdy chcemy dostać się do konsoli bashowej należy wprowadzić polecenie:
docker exec -it postgres_tmp /bin/bash
dzięki
-it
podpinamy się do stdout i stdin.
Analogicznie do powyższego możemy podpiąć się do stdout i stdin uruchomionej w kontenerze komendy przez zawołanie:
docker attach nazwa_kontenera/id_instancji
W przypadku wcześniej uruchomionej bazy danych postgres podepniemy się tym sposobem do głównego procesu bazy.
Komendy dokumentacji
Możliwości konfiguracyjne dockera, oraz zestaw komend jest znacznie obszerniejszy, dlatego wiele możemy przejrzeć przez powszechnie znaną komendę
--help
, której możemy użyć na różnych szczeblach zagnieżdzenia:
docker --help
docker komenda --help
# np.:
docker logs --help
Jak chcemy posprzątać jedną komendą
docker system prune # usuwa nieużywane instancje oraz wiszące obrazy (czyli starsze wersje zaktualizowanych)
docker system prune -all # ta usunie wszystkie nieużywane obrazy
System operacyjny w instancji dockera
Przykładową instancje systemu operacyjnego z obrazu
Ubuntu możemy pobrać zawołaniem:
docker run --interactive --tty --name ubuntu_dla_zabawy ubuntu:21.04
# tożsame z powyższym:
docker run -it --name ubuntu_dla_zabawy ubuntu:21.04
Gdzie:
W odpowiedzi pojawi się nam bashowa konsola roota obrazu, gdzie możemy zacząć działać, coś zainstalować, zbudować itp.
Za pierwszym razem zostanie nam ściągnięty obraz
ubuntu:21.04
, przez co będzie to chwilę trwało, natomiast mając ściągnięty obraz kolejne instancje tworzone są momentalnie.
Dostęp do wnętrza kontenera poprzez SSH
Doszliśmy do tego jak uruchomić obraz dockera, teraz warto sobie wejść na niego przez SSH. Jak pamiętamy mamy obecnie tylko użytkownika
root
, pamiętajmy też, że nie mamy jeszcze
openssh, dlatego musimy wykonać kilka komend (poniżej zakładam, że chcemy się logować przez SSH na roota).
apt-get update && apt-get install -y ssh --no-install-recommends
echo 'root:111' | chpasswd # ustawienia hasła do logowania
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
service ssh restart
ssh localhost # to na potrzeby testowania
Mając powyższe trzeba by pobrać adres naszej instancji, w tym celu wołamy:
docker inspect ubuntu_dla_zabawy
a następnie szukamy:
IPAddress
, albo po prostu wołamy:
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ubuntu_dla_zabawy
Mając adres łączymy się przez ssh:
ssh root@$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ubuntu_dla_zabawy)
Postawienie klienta SSH można zrobić prościej, wystarczy tylko jedna komenda, zamiast tego co powyżej. Aby więc szybko postawić kontener z
gotowym klientem SSH wołamy:
docker run --rm -it --entrypoint /keygen.sh linuxserver/openssh-server
Przykład gotowego kontenera
Poniżej przedstawiono listingi dla przykładowej instancji kontenera. Warto powiedzieć, że w opisanym powyżej listingu numer
918db77597be
to skrócone id_instancji oraz
postgres_tmp
nazwa_kontenera.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
918db77597be postgres "docker-entrypoint.s…" 1 second ago Up 1 second postgres_tmp
Możemy też chcieć zobaczyć pełną uruchomioną komendę, wtedy dodajemy dodatkowy parametr wyłączający przycinanie:
docker ps --no-trunc
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227 postgres "docker-entrypoint.sh -c max_prepared_transactions=100 -c port=5432" 10 seconds ago Up 9 seconds postgres_tmp
Dostęp do stdout i stderr instancji kontenera
docker logs nazwa_kontenera/id_instancji # uwaga: wyświetli to wszystkie logi
docker logs nazwa_kontenera/id_instancji --tail 100 # wyświetli ostatnie 100 logów
docker logs nazwa_kontenera/id_instancji -f # będzie wyświetlać logi na bieżąco
docker logs nazwa_kontenera/id_instancji --since 2021-12-01T00:00:00Z --until 2021-12-31T23:59:59Z # logi w zakresie czasowym
docker logs nazwa_kontenera/id_instancji --since 1h --until 20m
przykład wszystkich informacji zwróconych przez instancje dockera uruchomienego poleceniem
docker inspect postgres_tmp
:
[
{
"Id": "918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227",
"Created": "2021-12-05T09:05:26.112508793Z",
"Path": "docker-entrypoint.sh",
"Args": [
"-c",
"max_prepared_transactions=100",
"-c",
"port=5432"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 39725,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-12-05T09:05:26.555335177Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:a6cd86e1dfceceaf14408dbd29f6e5001296db8dbe2c34686a0432b5d102a869",
"ResolvConfPath": "/var/lib/docker/containers/918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227/hostname",
"HostsPath": "/var/lib/docker/containers/918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227/hosts",
"LogPath": "/var/lib/docker/containers/918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227/918db77597beed9206cce66f25e06a149d4f557f0224a527621dd7b2e399a227-json.log",
"Name": "/postgres_tmp",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "docker-default",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "host",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": true,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "private",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": null,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/65e8469709b15d6a76fbcbe2043cc90a2d68f3a53a105b39dcb34174bdfb82cb-init/diff:/var/lib/docker/overlay2/39ca13ad2c073bbe1d4ee7197917b44d2c5d6e57519f4f6a4784b53f4f2f19e1/diff:/var/lib/docker/overlay2/b80ecb598baa61c325cd6f9798487c5d5704b991dffeafcc1f43e0b8afc16d27/diff:/var/lib/docker/overlay2/ee39214669038c1f9f99ae27f0552ec1790bd7db9e8ea2ef29c7a8d26922c87e/diff:/var/lib/docker/overlay2/aa733eb5d5d012de24ce84239fd6a6448926c320ab0ce05e6a31d5104a308a7a/diff:/var/lib/docker/overlay2/6f6e56a278c8c84543e5e92d41166cae4f4377d5ee31b8c6cf0f7212ceb8f323/diff:/var/lib/docker/overlay2/dae4ac994edd81ffec298a5de4b918426b58a4b2c376fbbd19a4a95e7403d207/diff:/var/lib/docker/overlay2/01152ae4c246521d1d48b751afa471d7fae19cacc0f9833e97718a3a1a7d6dce/diff:/var/lib/docker/overlay2/b33fc10d8a163508fba4b5070c69107315e1758a97fcd20e2443690057646e3b/diff:/var/lib/docker/overlay2/4bcce04d2d6220b0ccca59c457963e867f531756f3e2a751b26589db0c3051af/diff:/var/lib/docker/overlay2/64c482e0edf1153645f1e9f8a7ece0e4e418b8170d6b906ba73ec3f9e77c4b1c/diff:/var/lib/docker/overlay2/c7d7485082f746e35735d34e6a540258ed40f998cc4586792ac433864b7ac469/diff:/var/lib/docker/overlay2/ec060a3b502da55694ae1b955abe9ea0452e3df6d0b32cdc4eccbeb94bc8ffe4/diff:/var/lib/docker/overlay2/ad241eabbab4257976fe25a1c6fcb77c90f62b905b19f2556a9c0012245d595d/diff:/var/lib/docker/overlay2/e7c7a9c794e34948917a7a92404795ec2cf74ad8f12e09be93a912453dcaabba/diff",
"MergedDir": "/var/lib/docker/overlay2/65e8469709b15d6a76fbcbe2043cc90a2d68f3a53a105b39dcb34174bdfb82cb/merged",
"UpperDir": "/var/lib/docker/overlay2/65e8469709b15d6a76fbcbe2043cc90a2d68f3a53a105b39dcb34174bdfb82cb/diff",
"WorkDir": "/var/lib/docker/overlay2/65e8469709b15d6a76fbcbe2043cc90a2d68f3a53a105b39dcb34174bdfb82cb/work"
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "volume",
"Name": "f7c392129a6560cb9b32576a2aa1ac532631b1dd3ee2d5882a3acd540aff8f76",
"Source": "/var/lib/docker/volumes/f7c392129a6560cb9b32576a2aa1ac532631b1dd3ee2d5882a3acd540aff8f76/_data",
"Destination": "/var/lib/postgresql/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "agh-pc",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"5432/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"POSTGRES_USER=postgres",
"POSTGRES_PASSWORD=mysecretpassword",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/13/bin",
"GOSU_VERSION=1.12",
"LANG=en_US.utf8",
"PG_MAJOR=13",
"PG_VERSION=13.1-1.pgdg100+1",
"PGDATA=/var/lib/postgresql/data"
],
"Cmd": [
"-c",
"max_prepared_transactions=100",
"-c",
"port=5432"
],
"Image": "postgres",
"Volumes": {
"/var/lib/postgresql/data": {}
},
"WorkingDir": "",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {},
"StopSignal": "SIGINT"
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "edba2e11a769386f5d9000bb57cde4b43bb39ca2d253ec533ec7cb37abeb685e",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/default",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"host": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "dc6ad81e1addd0ff7b524907c6555837bf32a458348d02efba106678c6c88735",
"EndpointID": "1f72587045b51043fc5b0dd9039c79e1035c05e88ceb07b7cd21d08608a9551a",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
}
]
Jak zapewne zauważyłeś wywołując
docker inspect nazwa_kontenera/id_instancji
możemy podglądnąć wszystkie parametry danej instancji kontenera.
Zagrożenia konteneryzacji z Dockerem
Nie jest tak, że zawsze należy używać dockera do wszystkiego. Kiedy jest to odradzane zostało opisane w
stosownym artykule. Jednakże ja wychodzę z założenia, że warto przeczytać do końca ten artykuł i potem zdecydować czy warto pójść w dockery czy nie. Wszak dopóki czegoś nie znamy wiemy jak żyć bez tego, a odkrywszy możemy sobie ułatwić życie.
Docker mimo iż jest izolowany niesie pewne zagrożenia, najpopularniejszym jest możliwość zapchania zasobów kontenera (CPU, RAM, dysk, ilość procesów) jak i też możliwość zablokowania określonych portów. Ale jeśli dokonamy mapowania ścieżek systemu operacyjnego dla instancji kontenera to mogą one być wykorzystane przez złośliwe oprogramowanie lub nasze omyłkowe
sudo rm -rf /
(
taka sytuacja miała miejsce w pewnej firmie i backupy też usunęło). Poza tym docker ma dostęp do pełnych zasobów (o ile nie ograniczymy ich dla kontenera), w tym do RAMu, czyli może wpisać coś złośliwego do RAMu, dlatego też warto limitować dostęp do pamięci dla kontenera. Inne częste problemy z bezpieczeństwem dockera znajdują się w
innym artykule.
Obraz dockera z instancji kontenera
Dzięki tej możliwości jak będziemy mieli już skonfigurowaną instancje kontenera dockera możemy ją przerobić na obraz. Aby to zrobić piszemy:
docker commit nazwa_kontenera/id_instancji nazwa_obrazu
co dla skonfigurowanego klienta SSH może wyglądać tak:
docker commit ubuntu_dla_zabawy ubuntu_ssh
#lub z uwzględnieniem wersji i kategorii:
docker commit ubuntu_dla_zabawy ubuntu_ssh/ubuntu:20.04
Taki obraz możemy zrzucić do pliku:
docker save -o nazwaPliku.tar nazwa_obrazu
czyli dla powyższego
docker save -o ubuntu_ssh_20.04.tar ubuntu_ssh/ubuntu:20.04
Obraz taki oczywiście można komuś wysłać i potem zainportować:
docker load -i plik.tar
W razie jeśli dysponujemy szybkimi maszynami, ale wciąż jeszcze wolnym/ograniczonym dostępem do internetu możemy się pokusić o
kopiowanie z kompresją z którego to przeklejam kod:
docker save <image> | bzip2 | pv | ssh user@host docker load
Komunikacja kontenera ze światem
Powyżej dowiedzieliśmy się, że jest to środowisko izolowane, no ale co jeśli chcemy przekazać pewne dane z serwera goszczącego? Właśnie temu zagadnieniu będzie poruszony ten rozdział.
Mapowanie katalogów między systemem gospodarzem a systemem gościem
Robi się to przez dodanie argumentu
-v
w następujący sposób:
-v katalog/gospodarza:/katalog/w/instancji/dockera
# alternatywnie:
--volume katalog/gospodarza:/katalog/w/instancji/dockera
# możemy też w formacie read-only:
-v katalog/gospodarza:/katalog/w/instancji/dockera:ro
Przykładowo możemy instancje uruchomić w następujący sposób:
docker run --rm --name ubuntu_na_jeden_numerek -v "$(pwd):/home:ro" --workdir /home ubuntu:21.04 ls
Powyższa komenda uruchamia instancje obrazu
ubuntu:21.04
o nazwie
ubuntu_na_jeden_numerek
która na zmapowanym katalogu
$(pwd)
w ścieżce roboczej
--workdir /home
wykona komendę
ls
, po czym dzięki fladze
--rm
przestanie istnieć po wykonaniu polecenia. A tak prościej: w ramach chwilowej instancji dockera zostanie w nim zawołana komenda
ls
.
Można ten mechanizm wykorzystać do skompilowania i uruchomienia projektu bez szkody dla systemu operacyjnego, ale wtedy zamiast
ls
trzeba by było dołożyć komendy instalujące
g++
, dlatego też warto na potrzeby jednorazowej kompilacji skorzystać z obrazu, który już ma kompilator (bo np. sami takowy przygotowaliśmy, lub z gotowego).
Niestety jeśli byśmy chcieli oficjalny obraz wydany przez zweryfikowanego dostawcę to dla C++ na moment pisania tego artykułu mamy jedynie:
gdy jednak odznaczymy te dwie opcje rzuca się w oczy
obraz przygotowany dla
pewnego amerykańskiego uniwesrytetu, przy jego pomocy możemy skompilować dany kod w następujący sposób:
docker run -it --rm -v `pwd`:/data --workdir /data byubean/cs235-env:latest bash -c 'g++ -o main main.cpp && ./main ; rm -rf ./main'
Katalogów możemy mapować "niemalże dowolną ilość", czyli dopóki długość komendy nie przekroczy dopuszczalnej długości
Podobnym sposobem możemy skorzystać z
gotowego, oficjalnego obrazu Pythona, który wołamy w następujący sposób:
docker run -it -v "$PWD":/usr/src/app python:latest python /usr/src/app/skrypt.py
Volumeny - lepszy mechanizm od mapowania katalogów
Bardziej zalecanym mechanizmem od mapowania katalogów są volumeny, ich zalety nad mapowaniem katalogów są wymienione w
oficjalnej dokumentacji, poniżej kilka przykładowych komend:
docker volume create artykul # tworzenie volumenu
docker volume ls # listowanie istniejacych volumenow
docker volume rm artykul # usuwanie volumenu
# mozemy tez zobaczyc szczegóły volumenu (w tym gdzie się znajdują dane):
docker volume inspect artykul
[
{
"CreatedAt": "2021-12-12T11:39:32+01:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/artykul/_data",
"Name": "artykul",
"Options": {},
"Scope": "local"
}
]
Powyższy volumen możemy podpiąć podając jego nazwę np.:
-v nazwa_volumenu:/katalog/w/instancji/dockera
# alternatywnie:
--volume nazwa_volumenu:/katalog/w/instancji/dockera
# możemy też w sposób bardziej human-readable:
--mount source=nazwa_volumenu,destination=/katalog/w/instancji/dockera,readonly
Mapowanie urządzeń
Zamontowany penrdive można zmapować z kontenerem, ale jest też inny
sposób.
Mapowanie portów i konfiguracja sieci między kontenerami a gospodarzem
Pierwszym zagadnieniem w tym temacie jest konfiguracja sieci między instancją kontenera a systemem gospodarza, robimy ją przy pomocy
--network
. Przykładowo jeśli chcemy zmapować instancje kontenera tak jakby wszystkie usługi sieciowe były uruchomione na systemie gospodarzu to używamy opcji
--network=host
. Mamy też inne możliwości, ale zagadnienie jest na tyle rozległe i skomplikowane, że odsyłam do
dokumentacji.
Bardzo często nasza aplikacja udostępnia pewne porty, część z nich chcemy wystawić na zewnątrz, część przekierować, a część aby była widoczna tylko dla komputera gospodarza lub dla wszystkich. Przykładowo jeśli napiszemy
serwer www w WT możemy wystawić port w momencie uruchamiania (
docker run ...
) w taki spobów:
-p 8080:8080 # zmapuje port 8080 gospodarza z portem 8080 instancji - wtedy każdy kto "uderzy" w port 8080 naszego komputera trafi bezpośrednio do portu w kontenerze
-p 127.0.0.1:8080:8080 # jw. ale tylko host może "uderzać" do portu w kontenerze
--publish 12345:8080 # zmapuje port 12345 gospodarza z portem 8080 instancji, jest to forma bardziej human-readable niż -p
-p 12345:8080/tcp # zmapuje port 12345 gospodarza z portem 8080 instancji, zaznaczając że jest to port protokołu TCP
-P # mapuje wszystkie porty wystawione przez kontener do losowych wolnych portów gospodarza
Sterowanie kontenerem przez zmienne środowiskowe
Jest to mechanizm stosowany do sterowania programem w C++ jako alternatywa do argumentów uruchomienia programu. Zmienne środowiskowe przekazujemy komendą
-e
w następujący sposób:
docker run ... -e KEY=Value ...
# alternatywnie:
docker run ... --env KEY=Value ...
Mając wiele zmiennych można je przekazać przez plik ze zmiennymi środowiskowymi stosując argument:
--env-file ścieżka/do/pliku
Przykładowy plik ze zmiennymi środowiskami wygląda tak:
# komentarz
COMPILER_FLAGS=-Wall -Wextra -pedantic -Werror
LINKER_FLAGS=-lboost_system -lboost_filesystem
CYTAT1=Pamiętajcie: "Musicie od siebie wymagać, nawet gdyby inni od was nie wymagali." - Św. Jan Paweł II
CYTAT2=To też warto pamiętać: "Najbardziej twórczą ze wszystkich prac jest praca nad sobą, która pozwala odnajdywać urok młodości. Nie ma większego bogactwa w narodzie nad światłych obywateli." - Św. Jan Paweł II
ich czytanie można szybko sprawdzić:
docker run --rm -it --env-file .env ubuntu:21.04 bash -c 'echo $CYTAT2'
Zmienne środowiskowe można przekazać chociażby dla
oficjalnego obrazu Ubuntu umożliwiające wybór języka.
Dockerfile dla programisty C++
W poprzednich rozdziałach dowiedzieliśmy się coś o obrazach, o tworzeniu instancji kontenera z tychże obrazów, dowiedzieliśmy się również, że po wejściu do instancji kontenera możemy zainstalować potrzebne zależności. W ten sposób można przygotować własny obraz, który po wyeksportowaniu do pliku można udostępnić. Jednakże taki kontener w pliku zajmuje trochę miejsca. Istnieje dużo lepsza możliwość! Wystarczy przygotować plik
Dockerfile z tekstowymi instrukcjami jak zbudować własny kontener od zera. Zawiera on instrukcje jak zbudować obraz dockera, jakie pliki przenieść, jakie komendy wywołać, czy w końcu jakie porty wystawia i jaka jest komenda uruchomionej instancji kontenera (tzw. entrypoint).
Jako, że niniejszy portal jest poświęcony językowi C++ skupię się na pisaniu prostych aplikacji w C++ i przygotowywaniu ich na dockerze, oraz tego co z funkcjonalności dockera jest nam potrzebne. Artykuł ten jest zainspirowany
wystąpieniem na konferencji CppNow w roku 2018 (
slajdy). Aby nie pisać tylko teoretycznie, dobrym przykładem programu w C++, który jest dobrze zdockeryzowany jest
Hyperledger Iroha, który ponadto jest tak zrobiony, że można ją zbudować i uruchomić również bez dockera.
Napiszmy więc przykładowy plik
Dockerfile
dla kompilatora g++:
FROM ubuntu:21.04
#### instalacja zaleznosci:
# RUN odpowiada za uruchomienie komendy podczas tworzenia obrazu, pamiętajmy, że mamy roota
RUN apt-get update && apt-get install -y --no-install-recommends g++
# flaga --no-install-recommends jest bardzo przydatna w instalacjach nieinteraktywnych, bez niej musieli byśmy podać pewne dane z konsoli, np. wybrać strefę czasową
WORKDIR /home/build/
ENTRYPOINT ["g++", "-Wall", "-Wextra"]
Powyższy plik
Dockerfile
pobiera obraz Ubuntu:21.04 (
FROM ubuntu:21.04
), na nim instaluje g++ (
RUN apt-get update && apt-get install -y --no-install-recommends g++
), następnie ustawia ścieżkę roboczą (
WORKDIR /home/build/
), która jak nie istnieje zostanie utworzona. Ostatnim elementem
Dockerfile
jest wystawienie tzw. entrypoint, czyli komendy uruchamianej w ramach dockera wraz z argumentami. Obraz z tego możemy utworzyć (komenda wołana w tym samym katalogu co
Dockerfile
):
docker build --tag gplusplus:1 .
# alternatywnie jeśli używamy innej nazwy niż Dockerfile
docker build --tag gplusplus:10.1 --file Dockerfile_v1 .
W ramach obrazu mamy już kompilator, więc aby skompilować plik wystarczy zawołać (dla skompilowania pliku
main.cpp
, który znajduje się w aktualnym katalogu
pwd
):
docker run -it --rm -v "$(pwd):/home/build/" gplusplus:1 main.cpp
Powyższe skompiluje w ramach instancji dockera plik źródłowy, natomiast jak chcemy uruchomić wynikowy plik już robimy to na komputerze gospodarza, aby móc uruchomić to również w ramach dockera musimy podmienić entrypoint:
docker run -it --rm -v "$(pwd):/home/build/" --entrypoint "./a.out" gplusplus:1
# możemy też podać argumenty do ./a.out:
docker run -it --rm -v "$(pwd):/home/build/" --entrypoint "./a.out" gplusplus:1 argumenty do funkcji main czyli odczytywalne przez argv i argc
Jak widzimy instancje kontenera są usuwane od razu po wykonaniu (flaga
--rm
), natomiast jakbyśmy chcieli usunąć obraz wołamy:
docker rmi gplusplus
Teraz spróbujmy ciut bardziej zaawansowany
Dockerfile
, który będzie zawierał jeszcze cmake'a:
FROM ubuntu:21.04
MAINTAINER Grzegorz Prowadzacy
#### instalacja zaleznosci:
# RUN odpowiada za uruchomienie komendy podczas tworzenia obrazu, pamiętajmy, że mamy roota
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y --no-install-recommends build-essential cmake
# flagi -y --no-install-recommends jest bardzo przydatna w instalacjach nieinteraktywnych, bez niej musieli byśmy podać pewne dane
#### Przygotowanie plikow
RUN mkdir -p /home/trunc/build
# kopiowanie katalogu gospodarza do katalogiem hosta, dzięki temu zmiany nie będą widoczne po usunięciu instancji kontenera
COPY . /home/trunc/
WORKDIR /home/trunc/build/
#### cmake generuje co trzeba
RUN cmake ..
RUN make -j4
# wystawienie komendy na zewnatrz:
CMD make -j4 && /home/trunc/build/bin/lab
Powyższy plik
Dockerfile
może posłużyć do wytworzenia obrazu:
docker build --tag gplusplus:10.3 .
# alternatywnie jeśli używamy innej nazwy niż Dockerfile
docker build --tag gplusplus:10.3 --file Dockerfile.newest .
Po skutecznym zbudowaniu obrazu możemy jednokrotnie uruchomić rezultat (uruchomienie
make
i uruchomienie aplikacji):
docker run --rm gplusplus
- po uruchomieniu instancja kontenera zostanie usunięta (flaga
--rm
). A jakbyśmy chcieli usunąć obraz wołamy:
docker rmi gplusplus
Zaletą dockera jest to, że nawet jak wiele razy będziemy musieli budować obraz, to nie będziemy za każdym razem czekali od samego początku, gdyż pewne rezultaty są cachowane, poprzez tworzenie wielu warstw dla komend, dzięki temu zyskamy na czekaniu. Nawet można skorzystać z
cachowania zewnętrznego. Użycie cache'a też można wyłączyć przez argument
--no-cache=true
. Aby dobrze wykorzystać ten mechanizm warto zacząć nasz Dockerfile od dłużej trwających komend, które rzadko będziemy zmieniać.
Powyższy Dockerfile jest dość skąpy, zwłaszcza w sytuacji gdy chcemy go lepiej skonfigurować, w tym celu możemy użyć komend
ARG
, która jest zmienną w trakcie budowania obrazu, a w momencie uruchamiania instancji już jej nie ma, oraz
ENV
, która jest dostępna zarówno w trakcie budowania jak i uruchamiania instancji, ale ustawić ją można bezpośrednio tylko w momencie uruchamiania. Aby to zobrazować zmodyfikujmy powyższy plik:
FROM ubuntu:21.04
MAINTAINER Grzegorz Prowadzacy
ARG ADDITIONAL_DEPENDENCIES
ARG THREADS_BUILDTIME=4
ENV THREADS $THREADS_BUILDTIME
ENV BINARY binary
LABEL reason.of.the.file="cpp0x.pl" main.programming.language="c++"
#### instalacja zaleznosci:
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y --no-install-recommends build-essential cmake $ADDITIONAL_DEPENDENCIES
RUN echo "******** g++ --version:" $(g++ --version | head -n1)
#### Przygotowanie plikow i katalogow
RUN mkdir -p /home/trunc/build
COPY . /home/trunc/
WORKDIR /home/trunc/build/
RUN cmake ..
RUN make -j${THREADS_BUILDTIME}
CMD make -j${THREADS} && /home/trunc/build/bin/${BINARY}
Aby zbudować obraz z wykorzystaniem opcji konfiguracyjnych przekazujemy parametry po fladze
--build-arg
przykładowo tak:
docker build --tag gplusplus . --build-arg 'ADDITIONAL_DEPENDENCIES=libgtest-dev git' --build-arg THREADS_BUILDTIME=8
Natomiast w trakcie tworzenia instancji kontenera przekazujemy argumenty flagą:
-e
lub
--env
np. w taki sposób:
docker run --rm -e BINARY=lab -e THREADS=100 gplusplus
Z innych możliwości dockerfile (więcej w
dokumentacji):
ENTRYPOINT
- jest podobne do CMD, z tą różnicą, że domyślne polecenie nie może być wtedy nadpisane (chyba, że użyjemy flagi
--entrypoint
).
EXPOSE port
EXPOSE port/protokół
- Jest to opcja, która tak naprawdę jest tylko informacją na temat pliku portów wystawionych przez instancje kontenera, ale w praktyce należy uzyć jeszcze -p
LABEL
- pewna informacja, którą można pobrać przez docker image inspect --format='' myimage
STOPSIGNAL
- sygnał stopujący kontener (nazwa, lub id) w razie niepodania SIGTERM
HEALTHCHECK
- komenda przy pomocy której się sprawdza czy wszystko w porządku z kontenerem
SHELL
- zmiana powłoki od danego momentu
USER
- ustawia innego użytkownika i opcjonalnie grupę do wykonywania dalszych komend
ONBUILD
- opcja, w ramach której możemy dodać pewne dodatkowe komendy wykonane w momencie, gdyby nasz obraz poslużył do zbudowania innego obrazu
VOLUME
- tworzy punkt montowania w obrazie, który w razie czego zostanie utworzony jako oddzielne volume??????
Jak sobie nie zaśmiecić dockerfile - czyli jeden obraz buduje drugi (tzw. multistage-build)
Multistage build to bardzo wygodny mechanizm pozwalający aby sobie nie zaśmiecić obrazu dodatkowymi pakietami, które potem będziemy chcieli usunąć. Mechanizm ten stosujemy przez użycie wielokrotnie klauzuli
FROM
.
Przykładowo aby kompilować pliki w C++ w ramach jednej instancji dockera, a uruchamiać w ramach innej możemy utworzyć następujący plik:
FROM ubuntu:21.04
MAINTAINER Grzegorz Prowadzacy
ARG ADDITIONAL_DEPENDENCIES
ARG FILES_TO_COMPILE
ARG BINARY=binary
RUN apt-get update && apt-get install -y --no-install-recommends g++ $ADDITIONAL_DEPENDENCIES
WORKDIR /home/build/
COPY . .
RUN g++ $FILES_TO_COMPILE -o $BINARY
###############################################################
FROM ubuntu:21.04
ARG BINARY=binary
ENV BINARY ${BINARY}
WORKDIR /home/
COPY --from=0 /home/build/${BINARY} /home/
CMD /home/${BINARY}
Przykładowe zbudowanie obrazu i uruchomienie (dla pliku
main.cpp
):
docker build --tag gplusplus:multi . --build-arg --build-arg FILES_TO_COMPILE=main.cpp
docker run -it --rm gplusplus:multi
Uruchamianie aplikacji okienkowej z dockera
Jest to jedno z odradzanych zastosowań dockera, niemniej jednak jest to możliwe. Niestety wiele zależy od systemu goszczącego i goszczonego, przykładowo dla Ubuntu na Ubuntu przez dockera można ten efekt osiągnąć tak:
docker run --rm -it --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix/:/tmp/.X11-unix/ --name ubuntu_dla_zabawy ubuntu:21.04
Opis jak coś takiego zrobić na windowsie w
oddzielnym artykule.
Jeśli chcemy bardziej przenośne rozwiązanie to możemy się pokusić o użycie protokołu Nomachine. Konfiguracja tego jest dość zaawansowana i jest jej poświęcony
oddzielny artykuł.
CppDock narzędzie tworzące obrazy dockera z zależnościami w konkratnych wersjach
Pod koniec
wystąpienia konferencyjnego, będącego inspiracją dla tego artykułu zostało przedstawione
narzędzie CppDock, które w oparciu o plik konfiguracyjny w formacie JSON wygeneruje obraz dockera zawierający wszystkie zależności C++owe w konkretnych wersjach. Po przejrzeniu się temu rozwiązaniu przyznaje, że pomysł szczytny, przydatność jest, jednakże lista przepisów, jak zbudować daną bibliotekę, jest bardzo ograniczona, dlatego wg mnie lepiej użyć managera zależności cieszącego się większą społecznością np.
vcpkg, który to jeszcze załatwia multiplatformowość.
Narzędzia wspierające zarządzanie aplikacjami na wielu dockerach równocześnie
Poznaliśmy podstawy ogólnego zastosowania. Jednakże pojawiają się pytania jak używać tego gdy m.in. chcemy zarządzać dużą ilością kontenerów. Poniżej popularne narzędzia, o których warto wiedzieć
Docker-compose
Nauczyliśmy się używać Dockera, widzimy tego zalety, ale pozostaje pytanie co jeśli chcemy aby wiele instancji ze sobą rozmawiało, przykładowo: aplikacja, baza danych, oraz command-line do aplikacji. Wtedy musieli byśmy mieć trzy obrazy i uruchamiać je w odpowiedniej kolejności pilnując aby wszystkie dane konfiguracyjne były jednolite. W takich sytuacjach przydaje się narzędzie
docker-compose
, które to zarządza kilkoma instancjami równocześnie pilnując wszystkiego, a konfigurujemy przy pomocy plików YAML.
Zasadniczo narzędzie ma podobne możliwości jak Dockerfile, czyli konfigurowanie argumentów uruchomienia, zmiennych środowiskowych, sieci, a poza tym możliwość wskazania, który kontener wymaga którego innego do uruchomienia.
Przykładowy plik ocker-compose możemy zobaczyć w
projekcie Hyperledger Iroha, którego treść znajduje się tutaj:
version: '3'
services:
node:
image: hyperledger/iroha:develop-build
ports:
- "${IROHA_PORT}:50051"
- "${IROHA_TLS_PORT}:55552"
- "${DEBUGGER_PORT}:20000"
environment:
- IROHA_POSTGRES_HOST=${COMPOSE_PROJECT_NAME}_postgres_1
- IROHA_POSTGRES_PORT=5432
- IROHA_POSTGRES_USER=iroha
- IROHA_POSTGRES_PASSWORD=helloworld
- CCACHE_DIR=/tmp/ccache
user: ${U_ID:-0}:${G_ID:-0}
depends_on:
- postgres
tty: true
volumes:
- ../:/opt/iroha:delegated
- ccache-data:/tmp/ccache:delegated
working_dir: /opt/iroha
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
postgres:
image: postgres:9.5
environment:
- POSTGRES_USER=iroha
- POSTGRES_PASSWORD=helloworld
command: -c 'max_prepared_transactions=100'
volumes:
ccache-data:
Jak widzimy w powyższym mamy tworzone volume, oprócz tego bazę danych, a następnie węzeł blockchain
Hyperledger Iroha.
Mając powyższy plik
docker-compose.yml
możemy zarządzać wszystkimi instancjami w następujący sposób:
docker-compose up -d # uruchamia w trybie detached wszystko z pliku
docker-compose up -d -f docker-compose.yml # jw. ale z podaniem pliku
docker-compose down # zatrzymuje
docker-compose rm # usuwa instancje (muszą być zatrzymane)
docker-compose rm -f # jw. zatrzymuje, jeśli nie są zatrzymane
docker-compose down -v --rmi all --remove-orphans # czyści wszystkie pozostałości po tym co z danego pliku powstało
Docker Swarn - zarządzanie dużą ilością konrenerów dockera
Docker Swarn jest to rozwiązanie klastrowe zarządzające dużą ilością kontenerów dostarczane wraz z Dockerem. Posiada własne api, dobrze jest zintegrowane z Dockerem i docker-compose. Niestety pewne funkcjonalności wymagają wersji Enterprice. Jest to narzędzie mniej popularne niż niżej opisany
Kubernetes, mimo iż jest prostrze lecz posiada mniej możliwości. Osoby zainteresowane dowiedzeniem się więcej odsyłam do oddzielnego
atykułu opisującego jak skonfigurować i uruchomić Docker Swarn na wielu serwerach.
Kubernetes
Kubernetes jest to platforma open-source do zarządzania kontenerami. Jest ona na tyle popularna, że wiele dostawców serwerów wirtualnych (ang. VPS) zawierają wsparcie i instrukcje jak uruchomić na ich serwerach klaster Kubernetes. W porównaniu z Docker Swarn narzędzie Kubernetes posiada znacznie więcej możliwości, bardziej szczegółowe porównanie tych dwóch narzędzi w
artykule: Swarn vs Kubernetes.
Alternatywy dockera
Docker jest najpopularniejszym narzędziem do konteneryzacji.
Dla użytkowników istnieją
alternatywy (lub
lista oprogramowania na stronie Wikipedii) m.in.:
OpenVz,
LDC (Linux Containers),
ZeroVM.
Spośród alternatyw na szczególną uwagę zasługuje
podman, który jest na tyle kompatybilny z dockerem, że można go używać jako
alias docker=padman
. Zaletą Podmana jest, że nie trzeba używać go jako użytkownik z uprawnieniami. Za wadę należy zuznać fakt, że niestety można go używać jedynie na Linuxie.
Przykładowo poniżej pokazano w jaki sposób pokstawić bazę danych postgres. Zwróć uwagę, że opcje polecenia są zgodne z tymi użytymi przy poleceniu docker.
podman run --name przykladowy_postgres \
-e POSTGRES_USER=uzytkownik \
-e POSTGRES_PASSWORD=haslo \
--network=host \
-d \
postgres
Bibliografia + literatura uzupełniająca
Autorzy artykułu