JavaScript a Docker

Na vývoj, testování, i v produkci používáme Docker. Vyvíjíme aplikace v JavaScriptu. Podívejte se, jak si tyto dvě technologie rozumí dohromady, jak je používat efektivně, jaké jsou naše zkušenosti s Dockerem po téměř dvou letech a co z toho vylezlo dobrého.

V MSD používáme Docker téměř všude. V článku vám chci přiblížit především jeho využití ve světě JavaScriptu a nejčastější triky, které využíváme.

Co je to Docker?

Docker je nástroj, který zjednodušuje práci s virtuálními kontejnery. Nejsnadnější je představit si kontejner jako virtuální stroj. Není to sice tak úplně pravda, ale jejich výhody i nevýhody jsou podobné. Na rozdíl od virtualizace, kontejner sdílí jádro se systémem.

O Dockeru se na Zdrojáku už psalo dřív:
– Lukáš Cír: Dockerizace Maven testů v CI Jenkins
– Michal Valoušek: Lokální vývoj s Dockerem nebo Vagrantem?
– Můj dřívější článek: Sběr logů z kontejnerů

Proč používáme Docker? Naše prostředí je díky němu robustnější, jednodušší na reprodukci a idempotentní. Díky Dockerfile je naše prostředí v kódu, můžeme ho verzovat v gitu a code review je snazší. Zbavili jsme se problému „Na mém stroji to funguje“.

Základní koncepty: Kontejner a Image

Základní koncepty Dockeru jsou Kontejner a Image.

Kontejner a Image (obraz)

Kontejner a Image (obraz)

Kontejner je obálka pro běžící proces. Metafora s kontejnery ve fyzické přepravě je podařená: přepravce se nemusí starat o vnitřek kontejneru. Vidí jenom standardní rozhraní a ihned s ním umí pracovat. Stejné výhody mají kontejnery virtuální. Díky konzistentnímu API není důležité, jaká technologie běží uvnitř. Oproti virtuálnímu stroji startuje mnohem rychleji, v řádu milisekund.

K vytvoření kontejneru slouží Image. Image je pouze statický záznam souborů (snapshot). Není možné ho spustit; slouží pouze jako počáteční stav nového kontejneru.

Image je, podobně jako cibule, strukturovaný ve vrstvách. Každá nová vrstva je jenom rozdíl (diff) oproti té předchozí. Díky tomu je snadné rozšířit jiný Image. Také to znamená, že odebrání souboru nezmenší celkovou velikost! Na druhou stranu, Image mezi sebou vrstvy sdílejí. Jestliže máme na jednom klientu deset Image a všechny rozšiřují node:6, pak je tento (base image) uložený pouze jednou.

K vytvoření Image používáme DockerfileDockerfile je textový soubor s popisem instrukcí. Během buildu si Docker vrstvy cachuje, takže kromě prvního jsou buildy rychlé.

Docker používáme především v příkazové řádce. Nejčastější příkazy jsou docker run pro spuštění nového kontejneru z Image, a docker build pro vytvoření Image z Dockerfile.

Nejčastější příkazy CLI Dockeru

Nejčastější příkazy CLI Dockeru

Dockerfile

FROM node:6
MAINTAINER Pavel Vanecek <email@pavelvanecek.cz>

WORKDIR /app
COPY ./package.json /tmp/package.json
RUN cd /tmp &&\
    npm install &&\
    cp -a /tmp/node_modules /app/ &&\
    rm /tmp/package.json

COPY . .
RUN npm test

Takto vypadá typický Dockerfile pro aplikaci napsanou v JavaScriptu. Každá instrukce vytvoří ve výsledném Image jednu vrstvu.

Instrukce FROM node:6 označuje základní Image, ke kterému budeme přidávat vrstvy. Tag za dvojtečkou označuje konkrétní verzi. Je možné ho vynechat, potom Docker použije :latest. Doporučuji tag vždy specifikovat! Mysleli jsme si, že nám latest stačí a naše buildy se nerozbijí. Pletli jsme se.

Následují instrukce COPY package.json a RUN npm install, které do Image zkopírují soubor a spustí příkaz. Pamatujete na cache vrstev? Docker při kopírování souborů kontroluje, jestli se nezměnil obsah. Kvůli tomu by se při změně kteréhokoliv souboru znovu instalovaly všechny balíčky. Díky tomuto triku se ale budou instalovat jenom při změně package.json.

Tento způsob není optimální. Nová instalace běží při každé změně package.json, byť jen v "description". Je možné použít volume a nechat npm, ať nestahuje balíčky znovu. Je také možné použít npm shrinkwrap nebo yarn. Zjistili jsme ale, že je výhodné nechat občas aplikaci nainstalovat od nuly; některé buildy sice běží déle než by bylo nutné, ale dříve zjistíme případné nedostatky.

Spouštění testů je součástí všech image. Na jednu stranu tak balíme do produkčního image i testy a testovací frameworky, na druhou jsme si jistí, že všechny naše image jsou otestované.

Tento příklad je nejmenší vhodný pro aplikaci. Nejspíš vás budou zajímat i volumes, logováníotevření portů, entrypoint nebo CMD, labely, uživatel bez root práv, instalace dalších balíčků, nebo healthcheck.

.dockerignore

node_modules

Složka node_modules má někde uvnitř pravděpodobně nativní kompilované balíčky a uvnitř kontejneru nemusí fungovat. Je lepší ji pomocí .dockerignore vynechat a dovolit npm install nainstalovat vše znovu. Navíc, Docker pro Mac (a možná i na Windows) kopíruje soubory zoufale pomalu.

Environment variables

const OUTPUT_FILENAME = process.env.OUTPUT_FILENAME || './output.txt'
const PORT = process.env.PORT || 8080

Vynechali jsme všechny konfigurační soubory a proměnné předáváme do našich aplikací pomocí environment variables. Docker a nástroje kolem něj si s tímto způsobem dobře rozumí a není potřeba řešit, kde že to vlastně zase chybí config.json.

Docker compose

Až budete hotovi s vaším Dockerfile, bude vše aplikace připravená fungovat uvnitř kontejneru. Aplikace nicméně málokdy běží sama o sobě; závisí na dalších službách. I ty běží v kontejneru? Samozřejmě.

Stejně tak samozřejmě je jednoduché spustit jeden kontejner, ale obsluhovat jich několik pomocí docker run a pamatovat si k tomu desítky přepínačů je otrava.

docker-compose

Práce s mnoha kontejnery může být zábava

Docker nabízí naštěstí i další nástroj: Docker compose. Slouží pro orchestraci jednoho i více kontejnerů na jednom stroji. Konfiguraci čte ze souboru docker-compose.yml. Ten může vypadat třeba takto:

version: '2'
services:
  mysql:
    image: mysql:5.7
    restart: always
    volumes:
      - "./.data/db:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: wordpress
  web:
    depends_on:
      - mysql
    build:
      context: .
      dockerfile: run.Dockerfile
    ports:
      - 80:80
    restart: always

Tento soubor při spuštění pomocí docker-compose up nastartuje dva kontejnery: jeden s databází, druhý s webovou aplikací. V tuto chvíli už není poznat, jaký jazyk nebo technologie uvnitř kontejneru vlastně běží.

Docker compose si umí chybějící image stáhnout, případně podle instrukcí sestavit sám. Data v databázi jsou díky volumes: uložená na filesystému mimo Docker a přežijí tak i smazání kontejneru.

Vzpomínáte si na environment variables? Toto je jeden ze způsobů, jak je aplikaci předat. Tento příklad není ale úplně vhodný: určitě nechcete heslo k databázi nechávat v git repozitáři! Nám se osvědčil Ansible Vault. V gitu jsou hesla uložená v zašifrovaném souboru a samotný docker-compose.yml pak necháme Ansible vyrobit ze šablony.

Pomocí depends_on můžete explicitně vyjádřit závislost mezi kontejnery. Docker compose je pak spouští ve správném pořadí. Jenom si dejte pozor, že Docker kontejnery spouští, ale nekontroluje, jestli je aplikace uvnitř připravená! To může zabrát pár vteřin, nebo minut. Zajistit, aby aplikace nezkolabovala, si musíte pohlídat sami.

Kontejnery jsou společně na privátní síti a nejsou zvenčí přístupné. To je sice fajn u databáze, ale webový server chcete mít vidět i odjinud. Pomocí ports můžete některé porty vystrčit ven.

Díky restart:always už nepotřebujeme žádné další process managery. Zbavili jsme se forever, pm2, i init skriptů. Docker udrží kontejnery naživu, i po restartu hostu.

K čemu je to všechno dobré

Docker od rána do večera. Docker na triku, Docker v CI, soubory navíc a učení se nových nástrojů. K čemu nám to vlastně pomohlo?

Příklad chyby, které se dalo předejít použitím Dockerfile

Ne úplně šťastný commit

Commit jako tento by mohl rozbít aplikaci, aniž by si toho někdo všimnul. Díky cache nainstalovaných balíčků zůstane React nainstalovaný pořád, dokud někdo nezavolá npm prune; a to se ani na localhostu, ani na dalších serverech nedělo často. Tato chyba by se teoreticky mohla projevit až po mnoha měsících, v nejhorší možnou chvíli. Dockerfile a build od nuly by tuto chybu objevil ihned.
My ji mimochodem našli během Code review. Děláte code review? Měli byste.

Výrazně jednodušší soubor README díky použití docker-compose

Razantně jednodušší instrukce pro nastavení nového stroje

Ukázka souboru README.md před a po použití docker-compose. Instalace prostředí na úplně novém stroji se zkrátila z celého dne (bez Dockeru) na několik hodin (jenom s pomocí Dockeru) na několik minut (Docker compose).

Díky Dockeru jsou naše aplikace, build i deployment jednodušší, rychlejší, odolnější a robustnější.

Docker není ale vhodný vždy. Osobně například preferuju vývoj na mém stroji přímo, bez kontejneru: verze node.js střídám pomocí nvm. Je to nicméně možné. Podívejte se na přednášku Davida Blurtona, kde ukazuje celé nastavení včetně vývoje přímo uvnitř kontejneru:

Docker, podobně jako většina nástrojů, má i svoje nevýhody. Zde jsou některé články, které z velké části popisují i naše zkušenosti a problémy s Dockerem:

To je vše, přátelé

Používáte Docker? Budu rád, když se podělíte s vlastními zkušenostmi.
Nepoužíváte? Vyzkoušejte!

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 2

Přehled komentářů

Richard
Robert Smol Proc ne contejner na vyvoj
Zdroj: https://www.zdrojak.cz/?p=19054