Empaqueter JupyterLite pour le déployer sur la plateforme globale d’applications Fly.io …

Karim
13 min readMar 1, 2023

Fly.io (que certains présentent comme la principale alternative à Heroku) est, comme beaucoup de plateformes d’infrastructure, un système complexe construit sur un certain nombre de projets open source avec notamment Nomad, Consul et Vault de Hashicorp.

Au cœur de l’exécution des applications se trouve une variante de Firecracker d’Amazon. Et une surveillance avec des tableaux de bord utilisant Prometheus et Grafana.

Fly.io utilise des microVMs Firecracker. Il s’agit de machines virtuelles légères et sécurisées basées sur une virtualisation matérielle forte.

Les machines Fly sont des VM Firecracker dotées d’une API REST rapide qui peuvent démarrer des instances en 300 ms environ, dans toutes les régions prises en charge par Fly.io.

Une des utilisations des machines est d’exécuter du code utilisateur pour étendre votre service, ou comme un service en soi. On appelle parfois cela “Functions-as-a-Service” (FaaS). L’idée générale est la suivante :

emballer le code des utilisateurs

définir un environnement d’exécution

lancer le code dans une VM

puis éteindre la VM lorsqu’elle est inactive afin d’économiser sur les factures de calcul.

Le tout en facturant les utilisateurs à la demande ! …

Je vais m’interesser ici au projet JupyterLite. JupyterLite est en effet une distribution JupyterLab qui s’exécute entièrement dans le navigateur, construite de A à Z à partir de composants et d’extensions JupyterLab.

Bien que JupyterLite soit actuellement développé par les développeurs principaux de Jupyter, le projet est encore non officiel.

Pour cela, dans un premier temps, récupération de Mambaforge :

ubuntu@julia50b1000001:~$ wget -c https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh
ubuntu@julia50b1000001:~$ chmod +x Mambaforge-Linux-x86_64.sh
ubuntu@julia50b1000001:~$ ./Mambaforge-Linux-x86_64.sh

Welcome to Mambaforge 22.11.1-4

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>>
Miniforge installer code uses BSD-3-Clause license as stated below.
Installing base environment...
Transaction finished
installation finished.
Do you wish the installer to initialize Mambaforge
by running conda init? [yes|no]
[no] >>> yes
no change /home/ubuntu/mambaforge/condabin/conda
no change /home/ubuntu/mambaforge/bin/conda
no change /home/ubuntu/mambaforge/bin/conda-env
no change /home/ubuntu/mambaforge/bin/activate
no change /home/ubuntu/mambaforge/bin/deactivate
no change /home/ubuntu/mambaforge/etc/profile.d/conda.sh
no change /home/ubuntu/mambaforge/etc/fish/conf.d/conda.fish
no change /home/ubuntu/mambaforge/shell/condabin/Conda.psm1
no change /home/ubuntu/mambaforge/shell/condabin/conda-hook.ps1
no change /home/ubuntu/mambaforge/lib/python3.10/site-packages/xontrib/conda.xsh
no change /home/ubuntu/mambaforge/etc/profile.d/conda.csh
modified /home/ubuntu/.bashrc

==> For changes to take effect, close and re-open your current shell. <==


__ __ __ __
/ \ / \ / \ / \
/ \/ \/ \/ \
███████████████/ /██/ /██/ /██/ /████████████████████████
/ / \ / \ / \ / \ \____
/ / \_/ \_/ \_/ \ o \__,
/ _/ \_____/ `
|/
███╗ ███╗ █████╗ ███╗ ███╗██████╗ █████╗
████╗ ████║██╔══██╗████╗ ████║██╔══██╗██╔══██╗
██╔████╔██║███████║██╔████╔██║██████╔╝███████║
██║╚██╔╝██║██╔══██║██║╚██╔╝██║██╔══██╗██╔══██║
██║ ╚═╝ ██║██║ ██║██║ ╚═╝ ██║██████╔╝██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝

mamba (1.1.0) supported by @QuantStack

GitHub: https://github.com/mamba-org/mamba
Twitter: https://twitter.com/QuantStack

█████████████████████████████████████████████████████████████

no change /home/ubuntu/mambaforge/condabin/conda
no change /home/ubuntu/mambaforge/bin/conda
no change /home/ubuntu/mambaforge/bin/conda-env
no change /home/ubuntu/mambaforge/bin/activate
no change /home/ubuntu/mambaforge/bin/deactivate
no change /home/ubuntu/mambaforge/etc/profile.d/conda.sh
no change /home/ubuntu/mambaforge/etc/fish/conf.d/conda.fish
no change /home/ubuntu/mambaforge/shell/condabin/Conda.psm1
no change /home/ubuntu/mambaforge/shell/condabin/conda-hook.ps1
no change /home/ubuntu/mambaforge/lib/python3.10/site-packages/xontrib/conda.xsh
no change /home/ubuntu/mambaforge/etc/profile.d/conda.csh
no change /home/ubuntu/.bashrc
No action taken.
Added mamba to /home/ubuntu/.bashrc

==> For changes to take effect, close and re-open your current shell. <==

If you'd prefer that conda's base environment not be activated on startup,
set the auto_activate_base parameter to false:

conda config --set auto_activate_base false

Thank you for installing Mambaforge!
ubuntu@julia50b1000001:~$ source .bashrc
(base) ubuntu@julia50b1000001:~$ mamba
usage: mamba [-h] [-V] command ...

conda is a tool for managing and deploying applications, environments and packages.

Options:

positional arguments:
command
clean Remove unused packages and caches.
compare Compare packages between conda environments.
config Modify configuration values in .condarc. This is modeled after the git config command. Writes to the user .condarc file (/home/ubuntu/.condarc) by default.
Use the --show-sources flag to display all identified configuration locations on your computer.
create Create a new conda environment from a list of specified packages.
info Display information about current conda install.
init Initialize conda for shell interaction.
install Installs a list of packages into a specified conda environment.
list List installed packages in a conda environment.
package Low-level conda package utility. (EXPERIMENTAL)
remove (uninstall)
Remove a list of packages from a specified conda environment.
rename Renames an existing environment.
run Run an executable in a conda environment.
search Search for packages and display associated information.The input is a MatchSpec, a query language for conda packages. See examples below.
update (upgrade) Updates conda packages to the latest compatible version.
notices Retrieves latest channel notifications.
repoquery Query repositories using mamba.

options:
-h, --help Show this help message and exit.
-V, --version Show the conda version number and exit.

conda commands available from other packages (legacy):
env


__
__ ______ ___ ____ _____ ___ / /_ ____ _
/ / / / __ `__ \/ __ `/ __ `__ \/ __ \/ __ `/
/ /_/ / / / / / / /_/ / / / / / / /_/ / /_/ /
/ .___/_/ /_/ /_/\__,_/_/ /_/ /_/_.___/\__,_/
/_/

Transaction

Prefix: /home/ubuntu/mambaforge

Et installation de JupyterLite avec Pip (il sera bientôt disponible dans conda forge) :

(base) ubuntu@julia50b1000001:~$ python -m pip install --pre jupyterlite

Collecting jupyterlite
Downloading jupyterlite-0.1.0b18-py3-none-any.whl (7.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.6/7.6 MB 71.3 MB/s eta 0:00:00
Collecting doit<1,>=0.34
Downloading doit-0.36.0-py3-none-any.whl (85 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 85.9/85.9 kB 17.4 MB/s eta 0:00:00
Collecting jupyter_core>=4.7
Downloading jupyter_core-5.2.0-py3-none-any.whl (94 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.3/94.3 kB 19.5 MB/s eta 0:00:00
Collecting cloudpickle
Downloading cloudpickle-2.2.1-py3-none-any.whl (25 kB)
Collecting importlib-metadata>=4.4
Downloading importlib_metadata-6.0.0-py3-none-any.whl (21 kB)
Collecting platformdirs>=2.5
Downloading platformdirs-3.0.0-py3-none-any.whl (14 kB)
Collecting traitlets>=5.3
Downloading traitlets-5.9.0-py3-none-any.whl (117 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.4/117.4 kB 21.0 MB/s eta 0:00:00
Collecting zipp>=0.5
Downloading zipp-3.15.0-py3-none-any.whl (6.8 kB)
Installing collected packages: zipp, traitlets, platformdirs, cloudpickle, jupyter_core, importlib-metadata, doit, jupyterlite
Successfully installed cloudpickle-2.2.1 doit-0.36.0 importlib-metadata-6.0.0 jupyter_core-5.2.0 jupyterlite-0.1.0b18 platformdirs-3.0.0 traitlets-5.9.0 zipp-3.15.0

Installation de quelques Kernels en accompagnement :

(base) ubuntu@julia50b1000001:~$ pip install jupyterlite-xeus-python
Collecting jupyterlite-xeus-python
Downloading jupyterlite_xeus_python-0.6.3-py3-none-any.whl (18.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 18.3/18.3 MB 4.0 MB/s eta 0:00:00
Requirement already satisfied: jupyterlite in ./mambaforge/lib/python3.10/site-packages (from jupyterlite-xeus-python) (0.1.0b18)
Collecting typer
Downloading typer-0.7.0-py3-none-any.whl (38 kB)
Collecting empack<3,>=2.0.9
Downloading empack-2.0.9-py3-none-any.whl (15 kB)
Requirement already satisfied: requests in ./mambaforge/lib/python3.10/site-packages (from jupyterlite-xeus-python) (2.28.2)
Requirement already satisfied: traitlets in ./mambaforge/lib/python3.10/site-packages (from jupyterlite-xeus-python) (5.9.0)
Collecting pyyaml
Downloading PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (682 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 682.2/682.2 kB 67.7 MB/s eta 0:00:00
Collecting networkx
Downloading networkx-3.0-py3-none-any.whl (2.0 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 73.2 MB/s eta 0:00:00
Collecting appdirs
Downloading appdirs-1.4.4-py2.py3-none-any.whl (9.6 kB)
Collecting pydantic
Downloading pydantic-1.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 116.2 MB/s eta 0:00:00
Requirement already satisfied: jupyter_core>=4.7 in ./mambaforge/lib/python3.10/site-packages (from jupyterlite->jupyterlite-xeus-python) (5.2.0)
Requirement already satisfied: doit<1,>=0.34 in ./mambaforge/lib/python3.10/site-packages (from jupyterlite->jupyterlite-xeus-python) (0.36.0)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in ./mambaforge/lib/python3.10/site-packages (from requests->jupyterlite-xeus-python) (1.26.14)
Requirement already satisfied: idna<4,>=2.5 in ./mambaforge/lib/python3.10/site-packages (from requests->jupyterlite-xeus-python) (3.4)
Requirement already satisfied: charset-normalizer<4,>=2 in ./mambaforge/lib/python3.10/site-packages (from requests->jupyterlite-xeus-python) (2.1.1)
Requirement already satisfied: certifi>=2017.4.17 in ./mambaforge/lib/python3.10/site-packages (from requests->jupyterlite-xeus-python) (2022.12.7)
Collecting click<9.0.0,>=7.1.1
Downloading click-8.1.3-py3-none-any.whl (96 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 96.6/96.6 kB 19.1 MB/s eta 0:00:00
Requirement already satisfied: cloudpickle in ./mambaforge/lib/python3.10/site-packages (from doit<1,>=0.34->jupyterlite->jupyterlite-xeus-python) (2.2.1)
Requirement already satisfied: importlib-metadata>=4.4 in ./mambaforge/lib/python3.10/site-packages (from doit<1,>=0.34->jupyterlite->jupyterlite-xeus-python) (6.0.0)
Requirement already satisfied: platformdirs>=2.5 in ./mambaforge/lib/python3.10/site-packages (from jupyter_core>=4.7->jupyterlite->jupyterlite-xeus-python) (3.0.0)
Collecting typing-extensions>=4.2.0
Downloading typing_extensions-4.5.0-py3-none-any.whl (27 kB)
Requirement already satisfied: zipp>=0.5 in ./mambaforge/lib/python3.10/site-packages (from importlib-metadata>=4.4->doit<1,>=0.34->jupyterlite->jupyterlite-xeus-python) (3.15.0)
Installing collected packages: appdirs, typing-extensions, pyyaml, networkx, click, typer, pydantic, empack, jupyterlite-xeus-python
Successfully installed appdirs-1.4.4 click-8.1.3 empack-2.0.9 jupyterlite-xeus-python-0.6.3 networkx-3.0 pydantic-1.10.5 pyyaml-6.0 typer-0.7.0 typing-extensions-4.5.0
(base) ubuntu@julia50b1000001:~$ pip install jupyterlite-p5-kernel
Collecting jupyterlite-p5-kernel
Downloading jupyterlite_p5_kernel-0.1.1-py3-none-any.whl (21 kB)
Installing collected packages: jupyterlite-p5-kernel
Successfully installed jupyterlite-p5-kernel-0.1.1
(base) ubuntu@julia50b1000001:~$ pip install jupyterlite_xeus_lua
Collecting jupyterlite_xeus_lua
Downloading jupyterlite_xeus_lua-0.3.3-py3-none-any.whl (2.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.2/2.2 MB 2.6 MB/s eta 0:00:00
Installing collected packages: jupyterlite_xeus_lua
Successfully installed jupyterlite_xeus_lua-0.3.3
(base) ubuntu@julia50b1000001:~$ pip install jupyterlite_xeus_wren
Collecting jupyterlite_xeus_wren
Downloading jupyterlite_xeus_wren-0.2.1-py3-none-any.whl (4.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.2/4.2 MB 3.9 MB/s eta 0:00:00
Collecting jupyter-wren-syntax
Downloading jupyter_wren_syntax-0.1.1-py3-none-any.whl (68 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 68.6/68.6 kB 8.8 MB/s eta 0:00:00
Installing collected packages: jupyter-wren-syntax, jupyterlite_xeus_wren
Successfully installed jupyter-wren-syntax-0.1.1 jupyterlite_xeus_wren-0.2.1

Et génération du contenu statique dans un répertoire contenant JupyterLite et ses extensions via cette commande :

(base) ubuntu@julia50b1000001:~$ jupyter lite build --output-dir dist
(base) ubuntu@julia50b1000001:~$ ls dist/
api config-utils.js icon-120x120.png jupyter-lite.ipynb kernelspecs package.json retro
bootstrap.js doc icon-512x512.png jupyter-lite.json lab piplite.schema.v0.json service-worker-b2fb40a.js
build extensions index.html jupyterlite.schema.v0.json manifest.webmanifest repl tree

Maintenant que les actifs statiques ont été construits, vous pouvez utiliser un serveur HTTP simple pour les servir et accéder à JupyterLite depuis un navigateur web.

La commande jupyter lite serve offre soit un serveur web propulsé par le module http.server intégré de Python, soit Tornado, qui sera probablement disponible si d’autres outils Jupyter sont installés :


(base) ubuntu@julia50b1000001:~$ jupyter lite serve

Serving JupyterLite Debug Server from:
/home/ubuntu/dist/_output
on:
http://127.0.0.1:8000/index.html

*** Exit by: ***
- Pressing Ctrl+C

Il ne me reste plus qu’à l’empaqueter pour le déployer dans la plateforme fly.io. Pour cela je m’inspire de la fiche documentaire consacrée au déploiement de contenus statiques :

Installation du client flyctl :

(base) ubuntu@julia50b1000001:~$ curl -L https://fly.io/install.sh | sh

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1475 0 1475 0 0 6089 0 --:--:-- --:--:-- --:--:-- 6069
######################################################################## 100.0%
set channel to shell
flyctl was installed successfully to /home/ubuntu/.fly/bin/flyctl
Manually add the directory to your $HOME/.bash_profile (or similar)
export FLYCTL_INSTALL="/home/ubuntu/.fly"
export PATH="$FLYCTL_INSTALL/bin:$PATH"
Run '/home/ubuntu/.fly/bin/flyctl --help' to get started

(base) ubuntu@julia50b1000001:~$ flyctl auth login

(base) ubuntu@julia50b1000001:~$ flyctl
This is flyctl, the Fly.io command line interface.

It doesn't look like you're logged in. Try "flyctl auth signup" to create an account,
or "flyctl auth login" to log in to an existing account.

flyctl does a lot of stuff! Don't panic, it's easy to get started:

* fly launch: launch a new application ("fly help launch" for more)

* fly apps: create and manage apps ("fly help apps")

* fly machines: create and manage individual Fly.io machines ("fly help machines")

* fly postgres: create and manage Postgres databases ("fly help postgres")

* fly redis: create and manage Redis databases ("fly help redis")

* fly help: for more help, and a complete list of commands.

J’ajoute ce fichier Dockerfile avec ce contenu :

(base) ubuntu@julia50b1000001:~$ ls
Dockerfile dist mambaforge

(base) ubuntu@julia50b1000001:~$ cat Dockerfile

FROM pierrezemb/gostatic
COPY ./dist/ /srv/http/

Génération du fichier fly.toml qui va servir au déploiement dans la plateforme …

(base) ubuntu@julia50b1000001:~$ flyctl launch

Creating app in /home/ubuntu
Scanning source code
Detected a Dockerfile app
? Choose an app name (leave blank to generate one):
automatically selected personal organization: Karim
Some regions require a paid plan (fra, maa).
See https://fly.io/plans to set up a plan.

? Choose a region for deployment: Paris, France (cdg)
Created app old-sunset-3102 in organization personal
Admin URL: https://fly.io/apps/old-sunset-3102
Hostname: old-sunset-3102.fly.dev
? Would you like to set up a Postgresql database now? No
? Would you like to set up an Upstash Redis database now? No
? Create .dockerignore from 4 .gitignore files? No
Wrote config file fly.toml
? Would you like to deploy now? No
Your app is ready! Deploy with `flyctl deploy`

Modifié pour prendre en compte le fait que le futur conteneur utilisera le port 8043 par défaut :

(base) ubuntu@julia50b1000001:~$ cat fly.toml 

# fly.toml file generated for old-sunset-3102 on 2023-03-01T22:42:29Z

app = "old-sunset-3102"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[env]

[experimental]
auto_rollback = true

[[services]]
http_checks = []
internal_port = 8043
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"

[[services.ports]]
force_https = true
handlers = ["http"]
port = 80

[[services.ports]]
handlers = ["tls", "http"]
port = 443

[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"

Lancement du déploiement :

(base) ubuntu@julia50b1000001:~$ flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-small-water-3057 ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.12 linux x86_64
[+] Building 54.5s (0/1)
[+] Building 84.2s (0/1)
[+] Building 16.3s (6/6) FINISHED
=> [internal] load remote build context 0.0s
=> copy /context / 11.6s
=> [internal] load metadata for docker.io/pierrezemb/gostatic:latest 0.7s
=> [1/2] FROM docker.io/pierrezemb/gostatic@sha256:7e5718f98f2172f7c8dffd152ef0b203873ba889c8d838b2e730484fc71f6acd 0.1s
=> => resolve docker.io/pierrezemb/gostatic@sha256:7e5718f98f2172f7c8dffd152ef0b203873ba889c8d838b2e730484fc71f6acd 0.0s
=> => extracting sha256:0cf3c901807f7df57d792cd4a926ac2eb4078eb337750316dbde44bc7e7acd83 0.1s
=> => sha256:0cf3c901807f7df57d792cd4a926ac2eb4078eb337750316dbde44bc7e7acd83 1.88MB / 1.88MB 0.0s
=> => sha256:7e5718f98f2172f7c8dffd152ef0b203873ba889c8d838b2e730484fc71f6acd 2.67kB / 2.67kB 0.0s
=> => sha256:f846dcfe68518bd5a624acb44abee440deedfca894e641b7947ba494f6e0f18a 527B / 527B 0.0s
=> => sha256:37dd3994986381311fdfc59ea190c8e60c6c8c5a38f3cdec3419ee8b333c9fa9 915B / 915B 0.0s
=> [2/2] COPY ./dist/ /srv/http/ 0.3s
=> exporting to image 0.5s
=> => exporting layers 0.5s
=> => writing image sha256:29bf836545e5b679b481077dc137a880d90baa8ca49839b603e5c4a2966133ea 0.0s
=> => naming to registry.fly.io/old-sunset-3102:deployment-01GTFNNV4ZS5S210YX9MPXXCNW 0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/old-sunset-3102]
688585856d25: Pushed
f347b3d1982a: Pushed
deployment-01GTFNNV4ZS5S210YX9MPXXCNW: digest: sha256:0c085f048ab642afe7481d6b85c4444b6ac5d3f553717a9527fa53170de9853f size: 740
--> Pushing image done
image: registry.fly.io/old-sunset-3102:deployment-01GTFNNV4ZS5S210YX9MPXXCNW
image size: 91 MB
==> Creating release
--> release v2 created

--> You can detach the terminal anytime without stopping the deployment
==> Monitoring deployment
Logs: https://fly.io/apps/old-sunset-3102/monitoring

1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully

Cette version v0 est opérationnelle :

Accompagnée de son monitoring :

Et les Kernels sont fonctionnels :

Avec suppression à la fois du volume et du conteneur associé à la fin de cette expérience …

Pour aller plus loin avec JupyterLite, ces prises en charges de Langages et Frameworks :

Sachant que pour Fly.io, les orchestrateurs relient des grappes de serveurs de travail et offrent une API pour exécuter des tâches sur ces grappes avec HashiCorp Nomad et ces mécanismes exposés dans cet article :

À Suivre !

--

--