Lanciamo Jupyter in orbita LXD

Hai mai sperimentato con codice o utilità di sistema in Linux in modo da non dover temere per il sistema di base e non cancellare tutto in caso di errore nel codice che deve essere eseguito con privilegi di root?

E che ne dici di dover testare o avviare un intero cluster di microservizi su una sola macchina? Cento o anche mille?

Con le macchine virtuali gestite da un hypervisor, queste esigenze possono essere soddisfatte, ma a quale prezzo? Ad esempio, un contenitore in LXD basato sulla distribuzione Alpine Linux consuma minimamente solo 7.60MB di RAM, e dove la partizione radice dopo l'avvio occupa 9.5MB! Che ne pensi, Elon Musk? Ti consiglio di dare un'occhiata alle funzionalità di base di LXD — un sistema di contenitori in Linux

Dopo aver chiarito cosa sono in generale i contenitori LXD, andiamo oltre e consideriamo cosa succederebbe se ci fosse una piattaforma-macchina dove potremmo eseguire codice in modo sicuro per l'host, generare grafici, collegare dinamicamente (interattivamente) widget UI con il nostro codice, e integrare il codice con testo e formattazione... Qualcosa come un blog interattivo? Wow... Voglio! Voglio! 🙂

Dai un'occhiata qui sotto dove lanceremo in un contenitore JupyterLab — la nuova generazione dell'interfaccia utente al posto del superato Jupyter Notebook, e installeremo moduli Python come NumPy, Pandas, Matplotlib, IPyWidgets che permetteranno di fare tutto ciò che abbiamo menzionato sopra e salvare tutto in un file speciale — un notebook IPython.

Lanciamo Jupyter in orbita LXD

Piano di decollo in orbita ^

Lanciamo Jupyter in orbita LXD

Stiliamo un piano d'azione breve, per semplificare la realizzazione del diagramma sopra:

  • Installeremo e avvieremo un contenitore basato sulla distribuzione Alpine Linux. Utilizzeremo questa distribuzione poiché è orientata alla minimalità e installeremo solo il software essenziale, nulla di superfluo.
  • Aggiungeremo un disco virtuale aggiuntivo nel contenitore che chiameremo — hostfs e montiamo al file system radice. Questo disco permetterà di utilizzare i file sull'host dalla directory specificata all'interno del contenitore. In questo modo, i dati saranno indipendenti dal contenitore. In caso di eliminazione del contenitore, i dati rimarranno sull'host. Inoltre, questo schema è utile per separare alcuni dati tra molti contenitori senza utilizzare i meccanismi di rete standard del sistema di distribuzione del contenitore.
  • Installeremo Bash, sudo, le librerie necessarie, aggiungeremo e configureremo un utente di sistema
  • Installeremo Python, i moduli e compileremo le dipendenze binarie per essi
  • Installeremo e avvieremo JupyterLab, configureremo l'aspetto, installeremo le estensioni per esso.

In questo articolo inizieremo con l'avvio di un contenitore, non tratteremo l'installazione e la configurazione di LXD, tutto questo puoi trovarlo in un altro articolo — Le funzionalità di base di LXD — sistemi di contenitori in Linux.

Installazione e configurazione del sistema di base ^

Creiamo un contenitore con il comando in cui specifichiamo l'immagine — alpine3, un identificatore per il contenitore — jupyterlab e, se necessario, profili di configurazione:

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

Qui utilizzo il profilo di configurazione hddroot che indica di creare un contenitore con la partizione root in Storage Pool situato su un disco HDD fisico:

lxc profile show hddroot

config: {}
description: ""
devices:
  root:
    path: \/
    pool: hddpool
    type: disk
name: hddroot
used_by: []
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

Questo mi consente di sperimentare con i contenitori su un disco HDD risparmiando risorse del disco SSD che ho nel mio sistema 🙂 per il quale ho creato un profilo di configurazione separato ssdroot.

Dopo aver creato il contenitore, si trova nello stato STOPPED, quindi dobbiamo avviarlo eseguendo al suo interno il sistema init:

lxc start jupyterlab

Visualizziamo l'elenco dei contenitori in LXD utilizzando l'opzione -c che specifica quali ccolonne visualizzare sullo schermo:

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

Al momento della creazione del contenitore, l'indirizzo IP è stato scelto casualmente, poiché abbiamo utilizzato il profilo di configurazione default che è stato precedentemente configurato nell'articolo Le funzionalità di base di LXD — sistemi di contenitori in Linux.

Modificheremo questo indirizzo IP in uno più memorabile, creando un'interfaccia di rete a livello di contenitore, piuttosto che a livello di profilo di configurazione come avviene attualmente nella configurazione esistente. Non è obbligatorio farlo, puoi saltare questo passaggio.

Creiamo un'interfaccia di rete eth0 che colleghiamo allo switch (ponte di rete) lxdbr0 nel quale abbiamo abilitato il NAT secondo l'articolo precedente e ora il contenitore avrà accesso a Internet, e assegnamo all'interfaccia un indirizzo IP statico — 10.0.5.5:

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

Dopo aver aggiunto il dispositivo, è necessario riavviare il contenitore:

lxc restart jupyterlab

Controlliamo lo stato del contenitore:

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

Installazione del software di base e configurazione del sistema ^

Per gestire il nostro contenitore, è necessario installare il seguente software:

Pacchetto
Descrizione

bash
La shell GNU Bourne Again

bash-completion
Completamento programmabile per la shell bash

sudo
Dare ad alcuni utenti la possibilità di eseguire alcuni comandi come root

shadow
Suite di strumenti per la gestione di password e account con supporto per file shadow e PAM

tzdata
Fonti per i dati sui fusi orari e sull'ora legale

nano
Clone dell'editor Pico con miglioramenti

Inoltre, puoi installare il supporto per le pagine man installando i seguenti pacchetti — man man-pages mdocml-apropos less

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

Analizziamo i comandi e le opzioni che abbiamo utilizzato:

  • lxc — Invoca il client LXD
  • exec — Metodo del client LXD che esegue il comando nel contenitore
  • jupyterlab — Identificatore del contenitore
  • -- — Opzione speciale che indica di non interpretare ulteriormente le opzioni come opzioni per lxc e passare l'intera riga rimanente così com'è al contenitore
  • apk — Gestore pacchetti della distribuzione Alpine Linux
  • add — Metodo del gestore pacchetti che installa i pacchetti indicati dopo il comando

Successivamente, imposteremo il fuso orario nel sistema Europe/Moscow:

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

Dopo aver impostato il fuso orario, il pacchetto tzdata non è più necessario, occuperà spazio, quindi lo rimuoviamo:

lxc exec jupyterlab -- apk del tzdata

Controlliamo il fuso orario:

lxc exec jupyterlab -- date

Mer Apr 15 10:49:56 MSK 2020

Per non perdere troppo tempo nella configurazione di Bash per i nuovi utenti all'interno del contenitore, procederemo copiando i file skel predefiniti dal sistema host. Questo permetterà di abbellire Bash nel contenitore in modalità interattiva. Il mio sistema host è Manjaro Linux e i file copiati /etc/skel/.bash_profile, /etc/skel/.bashrc, /etc/skel/.dir_colors sono adatti in linea di massima per Alpine Linux e non causano problemi critici, ma potrebbe essere diverso il vostro sistema e dovrete risolvere eventuali errori di avvio di Bash nel contenitore.

Copiamo i file skel nel contenitore. L'opzione --create-dirs creerà le directory necessarie, se non esistono:

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

Per l'utente root già esistente, copiamo nella home directory i file skel appena copiati nel contenitore:

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

In Alpine Linux, per gli utenti viene installata la shell di sistema /bin/sh, la sostituiremo con root sul Bash:

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

Per root se l'utente non è senza password, deve impostare una password. Il comando seguente genererà e installerà una nuova password casuale che vedrete sullo schermo della console dopo l'esecuzione:

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

Inoltre, creiamo un nuovo utente di sistema — jupyter per il quale configureremo successivamente JupyterLab:

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

Generiamo e impostiamo una password per lui:

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

Successivamente, eseguiremo due comandi: il primo creerà un gruppo di sistema sudo, e il secondo aggiungerà l'utente a questo gruppo jupyter:

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

Controlliamo in quali gruppi è presente l'utente jupyter:

lxc exec jupyterlab -- id -Gn jupyter

jupyter sudo

Tutto ok, andiamo avanti.

Consentiamo a tutti gli utenti che appartengono al gruppo sudo di utilizzare il comando sudo. Esegui il seguente script, dove sed decommenta la riga del parametro nel file di configurazione /etc/sudoers:

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

Installazione e configurazione di JupyterLab ^

JupyterLab — questa è un'applicazione Python, quindi dobbiamo prima installare questo interprete. Inoltre, JupyterLab installeremo utilizzando il gestore pacchetti di Python pip, e non quello di sistema, perché nel repository di sistema potrebbe essere obsoleto, e quindi dovremo risolvere manualmente le dipendenze installando i seguenti pacchetti — python3 python3-dev gcc libc-dev zeromq-dev:

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

Aggiorniamo i moduli Python e il gestore pacchetti pip alla versione attuale:

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

Installiamo JupyterLab tramite il gestore pacchetti pip:

lxc exec jupyterlab -- python3 -m pip install jupyterlab

Poiché le estensioni in JupyterLab sono sperimentali e non sono ufficialmente incluse nel pacchetto jupyterlab, dobbiamo installare e configurare questo manualmente.

Installeremo NodeJS e il gestore pacchetti per esso — NPM, poiché JupyterLab li utilizza per le sue estensioni:

lxc exec jupyterlab -- apk add nodejs npm

Affinché le estensioni per JupyterLab che installeremo funzionino, devono essere installate nella directory utente poiché l'applicazione verrà eseguita dall'utente jupyter. Il problema è che non c'è un parametro nel comando di avvio attraverso il quale puoi passare la directory, l'applicazione riconosce solo la variabile d'ambiente e quindi dobbiamo definirla. A tal fine, scriveremo il comando di esportazione della variabile JUPYTERLAB_DIR nell'ambiente utente jupyter, nel file .bashrc, che viene eseguito ogni volta che l'utente accede al sistema:

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

Con il prossimo comando installeremo un'estensione speciale — il gestore delle estensioni in 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"

Adesso è tutto pronto per il primo avvio JupyterLab, ma possiamo anche installare alcune estensioni utili:

  • toc — Indice, genera un elenco di intestazioni nell'articolo/nel notebook
  • jupyterlab-horizon-theme — Tema dell'interfaccia utente
  • jupyterlab_neon_theme — Tema dell'interfaccia utente
  • jupyterlab-ubu-theme — Un'altra tema dall'autore di questo articolo 🙂 Ma in questo caso, verrà mostrata l'installazione dal repository GitHub

Quindi, esegui le seguenti comandi in sequenza per installare queste estensioni:

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"

Dopo aver installato le estensioni, dobbiamo compilarle, poiché in precedenza, durante l'installazione, abbiamo specificato la chiave --no-build per risparmiare tempo. Adesso accelereremo notevolmente compilandole tutte insieme in una sola volta:

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

Ora esegui i seguenti due comandi per il primo avvio JupyterLab. Potremmo avviarlo con un solo comando, ma in tal caso, il comando di avvio, che si ha in mente, sarà memorizzato dal bash nel contenitore, e non sull'host, dove di comandi ce ne sono già abbastanza per essere registrati nella cronologia 🙂

Accediamo nel contenitore come utente jupyter:

lxc exec jupyterlab -- su -l jupyter

Successivamente esegui JupyterLab con le chiavi e i parametri come indicato:

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

Vai nel browser web all'indirizzo http://10.0.5.5:8888 e nella pagina aperta inserisci token di accesso che vedrai nella console. Copialo e incollalo nella pagina, quindi fai clic su Accesso. Dopo l'accesso, vai nel menu delle estensioni a sinistra, come mostrato nell'immagine sottostante, dove ti verrà chiesto, attivando il gestore delle estensioni, di accettare i rischi sulla sicurezza nell'installare estensioni di terze parti per cui il team JupyterLab development non si assume responsabilità:

Lanciamo Jupyter in orbita LXD

Tuttavia, noi isoliamo completamente JupyterLab e lo inseriamo in un contenitore, affinché estensioni di terze parti che richiedono e utilizzano NodeJS non possano rubare dati dal disco, tranne quelli che apriremo all'interno del contenitore. È improbabile che riescano ad accedere ai tuoi documenti privati sull'host /home e che i processi all'interno del contenitore possano accedere, e se ci riescono, necessitano di privilegi sui file nel sistema host, poiché stiamo eseguendo il contenitore in modalità non privilegiata. In base a queste informazioni, puoi valutare il rischio di attivare estensioni in JupyterLab.

I notebook IPython creati (pagine in JupyterLab) ora verranno creati nella directory home dell'utente — /home/jupyter, ma nei nostri piani c'è quello di separare i dati (condividerli) tra host e contenitore, quindi, torna nella console e interrompi JupyterLab premendo il tasto di scelta rapida — CTRL+C e rispondendo y alla richiesta. Quindi termina la sessione interattiva dell'utente jupyter premendo il tasto di scelta rapida CTRL+D.

Condividiamo i dati con l'host ^

Per condividere i dati con l'host, è necessario creare un dispositivo nel contenitore che lo consenta e per fare ciò eseguire il seguente comando dove specifichiamo le seguenti chiavi:

  • lxc config device add — Il comando aggiunge la configurazione del dispositivo
  • jupyter — L'identificativo del contenitore a cui viene aggiunta la configurazione
  • hostfs — L'identificativo del dispositivo. Puoi fornire qualsiasi nome.
  • disk — Viene specificato il tipo di dispositivo
  • path — Viene specificato il percorso nel contenitore in cui LXD monterà questo dispositivo
  • source — Viene specificata la sorgente, il percorso della directory sull'host che desideri condividere con il contenitore. Specifica il percorso secondo le tue preferenze
lxc config device add jupyterlab hostfs disk path=\/mnt\/hostfs source=\/home\/dv\/projects\/ipython-notebooks

Per la directory /home/dv/projects/ipython-notebooks deve essere impostata l'autorizzazione all'utente del contenitore che attualmente ha UID pari a SubUID + UID, consulta il capitolo Sicurezza. Privilegi dei contenitori nell'articolo Le funzionalità di base di LXD — sistemi di contenitori in Linux.

Imposta le autorizzazioni sull'host, dove il proprietario sarà l'utente del contenitore jupyter, e la variabile $USER indicherà il tuo utente host come gruppo:

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

Ciao, Mondo! ^

Se hai ancora una sessione della console aperta nel contenitore con JupyterLab, riavviala con la nuova chiave --notebook-dir specificando il valore /mnt/hostfs come percorso fino alla radice dei notebook nel contenitore per il dispositivo che abbiamo creato nel passaggio precedente:

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

Poi vai alla pagina http://10.0.5.5:8888 e crea il tuo primo notebook cliccando sul pulsante sulla pagina come mostrato nella figura qui sotto:

Lanciamo Jupyter in orbita LXD

Poi nel campo della pagina inserisci il codice in Python che stamperà il classico Hello World!. Al termine dell'inserimento premi CTRL+ENTER o il pulsante «play» nella barra degli strumenti in alto affinché JupyterLab lo esegua:

Lanciamo Jupyter in orbita LXD

Quasi tutto è pronto per l'uso, ma sarà noioso se non installiamo moduli Python aggiuntivi (applicazioni complete) che consentono di ampliare significativamente le funzionalità standard di Python in JupyterLab, quindi andiamo avanti 🙂

P.S. È interessante notare che la vecchia implementazione Jupyter con nome in codice Jupyter Notebook non è scomparsa ed esiste parallelamente a JupyterLab. Per passare alla vecchia versione vai al link aggiungendo alla URL il suffisso/tree, mentre il passaggio alla nuova versione avviene con il suffisso /lab, ma non è necessario specificarlo:

Espandiamo le funzionalità di Python ^

In questa sezione installeremo potenti moduli del linguaggio Python come NumPy, Pandas, Matplotlib, IPyWidgets i cui risultati vengono integrati nei notebook JupyterLab.

Prima di installare i moduli Python elencati tramite il gestore di pacchetti pip dobbiamo prima risolvere le dipendenze di sistema in Alpine Linux:

  • g++ — Necessario per compilare i moduli, poiché alcuni di essi sono implementati in C++ e si collegano a Python a runtime come moduli binari
  • freetype-dev — dipendenza per il modulo Python Matplotlib

Installiamo le dipendenze:

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

C'è un problema, nello stato attuale della distribuzione Alpine Linux non è possibile compilare una nuova versione di NumPy, si verificherà un errore di compilazione che non sono riuscito a risolvere:

ERROR: Impossibile costruire i pacchetti wheel per numpy che utilizzano PEP 517 e non possono essere installati direttamente

Pertanto, installeremo questo modulo come pacchetto di sistema che distribuisce già una versione compilata, ma leggermente più vecchia rispetto a quella disponibile ora sul sito:

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

Successivamente, installiamo i moduli Python tramite il gestore di pacchetti pip. Abbiate pazienza, poiché alcuni moduli verranno compilati e ciò richiederà alcuni minuti. Sul mio computer la compilazione ha impiegato circa 15 minuti:

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

Puliamo le cache di installazione:

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

Testiamo i moduli in JupyterLab ^

Se hai avviato JupyterLab, riavvialo per attivare i nuovi moduli installati. Per fare ciò, nella sessione della console premi CTRL+C dove è avviato e digita y alla richiesta di arresto, quindi riavvialo JupyterLab premendo il tasto freccia "su" sulla tastiera, per non dover digitare nuovamente il comando, e poi Invio per avviare:

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

Vai alla pagina http://10.0.5.5:8888/lab o aggiorna la pagina nel browser, quindi inserisci il seguente codice in una nuova cella del notebook:

%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

Dovresti ottenere un risultato simile all'immagine qui sotto, dove IPyWidgets genera un elemento UI sulla pagina che interagisce in modo interattivo con il codice sorgente, e inoltre Matplotlib mostra il risultato del codice sotto forma di immagine come grafico della funzione:

Lanciamo Jupyter in orbita LXD

Molti esempi IPyWidgets puoi trovare nei tutorial qui

Cos'altro? ^

Fate bravi se siete rimasti e siete arrivati fino alla fine dell'articolo. Ho deciso di non pubblicare uno script pronto alla fine dell'articolo che lo installerebbe JupyterLab con un «clic», per incoraggiare i lavoratori 🙂 Ma potete farlo da soli, dato che già sapete come farlo, raccogliendo i comandi in un unico script Bash 🙂

Inoltre, potete:

  • Assegnare un nome di rete al contenitore invece di un indirizzo IP scrivendolo in modo semplice /etc/hosts e digitare l'indirizzo nel browser http://jupyter.local:8888
  • Sperimentare con i limiti delle risorse per il contenitore, per questo leggete il capitolo su le possibilità di base di LXD o ottenere ulteriori informazioni dal sito dello sviluppatore di LXD.
  • Cambiare il tema:

Lanciamo Jupyter in orbita LXD

E molto altro potete fare! Questo è tutto. Vi auguro successo!

AGGIORNAMENTO: 15.04.2020 18:30 — Corretto errori nel capitolo «Hello, World!»
AGGIORNAMENTO: 16.04.2020 10:00 — Corretto e aggiunto testo nella descrizione dell'attivazione del gestore delle estensioni JupyterLab
AGGIORNAMENTO: 16.04.2020 10:40 — Corretto errori riscontrati nel testo e modificato leggermente in meglio il capitolo «Installazione del software di base e configurazione del sistema»

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster