Update Mai 2023: Folositi PyCharm

Folositi PyCharm Professional pentru lucrul pe o masina puternica. Mecanismul functioneaza si daca trebuie sa lucrati in interiorul unui docker, pe acea masina remote (Caz frecvent cand depanati cod pe un DGX sau echivalent)

In acest blog post explic cum se face!

Generalități

Aceasta pagina este pentru tine, daca ai acces la o masina puternica, masina care este partajata de mai multi useri. Pe masina respectiva, protocolul specifica ca fiecare user hackuie in docker-ul lui.

Daca esti singur pe masina, sau “e voie” sa rulati aplicatii nativ, atunci treburile sunt mai simple, va puteti folosi mai usor de tool-urile oferite de IDE-urile puternice

In toate instantele, task-ul care vreau sa il arat este cum fac development sau debug pe masina remote. Pentru porni si a monitoriza un docker, sunt alte tutoriale pe net.

Tool-urile folosite pentru dev: jupyter labPyCharm Professional edition si  Visual Studio Code.  Codul va fi executat pe masina remote, dar editarea se face de pe masina locala.

Inainte de a incepe, recomandarea mea e sa evitati scenariile de mai sus. Dezvoltati codul pe masina locala, puteti rula exemple mici cu date putine pentru a vedea ca “bitii curg bine” prin cod, apoi faceti deploy in docker, pe masina mare. Oricum tranzitia nu e smooth, probabil ca vor aparea conflicte la montatul de cai, dar, daca, mai aveti si erori de cod, e si mai greu. Totusi, sunt cazuri mai nisate cand dezvoltarea se poate face doar pe masina puternica (ex lucrul la codul antrenare paralela, lucrul nativ cu CUDA, etc).

Tl;dr docker remote

Situatia in care aveti acces la masina remote si aveti voie sa rulati codul doar in docker.

Recomand cu caldura aplicarea principiilor de baza ale ingineriei software. SOLID si DRY va permit sa scrieti cod testabil si versatil. Folositi fisiere de configurare sau variabile de mediu pentru a configura caile. Pana si un banal if, care decide ce cai sa seteze, in functie de numele masinii este un pas in fata privind flexibilitatea codului.

Dezvoltati codul local. Aveti de codat ceva non Pytorch/TF? Nu aveti scuza, puteti testa local. Daca trebuie sa implementati cod pytorch/TF puteti testa codul in modul GPU. Cititi in documentatie cum puteti specifica device-ul pentru tensori sau device-ul implicit unde se vor executa operatiile (cuda sau cpu)

A 2-a optiune, mai putin buna, este Jupyter Lab. Dezavantajul este ca nu aveti debug out-of-the-box si trebuie folosit pdb. Avantaje: setup deosebit de simplu, codare direct in docker, vizualizare imagini in browser. Toate setarile de docker specificate in alte tutoriale (montare de volume mari, legare de GPU-uri la container, port forwarding, etc) sunt aplicabile 1:1

A 3-a si a 4-a optiune, la distanta mare fata de Jupyter Lab sunt PyCharm si VSCode. Pycharm, dezavantaj ca debug-ul se face greoi. Are avantajul clar ca permite editare/sincronizarea usoara peste ssh a codului. Configurarea este visuala, nu e agresiv cu masina remote. VSCode este agresiv, setup-ul foarte greoi, cu optiuni ascunse ce trebuie cautate pe net (ex cum denumesti imaginea de docker? Sau containerul? Ce porturi se deschid? Cum se monteaza volume? Dar device-uri?) Avantajul e ca pentru script-uri simple de python, nu trebuie multe configurari pentru a face debug in containerul remote. Cunostintele de docker si setarile existente in alte tutoriale (configurare volume, device-uri cuda etc) nu sunt transferabile la VSCode.

Tl;dr remote nativ

Situatia in care aveti acces la masina remote prin ssh si aveti voie sa rulati cod nativ, fara docker.

Prima optiune este, de departe, PyCharm professional. Sincronizare automata a codului peste SSH, configurare, executie si debug, fara setari greoaie. Env-ul se poate crea cu tool-urile obisnuite (anaconda) iar PyCharm va recunoaste env-ul remote. Switch-ul dintre mediul local (ex vreti sa dezvoltati cod ce nu are neaparat nevoie de cuda) si cel remote se face cu 2 click-uri. La fel si sincronizarea fisierelor.

A 2-a optiune este Jupyter Lab, in cazul in care codul presupune multe imagini/grafice.

VSCode, out-of-the-box, e o gluma pentru acest use case. Trebuie muncit un pic pentru a avea sincronizare intre folderele locale si cele remote. Plugin-ul de remote, implicit, permite doar editare remote, fara sa faca sincronizare cu local. User-ul trebuie sa copieze manual codul initial pe masina remote. Si/sau sa aduca modificarile. Sigur exista optiuni pentru a face asta automat, dar in 1/2 zi alocata VSCode, nu am dat peste ea.

Setup software

La masina remote aveti acces SSH (vezi tutorialul cu ssh) si stiti sa configurati un local/remote port forwarding si sa va conectati.

Pe masina remote este instalat docker, puteti porni un docker, stiti sa porniti si sa va “logati” la el (pornit in modul consola).

Pentru exemplificare am creat un Dockerfile minimal, ce contine un env de python. Nimic spectaculos.

FROM python:3
RUN apt-get update;apt-get install -y       \
    build-essential                         \
    htop                                    \
    htop                                   \
    && apt-get clean && apt-get autoremove  \
    && rm -rf /var/lib/apt/lists/

ENV HOME /workspace/
WORKDIR /workspace/

RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh; bash ./Miniconda3-latest-Linux-x86_64.sh -b;rm Miniconda3-latest-Linux-x86_64.sh
RUN /workspace/miniconda3/bin/conda install -y pyyaml jupyterlab ipywidgets ipympl nodejs
RUN chmod -R a+rw /workspace

Pe masina remote, compilam dockerfile intr-o imagine. Eventual schimbati-i numele:

docker build -f Dockerfile -t visoft_python_sample_image .

In caz de erori, vedeti daca aveti toate caile setate corect.

Alegem portul 9500 ca local forward (toate cererile de pe masina locala la un anumit port vor fi captate de ssh si trimise la masina remote pe portul 9500) si 9400 ca remote forward. (Toate cererile de pe masina remote, adresate la un anumit port, vor fi trimise pe masina locala, la portul 9400).  Atentie, aceste porturi sunt partajate pe masina remote! Povestiti intre voi sau intrebati administratorul!

Pentru conventie, pe https folosim portul 8888 (Acesta va fi “captat” de ssh-ul local, respectiv va fi “ascultat” de serverele de web)

Un tool foarte bun pentru depanarea conexiunilor este SimpleHTTPServer din python:

python -m http.server 9500 --bind 127.0.0.1

Porneste un server ce asculta DOAR de la localhost, pe portul 9500. Va trimite o pagina web cu continutul folderului unde s-a lansat.

ATENTIE! NU INCERCATI BIND LA 0.0.0.0 FARA SA STITI CLAR CE FACETI!

Un ultim sfat, fiti tot timpul constienti pe ce masina executati comenzile, altfel o sa se incurce porturile.

Rularea unui server Jupyter in docker si conectare din masina locala

Situatia la care vrem sa ajungem este cea de mai jos:

Presupunem ca partea de local port forwarding este rezolvata si avem portul 8888 de pe masina locala, interceptat si trimis catre portul 9500 de pe masina remote. In config avem LocalForward 8888 127.0.0.1:9500.

Ca sa testam conexiunile de port forwarding, ne conectam pe masina remote, si rulam

python -m http.server 9500 --bind 127.0.0.1

Ar trebui sa apara un mesaj cum ca s-a pornit serverul de HTTP:

Serving HTTP on 127.0.0.1 port 9500 (http://127.0.0.1:9500/) ...

Acum, mergeti pe masina locala, deschideti un browser, si dati 127.0.0.1:8888 . Ar trebui sa va apara continutul din directorul remote.

Daca apare, partea de port forwarding merge. Daca nu, cititi cu atentie mesajele de eroare de la logarea cu SSH, mesajele de eroare date de server, etc.

Acum avem conexiunea facuta, ne concentram pe docker.

Pornim docker-ul si realizam conexiunea de la portul 9500 de pe masina locala, la portul 8888 din docker. Optiunea este -p 127.0.0.1:9500:8888 .

Pentru usurinta, am scris toata comanda de docker, care, in plus, mai monteaza si codul sursa in docker.

docker run -it --rm --name visoft_python_sample_container \
  -u $(id -u):$(id -g) \
  -v $HOME/work/codegolf:/workspace/codegolf  \
  -p 127.0.0.1:9500:8888                      \
  --ipc=host              \
  -w /workspace           \
  visoft_python_sample_image /bin/bash

Elementele colorate in albastru sunt specifice pentru masina voastra. Observati optiunea -it care va porni docker-ul in mod interactiv.

Aici ar trebui sa aveti acces la fiserele montate. Verificati!

Acum putem da drumul la jupyter. Navigam in directorul cu fisierele sursa si executam comanda de mai jos. ATENTIE! TREBUIE SA FITI IN DOCKER!!!!

~/miniconda3/bin/jupyter lab --ip 0.0.0.0 --port 8888 --no-browser

De obicei in docker nu este configurat bine user-ul si apare textul I have no name! la prompt.

Observati mesajele. Ar trebui sa porneasca serverul de jupyter. Daca apar erori, e posibil sa nu fiti in directorul corect (/workspace/)

Observatie: Implicit serverul de jupyter porneste cu un token de acces. Doar in scopuri de debug, puteti da --NotebookApp.token='' --NotebookApp.password='' ca sa nu faca acest lucru.

Atentie! Mai sus, ati specificat ca jupyter sa faca bind la 0.0.0.0 . Cat timp sunteti in docker, si porturile de la docker sunt bind-uite doar la 127.0.0.1, este ok. De fapt, in aceasta configuratie, este singurul mod in care docker-ul va trimite pachetele din exterior catre jupyter.

Acum, mergeti in browser-ul local si tastati localhost:8888 . Va veti conecta la jupyter-ul ce ruleaza in docker pe masina remote:

Observati ca apar fisierele montate de pe masina remote. Orice modificare a lor, in interiorul docker-ului se va reflecta in fisierele de pe masina remote.

Cand v-ati terminat treaba, puteti inchide linistit serverul de jupyter si docker-ul. Modificarile sunt pe masina remote. Cum anume le puneti intr-un loc mai sigur? Ori folositi git (cu Agent Forwarding) sau le sincronizati pe masina locala.

Alternativa la networking:

Comenzile de mai sus sunt oarecum “sigure”, pentru ca orice server care asculta la 0.0.0.0 va fi expus doar de parametrul -p de la docker run. Altfel, ramane inchis in docker. Ceea ce este excelent pentru securitate.

Docker-ul permite si un alt mod de networking [ok, permite mult mai multe] folosit, si anume “legarea” interfetei din docker de masina gazda. Se foloseste parametrul --network="host" la docker run. Daca faceti asta, renuntam la -p. Dar mare atentie! Orice server pornit din docker cu 0.0.0.0 va deschide port pe masina gazda. Combinati asta cu optiunile de eliminare a token-ului de autentificare la jupyter si veti mina bitcoini pentru altii in 3, 2, 

Inca o data, cititi de 5 ori instructiunea inainte de a da ENTER. Verificati unde sunteti. Masina locala? Docker? Masina remote?

Mai jos, comanda docker run si pornirea serverului. Observati diferentele.

docker run -it --rm --name visoft_python_sample_container \
  -u $(id -u):$(id -g) \
  -v $HOME/work/codegolf:/workspace/codegolf  \
  --network="host"                            \
  --ipc=host              \
  -w /workspace           \
  visoft_python_sample_image /bin/bash

iar comanda de jupyter lab executata in docker:

~/miniconda3/bin/jupyter lab --ip 127.0.0.1 --port 9500 --no-browser

Avand acum serverul “lipit” de masina remote [dpdv al retelei] se va asculta direct la portul la care face sshd-ul forward, adica 9500.  Adresa, obligatoriu localhost!

Din nou, optiunea network="host" combinata cu --ip 0.0.0.0 inseamna ca masina remote va fi hackuita [in sens rau] de primul script care face scanare pe porturi. [ok, in docker, dar daca e mapat / ?]

Editare cod cu PyCharm

Editia profesionala are sectiune de deployment dedicata. Daca vreti un editor de texte care sa aiba puterea unui IDE (code completion, syntax highlight, etc) care sa lucreze remote, puteti configura prin SFTP (vedeti mai jos cum) accesul la calculatorul remote. Cu inca cateva click-uri prin meniu se poate seta PyCharm sa faca upload automat la modificarile locale. 

Pentru rulare si depanare, din pacate treburile sunt mai complicate.

Editare direct pe masina remote, fara sa aduca documentul local, nu cred ca se poate.

Depanare cod cu PyCharm

Acest scenariu presupune ca aveti PyCharm Professional [disponibil gratuit cu licenta educativa] si ca stiti un pic sa lucrati. 

Avem din nou aceeasi imagine docker, un container care are montat in interior codul nostru si acces la ssh.

Din pacate treburile sunt mai complicate, un anunuit script din docker va trebui sa se conecteze la instanta de pycharm care ruleaza pe masina noastra. Pycharm va trebui sa aiba acces prin sftp la masina remote. 

Situatia va arata ca mai jos:

Filosofia aici este ca IDE-ul actioneaza ca un server, asteptand comenzi de la un client de debug, client ce ruleaza langa cod. Cum codul se executa remote, acest client de debug trebuie sa se execute si el remote. Apare necesitatea existentei unui canal de comunicare intre clientul de debug de pe masina remote si IDE-ul de pe masina noastra. Situatie ilustrata de linia rosie din figura de mai sus. Linia porneste din scripul de Python, se leaga la portul localhost:9400 care este forwardat la clientul de ssh de pe masina locala. Acesta, trimite cererile catre serverul localhost:9400 de pe masina locala (IDE-ul nostru). 
Pe langa asta, PyCharm trebuie sa poata sa actualizeze codul. Asta se face printr-o conexiune SFTP (conexiunea verde) Din fericire e suficient de specificat parametrii ssh si PyCharm-ul se descurca. Din cauza ca directorii cu cod sunt montati in docker, orice modificare din exterior se propaga in docker, automat. 

E complicat dar din fericire cei de la PyCharm ofera o documentatie foarte buna:

https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html

Urmariti sectiunea “Using the Python remote debug server configuration”.

Veti schimba setarile conform celor corespunzatoare situatiei voastre.

Prima data configurati sistemul de run/debug, ca sa accepte conexiuni de la 127.0.0.1 pe portul 9400. Acesta va genera si scriptul de conectare, script care va porni din docker. Observati sectiunea ce trebuie adaugata in cod.

Creati conexiunea de sftp. Daca folositi fisierul de config de ssh, specificati acest lucru. Eventualele schimbari sunt preluate automat.

Faceti deploy la cod (va fi copiat codul pe masina remote)

Porniti serverul de debug din IDE (!) 

Porniti codul (din consola docker) in mod interactiv. Daca totul e ok, in IDE va va aparea familiara linie albastra ce arata instructiunea urmatoare.

Comanda docker e un pic schimbata (setarea de network trebuie sa fie –network=”host”) si mai trebuie dat o comanda pip install inainte de a lansa scriptul. Comanda de pip install se face o singura data pentru un container.

docker run -it --rm --name visoft_python_sample_container \
  -u $(id -u):$(id -g) \
  -v $HOME/work/codegolf:/workspace/codegolf  \
  --network="host"                            \
  --ipc=host              \
  -w /workspace           \
  visoft_python_sample_image /bin/bash

In interiorul docker-ului dam (o singura data pentru un container):

pip install pydevd-pycharm~=xxx.yyy.zzz

conform instructiunilor din fereastra de configurare Run/Debug.

Apoi, navigati la sursele Python si porniti script-ul:

python debug_sample.py 

Acum PyCharm ar trebui sa intre in modul debug si sa va lase sa executati codul linie cu linie. Pentru codul de demo din documentatie,  input-ul se realizeaza in consola docker! 

Mai jos, exemple vizuale:

Setari Run/Debug. Aveti grija si la mapari, astfel, va sti de unde sa ia sursele.

Setarile de SSH. Toate sunt scrise in fisierul .ssh/config. Precizati acest lucru si PyCharm stie sa le “preia” de acolo.

Specificati cum anume se conecteaza PyCharm la codul sursa aflat remote. Agentul SSH e cel setat anterior.

Comenzile date in consola docker, pe masina remote. Se vede cum porneste docker, cum se face pip install, cum se porneste script-ul. Se introduce si prima valoare ceruta de cod.

IDE-ul local, cu facilitatile normale de debug. Urmatoarea linie executata, variabilele si memoria afisate. Observati valoarea lui a, introdusa in consola docker.

SSH direct in docker

PyCharm poate sa interactioneze direct cu daemonul de docker, DAR setup-ul e mai expus la greseli. 

Daca avem acces direct cu ssh la o masina, putem automatiza toate operatiile din PyCharm. Setam conexiunea de SSH si PyCharm va sti sa faca deploy, sa instaleze automat script-urile de debug si sa le si porneasca automat. Switch-ul intre local si remote se face cu o apasare de buton.

Din pacate conectarea de pe masina locala in docker cu ssh e mai dificila si presupune pornirea unui serviciu de SSH in docker. Lucru care contravine filosofiei docker si presupune editarea fisierului de dockerfile pentru a porni acel server tot timpul. Nu recomand, mai ales daca docker-ul va fi mai apoi folosit in productie. Nu se face separarea responsabilitatilor. Daca cautati pe net, gasiti tutoriale [trebuie configurat sshd din docker sa functioneze peste port forwarding sau sa il conectati direct la “exterior”, cu un pic de iresponsabilitate]

Folosirea Visual Studio Code pentru debug

Visual Studio Code, ca orice produs Microsoft este agresiv si incearca sa acopere accesul la tool-uri cu propriile metode de configurare si propriul limbaj.

Personal, nu recomand VSC deoarece in spatele a 2 plugin-uri “prietenoase” se intampla o gramada de lucruri despre care nu sunteti informati si asupra carora aveti putin control (sau accesul la optiuni presupune destul de multa frictiune).

Am instalat cateva plugin-uri (Pylance, Remote-developmentRemote-Containers)

Remote-development porneste automat un server de VSCode pe masina remote, fara sa ceara voie, fara sa se poata configura porturi, fara sa interactioneze cu userul. Pe langa asta, serverul mai si ramane pornit dupa inchiderea conexiunii. Nu e dragut.

Remote-containers se apuca de capul lui sa faca build la imagini si sa porneasca containerul. “Out of the box” nu se permite nici o configurare a modului de executie. Nume, resurse alocate, etc. Nimic. Trebuie sapat in documentatie si in fisierele de configurare. Care, evident, NU sunt 1:1 cu ceea ce foloseste docker-ul. 

Urmarind pasii de la Connect using the Remote – SSH extension am reusit sa pornesc un script simplu de python si sa il rulez/debug in container, pe masina remote. Un alt usor avantaj asupra PyCharm este folosirea implicita a lui debugpy, care poate fi pornit din consola, fara sa necesite modificarea codului. Tutorial, aici.

Montat volume? Montat resurse (ex GPU-uri?) Probabil, nu neg ca se poate, dar optiunile nu sunt expuse din prima si nici fisierele de configurare ale vscode nu expun limbajul docker standard.