Rulăm Jupyter pe orbită LXD

Ați experimentat vreodată cu codul sau utilitarele de sistem în Linux astfel încât să nu vă faceți griji pentru sistemul de bază și să nu ștergeți totul în cazul unei erori a codului care ar trebui să ruleze cu privilegii de root?

Și ce părere aveți despre cazul în care ar fi necesar să testați sau să rulați un întreg cluster de microservicii pe o singură mașină? O sută sau chiar o mie?

Cu mașinile virtuale gestionate de un hipervizor, aceste sarcini pot fi soluționate, dar la ce preț? De exemplu, un container LXD bazat pe distribuția Alpine Linux consumă minim 7.60MB RAM, și unde partiția rădăcină după pornire ocupă 9.5MB! Ce părere ai, Elon Musk? Îți recomand să te familiarizezi cu posibilitățile de bază ale LXD - sistemul de containere din Linux

După ce a devenit clar ce sunt containerele LXD, haideți să mergem mai departe și să ne gândim, ce ar fi dacă ar exista o platformă-combinat unde am putea rula în siguranță cod pentru gazdă, genera grafice, lega dinamic (interactiv) widget-uri UI de codul tău, adăuga text codului cu blackjack… și formatare? Ceva de genul unui blog interactiv? Wow… Vreau! Vreau! 🙂

Aruncă o privire sub cortina unde vom rula într-un container JupyterLab — următoarea generație a interfeței utilizatorului în locul vechiului Jupyter Notebook, și vom instala module Python precum NumPy, Pandas, Matplotlib, IPyWidgets care vor permite să facem tot ce am menționat mai sus și să salvăm totul într-un fișier special - un notebook IPython.

Rulăm Jupyter pe orbită LXD

Planul de lansare în orbită ^

Rulăm Jupyter pe orbită LXD

Să facem un plan scurt de acțiune pentru a ne facilita implementarea schemei de mai sus:

  • Vom instala și rula un container bazat pe distribuția Alpine Linux. Vom folosi această distribuție deoarece este orientată spre minimalism și vom instala în ea doar software-ul necesar, nimic în plus.
  • Vom adăuga un disc virtual suplimentar în container, căruia îi vom da un nume — hostfs și vom monta la sistemul de fișiere rădăcină. Acest disc va oferi posibilitatea de a utiliza fișierele gazdelor dintr-un director specificat în interiorul containerului. Astfel, datele vor fi independente de container. În cazul ștergerii containerului, datele vor rămâne pe gazdă. Această schemă este, de asemenea, utilă pentru a împărți datele între mai multe containere fără a folosi mecanismele de rețea din distribuția containerului.
  • Vom instala Bash, sudo, bibliotecile necesare, vom adăuga și configura utilizatorul de sistem
  • Vom instala Python, module și vom compila pentru ele dependențele binare
  • Vom instala și vom rula JupyterLab, vom configura aspectul, vom instala extensiile pentru acesta.

În acest articol, vom începe cu pornirea containerului, nu vom discuta despre instalarea și configurarea LXD, toate acestea le puteți găsi într-un alt articol — Funcțiile de bază LXD - sisteme de containere în Linux.

Instalarea și configurarea sistemului de bază ^

Creăm un container cu comanda în care specificăm imaginea — alpine3, identificatorul pentru container — jupyterlab și, dacă este necesar, profilele de configurare:

lxc init alpine3 jupyterlab --profile=default --profile=hddroot

Aici folosesc profilul de configurare hddroot care indică să creez un container cu o partiție root în Pool de stocare localizată pe un disc fizic HDD:

Containerele sunt create din imagini care sunt distribuții special concepute, fără nucleu Linux. De aceea, înainte de a lansa un container, acesta trebuie să fie desfășurat din această imagine. Sursa imaginilor este un depozit local în care imaginile sunt descărcate din depozite externe.
lxc storage show hddpool

config:
  size: 10GB
  source: /dev/loop1
  volatile.initial_source: /dev/loop1
description: ""
name: hddpool
driver: btrfs
used_by:
- /1.0/images/ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
- /1.0/profiles/hddroot
status: Created
locations:
- none

Aceasta îmi oferă posibilitatea de a experimenta cu containere pe disc HDD, economisind resursele discului SSD care este, de asemenea, prezent în sistemul meu 🙂 pentru care am creat un profil de configurare separat în care vom adăuga un dispozitiv de tip.

După crearea containerului, acesta se află în starea STOPPED, așadar trebuie să-l pornim, lansând în el sistemul init:

lxc start jupyterlab

Să afișăm lista containerelor în LXD folosind cheia -c care indică ce ccoloane să apară pe ecran:

lxc list -c ns4b
+------------+---------+-------------------+--------------+
|    NAME    |  STATE  |       IPV4        | STORAGE POOL |
+------------+---------+-------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.198 (eth0) | hddpool      |
+------------+---------+-------------------+--------------+

Când am creat containerul, adresa IP a fost aleasă aleatoriu, deoarece am folosit profilul de configurare default care a fost configurat anterior în articol Funcțiile de bază LXD - sisteme de containere în Linux.

Vom schimba această adresă IP cu una mai memorabilă, creând o interfață de rețea la nivelul containerului, nu la nivelul profilului de configurare, ca în configurația actuală. Nu este obligatoriu să faceți acest lucru, puteți sări peste aceasta.

Creăm o interfață de rețea eth0 pe care o legăm cu comutatorul (bridge de rețea) lxdbr0 în care am activat NAT conform articolului anterior și acum containerul va avea acces la Internet, iar interfeței îi vom atribui o adresă IP statică — 10.0.5.5:

lxc config device add jupyterlab eth0 nic name=eth0 nictype=bridged parent=lxdbr0 ipv4.address=10.0.5.5

După ce am adăugat dispozitivul, containerul trebuie repornit:

lxc restart jupyterlab

Verificăm statusul containerului:

lxc list -c ns4b
+------------+---------+------------------+--------------+
|    NAME    |  STATE  |       IPV4       | STORAGE POOL |
+------------+---------+------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.5 (eth0)  | hddpool      |
+------------+---------+------------------+--------------+

Instalarea și configurarea software-ului de bază și a sistemului ^

Pentru administrarea containerului nostru, trebuie să instalăm următorul software:

Package
Description

bash
The GNU Bourne Again shell

bash-completion
Finalizarea programabilă pentru shell-ul bash

sudo
Acordarea anumitor utilizatori capacitatea de a rula comenzi ca root

shadow
Suită de instrumente pentru gestionarea parolelor și conturilor care suportă fișiere shadow și PAM

tzdata
Surse pentru informațiile despre fusuri orare și ora de vară

nano
Clone de editor Pico cu îmbunătățiri

În plus, puteți instala suport pentru man-pages instalând următoarele pachete — man man-pages mdocml-apropos less

lxc exec jupyterlab -- apk add bash bash-completion sudo shadow tzdata nano

Să analizăm comenzile și opțiunile pe care le-am folosit:

  • lxc — Apelul clientului LXD
  • exec — Metoda clientului LXD, care rulează comanda în container
  • jupyterlab — Identificatorul containerului
  • -- — O opțiune specială care indică să nu se interpreteze mai departe opțiunile ca opțiuni pentru lxc și să se transmită întreaga linie rămasă ca atare în container
  • apk — Managerul de pachete al distribuției Alpine Linux
  • add — Metoda managerului de pachete care instalează pachetele specificate după comandă

Apoi, vom seta fusul orar în sistem Europe/Moscow:

lxc exec jupyterlab -- cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime

După ce am instalat fusul orar, pachetul tzdata nu mai este necesar în sistem, va ocupa spațiu, așa că să-l eliminăm:

lxc exec jupyterlab -- apk del tzdata

Verificăm fusul orar:

lxc exec jupyterlab -- date

Wed Apr 15 10:49:56 MSK 2020

Pentru a nu pierde mult timp configurând Bash pentru noii utilizatori din container, următoarele acțiuni, vom copia din sistemul gazdă fișierele skel care sunt deja gata. Aceasta va permite să embeliți Bash în container în modul interactiv. Sistemul meu gazdă este Manjaro Linux, iar fișierele copiate /etc/skel/.bash_profile, /etc/skel/.bashrc, /etc/skel/.dir_colors se potrivesc în principiu cu Alpine Linux și nu cauzează probleme critice, dar ar putea fi un alt distribuitor și va trebui să vă descurcați singuri în cazul unei erori la pornirea Bash în container.

Copiem fișierele skel în container. Opțiunea --create-dirs va crea directoarele necesare, dacă acestea nu există:

lxc file push /etc/skel/.bash_profile jupyterlab/etc/skel/.bash_profile --create-dirs
lxc file push /etc/skel/.bashrc jupyterlab/etc/skel/.bashrc
lxc file push /etc/skel/.dir_colors jupyterlab/etc/skel/.dir_colors

Pentru utilizatorul root deja existent, vom copia în directorul său Home fișierele skel tocmai copiate în container:

lxc exec jupyterlab -- cp /etc/skel/.bash_profile /root/.bash_profile
lxc exec jupyterlab -- cp /etc/skel/.bashrc /root/.bashrc
lxc exec jupyterlab -- cp /etc/skel/.dir_colors /root/.dir_colors

În Alpine Linux, shell-ul de sistem este setat pentru utilizatori /bin/sh, vom schimba acesta la root utilizator pentru Bash:

lxc exec jupyterlab -- usermod --shell=/bin/bash root

Pentru a root utilizatorul nu a fost fără parolă, trebuie să îi setăm o parolă. Următoarea comandă va genera și va seta o nouă parolă aleatoare pe care o veți vedea pe ecranul consolei după executarea sa:

lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "root:$PASSWD" | chpasswd && echo "New Password: $PASSWD""

New Password: sFiXEvBswuWA

De asemenea, vom crea un nou utilizator de sistem — jupyter pentru care vom configura ulterior JupyterLab:

lxc exec jupyterlab -- useradd --create-home --shell=/bin/bash jupyter

Vom genera și îi vom seta o parolă:

lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "jupyter:$PASSWD" | chpasswd && echo "New Password: $PASSWD""

New Password: ZIcbzWrF8tki

Apoi, vom executa două comenzi, prima va crea un grup de sistem sudo, iar a doua va adăuga utilizatorul în acesta jupyter:

lxc exec jupyterlab -- groupadd --system sudo
lxc exec jupyterlab -- groupmems --group sudo --add jupyter

Să vedem în ce grupuri este utilizatorul jupyter:

lxc exec jupyterlab -- id -Gn jupyter

jupyter sudo

Totul - bine, mergem mai departe.

Vom permite tuturor utilizatorilor care sunt în grupul sudo să folosească comanda sudo. Pentru asta, executați următorul script, care sed decommentază linia parametrului din fișierul de configurare /etc/sudoers:

lxc exec jupyterlab -- /bin/bash -c "sed --in-place -e '/^#[ t]*%sudo[ t]*ALL=(ALL)[ t]*ALL$/ s/^[# ]*//' /etc/sudoers"

Instalarea și configurarea JupyterLab ^

JupyterLab — este o aplicație Python, prin urmare, trebuie să instalăm întâi acest interpret. De asemenea, JupyterLab vom face instalarea cu ajutorul managerului de pachete Python pip, nu al celui sistemic, deoarece în depozitul sistemic poate fi învechit, și prin urmare, trebuie să permitem manual dependențele pentru acesta instalând următoarele pachete — python3 python3-dev gcc libc-dev zeromq-dev:

lxc exec jupyterlab -- apk add python3 python3-dev gcc libc-dev zeromq-dev

Vom actualiza modulele Python și managerul de pachete pip la versiunea actuală:

lxc exec jupyterlab -- python3 -m pip install --upgrade pip setuptools wheel

Instalăm JupyterLab prin managerul de pachete pip:

lxc exec jupyterlab -- python3 -m pip install jupyterlab

Deoarece extensiile în JupyterLab sunt experimentale și oficial nu sunt livrate împreună cu pachetul jupyterlab, așa că, trebuie să le instalăm și să le configurăm manual.

Vom instala NodeJS și managerul de pachete pentru acesta — NPM, deoarece JupyterLab le folosește pentru extensiile sale:

lxc exec jupyterlab -- apk add nodejs npm

Pentru ca extensiile pentru JupyterLab pe care le vom instala să funcționeze, trebuie să le instalăm în directorul utilizatorului deoarece aplicația va fi lansată de utilizator. jupyterProblema este că nu există un parametru în comanda de pornire la care să putem transmite directorul, aplicația percepe doar variabila de mediu și, prin urmare, trebuie să o definim. Pentru aceasta, vom scrie comanda de export a variabilei JUPYTERLAB_DIR în mediu utilizatorului jupyter, în fișierul .bashrc, care se execută de fiecare dată când utilizatorul se conectează la sistem:

lxc exec jupyterlab -- su -l jupyter -c "echo -e "nexport JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab" >> .bashrc"

Următoarea comandă va instala o extensie specială — managerul de extensii în JupyterLab:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager"

Acum totul este pregătit pentru prima lansare JupyterLab, dar putem instala și câteva extensii utile:

  • toc — Table of Contents, generează o listă de titluri în document/notebook
  • jupyterlab-horizon-theme — Temă de interfață UI
  • jupyterlab_neon_theme — Temă de interfață UI
  • jupyterlab-ubu-theme — O altă temă de la autorul acestui articol 🙂 Dar în acest caz, va fi arătată instalarea din repo-ul GitHub

Așadar, executați succesiune următoarele comenzi pentru a instala aceste extensii:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyterlab/toc @mohirio/jupyterlab-horizon-theme @yeebc/jupyterlab_neon_theme"
lxc exec jupyterlab -- su -l jupyter -c "wget -c https://github.com/microcoder/jupyterlab-ubu-theme/archive/master.zip"
lxc exec jupyterlab -- su -l jupyter -c "unzip -q master.zip && rm master.zip"
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build jupyterlab-ubu-theme-master"
lxc exec jupyterlab -- su -l jupyter -c "rm -r jupyterlab-ubu-theme-master"

După instalarea extensiilor, trebuie să le compilăm, deoarece anterior, la instalare am specificat cheia --no-build pentru a economisi timp. Acum ne vom accelera considerabil compilându-le împreună dintr-o dată:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter lab build"

Acum executați următoarele două comenzi pentru prima lansare JupyterLab. Ar fi fost posibil să-l lansăm cu o singură comandă, dar în acest caz, comanda de lansare, pe care trebuie să o memorezi, va fi reținută de bash în container, nu pe gazdă, unde oricum există destule comenzi care să fie scrise în istorie 🙂

Ne conectăm în container ca utilizator jupyter:

lxc exec jupyterlab -- su -l jupyter

Apoi, porniți JupyterLab cu parametrii și opțiunile specificate:

[jupyter@jupyterlab ~]$ jupyter lab --ip=0.0.0.0 --no-browser

Accesați în browser la adresa http://10.0.5.5:8888 și pe pagina deschisă, introduceți token accesul pe care îl veți vedea în consolă. Copiați-l și lipiți-l pe pagină, apoi faceți clic pe Autentificare. După autentificare, mergeți în partea stângă a meniului extensiilor, așa cum se arată în imaginea de mai jos, unde vi se va oferi, la activarea managerului de extensii, să acceptați riscurile de securitate asociate cu instalarea extensiilor din surse terțe pentru care echipa dezvoltare JupyterLab nu își asumă responsabilitatea:

Rulăm Jupyter pe orbită LXD

Cu toate acestea, tocmai de aceea izolăm complet JupyterLab și îl plasăm într-un container, astfel încât extensiile terțe care necesită și folosesc NodeJS să nu poată, cel puțin, să fure datele de pe disc, în afară de cele pe care le deschidem în interiorul containerului. Accesul la documentele voastre private pe gazdă /home procesele din container este puțin probabil să reușească, iar dacă reușesc, va necesita privilegii asupra fișierelor din sistemul gazdă, deoarece rulăm containerul în mod neprivilegiat. Pe baza acestor informații, puteți evalua riscul activării extensiilor în JupyterLab.

Notebook-urile IPython create (pagini în JupyterLab) vor fi acum create în directorul home al utilizatorului — /home/jupyter, dar în planurile noastre este să partajăm datele (să le împrăștiem) între gazdă și container, așa că reveniți în consolă și opriți JupyterLab executând combinația de taste — CTRL+C și răspunzând Stabiliți o parolă și păstrați-o în siguranță! la cerere. Apoi, terminați sesiunea interactivă a utilizatorului jupyter executând combinația de taste CTRL+D.

Împărțim datele cu gazda ^

Pentru a partaja datele cu gazda, trebuie să creați în container un dispozitiv care permite acest lucru și pentru aceasta executați următoarea comandă, unde specificăm următoarele chei:

  • lxc config device add — Comanda adaugă configurația dispozitivului
  • jupyter — Identificatorul containerului în care se adaugă configurația
  • hostfs — Identificatorul dispozitivului. Puteți să îi atribuiți orice nume.
  • disk — Se indică tipul dispozitivului
  • cale — Se indică calea din container la care LXD va monta acest dispozitiv
  • source — Se specifică sursa, calea către directorul de pe gazdă pe care doriți să îl partajați cu containerul. Indicați calea conform preferințelor dumneavoastră.
lxc config device add jupyterlab hostfs disk path=/mnt/hostfs source=/home/dv/projects/ipython-notebooks

Pentru director /home/dv/projects/ipython-notebooks trebuie să fie setată permisiunea utilizatorului containerului care are acum UID egal cu SubUID + UID, consultați capitolul Securitate. Permisiunile containerelor în articolul Funcțiile de bază LXD - sisteme de containere în Linux.

Setăm permisiunea pe gazdă, unde proprietarul va fi utilizatorul containerului jupyter, iar variabila $USER va indica utilizatorul dumneavoastră de pe gazdă ca grup:

sudo chown 1001000:$USER /home/dv/projects/ipython-notebooks

Hello, World! ^

Dacă aveți încă o sesiune de consolă deschisă în container cu JupyterLab, atunci reporniți-o cu cheia nouă --notebook-dir setând valoarea /mnt/hostfs ca calea către rădăcina notelor în container pentru dispozitivul pe care l-am creat în pasul anterior:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

Apoi accesați pagina http://10.0.5.5:8888 și creați prima dvs. notă apăsând butonul de pe pagină așa cum este indicat în imaginea de mai jos:

Rulăm Jupyter pe orbită LXD

Apoi, în câmpul de pe pagină, introduceți codul în limbajul Python care va genera clasicul Hello World!. La finalizarea introducerii, apăsați CTRL+ENTER sau butonul „play” de pe bara de instrumente de sus pentru a executa acest lucru în JupyterLab:

Rulăm Jupyter pe orbită LXD

Asta este aproape tot pentru utilizare, dar nu va fi interesant dacă nu instalăm module Python suplimentare (aplicații complete) care permit extinderea semnificativă a capabilităților standard Python în JupyterLab, așa că, să continuăm 🙂

P.S. Este interesant că vechea implementare Jupyter sub numele de cod Jupyter Notebook nu a dispărut și aceasta există paralel cu JupyterLab. Pentru a trece la versiunea veche, accesați linkul adăugând sufixul/tree, iar trecerea la noua versiune se face cu sufixul /lab, dar nu este obligatoriu să-l specificați:

Extindem capacitățile Python ^

În această secțiune, vom instala modulele puternice ale limbajului Python, cum ar fi NumPy, Pandas, Matplotlib, IPyWidgets rezultatele muncii cărora se integrează în note. JupyterLab.

Înainte de a instala modulele evidențiate prin managerul de pachete pip trebuie să permitem mai întâi dependențele de sistem în Alpine Linux:

  • g++ — Necesare pentru compunerea modulelor, deoarece unele dintre ele sunt implementate în limbajul C++ și se conectează la Python în momentul execuției ca module binare.
  • freetype-dev — dependență pentru modulul Python Matplotlib

Instalăm dependențele:

lxc exec jupyterlab -- apk add g++ freetype-dev

Există o problemă: în starea actuală a distribuției Alpine Linux, nu este posibil să compilăm o nouă versiune NumPy, va apărea o eroare de compilare pe care nu am reușit să o rezolv:

EROARE: Nu s-au putut construi roțile pentru numpy, care folosesc PEP 517 și nu pot fi instalate direct

Prin urmare, acest modul îl vom instala ca pachet de sistem care distribuie deja o versiune compilată, dar puțin mai veche decât cea disponibilă acum pe site:

lxc exec jupyterlab -- apk add py3-numpy py3-numpy-dev

Apoi, instalăm modulele Python prin managerul de pachete pip. Aveți răbdare, deoarece unele module vor fi compilate și acest lucru va dura câteva minute. Pe masina mea, compilarea a durat aproximativ 15 minute:

lxc exec jupyterlab -- python3 -m pip install pandas ipywidgets matplotlib

Curățăm cache-urile instalărilor:

lxc exec jupyterlab -- rm -rf /home/*/.cache/pip/*
lxc exec jupyterlab -- rm -rf /root/.cache/pip/*

Testăm modulele în JupyterLab ^

Dacă aveți JupyterLab, reporniți-l pentru a activa modulele nou instalate. Pentru aceasta, în sesiunea consolei apăsați CTRL+C unde este pornit și introduceți Stabiliți o parolă și păstrați-o în siguranță! la cererea de oprire, apoi reporniți JupyterLab apăsând săgeata de sus a tastaturii, astfel încât să nu fie nevoie să reintroduceți comanda și apoi Enter pentru a porni:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

Accesați pagina http://10.0.5.5:8888/lab sau reîncărcați pagina în browser, apoi introduceți următorul cod într-o nouă celulă a notebook-ului:

%matplotlib inline

from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show()

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot

Ar trebui să obțineți un rezultat similar cu imaginea de mai jos, unde IPyWidgets generează un element UI pe pagina care interacționează in mod interactiv cu codul sursă, precum și Matplotlib afişează rezultatul codului sub formă de grafice ale funcției:

Rulăm Jupyter pe orbită LXD

Multe exemple IPyWidgets le puteți găsi în tutoriale aici

Ce altceva? ^

Felicitări că ați rămas și ați ajuns până la sfârșitul articolului. Am decis să nu public un script gata la sfârșitul articolului care să instaleze JupyterLab în „un singur clic”, pentru a încuraja muncitorii 🙂 Dar puteți face acest lucru singuri, deoarece deja știți cum, adunând comenzile într-un singur script Bash 🙂

De asemenea, puteți:

  • Să atribuiți un nume de rețea pentru container în locul adresei IP scriindu-l într-un simplu /etc/hosts și în browser să introduceți adresa http://jupyter.local:8888
  • Să experimentați cu restricțiile de resurse pentru container, pentru aceasta citiți capitolul din posibilitățile de bază ale LXD sau obțineți mai multe informații pe site-ul dezvoltatorului LXD.
  • Schimbați tema de design:

Rulăm Jupyter pe orbită LXD

Și multe alte lucruri pe care le puteți face! Asta e tot. Vă doresc succes!

UPDATE: 15.04.2020 18:30 — Am corectat erorile din capitolul „Salut, lume!”
UPDATE: 16.04.2020 10:00 — Am corectat și adăugat text în descrierea activării managerului de extensii JupyterLab
UPDATE: 16.04.2020 10:40 — Am corectat erorile găsite în text și am modificat puțin, îmbunătățind capitolul „Instalarea software-ului de bază și configurarea sistemului”

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster