A hivatalos Dockerfile-bevált gyakorlatok rengeteg hasznos tartalmat kínálnak arról, hogyan teheted jobbá a Dockerfile-jaidat.
Elsősorban a teljesítményre érdemes optimalizálnod (különösen a tesztfuttatók esetében). Így a tooling a lehető leggyorsabban fut, és nem fut ki az időből.
A végrehajtási idő gyakori mérése nagyszerű módja annak, hogy ráérezz a tooling teljesítményére. Szokásoddá váljon, hogy egy változtatás után és előtt is mérd a végrehajtási időt. Még akkor is mérd meg a végrehajtási időt, ha „biztos” vagy benne, hogy a változtatás javítani fog a teljesítményen.
Amikor csak lehet, írj szkripteket a teljesítmény automatikus mérésére (ezt nevezik benchmarkingnak is). Egy nagyon hasznos parancssori eszköz a hyperfine, de nyugodtan használj bármit, ami a legjobban megfelel a toolingodnak.
Az újabb kurzus-tooling tárolók hozzáférnek a következő két szkripthez:
./bin/benchmark.sh: a kurzus-tooling kódjának benchmarkolása (forráskód)./bin/benchmark-in-docker.sh: a kurzus-tooling Docker image benchmarkolása (forráskód)Ha olyan kurzus-tooling tárolón dolgozol, amelyben nincsenek meg ezek a fájlok, nyugodtan másold be őket a tárolódba a fenti forráslinkek segítségével.
A benchmarkoló szkriptek segíthetnek megbecsülni a tooling teljesítményét. Tartsd azonban észben, hogy az Exercism éles szerverein a teljesítmény gyakran alacsonyabb.
Kísérletezz különböző alap image-ekkel (pl. Alpine az Ubuntu helyett), hogy lásd, az egyik (jelentősen) felülmúlja-e a másikat. Ha a teljesítmény nagyjából egyforma, válaszd a legkisebb image-et.
Nézd meg, hogy javít-e a teljesítményen, ha a none helyett az internal hálózatot használod.
További információért lásd a hálózati dokumentációt.
A kurzus toolingja egy egyszeri, rövid életű Docker konténert futtat, amely a következő lépéseket hajtja végre.
Ezért a 2. lépésben futó kód minden egyes tooling-futtatáskor lefut. Éppen ezért nagyszerű módja a teljesítmény javításának, ha csökkented a 2. lépésben futó kód mennyiségét. Ennek egyik módja, hogy a kódot futásidőből build időbe helyezed át. Míg a futásidejű kód minden egyes tooling-futtatáskor lefut, a build idején futó kód csak egyszer fut le (amikor a Docker image felépül).
A build idején futó kód egyszer fut le, a GitHub Actions munkafolyamat részeként. Ezért nem baj, ha a build idején futó kód (viszonylag) lassú.
Amikor a Haskell tesztfuttatóban teszteket futtatsz, néhány alapkönyvtárat le kell fordítani. Mivel minden tesztfutás egy friss konténerben történik, ez azt jelentette, hogy a fordítás minden egyes tesztfutáskor megtörtént! Ennek elkerülésére a Haskell tesztfuttató Dockerfile-ja a következő két parancsot tartalmazza:
COPY pre-compiled/ .
RUN stack build --resolver lts-20.18 --no-terminal --test --no-run-tests
Először a pre-compiled könyvtár bekerül az image-be.
Ez a könyvtár egy tesztfeladatként van beállítva, és ugyanazoktól az alapkönyvtáraktól függ, amelyektől a tényleges feladat.
Ezután lefuttatjuk a teszteket azon a könyvtáron, ami hasonló ahhoz, ahogy egy tényleges feladat tesztjei futnak.
A tesztek futtatása azzal jár, hogy az alap lefordul, de a különbség az, hogy ez build időben történik.
Az így létrejövő Docker image-ben tehát az alapkönyvtárak már le vannak fordítva.
Ez azt jelenti, hogy futásidőben már nincs szükség fordításra, ami (sokkal) gyorsabb végrehajtást eredményez.
Egyes nyelvek lehetővé teszik, hogy a kódot ahead-of-time vagy just-in-time fordítsd le. Ez egy build idő kontra futásidő kompromisszum, és a teljesítmény miatt ismét a build idején történő végrehajtást részesítjük előnyben.
A C# tesztfuttató Dockerfile-ja ezt a megközelítést használja: a tesztfuttatót előre (build időben) binárisba fordítják, ahelyett hogy a kódot futásidőben just-in-time fordítanák. Ez azt jelenti, hogy futásidőben kevesebb a munka, ami segít növelni a teljesítményt.
Igyekezz csökkenteni az image méretét, aminek köszönhetően az:
A különböző disztribúciós image-ek mérete eltérő.
Például az alpine:3.20.2 image tízszer kisebb, mint az ubuntu:24.10 image:
REPOSITORY TAG SIZE
alpine 3.20.2 8.83MB
ubuntu 24.10 101MB
Általánosságban az Alpine-alapú image-ek a legkisebbek közé tartoznak, ezért sok tooling image Alpine-ra épül.
Egyes image-eknek van speciális „slim” változata, amelyekből eltávolítottak néhány funkciót, így kisebb image-mérethez vezetnek.
Például a node:20.16.0-slim image ötször kisebb, mint a node:20.16.0 image:
REPOSITORY TAG SIZE
node 20.16.0 1.09GB
node 20.16.0-slim 219MB
A „slim” változatok azért kisebbek, mert kevesebb funkciót tartalmaznak. Lehet, hogy az image-ednek nincs szüksége a plusz funkciókra, és ha így van, fontold meg a „slim” változat használatát.
Kézenfekvő, ám nagyszerű módja az image méretének csökkentésének, ha eltávolítasz mindent, amire nincs szükséged. Ilyesmik lehetnek például:
A legtöbb Docker image-nek további csomagokat kell telepítenie, amit általában egy csomagkezelővel tesznek meg. Ezeket a csomagokat build időben kell telepíteni (mivel futásidőben nincs elérhető internetkapcsolat). Ezért a csomagkezelő gyorsítótár- és nyilvántartó fájljait a további csomagok telepítése után el kell távolítani.
Az apk csomagkezelőt használó disztribúciók (például az Alpine) az apk add használatakor a --no-cache kapcsolót használják a csomagok telepítéséhez:
RUN apk add --no-cache curl
Az apt-get/apk csomagkezelőt használó disztribúciók (például az Ubuntu) a csomagok telepítése után, ugyanabban a RUN parancsban futtassák az apt-get autoremove -y és rm -rf /var/lib/apt/lists/* parancsokat:
RUN apt-get update && \
apt-get install curl -y && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
A Dockernek van egy többlépcsős build nevű funkciója. Ezek segítségével a Dockerfile-odat külön szakaszokra bonthatod, és csak az utolsó szakasz kerül bele a létrejövő Docker image-be (a többi csak az utolsó szakasz felépítését segíti). Gondolhatsz úgy minden szakaszra, mint egy saját mini Dockerfile-ra; a szakaszok különböző alap image-eket használhatnak.
A többlépcsős buildek különösen akkor hasznosak, amikor a Dockerfile-odhoz olyan csomagokat kell telepíteni, amelyekre csak build időben van szükség. Ebben az esetben a Dockerfile-od általános felépítése így néz ki:
Ezzel a felállással a további csomagok csak a „build” szakaszban települnek, a „runtime” szakaszban nem, ami azt jelenti, hogy nem kerülnek bele a létrejövő Docker image-be.
A Fortran tesztfuttatónak curl-re van szüksége néhány fájl letöltéséhez.
A futásidejű image-ének azonban nincs szüksége a curl-re, ami tökéletes felhasználási esetté teszi ezt a többlépcsős buildhez.
Először a Dockerfile-ja definiál egy szakaszt („build” néven), amelyben a curl csomag települ.
Ezután a curl segítségével fájlokat tölt le abba a szakaszba.
FROM alpine:3.15 AS build
RUN apk add --no-cache curl
WORKDIR /opt/test-runner
COPY bust_cache .
WORKDIR /opt/test-runner/testlib
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/testlib/CMakeLists.txt
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/testlib/TesterMain.f90
WORKDIR /opt/test-runner
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/config/CMakeLists.txt
A Dockerfile második része egy új szakaszt definiál, és a COPY paranccsal átmásolja a letöltött fájlokat a „build” szakaszból a saját szakaszába:
FROM alpine:3.15
RUN apk add --no-cache coreutils jq gfortran libc-dev cmake make
WORKDIR /opt/test-runner
COPY --from=build /opt/test-runner/ .
COPY . .
ENTRYPOINT ["/opt/test-runner/bin/run.sh"]
A Ruby tesztfuttatónak telepíteni kell a git, openssh, build-base, gcc és wget csomagokat, mielőtt a szükséges könyvtárait (gemeket) telepíthetné.
A Dockerfile-ja egy szakasszal kezdődik (build néven), amely telepíti ezeket a csomagokat (apk add-dal), majd telepíti a függőségeket (bundle install-lal):
FROM ruby:3.2.2-alpine3.18 AS build
RUN apk update && apk upgrade && \
apk add --no-cache git openssh build-base gcc wget git
COPY Gemfile Gemfile.lock .
RUN gem install bundler:2.4.18 && \
bundle config set without 'development test' && \
bundle install
Ezután definiálja azt a szakaszt, amely a létrejövő Docker image-et alkotja majd.
Ez a szakasz nem telepíti azokat a függőségeket, amelyeket az előző szakasz telepített; ehelyett a COPY paranccsal átmásolja a telepített könyvtárakat a build szakaszból a saját szakaszába:
FROM ruby:3.2.2-alpine3.18
RUN apk add --no-cache bash
WORKDIR /opt/test-runner
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY . .
ENTRYPOINT [ "sh", "/opt/test-runner/bin/run.sh" ]
A C# tesztfuttató Dockerfile-ja valami hasonlót csinál, csak ebben az esetben a build szakasz használhat egy létező Docker image-et, amelyben már előre telepítve vannak a könyvtárak telepítéséhez szükséges további csomagok.
Az egységtesztek nagyon hasznosak lehetnek, de azt javasoljuk, hogy az integrációs tesztek írására összpontosíts. Legfőbb előnyük, hogy jobban tesztelik, hogyan fut a tooling éles környezetben, és így növelik a bizalmat a tooling implementációjában.
Ahhoz, hogy a lehető legjobban utánozzák az éles környezetet, az integrációs teszteknek az éles környezethez hasonlóan kell futtatniuk a toolingot. Ez azt jelenti, hogy felépítik a Docker image-et, majd a felépített image-et egy megoldáson futtatják, hogy ellenőrizzék a kimenetét.
Az integrációs teszteket golden tesztként érdemes definiálni, amelyek olyan tesztek, ahol az elvárt kimenet egy fájlban van tárolva. Ez tökéletes a kurzus-tooling integrációs tesztekhez, mivel a tooling kimenete is fájlokból áll.
Amikor a tesztfuttatót egy megoldáson futtatod, a kimenete egy results.json fájl.
Ezután összehasonlíthatjuk ezt a fájlt egy „ismerten jónak” (azaz „elvártnak” számító) kimeneti fájllal (expected_results.json néven), hogy ellenőrizzük, a tesztfuttató a szándéknak megfelelően működik-e.
A biztonság a fő oka annak, hogy Docker konténereket használunk a toolingunk futtatására.
Sok Docker image található a Docker Hubon, de igyekezz a hivatalosakat használni. Ezeket az image-eket gondozzák, és (sokkal) kisebb az esélyük arra, hogy nem biztonságosak.
Annak biztosítására, hogy a buildek stabilak legyenek (azaz ne törjenek el hirtelen), mindig rögzítsd az alap image-eidet konkrét tegekhez. Ez azt jelenti, hogy ahelyett, hogy:
FROM alpine:latest
ezt használd:
FROM alpine:3.20.2
Utóbbival a buildek mindig ugyanazt a verziót használják majd.
Alapértelmezés szerint sok image root jogosultságokkal rendelkező felhasználóval fut. Fontold meg, hogy nem privilegizált felhasználóként futtatod.
FROM alpine
RUN groupadd -r myuser && useradd -r -g myuser myuser
# RUN <COMMANDS THAT REQUIRE ROOT USER, E.G. INSTALLING PACKAGES>
USER myuser
Szinte mindig jó ötlet a legújabb verziókat telepíteni
RUN apt-get update && \
apt-get install curl
Arra biztatunk, hogy a Dockerfile-okat csak olvasható fájlrendszert feltételezve írd meg. Az egyetlen könyvtárak, amelyekről feltételezheted, hogy írhatók:
/tmp könyvtárAz éles környezetünk jelenleg nem kényszeríti ki a csak olvasható fájlrendszert, de a jövőben lehet, hogy fogja. Ezért az új tesztfuttató/elemző/representer alapsablonja csak olvasható fájlrendszerrel indul. Ha nem sikerül működésre bírni a dolgokat egy csak olvasható fájlon, nyugodtan feltételezz (egyelőre) írható fájlrendszert.