V přechozím díle jsme si ukázali, jak lze vytvořit obraz pro databázi PostgreSQL, který lze použít v PaaS jako je OpenShift. Dnes si ukážeme, jak vytvořit druhý důležitý prvek a to je předlohu pro aplikační obraz.

Princip builder obrazu

Proč používám slovo "předlohu" a ne samotný aplikační obraz je celkem zřejmé, pokud si uvědomíme, že chceme vytvářet znovupoužitelný obraz (platformu) pro více aplikací. Pro jednotlivé aplikace totiž chceme sdílet takový základ (base obraz), tedy chceme, aby se stejně spouštěly, podobně se chovaly apod. Pojďme se podívat, jak bude vypadat takový společný předobraz pro celou škálu Python aplikací.

Začneme podobně, jako jsme začali s obrazem PostgreSQL, nyní již na první pokus nainstalujeme balíky pythonu s platformou Python 3.4, bez uchování dočasných souborů DNF a zbytečných dokumentačních souborů:

FROM fedora:23

RUN yum install -y --setopt=tsflags=nodocs python python-setuptools python-pip && yum clean all

Obraz zbuildíme takto:

sudo docker build -t python-34-fedora

Jelikož zde nemáme dosud žádného démona a Python obraz chceme použít pouze jako základ pro další aplikační obrazy, můžeme nyní říci, že máme hotovo a uživatelé budou používat naší předlohu takto:

FROM python-34-fedora
ADD install-app /usr/bin/
RUN /usr/bin/install-app
CMD ["/usr/bin/python", "/opt/app-root/guestbook-pgsql/guestbook/bin.py"]

Jak již asi většina porozuměla, v ukázkovém Dockerfilu jsme přidali soubor (install-app) s instalačním skriptem aplikace a nastavili jsme jistý příkaz jako výchozí příkaz kontejneru. Takový instalační skript může dělat například toto:

#!/bin/bash
cd /opt/app-root
git clone https://github.com/hhorak/guestbook-pgsql.git
cd guestbook-pgsql/guestbook
./setup.py

Tedy skript naklonuje repositář git s aplikací a spustí setup.py script, který provede samotnou instalaci aplikace. To lze s úspěchem považovat za hotové dílo, nicméně instalační skript a Dockerfile by musel vytvářet každý uživatel a to není moc efektivní. O to méně efektivní bude případ, kdy aplikaci máme někde lokálně a ne v repositáři git.

Této neefektivity si všimli i autoři OpenShift a přišli s nápadem standardizovat způsob, jak do obrazu dostat koncovou aplikaci. Přesně k tomuto účelu slouží aplikace source-to-image, která dodává binárku s2i psanou v Go, tedy použitelnou bez dalších závislostí (Go je zatím stále staticky linkovaný jazyk) na různých platformách.

Jak dostat aplikaci do kontejneru aneb s2i

Po nainstalování tohoto nástroje nám tento umožní vytvořit nový obraz na základě aplikačního builder obrazu jedním příkazem, takto:

#> dnf -y install source-to-image
#> s2i build /path/to/guestbook python-34-fedora guestbook

Syntaxe příkazu s2i je celkem jednoduchá, poslední parametr v ukázce nahoře je název výsledného obrazu a předposlední parametr je název výchozího obrazu. Ve výsledku tedy uděláme to samé, co můžeme udělat s Dockerfilem a pár příkazy, nicméně nástroj s2i nám dává možnost dělat to samé efektivně a opakovaně. Krom toho to můžeme dělat stejně pro všechny podobné typy kontejnerů, ať již se jedná o Python, Ruby, NodeJS nebo obraz s kompilovaným jazykem.

Výsledný obraz potom můžeme spustit takto jednoduše:

#> docker run -p 8080:8080 guestbook

Aby to mohlo být takto jednoduché, musí builder obraz (tak říkáme obrazům, které neobsahují žádnou konkrétní aplikaci a jsou předlohou pro finální obrazy) obsahovat minimálně dva spustitelné skripty. Jedná se o skripty assemble a run -- pojďme se na ně podívat podrobněji.

Princip skriptů run a assemble

První skript assemble se spouští při samotném volání příkazu s2i a jeho úkolem je zkopírovat, nainstalovat a připravit pro běh aplikaci, která je při vykonávání skriptu assemble umístěna v předem domluvené složce. Tou složka je /tmp/src/ a následující skript ukazuje konkrétní jednoduchou ukázku skriptu assemble pro Python:

#!/bin/bash

cp -Rf /tmp/src/. ./

if [[ -f requirements.txt ]]; then
  pip install --user -r requirements.txt
fi

Jak vidíme, skript se snaží být chytrý a pokud aplikace obsahuje soubor requirements.txt, tak z něho nainstaluje potřebné balíky pomocí instalátoru pip. Podobných standardů můžeme podporovat více.

Naproti tomu skript run se spouští až v době, kdy uživatel spouští výsledný kontejner s aplikací, při s2i build se tento skript tedy pouze nastaví jako výchozí příkaz při spouštění. Jednoduchý skript run pro Python může vypadat následovně:

#!/bin/bash

function is_django_installed() {
  python -c "import django" &>/dev/null
}

manage_file=$(find . -maxdepth 2 -type f -name 'manage.py' | head -1)

if is_django_installed; then
  exec python "$manage_file" runserver 0.0.0.0:8080
fi

Jak vidíme, opět se snažíme odhadnout podle standardů Python aplikací, co se má vlastně provést. Pokud aplikace obsahuje manage.py a je nainstalován modul django, jedná se pravděpodobně o aplikaci Django a tím pádem víme, jak ji spustit. Zde bych doporučil zaměřit se vždy na podporu oblíbených frameworků a ty podporovat jak v assemble, tak ve skriptu run.

Kromě zmíněných dvou skriptů bychom měli vytvořit i třetí skript, usage, který, jak název napovídá, má při spuštění vytisknout informace o tom, jak daný obraz používat. Všechny skripty se potom instalují do společné složky, ideálně /usr/libexec/s2i.

Trochu štábní kultury nakonec, aneb metadata určená právě pro OpenShift, která říkají, kam se instalují skrypty assemble a run:

LABEL io.openshift.s2i.scripts-url=image:///usr/libexec/s2i
ENV STI_SCRIPTS_PATH=/usr/libexec/s2i

Tím jsme dokončili podporu s2i, nicméně když už jsme u Dockerfilu builder obrazu, pojďme se zaměřit na pár dalších věcí. Ve světě kontejnerů budujeme mikroslužby, tedy aplikace komunikující po síťovém portu, ne aplikace pracující například na bázi stdin/stdout. Aby se daly všechny aplikace dále zpracovávat stejným způsobem, bude šikovné pracovat vždy se stejným portem, v builder obrazu tedy nastavíme:

EXPOSE 8080

Bezpečnost a prostředí

Nejen v OpenShiftu, který to zatím ve výchozí konfiguraci vyžaduje, budeme kvůli bezpečnosti chtít výsledné kontejnery spouštět jako jiný uživatel, než root, pro ochranu hostitelského stroje. Nastavíme tedy výchozího uživatele na nově vytvořeného uživatele default:

RUN useradd -u 1001 -r -g 0 -d /opt/app-root/src -s /sbin/nologin \
            -c "Default Application User" default

Pro ochranu kontejnerů mezi sebou se potom může hodit spouštět každý kontejner s jiným, libovolným UID. Znamená to tedy, že aplikace při instalaci musí být schopna zapisovat do adresáře, kde se nachází instalovaná aplikace, ať již běží pod jakýmkoliv UID. To vše musíme taktéž zařídit již při vytváření builder obrazu, protože v době provádění skriptů assemble a run již operujeme pod tímto ne-root uživatelem.

RUN chown -R 1001:0 /opt/app-root && chmod -R g+rw /opt/app-root
USER 1001

Při instalaci Python modulů pomocí pip se rekurzivní závislosti nainstalují spolu s modulem. Nicméně zejména pokud pracujeme s minimálním prostředím, kterým kontejner bezesporu je, instalace některých binárních extenzí může selhat, protože na systému (tedy v kontejneru) není přítomen nějaký hlavičkový soubor nebo knihovna (obojí se v případě Fedory nachází v balících -devel). Jelikož balíky -devel z principu nejsou nikterak velké, můžeme si dovolit nainstalovat alespoň nejčastěji používané, abychom případným problémům přecházeli. Zároveň se může hodit mít k dispozici pár základních utilit jako find, lsof, patch a další. Seznam takových závislostí byl identifikován při vývoji builder obrazů pro OpenShift na tyto:

RUN dnf -y install \
  autoconf \
  automake \
  bsdtar \
  findutils \
  gcc-c++ \
  gd-devel \
  gdb \
  gettext \
  git \
  libcurl-devel \
  libxml2-devel \
  libxslt-devel \
  lsof \
  make \
  mariadb-devel \
  mariadb-libs \
  openssl-devel \
  patch \
  postgresql-devel \
  procps-ng \
  sqlite-devel \
  tar \
  unzip \
  wget \
  which \
  yum-utils \
  zlib-devel" && \
  dnf -y clean all

Mnoho z těchto věcí je použito v trochu komplikovanějším obrazu, který je postaven na balících s Python 3.4, dostupného jako Softwarové kolekce na https://github.com/openshift/sti-python/tree/master/3.4. Ten navíc nevychází z čistého distribučního base obrazu, ale ze společné mezivrstvy pro builder obrazu s podporou s2i, který je dosutpný na https://github.com/openshift/sti-base. Pro další detaily si můžete projít zdrojové soubory pro vytvoření obrazu tamtéž. Hodně zdaru při vytváření vlastních s2i obrazů!