Az I. ellenőrző pont: megismételhető kutatói
munkaasztal munkaasztalán a lab projekt lokálisan ugyanazt az
eredményt adja: a regisztrált kernel a projekt .venv-jére
mutat, a quickcheck.py failures=0 eredményt
ír, a futási jegyzék pedig rögzíti a hash-ellenőrzést. A probléma akkor
jelentkezik, amikor ezt a projektet egy kolléga gépére vagy CI-futásra
adjuk át. A uv.lock és a pyproject.toml a
Python-csomagok függőségeit rögzíti, de nem rögzíti sem az operációs
rendszer verzióját, sem a rendszerkönyvtárakat, sem magát a
Python-interpreter buildjét. A Másik gép, azonos
projekt alfejezetben tárgyalt OS-különbségekből tudjuk: a másik
gépen hiányozhat libgomp, más lehet a glibc vagy az
időzóna-adatbázis, ezért ugyanaz a lock még nem garantálja ugyanazt a
futási környezetet.
Ebben az alfejezetben a vizsgálati egység nem statisztikai minta,
hanem egy környezet-build: egy Dockerfile-ból épülő
image és az abból indított container.
A bemenet a lab projekt forrása (pyproject.toml,
uv.lock, src/, quickcheck.py,
generate_data.py, starter_experiment.py), a
kimenet pedig egy megismételhető futási környezet, amelyben a
quickcheck.py a kívülről csatolt adatfájlokat ellenőrzi. Az
ellenőrzési feltétel kettős: a Dockerfile ne tartalmazzon adatfájlokat
és .venv-et; a futó konténer pedig a projekt lockjának
megfelelő interpretert használja.
Induló becslésként érdemes feltenni, hogy a buildelési kontextus a
.dockerignore nélkül több száz megabájt is lehet a
.venv és az adatfájlok miatt; a szűréssel a forráskód
néhány megabájtra csökken. A Dockerfile-t ebben a generálási
környezetben nem futtatjuk, ezért az alábbi munkamenet
specifikáció és szemléltető példa, nem mérési
jegyzőkönyv.
A Docker-kép vagy image egy olvasható, rétegezett
fájlrendszer és hozzá tartozó metaadatok halmaza. Minden
RUN, COPY és FROM utasítás egy
újabb fájlrendszer-réteget hoz létre. A konténer az
image-ből indított futó példány: az image rétegeire egy írható réteget
tesz, külön processzteret, hálózatot és fájlrendszer-nézetet kap. A
konténer törlése után az írható rétege elveszik; ami megmarad, az a
volume-on vagy a bind mounton kívülre kerül.
A base image az a kiinduló image, amelyre a
Dockerfile épít. Példánkban python:3.12-slim egy
Debian-alapú, kisméretű Python-kép. A slim azt jelzi, hogy
a gyártó a felesleges fejlesztői eszközöket elhagyta. Az image-et a
FROM sor határozza meg; ha a projekt
.python-version fájlja 3.12-t ír, az csak
kérés a helyi eszköz számára, a tényleges interpretert a konténerben a
FROM python:3.12-slim adja.
A venv Python-szintű virtuális környezet:
interpretert és a projekt Python-csomagjait izolálja. A
uv sync --locked a uv.lock-ot oldja fel, tehát
a Python-csomagok verzióját és ellenőrző adatait rögzíti. A venv nem
rögzíti viszont az operációs rendszert, a rendszerkönyvtárakat és a
natív függőségeket. A két réteg felelőssége:
| Függőség | Példa | Rögzíti | Nem rögzíti |
|---|---|---|---|
| Image | glibc, libgomp, CA-tanúsítványok, tzdata,
OS-verzió |
Dockerfile FROM és build |
A kernel pontos verzióját, CPU-utasításokat |
| Python-lock | numpy, pandas, scipy, statsmodels verziója | uv.lock, uv sync --locked |
A rendszerkönyvtárakat, az interpreter buildjét |
| Adat- és splitmanifest | CSV-hash, cutoff | manifest.json, mount |
Az adat jövőbeli frissítését |
A volume és a bind mount a konténer
tárterét köti a külvilághoz. A bind mount a host egy könyvtárát teszi
elérhetővé, például -v "$(pwd)/data:/app/data:ro": a host
data könyvtára a konténeren belül /app/data
néven, csak olvashatóan jelenik meg. A volume ezzel szemben Docker által
kezelt tároló, amelyet a konténer életciklusa után is megőriz. A
Northstar-lab bemeneti CSV-ket bind mountként érdemes csatolni, hogy az
adatfájlok ne az image részei legyenek, hanem a futás pillanatában
érkezzenek.
A user a konténeren belüli processz UID-je. A
konténer alapból gyakran root felhasználóval indul, ami
adatkérdésben rossz gyakorlat: egy hibás kód a hostra is veszélyesebb
lehet, és a későbbi CI-rendszerben a jogosultság-ellenőrzés is sérül.
Ezért a Dockerfile-ban explicit USER app-et adunk meg. A
bind mount fájljogosultsága a host UID-jétől függ; ha a konténer
10001 UID-je nem egyezik az adatfájl tulajdonosával,
olvasási hiba léphet fel. A Másik gép, azonos
projekt fájljog-tanulsága itt visszatér: a hiba forrása nem az
algoritmus, hanem a fájlrendszer-hozzáférés.
A build context az a könyvtár, amelyet a
docker build . parancs a Docker daemonnak átad. A
build context tartalmazza az összes fájlt, amelyre a
COPY hivatkozhat; a .dockerignore szűri ki
belőle a felesleges elemeket. Ha a context nagy, a build már az elején
lelassul, és a .venv, az adatfájlok vagy a
.git könyvtár is bekerülhet a képbe. Ezért a
.dockerignore része a környezeti szerződésnek.
A Kernelregisztráció és tiszta újrafuttatás
alfejezetben a kernelspec argv[0] eleme mutatta a tényleges
Python-útvonalat. A konténerben az interpreter útvonala
/opt/venv/bin/python, tehát a uv sync a
/opt/venv-be telepít; az image FROM rétege a
rendszer-szintű részeket adja. A két réteg összefügg: ha a
python:3.12-slim image új buildet kap, ugyanaz a
uv.lock más glibc-vel futhat. Ha viszont a
uv.lock változik, a Docker-réteg-cache a
COPY pyproject.toml uv.lock sortól újraépül. Ez a
rétegcache-logika a Cache, backfill és
orchestrator input-hash alapú cache-elvéhez hasonlít: ha a bemenet
hash-e nem változik, a korábbi rétegek felhasználhatók.
A következő Dockerfile a lab projekt gyökerében áll, explicit
munkakönyvtárral (/app) és explicit userrel
(app). Az adatfájlokat és a .venv-et kizárjuk
az image-ből. A UV_VERSION=0.5.11 szemléltető
helyőrző, nem ellenőrzött aktuális verzió; a lab review-ban a
ténylegesen telepített uv --version kimenettel kell
rögzíteni. Ugyanez igaz a python:3.12-slim tagre: a
példában a könnyen olvashatóság kedvéért tag van, a következő szakaszban
a digeströgzítést külön tárgyaljuk.
# Dockerfile — szemléltető specifikáció, automatizált futtatás nélkül.
# Bázis kép: könnyen olvasható tag; éles rögzítéshez lásd a tag/digest szakaszt.
FROM python:3.12-slim
# Az uv verziója a lockkal együtt rögzítendő; az alábbi érték helyőrző.
ARG UV_VERSION=0.5.11
RUN python -m pip install --no-cache-dir uv==${UV_VERSION}
# Nem root felhasználó létrehozása.
RUN groupadd --gid 10001 app && \
useradd --uid 10001 --gid app --create-home app
ENV UV_PROJECT_ENVIRONMENT=/opt/venv
ENV VIRTUAL_ENV=/opt/venv
RUN python -m venv "$VIRTUAL_ENV"
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
WORKDIR /app
# A lab forrása; az adatfájlokat és a .venv-et szándékosan nem másoljuk.
COPY --chown=app:app pyproject.toml uv.lock ./
COPY --chown=app:app src ./src
COPY --chown=app:app quickcheck.py generate_data.py starter_experiment.py README.md ./
RUN uv sync --locked --no-dev --no-install-project
USER app
CMD ["python", "quickcheck.py"]A .dockerignore fájl kizárja a projekt méretes és futás
közben csatolt elemeit:
.git
.venv
__pycache__
*.pyc
.pytest_cache
.mypy_cache
.ruff_cache
data/
artifacts/
*.csv
*.parquet
.env
A build és a futás parancsa Linuxon, WSL2-ben vagy macOS-héjban (a
lab/ projektgyökér mint munkakönyvtár):
# Linux, WSL2 vagy macOS héj; munkakönyvtár: a lab projekt gyökere
docker build --pull --progress plain -t northstar-lab:dev .
docker run --rm \
-v "$(pwd)/data:/app/data:ro" \
-v "$(pwd)/artifacts:/app/artifacts" \
northstar-lab:devPowerShellben a sortörés backtick, a ${PWD} a környezeti
változó; a bind mount útvonala a helyi lab tényleges elérési útjához
igazítandó:
# PowerShell; munkakönyvtár: a lab projekt gyökere
docker build --pull --progress plain -t northstar-lab:dev .
docker run --rm `
-v "${PWD}/data:/app/data:ro" `
-v "${PWD}/artifacts:/app/artifacts" `
northstar-lab:devAz ellenőrzéshez a konténerbe lépve le lehet kérdezni az interpreter útvonalát és a user azonosítóját:
# Szemléltető ellenőrző parancs; a konkrét kimenet a lab és a Docker verziójától függ
docker run --rm northstar-lab:dev python -c "import sys; print(sys.executable)"
# Elvárt: /opt/venv/bin/python
docker run --rm northstar-lab:dev id -u
# Elvárt: 10001Az image tartalmának vizsgálata:
docker image inspect northstar-lab:dev --format '{{.Id}}'A .Id az image tartalomazonosítója, nem feltétlenül
rövid. A RepoDigests lista üres is lehet egy kizárólag
helyben épített image-nél; ekkor az index .RepoDigests 0
kifejezés hibát adhat. A build ellenőrzéséhez külön
docker image inspect northstar-lab:dev --format "{{.Id}}"
parancsot használjunk; registry-digestet csak ténylegesen közzétett vagy
letöltött image-nél várjunk.
A build napló eleje mutatja a context méretét; a
.dockerignore hatására a .venv és az
adatfájlok nem kerülnek a contextbe, ezért a forrás-copy néhány
megabájtos marad. A COPY --chown=app:app beállítja a fájlok
tulajdonosát a konténeren belül, így a USER app után a kód
elérhető. A uv sync --locked --no-dev a lockot
kikényszeríti: ha a pyproject.toml és a
uv.lock nincs összhangban, a build leáll, és ez
kívánt hiba. A runtime --no-dev a
fejlesztői csomagokat kihagyja; a quickcheck.py-hez nem
szükséges pytest, de ha a lab teljes tesztfuttatást kér, külön
teszt-image vagy build stage indokolt.
A futás során a data mount :ro jelzése
megakadályozza, hogy a vizsgált bemeneti fájlokat a konténer módosítsa;
az artifacts mount írható, így a futás eredménye a konténer
törlése után is megmarad. A --rm a leállás után törli a
konténert, de a bind mountok tartalma a hoston továbbra is elérhető. Az
invariáns: ha a Dockerfile, a uv.lock és a forrás ugyanaz,
akkor ugyanaz a quickcheck.py fut; a
failures=0 eredmény csak a csatolt adat sértetlenségét
igazolja, ahogy I. ellenőrző pont: megismételhető
kutatói munkaasztal alfejezetben is külön réteg volt.
Értelmezés. A kolléga gépe rendelkezik a
uv.lock másolatával, de nincs Docker. Magyarázd el, miért
nem elegendő a lock egyedül ahhoz, hogy a lab modellképzése bitazonos
környezetben fusson. Nevezz meg legalább két függőséget, amit a lock
nem rögzít, és mondd meg, melyik réteg fedi le
őket.
Implementáció. Egészítsd ki a fenti
.dockerignore-t a következő elemekkel, és indokold meg,
miért fontos mindegyik: uv.lock biztonsági másolata,
.ipynb_checkpoints, *.log,
artifacts/split.json. (A uv.lock maga a
Dockerfile-ban másolódik, de a kérdés azt vizsgálja, mi kerüljön a build
contextbe.)
Hibadiagnózis. A következő futási hibák közül válaszd ki, melyik utal konténer-konfigurációs hibára, és mi a javítás:
FileNotFoundError: data/customers.csvPermissionError: [Errno 13] Permission denied: '/app/artifacts/run.json'ModuleNotFoundError: No module named 'numpy'ERROR: uv sync: lockfile not foundMegváltozott feltétel. A lab CI-futás mostantól
nem bind mountot használ, hanem az adatokat egy Docker named
volume-ba másolja a futás előtt. Hogyan változik a :ro
szemantikája, és miért lehet mégis előnyösebb a bind mount a helyi
fejlesztésben? Adj egy konkrét docker volume create és
docker run -v példát.
A uv.lock a Python-csomagok verzióját és ellenőrző
adatait rögzíti, de a Python-interpretert magát az image
FROM python:3.12-slim adja; a lock nem tartalmazza a glibc,
a libgomp vagy a CA-tanúsítványok verzióját. A kolléga gépe
lehet más Debian-verzió, más időzóna-adatbázis, vagy akár Windows, ahol
a natív wheel-ek más fordítási beállításokkal futnak. Az ellenőrzés a
megfelelő réteg: a lock a venv-függőségeket, az image a system-réteget.
A kernel és a CPU-utasítás szintén nem rögzíthető a Docker image-szel;
ez a Tensor, device és autograd
device-kérdésének konténeres megfelelője.
uv.lock biztonsági másolata: maga a lock szükséges a
Dockerfile-ban, ezért a .dockerignore akkor kapná el, ha a
COPY . .-t írnánk; explicit
COPY pyproject.toml uv.lock ./ mellett a kettős bekerülés
elkerülhető. A másolatot nem a contextből, hanem a copy-lépésből kell
kihagyni; a .dockerignore-ban a uv.lock
szerepeltetése hibás lenne, mert akkor a COPY sem éri el.
.ipynb_checkpoints: notebook-mentések nem részei a
futtatandó kódnak, és nagy, nem reprodukálható tartalmat vihetnek be.
*.log: futási naplók a host feladata; a build contextbe
kerülve félrevezető állapotot rögzítenek.
artifacts/split.json: a futás kimenete, nem a build
bemenete; a Futásazonosító és kísérleti táblázat
run-manifest logikája szerint a split a futáshoz tartozik, nem a
környezethez. A context így csak a ténylegesen szükséges,
forrás-verziózott fájlokat tartalmazza.
FileNotFoundError: data/customers.csv: a bind mount
hiányzik vagy rossz az útvonal; a
-v "$(pwd)/data:/app/data:ro" után a quickcheck relatív
elérése igazítandó. PermissionError az artifacts mounton: a
host könyvtár tulajdonosa nem a 10001 UID; írható host
jogot vagy named volume-ot kell adni.
ModuleNotFoundError: No module named 'numpy': a
uv sync nem futott le vagy a VIRTUAL_ENV PATH
nem érvényes; a
RUN uv sync --locked --no-dev --no-install-project réteg
hibája. uv sync: lockfile not found: a
COPY pyproject.toml uv.lock ./ hiányzik, vagy a
.dockerignore kizárja a lockot — a build ilyenkor joggal
áll le, és a hibát a COPY és a .dockerignore
együttes vizsgálatával kell javítani.
A named volume a Docker által kezelt tároló; a :ro
itt azt jelenti, hogy a konténer olvashatja, de a futás alatt nem
módosíthatja a volume-ot. A bind mount előnye fejlesztés közben, hogy
azonnal látja a host fájljait; a named volume ehhez képest leválik a
host elérési útról, így a bejárás nehezebb. Példa:
docker volume create northstar-data
docker run --rm \
-v northstar-data:/app/data:ro \
-v "$(pwd)/artifacts:/app/artifacts" \
northstar-lab:devA named volume használata előtt az adatokat egyszer fel kell tölteni egy ideiglenes konténerrel vagy a Docker Desktop eszközével; ez a CI-ban megbízhatóbb, mert nem függ a host könyvtárstruktúrájától.
„Az image tag azonosítja a tartalmat.” Ez téves. A
python:3.12-slim tag egy mutató, amelyet a karbantartó újra
kiadhat. Tegnap a docker pull még a
sha256:aaaa... digestet adhatta, ma ugyanaz a tag a
sha256:bbbb... tartalomra mutathat; a két gép buildje ezért
eltérhet. Az ellenőrzéshez a tag mellé a digestet kell rögzíteni:
FROM python:3.12-slim@sha256:.... Ha egy rögzített
digesttel megadott bázisimage új tartalmat kap a tag alatt, a digest
továbbra is a régi tartalomhoz köt; az image letöltése akkor hibázik, ha
a digest már nem érhető el. A frissítéshez új digestet kell választani
és review-ban kezelni; ez a Modelljelölt, jóváhagyás
és registry modellregistry-jéhez hasonló: a champion
címke szintén mutálható, csak a tartalom-hash az azonosító.
Önellenőrzés: a futó konténerben ellenőrizd, hogy id -u
értéke 10001, a sys.executable
/opt/venv/bin/python, és a docker build
context-méret nem tartalmazza a .venv-et. Ha a context
nagy, a .dockerignore hibás vagy hiányzik. A bind mount
jogosultsági hibája esetén ne a Python-kódot javítsd először: előbb a
host UID és a mount jogosultságát vizsgáld.
A konténer-image az operációs rendszer, a rendszerkönyvtárak és a
Python-interpreter rétegét rögzíti; a uv.lock ugyanazon
belül a Python-csomagok verzióját. A Dockerfile explicit
munkakönyvtárral, nem root userrel, kizárt adatokkal és
.dockerignore-dzsal a lab futását hordozhatóvá teszi, de a
kernel és a CPU-utasítások továbbra is változhatnak. A bind mount
adatfájlként, a volume pedig tartós tárolóként szolgál; a tag helyett
digest azonosítja a buildet. A következő alfejezetben Távoli interpreter és notebook biztonságos elérése
a távoli interpretert és a notebook biztonságos elérését vizsgáljuk,
ahol ezek a környezeti rétegek a távoli gépen is érvényesek
maradnak.