32.2 Távoli interpreter és notebook biztonságos elérése

32.2.1 Cél és konkrét helyzet

A Northstar tanulóprojekt ~/projects/northstar könyvtára egy devbox-07 nevű, SSH-val elérhető Linux CPU-gépre került. A helyi géped böngészőjében szeretnéd szerkeszteni a notebookot, de a kernelnek a távoli gépen kell futnia, hogy a CPU-igényes starter_experiment.py futtatása és a lab/ adatfájlok elérése ne a hálózaton keresztül történjen. Ebben az alfejezetben pontosan megnevezzük, mi fut helyben és mi a távoli gépen, hogyan védjük a Jupyter hozzáférést, és hogyan rajzoljuk meg a munkamenet adat- és processztérképét.

A bemenet: az SSH hosztnév, a távoli felhasználónév, a projektkönyvtár elérési útja, valamint a helyi gépen egy böngésző. A megfigyelési egység a futó Jupyter szerverprocessz és annak hálózati kapcsolata. Az elvárt eredmény: a böngészőben megnyitott notebook cellája a távoli hosztnevet írja ki, és a távoli projektkönyvtárban lévő fájlokat éri el. Előzetes becslés: ha a devbox-07 hosztneve valóban devbox-07, akkor az ellenőrző cella socket.gethostname() értéke ezzel egyezik. A biztonsági ellenőrzés invariánsa: a Jupyter szerver csak a 127.0.0.1 loopback interfészen hallgat, a böngésző hozzáférése pedig SSH-alagúton át, token használatával történik.

32.2.2 Fogalmi magyarázat

A távoli notebookmunka három dolgot kever össze leggyakrabban: a helyi szerkesztőt, a távoli processzt és az adat helyét.

Az SSH (secure shell, biztonságos távoli parancsértelmező) titkosított csatornát épít a helyi és a távoli gép között. A port forwarding (porttovábbítás) ezt a csatornát használja: a helyi gép egyik portjára érkező forgalmat az SSH kapcsolaton át a távoli gép megadott portjára továbbítja. A -L 8888:localhost:8888 kapcsoló jelentése:

helyi 8888  ->  SSH csatorna  ->  távoli localhost:8888

A második localhost a távoli oldalon értendő, tehát a Jupyter szerver loopback címére mutat.

Miért kötjük a szervert a loopback interfészre? A Jupyter alapértelmezés szerint jelszót és tokent használ, de ha a szerver minden interfészen, 0.0.0.0-n hallgat, akkor a hálózat más gépei is elérhetik. A 127.0.0.1-re kötés azt jelenti, hogy a távoli gépre bejelentkezett felhasználók, illetve az SSH-alagút férhetnek hozzá, a külső hálózat nem. A remote kernel (távoli kernel) kifejezés arra utal, hogy a notebookkódot végrehajtó processz a távoli gépen fut; a böngésző sosem futtat Python-kódot.

A VS Code Remote egy másik útvonal ugyanarra a célra. A VS Code felület helyben marad, de a program egy VS Code Server nevű komponenst telepít a távoli gépre. A terminál, a fájlszerkesztő és a notebook kernel ezért a távoli gépen fut, és a VS Code saját csatornáján kezeli a hálózati forgalmat; ilyenkor a kézi porttovábbítás és a böngésző külön alagút nélkül működik.

32.2.3 Teljes minimális munkapélda

A következő munkamenet parancsai kipróbálhatók egy saját, SSH-val elérhető Linux gépen. Az itt közölt kimenet szemléltető, nem mért futási jegyzék: e könyvfejezet írásakor nem hajtottam végre a parancsokat egy adott devbox-07 gépen, ezért a saját géped hosztneve, elérési útja és a Jupyter logrészlete el fog térni.

Előfeltétel. A távoli gépen már működik a Northstar projekt uv környezete, ahogyan azt a Python-környezetekről szóló részben felépítettük. A JupyterLab legyen a projekt dev függősége; ha hiányzik, a távoli gépen add hozzá:

# Távoli Linux terminál, munkakönyvtár: ~/projects/northstar
uv add --dev jupyterlab
uv sync --locked

1. lépés: ellenőrizd az SSH-hozzáférést a helyi gépről. A példában a helyi gép Windows 11 PowerShellt használ, a munkakönyvtár C:\work.

ssh devbox-07 "hostname && uv --version"

A várható, szemléltető kimenet első sora a távoli hosztnév, a második az uv verziója. Ha a hosztnév nem devbox-07, akkor a notebook ellenőrző cellájának is ezt a tényleges nevet kell mutatnia.

2. lépés: indítsd a Jupyter szervert a távoli gépen. Nyiss egy másik SSH-terminált, és fuss a távoli gépen:

# Távoli Linux terminál, munkakönyvtár: ~/projects/northstar
cd ~/projects/northstar
uv run jupyter lab --no-browser --ip=127.0.0.1 --port=8888 \
  --ServerApp.token='s126-example-token'

A kapcsolók jelentése:

A terminálban megjelenő, szemléltető log:

[I 2026-10-20 09:00:00.000 ServerApp] Jupyter Server is running at:
[I 2026-10-20 09:00:00.000 ServerApp] http://127.0.0.1:8888/lab?token=s126-example-token

A szerver mostantól fut, és az indító terminál foglalt.

3. lépés: nyiss SSH-alagutat a helyi gépről. Egy új helyi PowerShell ablakban:

ssh -N -L 8888:localhost:8888 devbox-07

A -N azt jelenti, hogy nem kérünk távoli parancsértelmezőt, csak a továbbítást tartjuk fenn. Ez a terminál is foglalt marad, amíg az alagút él.

4. lépés: nyisd meg a notebookot a helyi böngészőben. A cím:

http://localhost:8888/lab?token=s126-example-token

A böngészőben a localhost a helyi gép 8888-as portját jelenti; az SSH-alagút továbbítja a kérést a devbox-07 loopback címére.

5. lépés: ellenőrző cella. Hozz létre egy új notebookot, és futtasd:

import socket
import pathlib

print("kernel_host:", socket.gethostname())
print("kernel_cwd:", pathlib.Path.cwd())
print("data_dir_exists:", pathlib.Path("data").is_dir())

A várható, szemléltető kimenet:

kernel_host: devbox-07
kernel_cwd: /home/youruser/projects/northstar
data_dir_exists: True

Ha a kernel_host a helyi géped neve lenne, akkor a kernel nem a távoli gépen futna, hanem véletlenül egy helyi Jupyter szolgálná ki.

6. lépés: ellenőrizd a szerver hálózati kötését. Egy újabb távoli SSH-parancsban:

ss -ltnp | grep 8888

A kívánt, szemléltető minta:

LISTEN 0      128          127.0.0.1:8888       0.0.0.0:*    users:(("python",pid=1234,fd=5))

A 127.0.0.1:8888 a fontos: ez mutatja, hogy a szerver nem hallgat a külső interfészeken. Ha itt 0.0.0.0:8888 vagy egy konkrét külső IP jelenik meg, a szerver nyilvánosan elérhető, és azonnal le kell állítani.

7. lépés: állítsd le. A szerver termináljában Ctrl-C, majd az alagút termináljában szintén Ctrl-C vagy kilépés.

32.2.4 Eredmény értelmezése

Az adat- és processztérkép a munkamenet lényege:

Helyi gép (Windows)                         Távoli gép (devbox-07, Linux)
+-------------------------+                 +----------------------------------+
| Böngésző                |   SSH-alagút    | Jupyter Server 127.0.0.1:8888    |
| localhost:8888          |================| kernel processz (Python)         |
| notebook nézet, kódküldés|                 | notebook fájl: ~/projects/...    |
|                         |                 | adat: ~/projects/.../data/       |
+-------------------------+                 +----------------------------------+

A böngésző a notebook felületét és a cellák kimenetét látja; a háttérben a szerver a távoli gépen tárolja a notebookot, és a kernel a távoli projektkönyvtárból olvassa az adatokat. A token a hozzáférés kulcsa: az URL ?token= része a böngésző és a szerver közötti ideiglenes hitelesítés, nem a notebook tartalmának része.

Az előző alfejezetben tárgyalt konzisztens adat- és splitazonosítás itt is fontos: a manifest.json hash-e a távoli projektkönyvtárban van, tehát a kernel pontosan azt a fájlt látja, amelyet a futásrekordban rögzíteni fogunk. A tanítási ciklusról szóló rész tanulsága ide is átvihető: ha a notebook egy hosszan futó modellillesztést indít, az a távoli CPU-n fut, és a böngésző bezárása nem feltétlenül állítja le a kernel processzt; a szerver leállításakor azonban az összes hozzá tartozó kernel is megszakad.

Az SSH-porttovábbítás önmagában nem helyettesíti a Jupyter hitelesítését. Ne használjunk nyilvános bind címet vagy kikapcsolt tokent megfelelő izoláció nélkül.

32.2.5 Feladatok

  1. Értelmezés. A fenti munkamenetben nevezd meg azt a három komponenst, amelynek más a helye, és mondd el, miért nem futtat a böngésző Python-kódot. Ha a böngésző címsorában localhost-ot látsz, miért lehet a kernel_host mégis devbox-07?

  2. Megvalósítás. Írd le a három parancsot, amelyekkel a devbox-07 gépen a ~/projects/northstar könyvtárból elindítod a JupyterLab szervert 127.0.0.1:8888-on, a helyi gépről megnyitod az alagutat, majd a böngészőből csatlakozol. Adj meg egy ellenőrző cellát is, amely a kernel hosztnevét és a projektkönyvtárban lévő fájlok számát írja ki. Mi az az invariant, amelyet a szerver logjában és a kernel kimenetében is ellenőrizni tudsz?

  3. Hibakeresés. A kolléga azt mondja: a böngészőben connection refused hiba jelenik meg a http://localhost:8888 címen, pedig a ps aux | grep jupyter a távoli gépen mutatja a folyamatot. Sorolj fel legalább három lehetséges okot, és mindegyikhez adj egy konkrét diagnosztikai parancsot a megfelelő gépen.

  4. Megváltozott feltétel. Másnap nem böngészőt, hanem VS Code-ot szeretnél használni ugyanazon a devbox-07 gépen. Hogyan változik a munkamenet a VS Code Remote SSH használatával? Mely komponens fut helyben, és melyik a távoli gépen? Miért nincs szükséged a -L 8888:localhost:8888 alagútra?

32.2.6 Megoldások és indoklás

1. A három komponens: a böngésző mint helyi szerkesztő, a Jupyter szerver mint távoli folyamat, és a projekt adatfájljai mint távoli fájlrendszerbeli erőforrások. A böngésző JavaScriptet futtat a felülethez, de a notebookcellák Python-kódját a szervernek küldi el; a válasz a kernel által kiszámított szöveges kimenet. A localhost a böngésző szemszögéből a helyi 8888-as portot jelenti, amelyet az SSH-alagút továbbít a távoli 127.0.0.1:8888-ra. Ezért a kernel hosztneve a távoli gépé.

2. A három parancs:

# Távoli terminál, munkakönyvtár: ~/projects/northstar
uv run jupyter lab --no-browser --ip=127.0.0.1 --port=8888 \
  --ServerApp.token='s126-example-token'
# Helyi PowerShell, munkakönyvtár: C:\work
ssh -N -L 8888:localhost:8888 devbox-07

A böngésző címe:

http://localhost:8888/lab?token=s126-example-token

Ellenőrző cella:

import socket
import pathlib

print("kernel_host:", socket.gethostname())
print("file_count:", len(list(pathlib.Path(".").iterdir())))

A várt kimenet a távoli hosztnév, és a projektgyökér fájljainak száma. Az invariant: a kernel hosztneve egyezik az ssh devbox-07 hostname parancs kimenetével, és a ss -ltnp szerint a szerver a 127.0.0.1:8888 címen hallgat.

3. Lehetséges okok:

4. VS Code Remote SSH esetén a VS Code felület marad a helyi gépen, de a távoli gépre települ egy VS Code Server. A terminál, a Python-interpreter, a kernel és a fájlhozzáférés a devbox-07-en fut. A VS Code a saját SSH-csatornáján proxyzza a notebookszervert, ezért a böngészőhöz tartozó kézi -L alagút felesleges. A notebook megnyitása a VS Code beépített Jupyter felületén történik, a kernel kiválasztásánál a távoli uv környezet kernelspecje látszik, amint azt a kernelregisztrációról szóló részben tárgyaltuk.

32.2.7 Gyakori tévedés ellenpéldával

Gyakori tévedés, hogy a notebook URL ismerete önmagában elegendő, és a token csak kényelmi kiegészítő, „úgysem jut el senki a portig, ha SSH-alagutat használunk”. Ez két ponton hibás.

Először: a token akkor is hitelesítő adat, ha az alagút szűkíti a hálózati expozíciót. Ha a szervert rosszul, --ip=0.0.0.0 --ServerApp.token='' módon indítod el, akkor a http://devbox-07:8888 cím a hálózat minden, a gépet elérő szereplője számára nyitva van, és üres token mellett bárki notebookot nyithat, parancsot futtathat a távoli felhasználó jogosultságával. A porttovábbítás ilyenkor már nem véd, mert a szerver maga publikusan hallgat.

Másodszor: a tokent a futtatás részének kell tekinteni, nem a notebook tartalmának. Ellenpélda a biztonságos munkamenetből: ha a Jupyter logjából kimásolt ?token=... URL-t beilleszted a notebook egyik markdown cellájába, majd a notebookot verziókezelőbe töltöd, a token a git-előzményekben is megjelenik. A szerver újraindításakor a token lejár, de a korábbi érték már nyilvános lehet. A helyes szokás: a token csak a böngésző címsorában vagy egy helyi, verziókezelt fájlon kívüli jelszókezelőben szerepeljen.

Önellenőrzés: indítsd el a szervert a fenti módon, és futtasd a socket.gethostname() cellát. Ha a kimenet a helyi géped neve, akkor valamelyik lépésben nem a távoli processzhez kapcsolódtál; ha a szerver ss kimenetében 0.0.0.0 szerepel, a hálózati kitettség hibás, még akkor is, ha a token erős.

32.2.8 Rövid összefoglaló

A távoli notebookmunka során a böngésző vagy a VS Code felület helyben fut, míg a Jupyter szerver, a kernel és a notebookfájl a távoli gépen. Az SSH-alagút a helyi 8888-as portot köti a távoli 127.0.0.1:8888-hoz, így a szerver nem válik nyilvános szolgáltatássá. A token a futtatás hitelesítő adata, ezért nem kerülhet notebookba vagy verziókezelőbe; a ss -ltnp ellenőrzése igazolja a loopback-kötést. A kernel hosztneve és a munkakönyvtár tartalma az a két kézzel ellenőrizhető invariáns, amely megmutatja, hogy valóban a távoli CPU dolgozik. A következő alkalmazásban ezt az izolált távoli környezetet CI-feladattá alakítjuk a CI: kódellenőrzés és kis számítás új környezetben alfejezetben.