Az elkészült alkalmazásunkat sokféleképpen közzé tudjuk tenni. FejlesztÅ‘i szempontból látszólag talán a legkönnyebb megoldás az, hogy egy ZIP fájlt odaadunk az alkalmazásunkból és majd az üzemeltetés megoldja a telepÃtését és beüzemelését. Ezen megoldás rejtett problémái hamar meg tudnak mutatkozni problémák esetén, például ha valami függÅ‘ségünk beüzemelése nagyon nem triviális. Ebben az esetben elkerülhetetlen, hogy kérdések legyenek. De nem ez a legnagyobb hátrány, hanem az, hogy egy ilyen módon terjesztett alkalmazás beüzemelésének nincs univerzális módja. Ahány alkalmazás, annyiféleképpen kell beüzemelni és működésre bÃrni, a folyamat automatizálhatósága is kérdéses és szinte biztos, hogy egy egyedi megoldást fog igényelni.
Éppen ezért született meg az igény az egyszerűen közzétehetÅ‘ és beüzemelhetÅ‘ alkalmazások iránt. Az egész folyamat talán a virtuális gépekkel indult. Egy virtuális gépbe telepÃtett alkalmazást üzemelteni és frissÃteni nagyon egyszerű, mivel csak a lemezképet kell frissÃteni. Azonban ezen megoldás hátránya, hogy egy egész virtuális gépet kell emulálni operációs rendszerrel együtt, illetve az alkalmazás méretére rárakódik a teljes operációs rendszer.
Ezen igények mentén született meg a konténerizáció, amit a Docker tett igazán népszerűvé. Itt fontos megjegyezni, hogy a Docker nem az egyetlen ilyen megoldás. De mi is az a konténerizáció? A konténerizáció egy szoftver telepÃtési és üzemeltetési módszer, ami lehetÅ‘vé teszi az alkalmazások és azok függÅ‘ségeinek az egybecsomagolását és futtatását különbözÅ‘ környezetekben. A módszer alapja a konténer, ami a futtatni kÃvánt kódot, az ahhoz szükséges futtatókörnyezetet, rendszereszközöket és programkönyvtárakat egyetlen hordozható és futtatható egységbe csomagolja.
A konténerizáció nem virtualizáció, vagyis nem emulálunk egy teljes virtuális gépet. Ennek előnye, hogy csak minimális erőforrás többlettel jár egy konténer futtatása. Ezen a ponton gondolhatnánk, hogy ezen megoldás kevésbé biztonságos, mint egy virtuális gép, de nem ez a helyzet. A konténerek egymástól izolált környezetben futnak és nem látják egymás folyamatait. Ezt a konténer alatt lévő operációs rendszer oldja meg.
Itt viszont némi problémába ütköztünk, mivel a különböző operációs rendszerek izolációs képességei és megoldásai eltérnek és sokszor nem kompatibilisek egymással. Ennek köszönhetően a Linux alapú megoldások szinte egyeduralkodók, olyan szinten, hogy a Docker Windows-on egy Linux alapú virtuális gépben futtatja a konténereket és nem véletlen született meg a WSL.
Docker vagy nem docker
A konténerizációt a Docker tette népszerűvé, azonban nem csak ez az egyetlen megoldás. Az alternatÃv megoldások terjedését a Docker licenc feltételei tették szükségessé és indokolttá. A Docker üzleti célú felhasználásra és fejlesztésre sem ingyenes termék. Egy teljesen ingyenes és szabad, 99%-ban kompatibilis implementációja a Podman, ami ugyanazokat a parancsokat és argumentumokat használja, de nem teljesen kompatibilis a Docker-al. Ez az esetek többségében nem okoz problémát, de ha igen, akkor tapasztalatom szerint rendesen meg tudja keserÃteni az ember életét, mivel jóval kevesebb dokumentáció és szakirodalom áll rendelkezésre a probléma elhárÃtásához. És akkor még a fejlesztÅ‘i eszközök támogatottságáról nem is beszéltem.
A Docker egyfajta de-facto szabvánnyá nőtte ki magát és annak ellenére, hogy a Podman szinte mindenben kompatibilis vele, fejlesztői eszközök tekintetében szinte nulla az integrációja.1 Éppen ezért a könyv ezen részében is dockert fogunk használni. Opcionálisan egy továbbfejlesztési ötlet lehet a rendszer podman-es beüzemelése.
A fejlesztői környezet
A Docker Windows használatához szükségünk van egy WSL alapú Linux disztribúcióra. Egy ilyet hoz magával a Docker Desktop (https://www.docker.com/products/docker-desktop/) is, azonban a Linux rendszerekkel való átjárhatóság szempontjából itt kizárólag csak parancssorra fogok támaszkodni egy Debian alapú rendszeren.
Ennek oka az, hogy a Debian alapú rendszerek esetén az operációs rendszer nem szállÃtja magával csomagoltan a .NET-et, Ãgy lényegében olyan verziót telepÃthetünk a Microsoft oldaláról, amilyet szeretnénk. Ubuntu esetén az Ubuntu szállÃtja magával a .NET-et a csomagkezelÅ‘jében, ami nem biztos, hogy a legújabb.
A WSL beüzemelése viszonylag egy egyszerű folyamat. Egy adminisztrátori powershell konzolból az alábbi parancsok segÃtségével tudjuk megtenni:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
Ezt követÅ‘en újra kell indÃtanunk az operációs rendszerünket, majd telepÃtenünk egy WSL disztribúciót. Jelen esetben ez a Debian lesz. Ez parancssorból az alábbi parancsokkal telepÃthetÅ‘:
wsl.exe --set-default-version 2
wsl.exe --update
wsl.exe --install -d Debian
Mivel verziónként eltér, hogy milyen függÅ‘ségek kellenek a Docker és .NET beüzemeléséhez, ezért csak a hivatalos dokumentációt linkelem itt, amit követve telepÃteni tudjuk mindkét szoftvert. A Docker aktuális Debian leÃrása a https://docs.docker.com/engine/install/debian/ cÃmen található meg. .NET esetén pedig a https://learn.microsoft.com/en-us/dotnet/core/install/linux-debian cÃmen találjuk az utasÃtásokat.
Ha minden jól ment, akkor ellenÅ‘rizhetjük az alábbi parancsok segÃtségével, hogy működÅ‘képesek-e:
dotnet --version
docker --version
Ezt követÅ‘en a dotnet parancs segÃtségével fordÃtsuk le Linuxon is az alkalmazásunkat, majd futtatva gyÅ‘zÅ‘djünk meg arról, hogy Linux-on is működÅ‘képes az alkalmazás. Az esetek többségében módosÃtás nélkül az lesz, ha nem használtunk natÃv, platformfüggÅ‘ kódot. Ha van platformfüggÅ‘ vagy natÃv kódunk, akkor azt elÅ‘ször át kell vinni Linuxra és a C# kódunkban meg kell oldanunk, hogy operációs rendszer függvényében melyik binárist töltse be.
NuGet csomagoknál a natÃv függÅ‘ségek problémájába beleszaladhatunk. Egyes csomagok esetén, mint az SQLite, a NuGet disztribúció beépÃtetten tartalmazza a natÃv függÅ‘ségeket minden platformra, mÃg más csomagok esetén, leginkább méret megfontolásból ezek szét vannak bontva és további csomag telepÃtése szükséges a Linux támogatáshoz.
A konténer elkészÃtése
Ha az alkalmazásunk probléma nélkül fut, akkor konténerizálhatjuk. A konténerizációhoz szükségünk lesz egy Dockerfile fájlra. Ez lényegében a konténer létrehozásához szükséges utasÃtások sorozata. A támogatott utasÃtások teljes referenciája a https://docs.docker.com/reference/dockerfile/ cÃmen található.
A konténerizáció és .NET esetén két fÅ‘ irány közül választhatunk: vagy mi készÃtjük el a konténert teljes egészében, vagy a Microsoft által biztosÃtott Docker konténert bÅ‘vÃtjük tovább az alkalmazásunkkal. Az elsÅ‘ megoldás akkor lehet hasznos, ha natÃv kódra fordÃtjuk az alkalmazásunkat és a lehetÅ‘ legkisebb méretű konténert szeretnénk kapni.
A második megoldás pedig azért jó, mert kényelmesebb. Lényegében minden .NET verzió kiadáshoz készül egy konténer, Ãgy az alkalmazásunk újracsomagoláskor mindig a legfrissebb biztonsági frissÃtések benne lesznek a konténerben, mÃg az elsÅ‘ megoldás esetén errÅ‘l nekünk kell gondoskodnunk, de cserébe mindent személyre tudunk szabni, még a biztonsági rések számát és komolyságát is. Nem véletlen külön szakterület a konténerizáció.
Az, hogy melyik irányt érdemes választani, szituációfüggÅ‘. Jelen esetben a Microsoft konténerbÅ‘vÃtés irányt választottam, mivel a könyv ezen fejezete igencsak hosszúra nyúlt már és ha még ebbe is beleásnánk magunkat, akkor csak egy újabb véget nem érÅ‘ nyúlüregben találnánk magunkat.
Ha a konténerbÅ‘vÃtés mellett döntünk, akkor is számos opcióból választhatunk. Azure és Ubuntu esetén létezik olyan konténer image, ami operációs rendszer függÅ‘ségeket sem tartalmaz, Ãgy igazán apró méretűek, cserébe viszont csak és kizárólag a választott operációs rendszeren hajlandóak működni.
Méret és szolgáltatások tekintetében az Alpine alapú docker image-ek kÃnálják a legjobb választást. Ezen konténerek tartalmazzák az operációs rendszer függÅ‘ségeket, de a lehetÅ‘ legminimálisabbra szorÃtva. Éppen ezért elÅ‘fordulhat, hogy extra csomagokat kell telepÃtenünk, például, ha globalizációra támaszkodunk.
Az alkalmazásunk konténerizációjához szükséges Dockerfile a következő lesz:
# alap image
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime
# konténer metaadatok
LABEL maintainer="Maintainert mail <user@mail.com>"
LABEL version="1.0.0"
LABEL description="Image description"
LABEL vendor="Image vendor"
# nyelvi függÅ‘ségek telepÃtése
RUN apk add --no-cache icu-libs
# Konténeren belül a /app mappába dolgozunk
WORKDIR /app
# Alkalmazás bemásolása
COPY ./UrlShortner.Web/bin/Release/net9.0/publish/ .
# Az alkalmazás a 80-as porton működik
EXPOSE 80
# Környezeti változók
ENV ASPNETCORE_URLS=http://+:80
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
ENV ASPNETCORE_ENVIRONMENT=Production
# user létrehozás, hogy az app ne root jogosultságokkal fusson a konténerben
RUN adduser -u 1000 -D appuser && \
chown -R appuser:appuser /app
# user váltás
USER appuser
# alkalmazás belépési pontja
ENTRYPOINT ["dotnet", "UrlShortner.Web.dll"]
A fenti utasÃtássorozatból az egyik legfontosabb a RUN apk add --no-cache icu-libs, mivel ez telepÃti a globalizáció működéséhez szükséges nyelvi csomagokat. Ezek hiányában az ASP.NET alkalmazásunk működésképtelen. A konténer, amit használunk, csak futtatásra képes és a publish folyamat eredményét pakolja bele a konténerbe, ezért mielÅ‘tt a docker image-et elkészÃtjük, a futtatható binárisoknak létezniük kell.
A másoláshoz használt COPY parancs mappák esetén mindent másol, ha specifikusan ki szeretnénk hagyni fájlokat vagy kiterjesztéseket, akkor azokat egy .dockerignore fájlban felsorolhatjuk. Jelen esetben az alkalmazás mellé nem kell a *.db fájlunk, ezért a .dockerignore-ba érdemes ezt beletenni.
A binárisok fordÃtását és a docker image készÃtést az alábbi két parancs segÃtségével tudjuk megtenni:
dotnet publish -c RELEASE
docker build -t urlshortner-web:latest .
A LABEL utasÃtások metaadatok megadására szolgálnak. Ezek közül a fájlban szereplÅ‘k az általánosan bevettek, de igazából bármilyen extra metaadat megadható. Ezek a docker inspect urlshortner-web:latest segÃtségével lekérdezhetÅ‘ek. A fájlnév esetén a latest egy Tag, ami az image verziójára hivatkozik a metaadatokon kÃvül. Bevett szokás, hogy a konténerünk legújabb verzióját a latest tag-el is publikáljuk, hogy az image használóinak a frissÃtési folyamatot megkönnyÃtsük.
Futtatás, compose
A fenti lépésekkel elkészült a konténerünk, de hogyan futtassuk? Természetesen a docker parancs segÃtségével tudjuk futtatni, megfelelÅ‘ paraméterekkel:
docker run -d -p 8080:80 -v $(pwd)/data:/app/data \ --name urlshortner-web-app urlshortner-web:latest
A fenti parancs a host számÃtógép 8080-as portját átirányÃtja a konténer 80-as portjára, majd elindÃtja a konténert. A -v egy kötetet csatol fel, mégpedig a jelenlegi könyvtár /data mappáját a konténer /data mappájába. Erre azért van szükség, mert valahova az adatbázist mentenie kell majd az alkalmazásunknak. A konténer /app mappája pedig csak olvasható.
Ez a fajta futtatás működőképes, de van ennek egy sokkal kényelmesebb módja, főleg akkor, ha több, egymástól független, de mégis összedolgozó alkalmazást szeretnénk egyszerre futtatni.
Erre találták ki a compose fájlt. A compose fájl egy YAML fájl, ami leÃrja a Docker számára, hogy melyik konténert hogyan és milyen beállÃtásokkal kell futtatni és kényelmesebb megoldást biztosÃt a parancssori parancsokkal szemben, mert a compose fájl tartalma a docker compose up parancs segÃtségével elindÃtható abból a mappából, ahol a compose fájl van. Egy ilyen compose fájl több konténer együttes indÃtását is lehetÅ‘vé teszi. Compose fájlt nem szükséges kreálnunk, teljesen opcionális folyamat, de érdemes, mivel egy csomó hosting megoldás alapértelmezetten támogatja, Ãgy lényegében egy szövegfájllal a teljes futtató infrastruktúra kiépÃthetÅ‘.
A korábbi docker run parancs docker compose szintaxisban:
services:
web:
image: urlshortner-web:latest
container_name: urlshortner-web-app
ports:
- "8080:80"
volumes:
- ./data:/app/data
restart: unless-stopped
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Data Source=/app/data/Application.db
Itt extra annyi, hogy a restart: unless-stopped direktÃvával a konténer a rendszer vagy a docker indÃtásával automatikusan futtatásra kerül, egészen addig, amÃg explicit meg nem állÃtjuk a konténer futtatását. Ez egy különösen hasznos beállÃtás szolgáltatások automatizálásánál.
Az environment: szekcióban bármilyen környezeti változót felül tudunk Ãrni. Éppen ezért, ha az alkalmazásunkat Docker segÃtségével tervezzük közzétenni, akkor célszerű a fontosabb konfigurációs beállÃtásokat környezeti változóból felülÃrhatóvá tenni, mivel ezeket közvetlenül elhelyezhetjük a compose fájlokban. A ConnectionStrings__DefaultConnection környezeti változóval az app.config módosÃtása nélkül felülÃrhatjuk a kapcsolódáshoz használt DB beállÃtásokat is.
A compose fájlt docker-compose.yml néven kell elneveznünk és a docker compose up -d parancssorból tudjuk elindÃtani, leállÃtani pedig a docker compose down parancs segÃtségével.
De hogy jutnak el a konténerek a cél gépre? Erre számos megoldás létezik. Az egyik a Docker által üzemeltetett DockerHub. Ez a https://hub.docker.com/ cÃmen lelhetÅ‘ fel. Ide regisztrációt követÅ‘en mi magunk is tölthetünk fel konténereket, amit aztán a compose fájl végrehajtásakor a docker automatikusan letölt majd. Ha nem szeretnénk a nagyvilágnak publikálni a konténereinket, akkor is számos opciónk van. Egy ilyen a Github. A Github rendelkezik egy Container Registry szolgáltatással, ahova privát konténereinket fel tudjuk tölteni, de lényegében bármilyen Container Registry kompatibilis szolgáltatást használhatunk erre a célra. A Github leÃrása a Container Registry szolgáltatásról a https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry cÃmen található meg.
Ha a konténer hub megoldás abszolút nem játszik, akkor az image-ek exportálhatóak is a docker save parancs segÃtségével:
docker save urlshortner-web:latest | gzip > ./urlshortner-web-latest.tar.gz
Ez egy .tar kiterjesztésű fájlt kreál, amit érdemes tömörÃteni. Ezt a tömörÃtett archÃvumot bármilyen más módon eljuttathatjuk a cél gépre, ahol kitömörÃtés után a docker load parancs segÃtségével importálni tudunk:
gzip -d -c ./urlshortner-web-latest.tar.gz > ./urlshortner-web-latest.tar
docker load -i ./urlshortner-web-latest.tar
-
A fejezet Ãrása közben megjelent a Visual Studio 2026, ami már rendelkezik Podman támogatással is, Docker mellett.↩