Docker Images 101

čo je to Dockerfile a ako zabaliť vlastnú aplikáciu do Docker obrazu

About

Doteraz sme spúšťali kontajnery z obrazov, ktoré niekto pripravil pre nás. Na tomto cvičení sa ale naučíme vytvárať vlastné obrazy a baliť do nich vlastné aplikácie.

Goals

  • rozumieť obsahu konfiguračného súboru Dockerfile
  • naučiť sa vytvárať vlastné Dockerfile súbory
  • naučiť sa vytvárať vlastné Docker obrazy

Content

The Dockerfile

Dockerfile je textový dokument obsahujúci všetky potrebné príkazy, pomocou ktorých je možné zostaviť obraz. Ako referenčnú príručku pre jednotlivé príkazy môžete použiť oficiálnu dokumentáciu.

Najlepším zdrojom dokumentácie sú však Dockerfile súbory existujúcich obrazov, ktoré je možné stiahnuť zo servera Docker Hub. My sa v tomto kroku pozrieme bližšie na Dockerfile, z ktorého sa zostavuje webový server nginx.

Task

Prejdite na stránku Docker Hub a vyhľadajte obraz pre webový server nginx.

Task

V časti Supported tags and respective Dockerfile links prejdite na odkaz pri značke latest.

Pod každou jednou značkou sa skrýva konkrétny Dockerfile, z ktorého bol obraz postavený. Najčastejšie môžete vidieť rozličné verzie pre rozličné verzie výslednej aplikácie alebo rozličné systémy, na ktorých je obraz založený.

Po kliknutí na linku budete presmerovaní na GitHub, kde sa nachádza konfigurácia uvedeného Dockerfile súboru. V priečinku, kde sa tento súbor nachádza, sa môžu nachádzať aj ďalšie súbory na zostavenie tohto obrazu.

Task

Prejdite si obsah zobrazeného súboru Dockerfile.

Každý riadok súboru začína špeciálnym kľúčovým slovom. Dokumentáciu k nim nájdete na tejto stránke.

Prejdite si uvedený súbor riadok po riadku:

  • FROM - obsahuje názov distribúcie, z ktorej je obraz zostavený.
    • táto distribúcia je výrazne menšia ako inštalátor oficiálnej distribúcie
    • ušetrí nám kopec času, pretože nemusíte začínať od piky (scratch) - na to sa môžeme pozrieť na Dockerfile Alpine linuxu, ktorý je tiež základným obrazom alebo sa môžeme “preklikať” cez odkazy na obrazy uvedené v tomto príkaze vždy vyššie a vyššie až na FROM scratch narazíme
    • ma uz kopec predinstalovanych veci
    • vyhodou je, ze oficialne obrazy su vzdy aktualne a obsahuju vzdy posledne verzie, ako bezpecnostne fixy a da sa na to spolahnut
    • jednou z obrovských vyhod je, ze pouziva balickovacieho manazera distribucie
  • ENV - premenne prostredia
    • sposob, ako nastavit premenne prostredia
    • mozeme si ich pozrieť tym, ze sa dostaneme do kontajnera a spustime prikaz env alebo si pozrieme obraz pomocou príkazu image inspect
    • jeden z dolezitych mechanizmov, ktory sa pouziva pri zostavovani obrazov, pretoze tymto sposobom mozeme dat vediet mnozstvo veci
    • vyhodou je podpora na každej platforme/operačnom systéme
    • nastavujeme tu teda verziu Nginx a od tohto momentu je mozne túto premennú pouzit kdekoľvek v Dockerfile (ako aj v zostavovanom systeme)
  • RUN - prikazy, ktore sa spustia pri zostavovani image-u
    • kazdy jeden RUN je vo vysledku reprezentovaný ako samostatna vrstva. preto sa castokrat ako prvy RUN nachádza pomerne dlhy prikaz, resp. prve prikazy su si velmi podobne
    • používa sa najmä na inštalovanie softvéru do obrazu a konfigurovanie obrazu
    • poznamka ku logovaniu - odporucany sposob, ako logovat, nie je do suborov, ale na obrazovku (standardny vystup). v image/kontajneri nie je totiz ziadny syslog ani nic podobne - o toto sa stara docker sam. v opačnom prípade, ak teda logujete do suboru, budete mat problem, ako sa k tomu suboru dostat a vytiahnut tieto udaje von z kontajnera. v tomto prípade teda linkujeme log subor so standardnym vystupom a standardnym chybovym vystupom.
  • EXPOSE - porty 80 a 443 sú otvorené v kontajneri
    • len tieto porty nasledne vieme otvorit/publishnut z kontajnera do hostovskeho pocitaca
  • CMD - nastavuje príkaz (spolu s parametrami), ktorý sa vykoná pri spustení kontajnera.
    • to je ten prikaz, ktory vidime pri vypise vsetkych kontajnerov v stĺpci COMMAND
    • tento príkaz môže byť prepísaný pri spúšťaní kontajnera
  • ENTRYPOINT - configures a container that will run as an executable

Building Minimal Image

Naším dnešným cieľom je zabaliť do obrazu vlastnú aplikáciu. Začneme tým, že vytvoríme obraz, ktorý nebude založený na žiadnej distribúcii. To znamená, že v príkaze FROM uvedieme hodnotu scratch. Tým vytvoríme čistý obraz, ktorý bude obsahovať len základ Alpine Linuxu.

Task

Vytvorte si samostatný priečinok s názvom mini/, v ktorom vytvoríme konfiguráciu pre náš obraz.

Každý obraz sa snažte vytvárať v samostatnom priečinku. Obraz totiž nie je tvorený len súborom Dockerfile, ale aj ďalšími súbormi (napr. konfiguračnými), ktoré sa do výsledného obrazu kopírujú.

Task

Zo stránky Alpine Linux - Downloads si do vytvoreného priečinku stiahnite Mini Root Filesystem pre vašu architektúru.

Ak pracujete v OS Linux, vašu architektúru si môžete zobraziť napríklad príkazom uname:

$ uname -p
x86_64

Podľa nej si teda správne stiahnite príslušný minimálny súborový systém.

Task

Vo vytvorenom priečinku vytvorte súbor Dockerfile a vložte do neho minimálnu konfiguráciu pre zostavenie obrazu od základu.

Pre vytvorenie minimálneho obrazu použijeme tri príkazy:

  • FROM - na akom inom obraze bude tento obraz založený - v našom prípade teda scratch
  • ADD - ktorý súbor (alebo balík) bude vložený (a rozbalený) na ktoré miesto do vznikajúceho obrazu - v našom prípade rozbalíme stiahnutý Mini Root Filesystem do koreňového priečinku / vznikajúceho obrazu
  • CMD - príkaz, ktorý sa vykoná pri spustení kontajneru

Obsah súboru Dockerfile bude teda vyzerať nasledovne:

FROM scratch
ADD alpine-minirootfs-3.13.2-x86_64.tar.gz /
CMD [ "/bin/sh" ]

Task

Do súboru Dockerfile pridajte značku maintainer, pomocou ktorej uvediete autora obrazu (teda seba).

Metaúdaje je možné do obrazu pridávať pomocou príkazu LABEL v tvare:

LABEL <key>=<value>

Správcu obrazu (maintainer) teda pridáme pridaním nasledujúceho riadku:

LABEL maintainer="Juraj Jánošík <juraj.janosik@student.tuke.sk>"

Task

Zostavte výsledný obraz s menom mini.

V priečinku, kde sa nachádza konfiguračný súbor Dockerfile spustíme nasledovný príkaz:

$ docker image build -t mini .
Sending build context to Docker daemon  2.732MB
Step 1/4 : FROM scratch
 --->
Step 2/4 : LABEL maintainer="Juraj Jánošík <juraj.janosik@student.tuke.sk>"
 ---> Running in 5368bf4f2695
Removing intermediate container 5368bf4f2695
 ---> 42fff6bea630
Step 3/4 : ADD alpine-minirootfs-3.13.2-x86_64.tar.gz /
 ---> e47bc82d1dcc
Step 4/4 : CMD [ "/bin/sh" ]
 ---> Running in 4250a89e248e
Removing intermediate container 4250a89e248e
 ---> 537b16496761
Successfully built 537b16496761
Successfully tagged mini:latest

Docker obraz pracuje na princípe vrstvového súborového systému AuFS, teda sa skladá z vrstiev, kde sa každá odkazuje na svojho „rodiča“. Každá inštrukcia v konfiguračnom súbore Dockerfile vytvára novú vrstvu závislú na predošlých, vďaka tomu sa pri zostavovaní nového obrazu nevykonáva každá inštrukcia v Dockerfile, ale len tie, ktoré následujú po tej, ktorá sa zmenila.

Vrstvy kontajnerov a obrazov

Task

Overte, či bol obraz naozaj vytvorený.

To, či bol obraz naozaj vytvorený, overíme podpríkazom ls príkazu docker image:

docker image ls
REPOSITORY                 TAG                IMAGE ID       CREATED              SIZE
mini                       latest             537b16496761   About a minute ago   5.61MB

Task

Z vytvoreného obrazu spustite/vytvorte kontajner.

Ak ste postupovali správne, spustením kontajnera sa spustí príkazový riadok:

$ docker container run -it --rm mini
/ #

Packaging Own Dashboard Application

Prázdny obraz už máme a použijeme ho ako základ pre obraz, do ktorého zabalíme našu aplikáciu. Bude to jednoduchý Dashboard napísaný v jazyku Python pomocou skvelého rámca FastAPI.

Task

Vytvorte si priečinok s názvom dashboard/, prekopírujte do neho všetky súbory z priečinku mini/.

Task

Pomocou príkazu ADD pridajte do obrazu aplikáciu zabalenú v súbore app.tgz rozbalením do priečinku /.

Príkaz ADD samozrejme napíšte až po tom, ako sa v obraze rozbalil Mini Root Filesystem.

Task

Z vytvoreného súboru Dockerfile zostavte obraz s názvom dashboard a spustite z neho kontajner v interaktívnom režime.

Task

Pomocou príkazu RUN pridajte do obrazu všetky závislosti pre spustenie aplikácie dashboard.py v priečinku /app.

Pre realizáciu tejto úlohy vám odporúčame tento postup:

  1. závislosti riešte postupne v spustenom kontajneri zadávaním potrebných príkazov priamo z príkazového riadku
  2. vždy, keď sa pohnete o krok ďalej (vyriešite niektorú zo závislostí) pripíšte potrebný príkaz do súboru Dockerfile
  3. pokračujte opäť krokom 1, kým nevyriešite všetky závislosti a aplikácia sa nespustí

Začneme teda spustením Python skriptu dashboard.py v priečinku /app:

$ cd /app
$ python3 -m dashboard.main
/bin/sh: python3: not found

Na základe chybovej hlášky je zrejmé, že v systéme sa zatiaľ nenachádza interpreter jazyka Python. Takže ho doinštalujeme príkazom apk add:

$ apk add python3
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/10) Installing libbz2 (1.0.8-r1)
...
(10/10) Installing python3 (3.8.7-r1)
Executing busybox-1.32.1-r3.trigger
OK: 53 MiB in 24 packages

Po nainštalovaní vyskúšame spustiť skript znova:

$ python3 -m dashboard.main
raceback (most recent call last):
  File "dashboard.py", line 4, in <module>
    import uvicorn
ModuleNotFoundError: No module named 'uvicorn'

V tomto momente vieme, že inštalácia interpretera jazyka Python bola úspešná. Aktualizujeme teda súbor Dockerfile o inštaláciu balíka python3:

RUN apk add python3

Ďalší problém hovorí o tom, že Python nepozná modul uvicorn. Pre inštaláciu ďalších modulov jazyka sa používa nástroj pip. Pokúsime sa teda nainštalovať tento modul:

$ pip3 install uvicorn
/bin/sh: pip3: not found

Chybová hláška opäť hovorí, že príkaz pip3 sa v systéme nenachádza. Doinštalujeme teda balík py3-pip, ktorý nainštaluje príkaz pip3 spolu so všetkými závlosťami:

$ apk add py3-pip

A môžeme opäť vyskúšať spustiť predchádzajúci príkaz:

$ pip3 install uvicorn
Collecting uvicorn
...
Successfully installed click-7.1.2 h11-0.12.0 uvicorn-0.13.4

Po úspešnej inštalácii môžeme aktualizovať príkaz RUN pridaním ako inštalácie balíku py3-pip do systému:

RUN apk add python3 py3-pip

Opäť sa pokúsime spustiť aplikáciu:

$ python3 dashboard.py
Traceback (most recent call last):
  File "dashboard.py", line 5, in <module>
    from fastapi import FastAPI, Request, Form
ModuleNotFoundError: No module named 'fastapi'

Inštaláciou balíka uvicorn sme teda vyriešili ďalšiu závislosť a pridáme ho teda do príkazu RUN v súbore Dockerfile:

RUN apk add python3 py3-pip \
    && pip3 install uvicorn

Podobným spôsobom budeme pokračovať ďalej, až kým nevyriešime všetky závislosti a aplikácia sa spustí. V tom prípade bude výstup vyzerať takto:

$ python3 dashboard.py
INFO:     Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
INFO:     Started reloader process [44] using statreload
INFO:     Started server process [47]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Aktuálna podoba súboru Dockerfile bude vyzerať nasledovne:

FROM scratch
LABEL maintainer="Juraj Jánošík <juraj.janosik@student.tuke.sk>"

ADD alpine-minirootfs-3.13.2-x86_64.tar.gz /
ADD app.tgz /

RUN apk add python3 \
    pip3 install uvicorn fastapi jinja2 python-multipart

CMD [ "/bin/sh" ]

Task

Pomocou príkazu EXPOSE pridajte do súboru Dockerfile číslo portu, na ktorom balená aplikácia komunikuje.

Z výstupu aplikácie je vidieť, že port, na ktorom komunikuje, je 5000. Do súboru Dockerfile teda pridáme príkaz:

EXPOSE 5000

Task

Na základe vytvoreného súboru Dockerfile zostavte obraz s menom dashboard.

Výsledná podoba súboru Dockerfile je nasledovná:

FROM scratch
LABEL maintainer="Juraj Jánošík <juraj.janosik@student.tuke.sk>"
ADD alpine-minirootfs-3.13.2-x86_64.tar.gz /
ADD app.tgz /
RUN apk add python3 py3-pip \
    && pip3 install uvicorn fastapi jinja2 python-multipart

EXPOSE 5000

CMD [ "/bin/sh" ]

Task

Z vytvoreného obrazu dashboard spustite kontajner a v ňom aplikáciu dashboard.py tak, aby bol port 5000 dostupný z vonku kontajnera na rovnakom porte.

Ak ste postupovali správne, po spustení kontajnera a v ňom aplikácie dashboard.py môžete otvoriť prehliadač na adrese http://localhost:5000, kde sa vám zobrazí výsledná aplikácia.

Dashboard App

Running Application when Container Starts

Aktuálny príkaz, ktorý sa spustí po štarte kontajnera, spustí len štandardný interpeter príkazov /bin/sh. V tomto kroku zabezpečíme, aby sa po štarte kontajnera automaticky spustila aplikácia zo súboru /app/dashboard.py.

Task

Nastavte pracovný priečinok pre príkaz uvedený v príkaze CMD na /app.

Tento priečinok nastavíme pomocou príkazu WORKDIR:

WORKDIR /app

Task

Aktualizujte príkaz CMD tak, aby zabezpečil spustenie aplikácie dashboard.py pomocou interpretera python3.

Samozrejme vieme spustenie zabezpečiť viacerými spôsobmi. My však budeme spúšťať tento skript takto:

$ /usr/bin/env python3 dashboard.py

Tým zabezpečíme prenositeľnosť a síce, že umiestnenie interpretera jazyka Python nie je podmienená konkrétnym umiestnením v systéme.

Príkaz CMD v súbore Dockerfile teda aktualizujeme nasledovne:

CMD [ "/usr/bin/env", "python3", "dashboard.py" ]

Task

Overte, či sa po spustení kontajnera z uvedeného obrazu aplikácia automaticky spustí sama.

Base Your Image on Another Image

Nie vždy je nutné vytvárať obraz od nuly. Je totiž výhodné vytvoriť svoj obraz z už existujúceho obrazu. To so sebou prináša niekoľko výhod, kedy sa nemusíte zbytočne sústreďovať na prípravu systému samotného, ale vyslovene len na vašu aplikáciu.

V našom prípade teda založíme obraz na obraze python:alpine, ktorý už obsahuje predinštalovaný systém s Python interpreterom.

Task

Aktualizujte váš súbor Dockerfile tak, aby sa zakladal na obraze python:alpine.

Zmena v príkaze FROM nie je jediná, ktorú bude potrebné vykonať. Vždy je potrebné zvážiť, ktoré ďalšie príkazy je potrebné upraviť. Preto je dobré napr. zakomentovať príkaz RUN, zostaviť obraz nanovo (už na základe nového obrazu), spustiť ho len s intepreterom príkazov /bin/sh a postupne jednotlivé príkazy skúšať ručne. Tým zistíte, ktoré z nich sú potrebné, a ktoré naopak nie.

Touto úpravou zistíte, že nie je potrebné inštalovať balíky python a py3-pip, pretože sú predinštalované v rodičovskom obraze.

Výsledný súbor Dockerfile bude vyzerať nasledovne:

FROM python:alpine
LABEL maintainer="Juraj Jánošík <juraj.janosik@student.tuke.sk>"
ADD alpine-minirootfs-3.13.2-x86_64.tar.gz /
ADD app.tgz /
RUN pip3 install uvicorn fastapi jinja2 python-multipart

EXPOSE 5000

WORKDIR /app

CMD [ "/usr/bin/env", "python3", "dashboard.py" ]

Task

Overte správnosť vašej úpravy.

Ak ste postupovali správne, aplikácia sa spustí a vy k nej môžete pristúpiť otvorením prehliadača na adrese http://localhost:5000.

Ensuring the Data Persistence

Zakaždým, keď sa aplikácia spustí, je jej hodnota rovnaká: teplota -3°C nameraná v novembri 2020. Ak nechceme prísť o predchádzajúce merania, potrebujeme zabezpečiť trvácnosť vytvorených dát, ktoré sa nachádzajú v priečinku /app/data.

Task

Pomocou príkazu VOLUME pridajte do súboru Dockerfile cestu k priečinku /app/data.

Tento priečinok bude slúžiť ako bod pripojenia pre zabezpečenie trvácnosti údajov mimo spustený kontajner.

VOLUME /app/data

Task

Zostavte obraz a overte trvácnosť zaznamenaných údajov vytvorením pomenovaného zväzku ako aj lokálneho pripojenia.

Po opätovnom zostavení obrazu vytvoríme pomenovaný zväzok nasledovným príkazom:

$ docker container run -it --rm --volume dashboard_data:/app/data dashboard

Lokálny priečinok data/ vieme zasa pripojiť týmto príkazom:

$ docker container run -it --rm --volume ${pwd}/data:/app/data dashboard

In Next Episode of IoT…

Nabudúce sa pozrieme na nástroj Node-RED. Predtým, ako sa však do neho pustíme, si ho potrebujete nainštalovať. To samozrejme znamená - stiahnuť príslušný Docker obraz.

Okrem toho si vytvorte účet v službe openweathermap.org, aby ste mali prístup k jeho API. Budeme ho používať ako zdroj dát.

Task

Spustite docker container nodered/node-red v interaktívnom režime s presmerovaním portu 1880 lokálne a pripojením lokálneho priečinku node_red_data/ na priečinok kontajnera /data.

$ docker run --rm -it -p 1880:1880 -v node_red_data:/data --name nodered nodered/node-red

Ak ste postupovali správne, zobrazí sa vám v prehliadači webová aplikácia Node-RED.

Task

Otvorte prehliadač na adrese http://127.0.0.1:1880/

Node-RED: First Run

Task

Zaregistrujte sa v službe openweathermap.org a z9skajte po registrácii svoj API kľúč.

Tento API kľúč budeme potrebovať na získavanie informácií o aktuálnom počasí.

Additional Tasks

  1. Aplikácia je postavená na rámci [FastAPI], pre ktorú existuje osobitný Docker obraz od autora tohto rámca tiangolo/uvicorn-gunicorn-fastapi. Založte váš obraz na tomto obraze.