Conteneurs avec Singularity
Introduction
La conteneurisation des logiciels est devenue très populaire, particulièrement via l'utilisation de Docker. Néanmoins, Docker ne peut pas fonctionner dans un environnement HPC pour des raisons de sécurité. Il existe plusieurs alternatives à Docker qui s'accordent parfaitement avec l'environnement HPC, et Singularity est celle qui couvre le plus grand nombre d'usages (dont par exemple l'importation de conteneurs Docker).
Singularity est un logiciel open-source qui permet d'exécuter des conteneurs, autrement dit qui permet la virtualisation au niveau système d'exploitation. L'un de ses principaux usages est d'apporter la conteneurisation et la reproductibilité à l'informatique scientifique et au monde HPC (offrant donc la possibilité à chaque utilisateur de gérer ses environnements reproductibles).
Pourquoi Singularity ?
Il reste néanmoins des différences importantes entre Singularity et Docker :
- Docker et Singularity ont leur propre format de conteneur ;
- les conteneurs Docker peuvent être importés par Singularity ;
- les conteneurs Docker nécessitent des droits administrateur pour fonctionner, ce qui n'est pas possible sur un cluster de calcul partagé ;
- Singularity permet de travailler avec des conteneurs avec les droits utilisateur classique.
Avec Singularity, l'utilisateur est autonome :
- il gère ses images, de simples fichiers
.sif
dans son répertoirehome
; - il peut récupérer des images à partir du docker hub, du singularity hub, du Sylabs container library ou d'autres sources ;
- il lance les conteneurs au sein d'un job sur le cluster.
Sur le cluster
Singularity est déployé à la fois sur les noeuds de calcul et sur les frontaux. Cela permet aux utilisateurs de préparer le chargement des images à partir des frontaux, et d'utiliser le temps alloué sur les noeuds pour le calcul.
[user@hpclogin01 ~]$ singularity --version
singularity version 3.7.3-1.el7
Récupérer une image
Vous pouvez envoyer dans votre répertoire personnel vos images Singularity avec un scp
par exemple. Vous pouvez également utiliser la commande pull
de Singularity pour télécharger des images pré-construites à partir de différentes sources.
Par exemple, à partir du Singularity hub :
[user@hpclogin01 ~]$ singularity pull shub://vsoch/hello-world
INFO: Downloading shub image
59.8MiB / 59.8MiB [================================] 100 % 20.9 MiB/s 0s
[user@hpclogin01 ~]$ ls
hello-world_latest.sif
hello-world_latest.sif
; on peut également préciser le nom de destination :
[user@hpclogin01 ~]$ singularity pull --name hello.sif shub://vsoch/hello-world
INFO: Downloading shub image
59.8MiB / 59.8MiB [================================] 100 % 20.9 MiB/s 0s
[user@hpclogin01 ~]$ ls
hello.sif
Ou à partir du Docker hub :
[user@hpclogin01 ~]$ singularity pull docker://godlovedc/lolcow
INFO: Converting OCI blobs to SIF format
INFO: Starting build...
Getting image source signatures
[...]
Writing manifest to image destination
Storing signatures
2021/05/20 11:33:07 info unpack layer: sha256:8e860504ff1ee5dc7953672d128ce1e4aa4d8e3716eb39fe710b849c64b20945
INFO: Creating SIF file...
[user@hpclogin01 ~]$ ls
lolcow_latest.sif
Pour plus d'informations, consultez la documentation.
Note 1 : cache Singularity
Singularity gère un cache d'images localement dans votre répertoire home (~/.singularity/cache
), pour permettre de ne pas re-télécharger inutilement des images qui n'auraient pas changé. Ce cache peut vite prendre beaucoup d'espace. Vous pouvez le vider avec singularity cache clean
.
Note 2 : espace temporaire
Lorsque Singularity assemble une image à partir des morceaux qu'il récupère sur les sources externes (Docker hub, Singularity hub, Sylabs Container Library, ...), il peut avoir besoin d'une grande quantité d'espace disque temporairement, qu'il va par défaut aller chercher dans /tmp
. Cependant, sur les frontaux et sur les noeuds, cet espace /tmp
est plutôt petit (4Go maximum). Il est conseillé de changer le répertoire utilisé par défaut pour les fichiers temporaires en faisant par exemple :
$ TMPDIR=/storage/scratch/$LOGNAME/tmp
$ mkdir -p $TMPDIR
$ export TMPDIR
$ singularity pull ...
Travailler avec les images
Supposons que l'on dispose déjà d'une image nommée hello.sif
récupérée comme précédemment.
Shell
Avec la commande shell
, on peut démarrer un nouveau shell à l'intérieur de l'image du conteneur et interagir avec, comme si c'était une petite machine virtuelle :
[user@hpclogin01 ~]$ singularity shell hello.sif
Singularity> pwd
/home/user
Singularity> ls
hello.sif
Singularity> cat /etc/os-release
NAME="Ubuntu"
VERSION="14.04.6 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.6 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
On remarque bien que la commande cat /etc/os-release
est exécutée au sein du conteneur, étant donné que les serveurs du cluster utilisent CentOS et non Ubuntu 14.04.6. Cela nous apprend également que le conteneur avec lequel on travaille pour l'exemple a été construit à partir d'Ubuntu 14.04.6.
Commandes dans un conteneur
La commande exec
permet d'exécuter directement des programmes au sein du conteneur. Par exemple :
[user@hpclogin01 ~]$ singularity exec hello.sif cat /etc/os-release
NAME="Ubuntu"
VERSION="14.04.6 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.6 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
Exécution d'un conteneur
Une image Singularity contient un script par défaut qui peut être lancé avec la commande run
:
[user@hpclogin01 ~]$ singularity run hello.sif
RaawwWWWWWRRRR!! Avocado!
Au sein de l'image, ce script par défaut est /singularity
:
[user@hpclogin01 ~]$ singularity shell hello.sif
Singularity> cat /singularity
#!/bin/sh
exec /bin/bash /rawr.sh
Singularity> cat /rawr.sh
#!/bin/bash
echo "RaawwWWWWWRRRR!! Avocado!"
Accès aux fichiers depuis un conteneur
Lorsqu'on se trouve à l'intérieur d'une exécution d'un conteneur, tous les fichiers et chemins ne sont par défaut pas accessibles. Exemple :
[user@hpclogin01 ~]$ echo "Hello" > /storage/scratch/hellofile.txt
[user@hpclogin01 ~]$ cat /storage/scratch/hellofile.txt
Hello
[user@hpclogin01 ~]$ singularity shell hello.sif
Singularity> ls /storage/scratch/
ls: cannot access /storage/scratch/: No such file or directory
Il suffit d'indiquer à Singularity que l'on souhaite rendre disponibles certains chemins au sein du conteneur lors de son exécution avec l'option --bind
:
[user@hpclogin01 ~]$ singularity shell --bind /storage:/storage hello.sif
Singularity> cat /storage/scratch/hellofile.txt
Hello
On peut également définir une variable d'environnement SINGULARITY_BINDPATH
qui a le même effet :
[user@hpclogin01 ~]$ export SINGULARITY_BINDPATH="/storage"
[user@hpclogin01 ~]$ singularity shell hello.sif
Singularity> cat /storage/scratch/hellofile.txt
Hello
Votre répertoire home
est déjà accessible par défaut au sein du conteneur, avec le même chemin.
Jobs sur le cluster avec Singularity
Bien peu de contraintes sont liées à l'utilisation de Singularity sur le cluster au sein d'un job.
Dans un job interactif
Dans un job interactif, on peut par exemple faire comme suit :
[user@hpclogin01 ~]$ srun --partition=debug --ntasks=1 --cpus-per-task=4 --mem=8G --pty bash
[user@hpcphi01 ~]$ singularity run hello.sif
RaawwWWWWWRRRR!! Avocado!
[user@hpcphi01 ~]$ singularity exec hello.sif bash
Singularity> echo $SLURM_JOB_ID
37166694
Singularity> echo $SLURM_CPUS_ON_NODE
4
Dans un job classique
Voici un exemple de job lançant un conteneur avec Singularity :
#!/bin/bash
#SBATCH --job-name=singularity-example
#SBATCH --partition=normal
#SBATCH --ntasks=1
#SBATCH --nodes=1
#SBATCH --cpus-per-task=8
#SBATCH --mem=32g
#SBATCH --mail-type=ALL
#SBATCH --mail-user=user.email@uca.fr
#SBATCH --time=06:00:00
# Prepare working directory on node
WORKDIR=/storage/scratch/$LOGNAME/$SLURM_JOB_ID
TMPDIR=$WORKDIR/tmp
INPUTDIR=$WORKDIR/input
mkdir -p $WORKDIR
mkdir -p $TMPDIR
mkdir -p $INPUTDIR
# Tell programs to use $TMPDIR instead of /tmp/ when creating temporary files
# (/tmp/ is small, /storage/scratch/ offers more space)
export TMPDIR
# Get input data
INPUTFILE=$INPUTDIR/genome.fasta
wget "http://biodatabanks.example/files/genome.fasta" -O $INPUTFILE
...
# Launch a tool in the Singularity container
singularity exec --bind /storage:/storage my_workflow_image.sif bash /workflow.sh --input=$INPUTFILE --workdir=$WORKDIR --threads=$SLURM_CPUS_ON_NODE
# Get output results
cp $WORKDIR/output_results.txt ~/
# Clean-up
rm -rf $WORKDIR
# end of script
Dans un job avec GPU
Rappel : accès GPU
L'accès aux files GPU sur le cluster se fait sur demande auprès du Mésocentre.
On commence par préparer l'image qui nous intéresse :
[user@hpclogin01 ~]$ singularity build tf-py3.simg docker://tensorflow/tensorflow:1.14.0-gpu-py3
INFO: Starting build...
Getting image source signatures
[...]
INFO: Creating SIF file...
INFO: Build complete: tf-py3.simg
On demande ensuite une allocation GPU au cluster et on lance notre test avec Singularity :
[user@hpclogin01 ~]$ srun --partition=gpu --ntasks=1 --cpus-per-task=2 --mem=4G --gres=gpu:1 --pty bash
[user@hpcgpu01 ~]$ singularity exec --nv tf-py3.simg python -c 'import tensorflow as tf; print("GPU available = ", tf.test.is_gpu_available())'
2021-05-20 14:20:29.742655: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2021-05-20 14:20:29.773686: I tensorflow/stream_executor/platform/default/dso_loader.cc:42] Successfully opened dynamic library libcuda.so.1
2021-05-20 14:20:31.006915: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x3bf4780 executing computations on platform CUDA. Devices:
2021-05-20 14:20:31.006949: I tensorflow/compiler/xla/service/service.cc:175] StreamExecutor device (0): Tesla V100-SXM2-32GB, Compute Capability 7.0
[...]
2021-05-20 14:20:31.625934: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1763] Adding visible gpu devices: 0
2021-05-20 14:20:31.628651: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1181] Device interconnect StreamExecutor with strength 1 edge matrix:
2021-05-20 14:20:31.628687: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1187] 0
2021-05-20 14:20:31.628700: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1200] 0: N
2021-05-20 14:20:31.633252: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1326] Created TensorFlow device (/device:GPU:0 with 30591 MB memory) -> physical GPU (device: 0, name: Tesla V100-SXM2-32GB, pci bus id: 0000:0b:00.0, compute capability: 7.0)
GPU available = True
Les bibliothèques CUDA chargées dans cet exemple sont intégrées dans le conteneur, il n'y a donc pas besoin de faire de module load cuda/11.2.152
par exemple.
En revanche, le lecteur attentif aura bien noté le paramètre --nv
passé à Singularity qui permet l'accès aux GPU. Le conteneur n'a bien entendu accès qu'aux GPU alloués dans le cadre du job, ce que l'on peut vérifier avec la commande nvidia-smi
dans le conteneur :
[user@hpcgpu01 ~]$ singularity exec --nv tf-py3.simg nvidia-smi
Thu May 20 14:31:18 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla V100-SXM2... Off | 00000000:0B:00.0 Off | 0 |
| N/A 33C P0 55W / 300W | 0MiB / 32510MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
Enfin, pour un job non interactif avec GPU, on peut utiliser par exemple :
#!/bin/bash
#SBATCH --job-name=singularity-gpu-example
#SBATCH --partition=gpu
#SBATCH --ntasks=1
#SBATCH --nodes=1
#SBATCH --cpus-per-task=2
#SBATCH --mem=8g
#SBATCH --mail-type=ALL
#SBATCH --mail-user=user.email@uca.fr
#SBATCH --time=09:00:00
#SBATCH --gres=gpu:1
# Prepare working directory on node
WORKDIR=/storage/scratch/$LOGNAME/$SLURM_JOB_ID
TMPDIR=$WORKDIR/tmp
INPUTDIR=$WORKDIR/input
mkdir -p $WORKDIR
mkdir -p $TMPDIR
mkdir -p $INPUTDIR
# Tell programs to use $TMPDIR instead of /tmp/ when creating temporary files
# (/tmp/ is small, /storage/scratch/ offers more space)
export TMPDIR
# Get input data
INPUTFILE=$INPUTDIR/genome.fasta
wget "http://biodatabanks.example/files/genome.fasta" -O $INPUTFILE
...
# Launch a tool using GPU in the Singularity container
singularity exec --nv --bind /storage:/storage tensorflow_workflow_image.sif bash /workflow.sh --input=$INPUTFILE --workdir=$WORKDIR --threads=$SLURM_CPUS_ON_NODE
# Get output results
cp $WORKDIR/output_results.txt ~/
# Clean-up
rm -rf $WORKDIR
# end of script