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.
A távoli notebookmunka három dolgot kever össze leggyakrabban: a helyi szerkesztőt, a távoli processzt és az adat helyét.
devbox-07 gépen fut. A notebook fájl is a távoli
fájlrendszerben van, ezért a kernel közvetlenül látja a
~/projects/northstar/data/ könyvtárat.~/.ssh/authorized_keys fájlban van. A Jupyter saját
belépési adata a token, amelyet a szerver indításakor adunk meg vagy
generáltatunk.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.
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 --locked1. 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:
--no-browser: a távoli gépen ne próbáljon böngészőt
nyitni.--ip=127.0.0.1: csak a loopback interfészen
hallgasson.--port=8888: rögzített port, hogy az alagút célja
egyértelmű legyen.--ServerApp.token='s126-example-token': a belépési
token. Ez itt példaértékű, tanulási célú; éles munkában ne használj
kitalált, rövid tokent, hanem engedd, hogy a Jupyter véletlen tokent
generáljon, vagy használj erős titkot.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-07A -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 8888A 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.
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.
É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?
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?
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.
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?
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-07A 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:
netstat -ano | findstr 8888; ha nem jelenik meg
LISTENING a helyi 8888-as porton, az alagút nem jött
létre.ss -ltnp | grep 8888 és a szerver logjának
ellenőrzése.127.0.0.1-en, hanem egy másik interfészen
hallgat, így a localhost:8888 cél nem éri el. Ugyanaz a
ss kimenet választ ad.Test-NetConnection localhost -Port 8888 próba tisztázza
PowerShellben.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.
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.
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.