Docker
docker.png

(A kép forrása: https://developers.redhat.com/blog/2014/05/15/practical-introduction-to-docker-containers)

Bevezető

Áttekintés

A Docker egy virtualizációs eszköz.

Aki ismeri a VirtualBoxot: funkciója lényegében hasonló, ám van egy lényeges eltérés: míg VirtualBoxban feltelepítünk egy teljes operációs rendszert, és azon belül telepítünk programokat, addig Dockerben egyes programokat telepítünk, amit a rendszer "körülvesz" egy egyszerűsített Linux operációs rendszerrel. Ha például egy rendszer 5 komponensből áll, akkor VirtualBoxban általában ugyanabban a virtuális operációs rendszeren belül telepítjük mind az ötöt míg Dockerben 5 különböző virtuális operációs rendszer fog futni.

Fogalmak

  • Képfájl (image): egy olyan program, amit a Docker el tud indítani. Használhatunk mások által készített image-et, és mi magunk is létrehozhatunk.
  • Konténer (container): ha elindítunk egy képfájlt, akkor létrejönnek konténerek. Úgy is felfoghatjuk, hogy a konténer a képfájlt feltelepített példánya, ami lehet futó vagy leállított. Egy futó konténer lényegében egy olyan operációs rendszerbe csomagolt program, ami nem a gazda operációs rendszer fájlrendszeréhez és portjaihoz kapcsolódik, hanem egy saját fájlrendszerhez és portokhoz. A fájlrendszerbe bemásolhatunk a gazdáról dolgokat, vagy felcsatolhatunk könyvtárakat, a portokat pedig leképezhetjük a gazda portjaira.
  • Docker Hub: egy olyan weboldal, ahol már elkészített képfájlok vannak. Itt mi magunk is meg tudjuk osztani a mi képfáljainkat.

A konténer leállítható és újraindítható, így létezik nem futó konténer is. Ha egy olyan képfájlt szeretnénk indítani, amihez nem tartozik konténer, akkor létrejön egy, egyébként az a konténer indul.

Telepítés

A Docker telepítése nem egyszerű, nem felhasználóbarát és igen erőforrás igényes. Ráadásul az idők folyamán változik (nem feltétlenül az előnyére). Itt a Windows telepítésről lesz szó.

  • BIOS szinten engedélyezni kell a virtualizációt (ez általában alapértelmezésben engedélyezve van).
  • El kell döntenünk a backend típusát:
    • Hyper-V: operációs rendszer szinten engedélyezni kell a Hyper-V-t (ez alapértelmezésben nincs engedélyezve): Control Panel (Vezérlőpult) → Programs and Features (Programok és Szolgáltatások) → Turn Windows features on and off (Windows-szolgáltatások be- és kikapcsolása) → itt a Hyper-V-t kapcsoljuk be (mindet) → OK, majd újra kell indítani a számítógépet. Ezzel egyébként lehetetlenné tesszük a VirtualBox indulását, így ha azt szeretnénk használni, akkor ki kell kapcsolni a Hyper-V-t, és ismét újra kell indítani a számítógépet.
    • WSL 2: ha nincs engedélyezve a Hyper-V, akkor telepítés során felkínálja ennek a telepítését.
  • Töltsük le a Docker Desktop on Windows-t: https://docs.docker.com/docker-for-windows/install/.
  • Telepítsük fel. Készüljünk fel arra, hogy a telepítés során újra kell indítani a számítógépet.
  • Indítsuk el.

Ha minden rendben befejeződött, akkor a jobb alsó sarokban megjelenik egy konténerhajó ikon, valamint parancssorban ki lehet adni a docker parancsot. Próbáljuk ki:

docker version

A telepítés során nem tudjuk kiválasztani, hogy hova telepítse, és azt sem tudjuk megadni, hogy hol legyenek az image-ek. Tehát jó sok helyre lesz szükségünk a C meghajtón. Ezen kívül a WSL 2 le fogja foglalni egy gyengébb számítógép szinte teljes erőforrását; ilyen esetben tényleg csak kipróbálni tudjuk, majd érdemes törölni.

A Docker által érintett könyvtárak (Csaba helyett az aktuális felhasználónév):

  • c:\Program Files\Docker\
  • c:\Users\Csaba\.docker\
  • c:\Users\Csaba\AppData\Local\Docker\
  • c:\Users\Csaba\AppData\Roaming\Docker\
  • c:\Users\Csaba\AppData\Roaming\Docker Desktop\
  • c:\Users\Public\Documents\Hyper-V\
  • c:\ProgramData\Docker\
  • c:\ProgramData\DockerDesktop\

Program indítása Dockerben

Adjuk ki a következő parancsot!

docker run hello-world

Nem túl kedves módon egy hibaüzenettel indít, majd letölt valamit:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:df5f5184104426b65967e016ff2ac0bfcd44ad7899ca3bbcf8e44e4461491a9e
Status: Downloaded newer image for hello-world:latest

A tulajdonképpeni program eredménye a következő:

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

A következő történt:

  • Megmondtuk a Dockernek, hogy indítsa el a hello-world alkalmazást.
  • Mivel nem találta lokálisan, automatikusan letöltötte.
  • Ezt követően elindította.
  • Az eredményt kiírta, és leállt.

Lássunk egy másik, kissé bonyolultabb példát!

docker run -d -p 80:80 docker/getting-started

Majd nyissuk meg böngészőből a http://localhost URL-t.

Itt az alábbi történt:

  • A fentihez hasonlóan nem találta, letöltötte, majd elindította.
  • A -d paranncsal azt mondtuk meg neki, hogy háttérben fusson (az angol detached (lecsatolt) rövidítéseként).
  • A -p 80:80 azt jelenti, hogy a virtuális operációs rendszer 80-as portja (a : utáni szám) legyen elérhető a gazda számítógépen, szintén a 80-as porton (a : előtti szám). Ez a HTTP port.

Így böngészőből meg tudjuk nyitni, és egy 10 lépéses bevezető tananyagot kaptunk eredményül. Kb. ugyanez érhető el egyébként a https://docs.docker.com/get-started/ oldalon, és részben ez alapján készítettem el ezt a leírást is.

Grafikus felület

A Dockernek van egy grafikus felülete, amit úgy érhetünk el, hogy a jobb alsó sarokban található (esetenként elrejtett; ez esetben a felfele mutató nyílvégre kell kattintani) konténerhajó ikonra kell kattintani. Ez a felület az idők folyamán változott (nem feltétlenül előnyére). Az írás pillanatában a két legfontosabb fül a Containers / Apps, valamint az Images. Lényegében itt lehet az ezekkel kapcsolatos alapműveleteket végrehajtani: pl. elindítani, leállítani, törölni.

Parancssor

A fejlesztők általában nem hagyatkozhatnak a grafikus felületre, egyrészt mert az nem tud mindent, amit a parancssor tud, másrészt a gyakorlatban sok esetben csak egy terminál áll rendelkezésre, grafikus felület nem. A docker parancs kiadásával egy hosszú listát kapunk a lehetőségekről:

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default
                           "C:\\Users\\Csaba\\.docker")
  -c, --context string     Name of the context to use to connect to the
                           daemon (overrides DOCKER_HOST env var and
                           default context set with "docker context use")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level
                           ("debug"|"info"|"warn"|"error"|"fatal")
                           (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default
                           "C:\\Users\\Csaba\\.docker\\ca.pem")
      --tlscert string     Path to TLS certificate file (default
                           "C:\\Users\\Csaba\\.docker\\cert.pem")
      --tlskey string      Path to TLS key file (default
                           "C:\\Users\\Csaba\\.docker\\key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  builder     Manage builds
  buildx*     Build with BuildKit (Docker Inc., v0.5.1-docker)
  compose*    Docker Compose (Docker Inc., v2.0.0-beta.6)
  config      Manage Docker configs
  container   Manage containers
  context     Manage contexts
  image       Manage images
  manifest    Manage Docker image manifests and manifest lists
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  scan*       Docker Scan (Docker Inc., v0.8.0)
  secret      Manage Docker secrets
  service     Manage services
  stack       Manage Docker stacks
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

Commands:
  attach      Attach local standard input, output, and error streams to a running container
  build       Build an image from a Dockerfile
  commit      Create a new image from a container's changes
  cp          Copy files/folders between a container and the local filesystem
  create      Create a new container
  diff        Inspect changes to files or directories on a container's filesystem
  events      Get real time events from the server
  exec        Run a command in a running container
  export      Export a container's filesystem as a tar archive
  history     Show the history of an image
  images      List images
  import      Import the contents from a tarball to create a filesystem image
  info        Display system-wide information
  inspect     Return low-level information on Docker objects
  kill        Kill one or more running containers
  load        Load an image from a tar archive or STDIN
  login       Log in to a Docker registry
  logout      Log out from a Docker registry
  logs        Fetch the logs of a container
  pause       Pause all processes within one or more containers
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rename      Rename a container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images
  run         Run a command in a new container
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  search      Search the Docker Hub for images
  start       Start one or more stopped containers
  stats       Display a live stream of container(s) resource usage statistics
  stop        Stop one or more running containers
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
  top         Display the running processes of a container
  unpause     Unpause all processes within one or more containers
  update      Update configuration of one or more containers
  version     Show the Docker version information
  wait        Block until one or more containers stop, then print their exit codes

Run 'docker COMMAND --help' for more information on a command.

To get more help with docker, check out our guides at https://docs.docker.com/go/guides/

Ezeknek csak egy részét nézzük meg az alábbiakban.

Komponensek

Képfájl

Angolul image. Úgy is elképzelhetjük, mint egy telepíthető program, amit már letöltöttünk. Lee tudunk tölteni képfájlokat, ill. mi magunk is tudunk készíteni; ez utóbbit ld. később. Képfájlt letölteni a docker pull parancs segítségével tudunk, pl.:

docker pull ubuntu

A képfájlokat kezelni a docker image parancs segítségével tudjuk. Pl. kilistázni a már letöltött képfájlokat:

docker image ls

Ennek eredménye:

REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
ubuntu                   latest    1318b700e415   5 days ago     72.8MB
docker/getting-started   latest    083d7564d904   7 weeks ago    28MB
hello-world              latest    d1165f221234   4 months ago   13.3kB

A fentiekben letöltöttük a hello-world és a docker/getting-started képfájlokat (a docker run letölti, ha nem áll rendelkezésre), az ubuntu képfájlt pedig most töltöttük le.

A docker image ls parancsnak van egy másik változata is:

docker images

Törölni a docker image rm parancs segítségével tudunk:

docker image rm hello-world

Jelen esetben hibaüzenetet kapunk:

Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container c37fa40e2690 is using its referenced image d1165f221234

Ez azt jelenti, hogy van egy futtatható konténer, ami erre hivatkozik, emiatt nem engedi letörölni. A konténerekről később lesz szó; most töröljük le a megadott azonosítóval:

docker container rm c37fa40e2690

Ezzel ekvivalens a következő rövidítés:

docker rm c37fa40e2690

Ha többször elindítottuk, akkor mindegyik konténert ki kell törölni egyesével, végül sikeresen kiadhatjuk a képfájl törlése parancsot:

docker image rm hello-world

Eredmény:

Untagged: hello-world:latest
Untagged: hello-world@sha256:df5f5184104426b65967e016ff2ac0bfcd44ad7899ca3bbcf8e44e4461491a9e
Deleted: sha256:d1165f2212346b2bab48cb01c1e39ee8ad1be46b87873d9ca7a4e434980a7726
Deleted: sha256:f22b99068db93900abe17f7f5e09ec775c2826ecfe9db961fea68293744144bd

Ellenőrzés, hogy sikerült-e:

docker images

Eredmény:

REPOSITORY               TAG       IMAGE ID       CREATED       SIZE
ubuntu                   latest    1318b700e415   5 days ago    72.8MB
docker/getting-started   latest    083d7564d904   7 weeks ago   28MB

Futtatás

Amint azt láttuk, a docker run paranccsal tudunk képfájlt indítani. Ez egészen pontosan a következőt hajtja végre:

  • Létrehoz egy konténert az adott program számára.
  • Elindítja a programot.

A programtelepítés példában ez olyan, mintha a letöltött programot (képfájl) egyszerre feltelepítenénk, és el is indítanánk. Ebben az analógiában a feltelepített program "visszahivatkozik" a telepítőre, így a telepítőt addig nem lehet letörölni, amíg van feltelepített példány.

Emlékeztetőül a fenti két indítás:

docker run hello-world

Ez a legegyszerűbb, nincs paraméter. A másik:

docker run -d -p 80:80 docker/getting-started

Most próbáljuk meg elindítani a fent letöltött Ubuntu-t:

docker run ubuntu

Egyből visszakaptuk a parancssort, nem írt ki semmit. A futó programok kilistázása:

docker ps

Eredménye:

CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                               NAMES
e447a3508082   docker/getting-started   "/docker-entrypoint.…"   32 minutes ago   Up 32 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   gifted_newton

A getting-started fut, de az ubuntu nem. Ráadásul bizonyos információk el vannak rejtve. A —no-trunc paraméterrel tudjuk megjeleníteni:

docker ps --no-trunc
CONTAINER ID                                                       IMAGE                    COMMAND                                          CREATED          STATUS          PORTS                               NAMES
e447a350808259f644e4d36abe9e390baf3242cd0bc301c7f08269a28ab4f456   docker/getting-started   "/docker-entrypoint.sh nginx -g 'daemon off;'"   33 minutes ago   Up 33 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   gifted_newton

Visszatérve az Ubuntu indítására, használjuk az alábbi parancsot:

docker run -i -t --rm ubuntu bash

Ekkor már elindul, kapunk egy parancssori terminált, melyben szokásos Linux parancsokat adhatunk ki:

root@a93581264a9a:/# ls -al
total 56
drwxr-xr-x   1 root root 4096 Aug  1 10:42 .
drwxr-xr-x   1 root root 4096 Aug  1 10:42 ..
-rwxr-xr-x   1 root root    0 Aug  1 10:42 .dockerenv
lrwxrwxrwx   1 root root    7 Jul 23 17:35 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
drwxr-xr-x   5 root root  360 Aug  1 10:42 dev
drwxr-xr-x   1 root root 4096 Aug  1 10:42 etc
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
lrwxrwxrwx   1 root root    7 Jul 23 17:35 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Jul 23 17:35 lib32 -> usr/lib32
lrwxrwxrwx   1 root root    9 Jul 23 17:35 lib64 -> usr/lib64
lrwxrwxrwx   1 root root   10 Jul 23 17:35 libx32 -> usr/libx32
drwxr-xr-x   2 root root 4096 Jul 23 17:35 media
drwxr-xr-x   2 root root 4096 Jul 23 17:35 mnt
drwxr-xr-x   2 root root 4096 Jul 23 17:35 opt
dr-xr-xr-x 167 root root    0 Aug  1 10:42 proc
drwx------   2 root root 4096 Jul 23 17:38 root
drwxr-xr-x   5 root root 4096 Jul 23 17:38 run
lrwxrwxrwx   1 root root    8 Jul 23 17:35 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jul 23 17:35 srv
dr-xr-xr-x  11 root root    0 Aug  1 10:42 sys
drwxrwxrwt   2 root root 4096 Jul 23 17:38 tmp
drwxr-xr-x  13 root root 4096 Jul 23 17:35 usr
drwxr-xr-x  11 root root 4096 Jul 23 17:38 var
root@a93581264a9a:/# exit
exit

Figyeljük meg, hogy paraméterként meg kellett adnunk az, hogy mi fusson: ez a bash.

A következő parancs kiírja az indítás kilométer hosszú paraméterlistáját:

docker run --help

Most csak néhányat nézünk meg, ill. ismétlünk át:

  • -i: az interaktív módot jelenti.
  • -t: terminál létrehozása. Az egy kötőjeles paraméterek összevonhatóak, ráadásul a -i és a -t "kéz a kézben jár", így a tipikus használata pl. ez: docker run -it ubuntu bash.
  • -p: belső port továbbítása a gazda gép felé, gazdaport:belsőport formában.
  • -d: futtatás a háttrében.
  • —rm: futás után törölje le a konténert. A programtelepítős példában ez azt jelenti, hogy telepítsük fel, indítsuk el, és amikor leállítjuk, akkor automatikusan töröljük is le. A telepítő (a képfájl) tehát megmarad, de a feltelepített (a konténer) nem. Alapértelmezésben megmarad a konténer, amit el tudunk indítani (ld. később), az újabb docker run viszont újabb konténert hoz létre. Ez olyan, mintha minden egyes induláskor a program feltelepülne újabb és újabb könyvtárba.

Konténerek

Konténerek a docker container parancs segítségével tudunk kezelni. Pl. a következő parancs kilistázza az éppen futó konténereket:

docker container ls

Ez ekvivalens az alábbival:

docker ps

A -a paraméter az összes konténert kiírja, tehát a nem futókat is:

docker container ls -a

Eredmény:

CONTAINER ID   IMAGE                    COMMAND                  CREATED              STATUS                          PORTS                               NAMES
3a459a3d26f3   ubuntu                   "bash"                   About a minute ago   Exited (0) About a minute ago                                       elegant_ganguly
e447a3508082   docker/getting-started   "/docker-entrypoint.…"   55 minutes ago       Up 55 minutes                   0.0.0.0:80->80/tcp, :::80->80/tcp   gifted_newton

Láthatjuk, hogy ott van a hibásan indított Ubuntu; ezt töröljük le a docker container rm paranccsal:

docker container rm 3a459a3d26f3

Elegendő egyértelmű megadni egy egyértelmű előtagot; pl. a következő parancs is működne:

docker container rm 3a

Hogy igazán megértsük, mit jelent a konténer, hajtsuk végre az alábbi parancsot:

docker run -it --name my_ubuntu ubuntu bash

(Itt megismerünk egy újabb parancsot: a —name segítségével nevet adhatunk; egyébként generált neveket kapunk, pl. a fenti elegant_ganguly vagy gifted_newton.) Hozzunk létre egy fájlt, majd lépjünk ki:

root@0dc7755587d0:/# echo Hello world! > mytext.txt
root@0dc7755587d0:/# ls -al mytext.txt
-rw-r--r-- 1 root root 13 Aug  1 11:02 mytext.txt
root@0dc7755587d0:/# cat mytext.txt
Hello world!
root@0dc7755587d0:/# exit
exit

Listázzuk ki a konténereket:

docker container ls -a

Eredmény:

CONTAINER ID   IMAGE                    COMMAND                  CREATED             STATUS                     PORTS                               NAMES
0dc7755587d0   ubuntu                   "bash"                   4 minutes ago       Exited (0) 3 minutes ago                                       my_ubuntu
e447a3508082   docker/getting-started   "/docker-entrypoint.…"   About an hour ago   Up About an hour           0.0.0.0:80->80/tcp, :::80->80/tcp   gifted_newton

Az ubuntu tehát befejeződött. Most indítsuk újra:

docker run -it ubuntu bash

Próbáljuk meg kiírni a mytext.txt tartamát:

root@809d788edc08:/# cat mytext.txt
cat: mytext.txt: No such file or directory
root@809d788edc08:/# exit
exit

Nem sikerült, mert nincs ilyen. De mit történt?

docker container ls -a

Az eredmény:

CONTAINER ID   IMAGE                    COMMAND                  CREATED              STATUS                      PORTS                               NAMES
809d788edc08   ubuntu                   "bash"                   About a minute ago   Exited (1) 45 seconds ago                                       upbeat_napier
0dc7755587d0   ubuntu                   "bash"                   8 minutes ago        Exited (0) 8 minutes ago                                        my_ubuntu
e447a3508082   docker/getting-started   "/docker-entrypoint.…"   About an hour ago    Up About an hour            0.0.0.0:80->80/tcp, :::80->80/tcp   gifted_newton

Létrejött egy másik ubuntu példány! Indítsuk el az elsőt:

docker container start -i my_ubuntu

Láthatjuk, hogy konténer azonosító mellett név alapján is hivatkozhatunk rá. Most már ki tudjuk listázni a mytext.txt tartalmát:

root@0dc7755587d0:/# cat mytext.txt
Hello world!
root@0dc7755587d0:/# exit
exit

Végül: a docker container stop paranccsal tudjuk leállítani a konténert:

docker container stop e447a3508082

Ennek egyszerűbb alternatívája a docker stop

docker stop e447a3508082

Tárhely

Konténer létrehozásakor szükség lehet tárhelyre, ami automatikusan létrejön, pl. a MySQL adatbázis esetén (ld. később). Mi magunk is létre tudunk hozni tárhelyet, a docker volume paranccsal. Volume létrehozása:

docker volume create my_volume

Kilistázása:

docker volume ls

Használata:

docker run -v my_volume:/test -it ubuntu bash

A létrehozott volume-ra a /test könyvtár hivatkozik. Példaként hozzunk létre egy fájlt benne, majd lépjünk ki. Indítsuk el még egyszer a fenti parancsot. Ekkor újabb Ubuntu példány jön létre, de a /test könyvtárban ott lesz a másikban létrehozott fájl.

Volume törlése:

docker volume rm my_volume

De előtte le kell törölni azokat a konténereket, amelyek erre hivatkoznak.

Hálózat

Ha több programot szeretnénk indítani, amelyek egymással kommunikálnak, akkor szükség lehet külön hálózat létrehozására. Erre később látunk majd példát, amikor külön hálózatba kerül egy webalkalmazás és a hozzá tartozó adatbázis. Itt a hálózatok kezelését nézzük meg. A hálózatokat a docker network paranccsal tudjuk kezelni. Pl. kilistázása:

docker network ls

Hálózat létrehozása:

docker network create my_network

Hálózat törlése:

docker network rm my_network

Népszerű képfájlok

NGINX

Ez a legnépszerűbb Docker képfájl. Ez egy webszerver, így önálló használata ritka, viszont számos más alkalmazás alapját képezi. Önálló használatára példa: hozzunk létre egy könyvtárat, pl. d:\Docker\ngnix, és oda másoljunk be egy fájlt index.html néven, valamilyen egyszerű tartalommal. Indítsuk el a következőt:

docker run -v d:\Docker\ngnix:/usr/share/nginx/html -p 12345:80 -d nginx

Majd egy böngészőből nyissuk meg a http://localhost:12345/ oldalt.

Ubuntu

Volt már szó arról, hogy a Docker programokat "körülöleli" egy leegyszerűsített Linux operációs rendszer. Valójában magát az operációs rendszert is letölthetjük:

docker pull ubuntu

Indítása:

docker run -t -i ubuntu /bin/bash

Ezzel egyből el is indítottuk a shell-t. Az operációs rendszer viszont annyira le van egyszerűsítve, hogy még egy vi sincs benne. (Nem igaz tehát az a mondás, hogy vi minden Unix rendszeren van!) Viszont hogy lássuk, ez már egy "igazi" Linux, fel tudjuk telepíteni az alábbi két paranccsal:

apt-get update
apt-get install vim
vim

Ne feledjük, kilépés a vi-ból: q: (Enter), bonyolultabb esetekben Esc :q! Enter.

Ha most kilépünk a rendszerből exit, akkor docker run helyett érdemes a már egyszer elindított konténert újraindítani. Nézzük meg a konténer azonosítót a következő paranccsal:

docker ps -a

Majd indítsuk el magát a konténert (példaként a nálam generált konténer azonosítóval):

docker container start -i 6502292e765c

Busybox

Ahogy arról már volt szó, Dockerben tipikusan mindegyik program külön konténert kap, amit egy Linux operációs rendszer "vesz körül". Ha sok programot szeretnénk futtatni, akkor nem mindegy, hogy mekkora az az operációs rendszer. A legfrissebb Ubuntu mérete az írás pillanatában 72,8 MB. Ez operációs rendszer mércével mérve nem nagy, de ha fel kell szorozni egy viszonylag nagy számmal, akkor már nagyra nőhet. Emiatt van jelentősége a Busybox Linux konténernek, ami egy floppy lemezre is ráférne: mindössze 1,24 MB a mérete. Indítása:

docker run -it --rm busybox sh

Ez tehát egy shell, azon belül is a legegyszerűbb sh, hogy ezzel is spóroljanak a méreten, és amint kilépünk, az operációs rendszer leáll.

MySQL

Letöltése:

docker pull mysql

Indítása:

docker run -e MYSQL_ROOT_PASSWORD=farago -d mysql

Kapcsolódás a frissen indított szerverhez:

docker exec -it ace3f46a10e4 mysql -p

(A ace3f46a10e4 a konténer azonosító, amit a docker ps paranccsal tudunk lekérdezni.)

Itt meg kell adnunk a fenti jelszót, majd SQL parancsokat tudunk kiadni, pl.:

show databases;
connect mysql;
show tables;
select * from time_zone_name where name like 'Europe%';

Ha leállítjuk, majd ugyanazt a konténert elindítjuk (tehát nem docker start, hanem docker run [contaniner_id]), akkor a korábbi változtatások megmaradnak.

PostgreSQL

Letöltés:

docker pull postgres

Indítás:

docker run -e POSTGRES_PASSWORD=farago -d postgres

Kapcsolódás:

docker exec -it 9df5bae94ac5 psql -U postgres

Itt a 9df5bae94ac5 a konténer azonosító, amit a docker ps paranccsal tudunk lekérdezni.

Jenkins

Letöltése:

docker pull jenkins/jenkins

Indítása:

docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins

Kipróbáláshoz ez is megfelelő. Igazi üzemeltetéshez viszont érdemes elolvasni a https://www.jenkins.io/doc/book/installing/docker/ oldalon a tudnivalókat.

Első induláskor a következő jelenik meg a naplóban:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

50b5629175bd41c58ce4a4aa975474f4

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

Ha nem jegyeztük fel, akkor a következőképpen tudjuk kiolvasni:

>docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED              STATUS              PORTS                                                                                      NAMES
69a309cfbc78   jenkins/jenkins   "/sbin/tini -- /usr/…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp   xenodochial_euclid

Majd megadva a konténer azonosítót:

docker exec 69a309cfbc78 cat /var/jenkins_home/secrets/initialAdminPassword

Nyissuk meg a http://localhost:8080/ oldalt. Ott kérni fogja a fenti jelszót, az másoljuk be. Majd folytassuk a beállításokat a webes felületen úgy, mintha lokálisan telepítettük volna.

Git

A Dockerből futtatott programok nem feltétlenül kell, hogy szolgáltatásként fussanak a háttérben, "egyszer használatos" parancsokat is kiadhatunk. Pl. git repository-t klónozhatunk az alábbi paranccsal:

docker run -ti --rm -v ${HOME}:/root -v ${pwd}:/git alpine/git clone https://github.com/faragocsaba/teszt.git

Ez Windows alatt csak PowerShell-ben működik. Normál parancssorból meg kell adni a könyvtárakat, pl.:

docker run -ti --rm -v C:\Users\Csaba:/root -v D:\Docker:/git alpine/git clone https://github.com/faragocsaba/teszt.git

Képfájl készítése

A készítés lépései

Mi magunk is tudunk Docker képfájlt készíteni. A módszert példákon keresztül nézzük meg, a végén pedig összefoglaljuk a tanultakat. Első példaként hozzunk létre egy fájlt Dockerfile néven, pl. az alábbi tartalommal:

FROM nginx
COPY content /usr/share/nginx/html

A Dockerfile mellé hozzunk létre egy content könyvtárat, bele egy index.html fájlt, valamilyen minimális tartalommal. Majd adjuk ki az alábbi parancsot:

docker build -t my_webapp .

Ha sikeres volt a művelet, a docker images parancs kilistázza az imént létrejött képfájlt is. Indítása:

docker run -p 80:80 -d my_webapp

A http://localhost/ oldalt megnyitva az index.html tartalmát kell, hogy lássuk. (Megjegyzés: noha a getting-started futtatását leállítottam, a böngésző "megjegyezte", hogy át kell irányítani a localhost 80-at, így hibára futottam. A problémát nem tudtam megoldani; egy másik böngészőből rendesen működött.)

A Docker képfájlt létrehozásának a lépései tehát:

  • Hozzunk létre egy leíró fájlt Dockerfile néven, és adjuk meg benne a szükséges lépéseket.
  • Adjuk ki a docker build parancsot.
  • Indítsuk hagyományos módon.

Ubuntu példa

Arról már volt szó, hogy az Ubuntu Docker képfájl egy nagyon lecsupaszított disztribúciót tartalmaz, ami még vi szövegszerkesztőt sem tartalmaz. Készítsünk egy olyan képfájlt, ami az alap Ubuntu disztribúción felül ezt is tartalmazza.

Dockerfile

FROM ubuntu
RUN apt-get update
RUN apt-get install -y vim

Képfájl létrehozása:

docker build -t ubuntu_with_vi .

Indítása:

docker run -it ubuntu_with_vi bash

Parancssorból el tudjuk indítani a vi-t!

NodeJS példa

Győződjünk meg arról, hogy nem fut semmi a 8080-as porton (vagy módosítsuk a lenti példát ennek megfelelően). Készítsünk el egy egyszerű webszervert my_node_webserver.js néven:

const http = require('http');
 
http.createServer(function (req, res) {
  res.write('Hello from Node.js!');
  res.end();
}).listen(8080);

Próbáljuk is ki:

node my_node_webserver.js

Nyissuk meg a böngészőből a http://localhost:8080/ oldalt. A fenti szöveget kell látnunk. Állítsuk le a webszervert.

Készítsünk most ebből Docker képfájlt! A Dockerfile tartalma:

FROM node:12-alpine
WORKDIR /app
COPY my_node_webserver.js .
CMD ["node", "my_node_webserver.js"]

A képfájlt elkészítése:

docker build -t my_node_webapp .

Indítása:

docker run -p 8080:8080 -d my_node_webapp

A http://localhost:8080/ oldalon a fenti szöveget kell látnunk.

Python példa

Hozzunk létre egy webszervert Pythonban (my_python_webserver.py):

from flask import Flask
 
server = Flask("my_python_webserver")
 
@server.route("/")
def hello():
    return "Hello from Python!"
 
server.run(host='0.0.0.0')

Ehhez szükségünk van a flask könyvtárra; telepítsük fel:

pip install flask

Indítsuk el:

python my_python_webserver.py

A kiírtak alapján a http://192.168.1.104:5000/ oldalon érhető el, de egyébként a http://localhost:5000/ oldalon is megjelenik a szöveg. Állítsuk le, hogy felszabaduljon az 5000-es port. (Győződjünk meg arról, hogy valóban felszabadult.)

Készítsünk ebből Docker képfájlt!

Dockerfile

FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY my_python_webserver.py .
CMD ["python", "my_python_webserver.py"]

Hozzunk létre egy fájlt requirements.txt néven, az alábbi tartalommal:

Flask==1.1.1

A program gyökerében tehát 3 fájl van: my_python_webserver.py, Dockerfile és requirements.txt.

Készítsük el a képfájlt:

docker build -t my_python_webapp .

Indítsuk el:

docker run -p 5000:5000 -d my_python_webapp

Ha minden rendben történt, akkor a http://localhost:5000/ oldalon megjelenik a szöveg.

Java példa

A Java webszervert Spring Boot segítségével készítjük el. Az eredmény egy futtatható jar fájl, ami minden szükségeset tartalmaz. A Docker bemenete valójában csak ez a jar fájl, és "nem tudja", hogy mögötte Spring Boot van. Így ugyanezzel a módszerrel akármilyen futtatható jar fájlból készíthetünk Docker képfájlt.

A művelethez szükség lesz egy legalább 11-es Java-ra, valamint Maven-re (https://maven.apache.org/). Győződjünk meg arról, hogy a 8080-as porton nem fut semmi.

Először készítsük el a programot! Ez már bonyolultabb lesz, mint a Node.js vagy a Python. Hozzuk létre az alábbi fájlszerkezetet:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>
 
    <groupId>hu.faragocsaba</groupId>
    <artifactId>my_java_webserver</artifactId>
    <version>1.0</version>
 
    <properties>
        <java.version>11</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

src/main/java/hu/faragocsaba/MyApplication.java

package hu.faragocsaba;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

src/main/java/hu/faragocsaba/MyController.java

package hu.faragocsaba;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class MyController {
    private Logger logger = LoggerFactory.getLogger(MyController.class);
 
    @GetMapping("/")
    public String index() {
        logger.info("GET /");
        return "Hello from Spring Boot!";
    }
}

Fordítás:

mvn clean package

Az eredmény: target/my_java_webserver-1.0.jar. Indítsuk el:

cd target
java -jar my_java_webserver-1.0.jar

Próbáljuk ki, hogy működik-e; nyissuk meg a http://localhost:8080/ oldalt. Ha minden rendben ment, a böngészőben láthatjuk a szöveget. Győződjünk meg arról, hogy a naplóban megjelenik a GET bejegyzés. Majd állítsuk le a programot (Ctrol + C).

Hozzuk létre a program gyökerében (ahol a pom.xml található) a Dockerfile-t az alábbi tartalommal:

FROM openjdk:11-slim
WORKDIR /app
COPY ./target/my_java_webserver-1.0.jar  my_java_webserver.jar
CMD "java" "-jar" "my_java_webserver.jar"

A képfájl létrehozása:

docker build -t my_java_webapp .

Indítása:

docker run -p 8080:8080 -d my_java_webapp

Kipróbálás: a http://localhost:8080/ oldal megnyitásával.

Lejjebb arról is szó lesz, hogy hogyan tudunk belépni a virtuális gépre, ill hogy hogyan tudjuk kívülről megnézni a naplófájlt.

Paraméterek

A Dockerfile legfontosabb parancsai tehát az alábbiak:

  • FROM: ezzel adjuk meg azt, hogy mi legyen az alap. Láthattunk példát webszerverre, Linux operációs rendszerre, Node.js-re, Pythonra, Java-ra.
  • WORKDIR: ezzel adjuk meg az alapértelmezett könyvtárat a képfájlban.
  • COPY: fájlokat tudunk másolni a lokális gépről a képfájlba.
  • RUN: magában a képfájlban lefutó parancsok. A képfájl már ezek eredményét tartalmazza.
  • CMD: ez az a parancs, ami indításkor lefut.

Műveletek

Shell indítása

Már futó alkalmazáshoz a docker exec paranccsal tudunk csatlakozni, pl.:

docker exec -it c8796e5adce4 bash

A c8796e5adce4 helyére a mi konténerünk azonosítóját kell írni, ill. fontos, hogy legyen bash. Elképzelhető, hogy csak sh van, bash nincs.

Naplófájl megjelenítése

A naplófájlt az alábbi paranccsal tudjuk megjeleníteni:

docker logs -f c8796e5adce4

A c8796e5adce4 helyére a mi konténerünk azonosítóját kell írni.

Fájl másolása konténerbe

A docker cp parancs segítségével tudunk fjlt másolni konténerbe ill. onnan ki, pl.:

docker cp testfile.txt 54d6bcffccd5:/testfile.txt

ill.

docker cp 54d6bcffccd5:/testfile.txt testfile.txt

A 54d6bcffccd5 heéyre értelemszerűen a mi konténer azonosítónkat kell írni, ill. a testfile.txt-nek léteznie kell.

Tag-elés és programok megosztása

A Docker Hub-on mi magunk is meg tudunk osztani komponenseket. Ennek lépései:

  • Regisztráljunk a https://hub.docker.com/ oldalon. Ez ingyenes.
  • Tag-eljük meg a megosztani kívánt képfájlt. Prefixként a saját felhasználónevet használjuk, pl.:
docker tag ubuntu_with_vi fcsaba77/ubuntu_with_vi
  • Lépjünk be parancssorból a Docker Hub-ra (értelemszerűen a saját felhasználónevet megadva):
docker login -u fcsaba77
  • Adjuk ki a docker push parancsot:
docker push fcsaba77/ubuntu_with_vi

Ha nincs másik számítógépünk, ki tudjuk próbálni ezen az oldalon: https://labs.play-with-docker.com/.

Összetett alkalmazások

Egy Spring Boot példa

Készítsünk egy Spring Boot webalkalmazást, amivel adatokat mentünk az adatbázisba, valamint onnan ki is olvasunk. Az egyszerűség érdekében a további műveleteket mellőzzük. Konkrétabban, a következő GET HTTP hívások hatására bekerülnek az adatbázisba értékek:

A következővel pedig ki tudjuk listázni a beírt értékeket: http://localhost:8080/.

Az alkalmazás forráskódja:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>
 
    <groupId>hu.faragocsaba</groupId>
    <artifactId>fruits</artifactId>
    <version>1.0</version>
 
    <properties>
        <java.version>11</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

src/main/java/hu/faragocsaba/FruitApp.java

package hu.faragocsaba;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class FruitApp {
    public static void main(String[] args) {
        SpringApplication.run(FruitApp.class, args);
    }
}

src/main/java/hu/faragocsaba/Fruit.java

package hu.faragocsaba;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Entity
public class Fruit {
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

src/main/java/hu/faragocsaba/FruitRepository.java

package hu.faragocsaba;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
public interface FruitRepository extends JpaRepository<Fruit, Long> {}

src/main/java/hu/faragocsaba/FruitController.java

package hu.faragocsaba;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
public class FruitController {
    private Logger logger = LoggerFactory.getLogger(FruitController.class);
 
    @Autowired
    private FruitRepository fruitRepository;
 
    @GetMapping
    public List<Fruit> getAllFruits() {
        logger.info("Get all fruits");
        return fruitRepository.findAll();
    }
 
    @GetMapping("/add")
    public Fruit addFruit(@RequestParam String name) {
        logger.info("Add fruit: " + name);
        Fruit fruit = new Fruit();
        fruit.setName(name);
        return fruitRepository.save(fruit);
    }
}

src/main/resources/application.properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/fruits
spring.datasource.username=postgres
spring.datasource.password=farago

A program letölthető innen: fruits.zip.

Fordítsuk le a szokásos módon:

mvn clean package

Indítás előtt győződjünk meg arról, hogy fusson egy PostgreSQL adatbázis. A beállításhoz segítséget kaphatunk a Adatbázisok oldalon. Esetünkben a psotgres felhasználó jelszava farago, és van egy fruits adatbázis (CREATE DATABASE my_db;).

Indítsuk el a backend programot:

cd target
java -jar fruits-1.0.jar

Böngészőből próbáljuk ki (http://localhost:8080/add?name=apple, http://localhost:8080/add?name=banana, http://localhost:8080/add?name=orange, http://localhost:8080/), ill. érdemes az adatbázist is ellenőrizni, pl. a psql parancssori alkalmazással. (Csatlakozás az adatbázishoz: \c fruits, majd select * from fruit;).

Ha működik, álltsuk le az alkalmazást és az adatbázist is. Ez utóbbi leállítása: Szolgáltatások (Services) → itt keressük meg a P betűnél, majd állítsuk le.

Csatlakozás Dockerben futó adatbázishoz

Győződjünk meg arról még egyszer, hogy szabad az 5432 és a 8080 port is. Indítsuk el a PostgreSQL adatbázist Dockerből:

docker run -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=farago -e POSTGRES_DB=fruit -d postgres

A szokásos eszközökkel (parancssori psql vagy DBeaver) meggyőződhetünk arról, hogy valóban elindult. Elvben nem lenne szabad, hogy másképp működjön, mint a saját gépre közvetlenül telepített, így a java -jar fruits-1.0.jar hatására el kell, hogy induljon. Valóban elindul, bár sok esetben nehezen értelmezhető kivételeket dob. Minden esetben arra jutottam, hogy én gépeltem el valamit, szóval másodszor, harmadszor is ellenőrizzük, hogy betűről betűre jól írtuk-e.

A backend és az adatbázis is Dockerben fut

A már bemutatott Java alkalmazás mintájára készítsük el ennek is a Docker képfájlját.

Dockerfile

FROM openjdk:11-slim
WORKDIR /app
COPY ./target/fruits-1.0.jar fruits.jar
CMD "java" "-jar" "fruits.jar"

Képfájl generálása:

docker build -t fruits .

Győződjünk meg, hogy létrejött a docker images parancs kiadásával.

A két komponens együttes indítása már trükkösebb; hosszú időbe telt, mire sikerült működésre bírni. Először is létre kell hozni egy hálózatot:

docker network create fruit

Indítás előtt győződjünk meg, hogy nem fut egy másik példány sem; szükség esetén állítsuk le (docker ps, majd docker stop). Indítsuk el a PostgreSQL-t ezzel a hálózattal:

docker run --name fruit_db -p 5432:5432 --network fruit -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=farago -e POSTGRES_DB=fruits -d postgres

Majd a webalkalmazést:

docker run -p 8080:8080 --network fruit -e SPRING_DATASOURCE_URL=jdbc:postgresql://fruit_db:5432/fruits fruits

Figyeljük meg, hogy a spring.datasource.url-t felüldefiniáltuk a SPRING_DATASOURCE_URL környezeti változóval. A konvenció tehát:

  • nagybetűssé kell alakítani,
  • a pontokat aláhúzásokká kell alakítani.

Ha minden rendben történt, akkor a fenti tesztet így is végre tudjuk hajtani.

Állítsuk le a webalkalmazást és az adatbázist is (docker ps, docker stop).

Docker Compose

Ez már így is nehézkes, és képzeljük el, hogy több komponenst szeretnénk indítani, sok egyéb beállítással; azt nehéz lenne parancssorból megfelelően kezelni. Erre találták ki a Docker Compose fájlt. A fentivel többé-kevésbé ekvivalens megoldás az alábbi:

docker-compose.yml

version: "3.9"
services:
  fruit_database:
    image: "postgres"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=farago
      - POSTGRES_DB=fruits
    ports:
      - "5432:5432"
  fruit_web:
    image: "fruits"
    ports:
      - "8080:8080"
    depends_on:
      - fruit_database
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://fruit_database:5432/fruits

Hálózatot nem kell külön létrehozni, azt automatikusan létrehozza a Docker. Indítása:

docker compose up

Vagy háttérben:

docker compose up -d

A háttérben indított rendszer leállítása:

docker compose stop

További beállítások

A Docker Compose igazi erejét a rendszer némi átalakításával illusztráljuk. Tegyük fel, hogy az application.properties fájlba az adott fejlesztő számítógépének az adatai kerültek. Az egyes üzemeltetői gépeken ez eltér. Tegyük fel, hogy a PostgreSQL adatbázis azonosítója és jelszava eltér. Ez esetben nem kell módosítani az application.properties fájlt, sőt, másikat sem kell feltétlenül létrehozni, hanem a már elkészített Docker képfájlt használhatjuk, mindössze két környezeti változót kell beállítani:

version: "3.9"
services:
  fruit_database:
    image: "postgres"
    environment:
      - POSTGRES_USER=fruituser
      - POSTGRES_PASSWORD=fruitpassword
      - POSTGRES_DB=fruits
    ports:
      - "5432:5432"
  fruit_web:
    image: "fruits"
    ports:
      - "8080:8080"
    depends_on:
      - fruit_database
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://fruit_database:5432/fruits
      - SPRING_DATASOURCE_USERNAME=fruituser
      - SPRING_DATASOURCE_PASSWORD=fruitpassword

A konvenció ismét: nagybetűsítés + pont aláhúzásra cserélése.

Többlépcsős építkezés

Legalábbis erre próbáltam lefordítani az angol multi-stage builds kifejezést. Arról van szó, hogy egy Dockerfile több lépést is tartalmazhat.

Egy tipikus használati eset az, hogy első lépésben lefordítjuk a programot, a másodikban pedig futtatjuk. Ebben az esetben a felhasználó akkor is tud Docker képfájlt készíteni, ha nincs a számítógépén se Java, se Maven. Ennek meg van az az előnye is, hogy a fordítás mindig tiszta olyan értelemben, hogy frissen töltődik le minden függőség, és frissen fordul le minden egy úgymond szűz rendszeren, azaz nem fordulhat elő az, hogy a fejlesztői gépen valami beállítás máshol megismételhetetlen eredményre vezet.

Módosítsuk a programot úgy, hogy H2 adatbázist használjon! A pom.xml-bőltöröljük a PostgreSQL függőséget, és írjuk be a helyére ezt:

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

Az src/main/resource/application.properties tartalma az alábbi legyen:

spring.datasource.name=fruits
spring.datasource.generate-unique-name=false
spring.datasource.username=sa
spring.datasource.url=jdbc:h2:mem:fruits
spring.jpa.hibernate.ddl-auto=create
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true

Itt a spring.h2.console.settings.web-allow-others=true amiatt kell, mert enélkül nem lenne elérhető kívülről a H2 webes konzol.

A Dockerfile:

# Build stage
FROM maven:3.8.2-jdk-11-slim AS build
COPY src /app/source/src
COPY pom.xml /app/source
RUN mvn -f /app/source/pom.xml clean package

# Package stage
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/source/target/fruits-1.0.jar /app/fruits.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "fruits.jar"]

Lássuk az újdonságokat!

  • Megjegyzéseket a # segítségével tudunk beszúrni.
  • FROM maven:3.8.2-jdk-11-slim AS build: ez maga a Maven. Vegyük észre a végén az AS build-et ezzel a névvel hivatkozunk a második fázisban.
  • COPY —from=build: itt azt adjuk meg, hogy nem a saját gépünkről szeretnénk másolni, hanem az első fázis eredményéből.
  • EXPOSE 8080: ez nem csinál semmit. Valójában ezzel üzen a Dockerfile készítője a felhasználójának, hogy futtatáskor mely portokat kell kiajánlani.
  • ENTRYPOINT ["java", "-jar", "fruits.jar"]: nagyjából ugyanazt jelenti, mint a CMD; a különbség az, hogy a CMD felülírható, az ENTRYPOINT nem.

A fordítás és a futtatás ugyanúgy történik, mint idáig:

docker build -t fruits .

ill.

docker run -p 80:8080 fruits

Fordításkor az alábbi történik:

  • Létrejön egy Docker képfájl a maven:3.8.2-jdk-11-slim alapján.
  • Bemásolódnak a szükséges forrásfájlok.
  • A mvn -f /app/source/pom.xml clean package lefordítja. Ez azt is jelenti, hogy letölti az internetről a jar fájlokat. Ez minden esetben bekövetkezik, nem menti el a korábbiakat. (Emiatt nem érdemes mobilnetről futtatni.)
  • Az eredmény belekerül a végleges képfájlba. A végleges képfájl alapja a openjdk:11-jre-slim, ami nem tartalmazza sem a Mavent, sem a forrásokat. (Figyeljük meg, hogy itt a {{-jre} képfájlt használtuk alapként.})

A futtatásnál is változtattunk egy picit a dolgon: a 80-as porton tettük elérhetővé, így nem kell beírni a :8080-at. Próbáljuk ki:

A program letölthető innen: fruits-multistage.zip.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License