Zabalenie služby do Docker obrazu

o tom, ako distribuovať svoju aplikáciu vo forme Docker obrazu

Video pre cvičenie

Motivácia

Na minulom cvičení sme si vytvorili jednoduchú notifikačnú službu s názvom Notifier, pomocou ktorej bude môcť ktokoľvek, kto je šúčasťou infraštruktúry Smart department posielať notifikácie pomocou knižnice [Apprise]. A keďže budeme chcieť na vytvorenej službe zarobiť obrovské prachy, pokúsime sa ju dnes zabaliť do Docker obrazu, aby sme ju tým pádom vedeli nasadiť kdekoľvek.

Smart Department: Architektúra

Ciele

  • naučiť sa vytvoriť Docker obraz pre svoju aplikáciu

  • extrahovať konfiguráciu aplikácie mimo jej kód

  • naučiť sa ovládať aplikáciu bežiacu v kontajneri pomocou premenných prostredia

Postup

Make Something Working

V prvom kroku sa pokúsime spraviť nutné minimum na to, aby sme vytvorili Docker obraz s našou službou. Nutné minimum znamená, že neaplikujeme best practices, ale že sa sústredíme len na to, aby sme službu ako Docker kontajner vedeli úspešne spustiť.

Task

Vo svojom projekte vytvorte súbor Dockerfile, pomocou ktorého vytvoríme Docker obraz služby.

Súbor Dockerfile vytvorte v koreňovom priečinku vášho projektu. Podoba projektu po jeho vytvorení bude vyzerať nasledovne:

./
├── Dockerfile
├── readme.md
├── requirements.txt
└── src/
    ├── main.py
    └── models.py

Task

Vytvorte minimálny funkčný Docker obraz.

Vytvorenie každého Docker obrazu sa obyčajne skladá z týchto základných častí:

  • výber základného obrazu pomocou kľúčového slova FROM,
  • inštalácia potrebných balíkov pre spustenie aplikácie pomocou kľúčového slova RUN,
  • prekopírovanie zdrojových súborov aplikácie pomoocu kľúčového slova COPY, a
  • spustenie aplikácie pri spustení kontajnera pomocou kľúčového slova CMD alebo ENTRYPOINT

Základná podoba tohto súboru bude pre nás vyzerať nasledovne:

FROM python:3.13-slim
RUN pip3 install apprise loguru paho-mqtt pydantic pydantic-settings
COPY src/ /app
WORKDIR /app
CMD [ "/usr/bin/env", "python3", "main.py" ]

Task

Na základe vytvoreného súboru Dockerfile vytvorte Docker obraz s názvom notifier.

Docker obraz môžete vytvoriť nasledovným príkazom:

$ docker image build --tag notifier .

Overiť si úspešne vytvorený obraz môžete spustením príkazu na vypísanie zoznamu všetkých obrazov:

REPOSITORY                           TAG                 IMAGE ID       CREATED        SIZE
notifier                             latest              2a290634240b   6 hours ago    161MB
python                               3.12-slim           668757ec60ef   4 weeks ago    124MB

Task

Overte vytvorený Docker obraz spustením kontajnera.

Overiť vytvorený obraz môžete spustením nasledujúceho príkazu z príkazového riadku:

$ docker container run --rm -it notifier

Application Settings

Momentálne je súčasťou kódu služby aj jej konfigurácia. To však nie je dobre, pretože tým pádom nebude naša služba prenositeľná na iné riešenia. Rovnako je v aplikácii bezpečnostné riziko, pretože prihlasovacie meno a heslo pre prístup k MQTT broker-u je tiež súčasťou kódu.

V tomto kroku teda zabezpečíme všetko potrebné, aby sme všetko, čo súvisí s konfiguráciou, dostali z aplikácie von. A keďže sa služba bude spúšťať vo forme Docker kontajneru, bude možné konfiguráciu podľa odporúčaní Twelve factor app zadať pomocou premenných prostredia (tzv. environment variables).

Task

Vytvorte nový model Settings, ktorý bude obsahovať konfiguráciu aplikácie.

Model Settings bude tentokrát potomkom triedy BaseSettings. Do konfigurácie uložíme nasledovné informácie:

  • broker - adresa MQTT brokera
  • port - číslo portu, na ktorom MQTT broker komunikuje, predvolená hodnota nech je 1883
  • user - prihlasovacie meno, predvolená hodnota nech je None
  • password - prihlasovacie heslo, predvolená hodnota nech je None
  • base_topic - základná téma, na ktorej bude služba pracovať

Výsledný model bude vyzerať nasledovne:

class Settings(BaseSettings):
    broker: str
    port: int = 1883
    user: str | None = None
    password: str | None = None
    base_topic: str

    model_config = SettingsConfigDict(
        env_prefix='NOTIFIER_',
        env_file_encoding='utf-8'
    )

Task

Vytvorte inštanciu triedy Settings v module main.py ako globálnu premennú a v kóde nahraďte príslušné konštanty hodnotami z nastavení.

Task

Overte správnosť svojej implementácie.

Vlastnosti služby je možné nastaviť pri spustení pomocou premenných prostredia. To je možné urobiť dvoma spôsobmi:

  1. Pomocou IDE - Premenné prostredia je možné nastaviť v dialógu pre spustenie ako aplikácie z prostredia, tak aj pre spustenie kontajnera zo súboru Dockerfile.

  2. Z príkazového riadku pomocou príkazu:

    $ docker container run --rm -it \
        --name notifier \
        --env NOTIFIER_BROKER=147.232.205.176 \
        --env NOTIFIER_USER=maker \
        --env NOTIFIER_PASSWORD=mother.mqtt.password \
        --env NOTIFIER_BASE_TOPIC=services/notifier/ab123cd \
        notifier

Premenné prostredia je možné zadať aj pomocou tzv. .env súboru. Jedná sa o súbor s premennými prostredia. To je výhodné najmä vtedy, ak je premenných prostredia veľa. Vyzerať môže napríklad takto:

# local.env
NOTIFIER_BROKER=147.232.205.176
NOTIFIER_USER=maker
NOTIFIER_PASSWORD=mother.mqtt.password
NOTIFIER_BASE_TOPIC=services/notifier/ab123cd

Opäť je možné vybrať cestu k súboru aj pomocou IDE a rovnako je možné ho použiť aj pri spúšťaní z príkazového riadku:

$ docker container run --rm -it \
    --name notifier \
    --env-file local.env \
    notifier

Publishing Image to Repository

Obraz sme vytvorili a je pripravený na používanie. Vypublikujeme ho do repozitára hub.docker.com, aby sme ho vedeli použiť odkiaľkoľvek a rovnako, aby ho vedel použiť ktokoľvek.

Task

Prihláste sa do účtu hub.docker.com.

$ docker login

Task

Odošlite do svojho účute v repozitári hub.docker.com vytvorený obraz služby notifier.

Najprv označkovať obraz:

$ docker image tag notifier:latest USER/notifier:latest
$ docker image tag notifier:latest USER/notifier:2024
$ docker image tag notifier:latest USER/notifier:2024.1

Následne obraz odošleme spolu so všetkými značkami:

$ docker image push --all-tags USER/notifier

Task

Overte úspešnosť odoslania.

Ak ste postupovali správne, tak po zobrazení svojho profilu na serveri hub.docker.com, uvidíte svoj obraz.

(Some) Best Practices

Ak sa budete snažiť nasadzovať svoje riešenia vo forme Docker kontajnerov, určite budete hľadať spôsoby, ako to robiť čo najlepšie. Na internete nájdete obrovské množstvo videí a iných zdrojov, ktoré sa vám budú snažiť ponúkať tie najlepšie best practices. My sa v tomto kroku pozrieme na niekoľko z nich.

Task

Zabezpečte, aby sa aplikácia v kontajneri spustila pod právami používateľa maker.

Používateľa treba v obraze najprv vytvoriť. To je možné napr. nasledovným rozšírením príkazu RUN v súbore Dockerfile:

RUN groupadd --gid 1000 maker \
    && useradd --uid 1000 --gid 1000 --no-create-home maker \
    && pip3 install apprise loguru paho-mqtt pydantic pydantic-settings

Po poslednom príkaze RUN použíjeme príkaz USER s uvedením mena používateľa, pod ktorým sa spustí aplikácia v kontajneri. Výsledný súbor Dockerfile bude vyzerať nasledovne:

FROM python:3.13-slim
RUN groupadd --gid 1000 maker \
    && useradd --uid 1000 --gid 1000 --no-create-home maker \
    && pip3 install apprise loguru paho-mqtt pydantic pydantic-settings
COPY src/ /app
WORKDIR /app
USER maker
CMD [ "/usr/bin/env", "python3", "main.py" ]

Task

Overte úspešnosť vykonaných zmien.

Do spusteného kontajnera sa môžete dostať spustením interpretera príkazov v ňom. To je možné opäť pomocou vývojového prostredia ako aj priamo z príkazového riadku príkazom:

$ docker container exec --it notifier bash

Meno používateľa uvidíte rovno v prompte alebo si aktuálneho používateľa spolu s jeho skupinou môžete zistiť spustením príkazu id v kontajneri:

$ id
uid=1000(maker) gid=1000(maker) groups=1000(maker)

Task

Vytvorte samostatný skript healthcheck.py, pomocou ktorého zabezpečíte kontrolu stavu služby.

Healthcheck mechanizmus je veľmi dôležitý a umožní nám overiť, či služba naozaj funguje tak, ako má. V našom prípade spravíme jednoduché overenie, ktoré bude vyzerať asi takto:

  1. Pripojíme sa na MQTT broker. Tým overíme aj to, či konfigurácia, ktorú pre službu máme, je správna.
  2. Prihlásime sa na odber témy o stave našej služby. Po jej prijatí overíme, či služba stále pracuje a nedošlo náhodou k uviaznutiu v kóde, kedy proces zostal spustený, ale prestal komunikovať s MQTT brokerom. Netreba zabudnúť overiť situáciu, ak správa prijatá nebola.
  3. Odpojíme sa.

Dôležité však bude nastaviť návratový kód (tzv. exit status) podľa výsledku kontroly. To znamená, že:

  • ak kontrola prebehla neúspešne (nesprávna konfigurácia, nesprávna správa v téme /status, žiadna prijatá správa z témy /status, správa offline), ukončite aplikáciu s kódom 1
  • ak kontrola prebehla úspešne, ukončite aplikáciu s kódom 0

Task

Overte správnosť svojej implementácie.

Skontrolujte stavový kód sputenej aplikácie pre všetky možné prípady.

Task

Pridajte do súboru Dockerfile kontrolu stavu zdravia pomocou kľúčového slova HEALTHCHECK.

Kontrolu zdravia nastavte nasledovne:

  • interval kontrol nastavte na každých 30 sekúnd
  • interval timeout nastavte na 10 sekúnd
  • stav nezdravý vyhláste po 3 pokusoch

Výsledná konfigurácia môže vyzerať takto:

HEALTHCHECK \
    --interval=30s \
    --timeout=10s \
    --retries=3 \
    CMD /usr/bin/env python3 /app/healthcheck.py || exit 1

Task

Overte správnosť svojej konfigurácie.

Ďalšie úlohy

  1. Konfigurácia sa načíta raz a to pri štarte aplikácie. Konfigurácia sa počas behu aplikácie preto nebude meniť. Vytvorili sme preto síce globálnu premennú, ale nie je to úplne ideálny prístup. Lepšie je spraviť buď jedináčika alebo funkciu, ktorá bude dekorovaná dekorátorom z balíka functools - buď @cache alebo @lru_cache. Ak takýto dekorátor použijeme nad funkciou, výsledok jej volania sa uloží do cache a pri každom ďalšom volaní sa tento výsledok z cache aj použije.

    Vytvorte preto v module models.py funkciu get_settings(), ktorá vráti inštanciu triedy Settings. Nad touto funkciou použite dekorátor @lru_cache alebo @cache.

  2. Zabezpečte aktuailizovaný kód tak, aby nedošlo k vzniku žiadnej neošetrenej výnimky. Ošetrite aj situáciu, ak pre spustenie aplikácie chýbajú potrebné nastavenia.

  3. Aktualizujte súbor readme.md a spravte z neho podklad pre opis obrazu pre váš obraz na stránke hub.docker.com.

Ďalšie zdroje