Docker kontejnerizace

21.12.2020

Docker je nástroj pro tzv. aplikační kontejnerizaci (dockerizace :-), která se stává standardem. Co to znamená, přehled základních příkazů a příklad s asp.net core službou se pokusím popsat v tomto článku.

Slovníček

Docker host Fyzický nebo virtuální počítač, na kterém běží docker engine. Docker engine je možné nainstalovat na Linux, Windows nebo macOS a umožňuje provoz docker kontejnerů.
Docker engine Skládá se ze serveru (na pozadí běžící proces docker daemon), REST API a docker klienta (CLI).
Docker klient CLI (aplikace příkazového řádku) pro správu šablon a kontejnerů.
Docker desktop GUI pro správu šablon (image) a kontejnerů.
Docker register Repozitář již vytvořených a připravených šablon, které si můžeme stáhnout a použít. Veřejným repositářem je hub.docker.com.
Image Šablona, podle které se vytváří konkrétní kontejner nebo více kontejnerů. Obsahuje build instrukce.
Kontejner Spustitelný balíček, který obsahuje aplikační kód včetně potřebných systémových částí (runtime, knihovny, nastavení, ...). Lze ho spustit všude, kde je nainstalovaný docker engine.
Zapisovací vrstva kontejneru Kontejner zapisuje data do zapisovací vrstvy (writeable layer), která je součástí kontejneru a po smazání kontejneru se také smaže. Zapisovací vrstva je pomalejší než nativní file systém. Pokud bychom chtěli kontejner optimalizovat nebo některé soubory zachovat i po smazání kontejneru, musíme použít tzv "volume".
Volume Úložiště plně spravované dockerem a není součástí kontejneru. Může sloužit jako sdílené úložiště pro více kontejnerů zároveň.
Bind mount Alternativní možnost jak propojit zapisovací vrstvu kontejneru se souborovým systémem na hostitelském počítači. Pomalejší a méně preferovaná varianta než volume, ale může se hodit v některých případech.

Obsah

Úvod

Docker kontejner si můžeme představit jako odlehčenou virtualizaci, kdy nespouštíme znova celý operační systém, ale pouze aplikační část. Naše řešení se může skládat z několika kontejnerů, kde každý kontejner bude zastupovat konkrétní službu, například databázi, proxy server, webový server, cache úložiště a podobně. Tyto služby (šablony) se dají stáhnout z veřejného repositáře hub.docker.com a můžeme si je dále upravovat.

To má hned několik výhod

  • Kontejner je výpočetně méně náročný než plná virtualizace (VMware, VirtualBox, Hyper-V, KVM), to nám umožňuje rychlou správu a zjednodušuje tvorbu složitější infrastruktury.
  • Infrastrukturu si připravíme u sebe na počítači a můžeme jí pak nasadit kdekoliv. Například jí sdílet kolegovi, který si buildne šablony a nemusí už nic instalovat a konfigurovat, což ušetří čas a případné komplikace.
  • Po smazání kontejnerů nám zůstane počítač čistý, než pokud bychom si vše instalovali přímo na hostitelský systém.
  • Šablony můžeme verzovat a s kontejnerama dále pracovat, škálovat je, rollbacknout službu na předchozí verzi, automatizovat deployment apod.

Architektura

Docker architektura se skládá z hosta, na kterém je nainstalovaný docker engine (daemon, klient). Pomocí klienta vše ovládáme, z registru si stáhneme připravenou šablonu, tu buildneme, tím se vytvoří kontejner a ten poté můžeme spustit a ovládat.

zdroj

Rozdíl mezi kontejnerem a plnou virtualizací

Hlavní rozdíl je v tom, že kontejner neobsahuje operační systém, ale pomocí dockeru sdílíme operační systém hosta. Nevirtualizujeme tedy celý server, ale pouze aplikaci. Tím se šetří výpočetní výkon a práce s kontejnerama je rychlejší.

zdroj

Přehled příkazů

Tam kde není specifikováno jinak můžeme pro šablonu nebo kontejner zadat jak název tak i identifikátor nebo jen část identifikátoru (pokud by část identifikátoru nebyla unikátní tak nás docker upozorní, že neví který kontejner máme na mysli).

# Stáhnutí šablony z hub.docker.com
> docker pull image_name

# Výpis stáhnutých šablon
> docker images

# Vytvoření a spuštění kontejneru,
#   --name jméno pro nový kontejner,
#   -p namapování portu hosta na vnitřní port služby v kontejneru,
#   -d (daemon), kontejner se spustí na pozadí a my můžeme s příkazovou řadkou hosta dále pracovat.
> docker run --name kontejner -p port:port -d image_name

# Spuštění nebo zastavení již vytvořeného kontejneru
> docker stop container
> docker start container

# Výpis běžících kontejnerů
> docker ps 

# Výpis všech kontejnerů (běžících i pozastavených)
> docker ps -a

# Smazání kontejneru a šablony,
#   -v smaže kontejner i s volume.
> docker rm -v container
> docker rmi image

# Přejmenování existujícího kontejneru
> docker rename container new_container_name

# Zobrazení konfigu šablony nebo kontejneru
> Docker inspect image_or_container

# Zobrazení logu. Hodí se například pokud spustíme šablonu, ale vytvořený kontejner neběží.
> docker logs container

# Spuštění terminálu u běžícího kontejneru. Příkazem exit z terminálu zase vyskočíme.
> docker exec -it container bash

Spuštění webové aplikace v kontejneru

Jako příklad si připravíme asp.net core webovou aplikaci, kterou spustíme v docker kontejneru. Nejdříve jí spustíme v kontejneru, který bude obsahovat dotnet SDK, tomu nasdílíme přímo zdrojové kódy aplikace, které budou uložené ve složce na souborovém systému hosta. Poté jí spustíme v kontejneru, který bude obsahovat pouze dotnet runtime, kdy kontejneru nasdílíme pouze výsledný build aplikace. Nakonec si vyzkoušíme variantu, kdy si vytvoříme vlastní šablonu, která vytvoří kontejner, který již build aplikace bude obsahovat v sobě. První dvě varianty jsou spíše pro vyzkoušení si jak docker funguje, variantu s konfiguračním souborem dockerfile již můžeme použít pro deployment.

Vytvoření aplikace

Vytvoříme si asp.net core mvc projekt. Zároveň musí existovat solution, abychom mohli později použít dotnet run příkaz.

dotnet new sln
dotnet new mvc
dotnet sln add Webapp.csproj

Do Home/Index.cshtml vložíme následující kód, abychom viděli v jakém prostředí nám aplikace běží (proměnná ASPNETCORE_ENVIRONMENT).

<environment names="Development">
  <p class="alert alert-success">Development</p>
</environment>

<environment names="Production">
  <p class="alert alert-danger">Production</p>
</environment>

Namapování zdrojových souborů aplikace do kontejneru a spuštění

Stáhneme si oficiální šablonu od Microsoftu, která obsahuje vývojové prostředí dotnet (SDK). Do kontejneru namapujeme adresář se zdrojovými kódy a pomocí příkazu dotnet run aplikaci spustíme.
docker pull mcr.microsoft.com/dotnet/sdk
Upravíme soubor Properties/launchSettings.json do kterého si přidáme Development profil, který použijeme pro spuštění aplikace. Důležité je nastavit proměnnou aplicationUrl, aby nám aplikace naslouchala na libovolné IP adrese a proměnnou ASPNETCORE_ENVIRONMENT, pomocí které dáváme aplikaci vědět, že běží ve vývojovém prostředí.
"Development": {
  "commandName": "Project",
  "dotnetRunMessages": "true",
  "launchBrowser": false,
  "applicationUrl": "http://0.0.0.0:5000",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
Foreground mód
Spustíme kontejner v tzv. foreground módu, kdy máme k dispozici příkazovou řádku, ve které spustíme aplikaci ručně.
docker run --rm -it -p 5000:5000 -v %cd%:/app -w "/app" --name webapp-dev mcr.microsoft.com/dotnet/sdk bash
dotnet run --launch-profile=Development
  • --rm smaže kontejner po jeho zastavení
  • -it foreground mód, používá se zároveň s příkazem bash
  • -p mapování portu hosta na port kontejneru, na kterém naslouchá služba
  • -v mapování adresáře hosta na adresář v kontejneru
  • %cd% windows cmd zkratka pro aktuální adresář. V powershellu lze použít ${PWD} a na Linuxu $(pwd)
  • -w adresář, ve kterém se spouští následné příkazy
  • --name jméno nově vytvářeného kontejneru
  • mcr.microsoft.com/dotnet/sdk šablona, podle které kontejner vytváříme
  • dotnet run --launch-profile=Development příkaz, který se spustí v kontejneru po jeho vytvoření

místo parametru -v můžeme použít novější parametr --mount, který funguje velmi podobně a použití by bylo následovné
--mount type=bind,source=%cd%,target=/app

Background mód
Ve druhé variantě spustíme kontejner na pozadí. Díky tomu můžeme příkazovou řádku hosta použít pro další příkazy nebo jí zavřít. Místo bashe spustíme přímo aplikační službu, ze dvou příkazů nám tím vznikne jediný.
docker run --rm -d -p 5000:5000 -v %cd%:/app -w "/app" --name webapp-dev mcr.microsoft.com/dotnet/sdk dotnet run --launch-profile=Development
  • -d background mód
Ať již kontejner spustíme ve foreground nebo background módu, aplikace bude dostupná z webového prohlížeče

Namapování buildu aplikace do kontejneru a spuštění

Stáhneme si šablonu s nainstalovaným dotnet runtime

docker pull mcr.microsoft.com/dotnet/aspnet

Vytvoříme build aplikace do out složky

dotnet publish -c release -o out
Jelikož nemáme v kontejneru namapované zdrojové kódy ani nainstalované SDK, nemůžeme využít soubor Properties/launchsettings.json. Potřebné proměnné ale můžeme nastavit přímo
docker run --rm -d -p 5000:5000 -v %cd%/out:/app -w "/app" --name webapp-dev mcr.microsoft.com/dotnet/aspnet dotnet Webapp.dll --urls=http://0.0.0.0:5000 --environment=Development
Aplikace nyní běží a je dostupná z webového prohlížeče

Vytvoření vlastní šablony pomocí dockerfile souboru

Dockerfile je konfigurační soubor, pomocí kterého můžeme automatizovat proces buildování aplikace a vytvořit šablonu, která již bude aplikační soubory obsahovat (nemusíme je tedy mapovat z hostitelského počítače). Tím významně zjednodušíme distribuci a spouštění aplikace/služby.

Build již máme k dispozici

Pokud náhodou ne, tak build vytvoříme

dotnet publish -c release -o out

Vytvoříme si dva dockerfile soubory, jeden pro vývoj a druhý pro produkci.

webapp/development.dockerfile
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app

# Copy published asp.net core application to /app folder in docker
COPY out/ /app

ENTRYPOINT ["dotnet", "Webapp.dll", "--urls=http://0.0.0.0:5000", "--environment=Development"]
webapp/production.dockerfile
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app

# Copy published asp.net core application to /app folder in docker
COPY out/ /app

ENTRYPOINT ["dotnet", "Webapp.dll", "--urls=http://0.0.0.0:5000", "--environment=Production"]
Nyní pomocí dockerfile souborů vytvoříme dvě nové šablony
docker build -f development.dockerfile -t lukasurban/webapp-dev .
docker build -f production.dockerfile -t lukasurban/webapp-prod .
  • -f nám specifikuje konfigurační soubor, který chceme použít. Bez parametru se jinak použije Dockerfile.
  • -t jméno naší šablony.
  • . nesmíme zapomenout na tečku, která řiká, že chceme použít adresář, ve kterém se nacházíme jako kořenový adresář pro build.
Pokud vše proběhlo bez chyby, měli bychom vidět naše nové šablony ve výpisu dostupných šablon
> docker images
REPOSITORY                        TAG           IMAGE ID       CREATED          SIZE
lukasurban/webapp-prod            latest        5d06b2155018   35 minutes ago   210MB
lukasurban/webapp-dev             latest        6ab303cfad64   35 minutes ago   210MB
Nyní můžeme začít vytvářet kontejnery, spustíme dvě instance pro vývoj a jednu pro produkci
docker run -d -p 81:5000 --name webapp-dev-01 lukasurban/webapp-dev
docker run -d -p 82:5000 --name webapp-dev-02 lukasurban/webapp-dev
docker run -d -p 80:5000 --name webapp-prod lukasurban/webapp-prod
Vypíšeme si běžící kontejnery, jestli nedošlo k chybě
> docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED              STATUS              PORTS                  NAMES
42a4eb940283   lukasurban/webapp-prod   "dotnet Webapp.dll -…"   43 seconds ago       Up 39 seconds       0.0.0.0:80->5000/tcp   webapp-prod
5df22f443cb3   lukasurban/webapp-dev    "dotnet Webapp.dll -…"   About a minute ago   Up About a minute   0.0.0.0:82->5000/tcp   webapp-dev-02
68a8eda8c3d0   lukasurban/webapp-dev    "dotnet Webapp.dll -…"   About a minute ago   Up About a minute   0.0.0.0:81->5000/tcp   webapp-dev-01
Na všechny kontejnery se nyní můžeme připojit z webového prohlížeče
Vytvoření šablony ze zdrojových souborů

Pokud bychom chtěli vytvořit šablonu přímo ze zdrojových souborů, musíme upravit stávající dockerfile na tzv. multi-stage dockerfile build. Ten nám také umožní pracovat s více šablonami najednou. V našem případě použijeme dotnet/sdk pro build aplikace a dotnet/aspnet pro spuštění. Důvod proč nepoužít dotnet/sdk i pro spuštění je ten, že výsledný kontejner by byl zbytečně velký. To nám může a nemusí vadit, jako vždy záleží na kontextu.

webapp/development.multistage.dockerfile
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish Webapp.sln -c release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "Webapp.dll", "--urls=http://0.0.0.0:5000", "--environment=Development"]
Buildneme dockerfile
docker build -f development.multistage.dockerfile -t lukasurban/webapp-dev-from-source .
Spustíme kontejner
docker run -d -p 5000:5000 --name webapp-dev-from-source lukasurban/webapp-dev-from-source
Aplikace nám již běží a můžeme si jí zobrazit v prohlížeči

Závěr

Nyní umíme vytvářet, spouštět a spravovat kontejnery a také si vytvářet vlastní šablony :-) Některé věci jsem popsal do většího detailu než jsem původně zamýšlel, ale věřím, že je to lepší než se pak zbytečně zaseknout na nějakém detailu. Chtěl jsem také popsat docker compose, který se mi ale již do tohoto článku bohužel nevešel, tak ho snad popíšu v příštím článku, který bude již v novém roce

Odkazy