Utiliser des images Apptainer

Pourquoi utiliser des conteneurs ?

Un conteneur est un objet immuable qui peut être vue comme un environnement virtuel léger (comparé aux machines virtuelles) permettant une bonne isolation de processus, code ou script. Toutes les librairies nécessaires au runtime sont embarquées au sein du conteneur ce qui assure une certaine portabilité, reproductibilité et distribution quel que soit le système d’exploitation. Cela permet notamment de “figer” des librairies ou codes dans le temps, en garantissant leur utilisation au delà de leur dépréciation.

Un système de conteneurs permet de lancer un conteneur sur une machine personnelle, sur un cluster local dans un laboratoire ou une université, ou dans un mésocentre. Il permet également d’exécuter n’importe quel code de manière flexible avec les mêmes droits utilisateurs que votre compte.

Les systèmes de conteneurs disponibles et leurs versions sont :

Attention, Singularity n’est actuellement plus mis à jour. Il sera déprécié à moyen terme au profit d’Apptainer qui est un projet open-source.

Cette documentation se limite à expliquer l’usage d’Apptainer (et de conteneurs) sur les machines de calcul, elle n’aborde pas du tout la création d’images.

Exemples d’utilisation

Les systèmes de conteneurs sont disponibles sur les clusters Dahu et Bigfoot, et sont compatibles avec l’utilisation de CiGri. Cela signifie que vous n’avez pas besoin d’installer Apptainer avec Nix ou Guix. Les commandes Apptainer sont disponibles sur les nœuds de calcul (mais pas sur les frontales).

L’utilisation des systèmes de conteneurs n’est pas recommandée sur le cluster Luke.

Vous devez disposer d’une image de conteneur déjà existante (fichier .sif) et la télécharger sur les machines. Il ne vous sera pas possible de créer une image sur les clusters de calcul. Nous ne détaillerons pas ici toutes les options proposées par Apptainer, merci de vous reporter à leur documentation.

Lancer une image Apptainer sur un cœur

L’exécution par défaut d’un conteneur s’obtient avec la commande run :

apptainer run image_apptainer.sif

Cette commande run exécute les commandes qui ont été renseignées dans la section %runscript lors de la création de l’image. Il faut donc jeter un œil au fichier de définition du conteneur.

Il faut privilégier la commande exec pour exécuter les commandes que vous voulez dans le conteneur :

apptainer exec image_apptainer.sif commande...

Apptainer monte certains dossiers par défaut dans le conteneur comme $HOME, le répertoire courant, /tmp, /proc, /dev, … Pour isoler le conteneur de la machine hôte, on peut utiliser le flag --no-mount (pour ne pas monter $HOME) ou --containall pour totalement isoler le conteneur de la machine hôte.

L’utilisation recommandée d’Apptainer est avec le flag --containall, suivi de plusieurs flags --bind pour monter uniquement les dossiers nécessaires à l’exécution du conteneur :

apptainer exec \
--containall \
--bind chemin_dossier1_machine_hote:chemin_dossier1_conteneur \
--bind chemin_dossier2_machine_hote:chemin_dossier2_conteneur \
image_apptainer.sif commande...

Lancer une image Docker avec Apptainer

Docker est une solution populaire pour la conteneurisation. Il se peut que le code ou le script que vous souhaitez utiliser ait été empaqueté dans une image Docker. Apptainer vise une compatibilité maximale avec Docker, ce qui permet l’utilisation de commandes Apptainer pour lancer une image Docker :

apptainer exec docker://image_docker

La recommandation sur les images docker consistent à créer une image Apptainer à partir de l’image Docker sur votre machine personnelle, puis de la téléverser sur les clusters et d’utiliser les commandes Apptainer. Cela se fait grâce à la commande apptainer pull docker://image_docker. N’hésitez pas à jeter un œil à la documentation d’Apptainer sur le sujet.

Lancer une image Apptainer sur plusieurs cœurs avec OpenMPI

Certains codes/scripts conteneurisés sont massivement parallèles et reposent sur l’utilisation d’OpenMPI qui est alors embarqué dans le conteneur. Il y a un donc un “dialogue” qui s’opère entre OpenMPI de la machine hôte (Dahu par exemple) et OpenMPI embarqué dans l’image Apptainer. Cette méthode s’appelle le modèle hybride.

Dans un premier temps, il faut installer OpenMPI dans son profil Nix ou sa session Guix. Ensuite, il faut utiliser les instances Apptainer pour homogénéiser les namespaces des processus OpenMPI. Pour cela, il est recommandé d’utiliser un script de soumission. En pratique, il faut rajouter ces lignes dans le fichier .sh (ou .oar).

# si OpenMPI a été installé sur un profil NIX, on exécute
source /applis/site/nix.sh

# on fixe les paramètres pour OpenMPI
export OMPI_MCA_btl_openib_allow_ib=true
export OMPI_MCA_pml=cm
export OMPI_MCA_mtl=psm2

# on lance une instance apptainer sur chaque nœud de calcul
mpirun -npernode 1 \
        --machinefile $OAR_NODE_FILE \
        --mca plm_rsh_agent "oarsh" \
        --prefix $HOME/.nix-profile \
        apptainer instance start \
        image_apptainer.sif nom_instance

# on lance le code/script avec la commande MPI
mpirun -np `cat $OAR_FILE_NODES|wc -l` \
        --machinefile $OAR_NODE_FILE \
        --mca plm_rsh_agent "oarsh" \
        --prefix $HOME/.nix-profile \
        apptainer exec instance://nom_instance \
        /bin/bash -c "commande..."

# pour lancer les instances de chaque noeud
mpirun -npernode 1 \
        --machinefile $OAR_NODE_FILE \
        --mca plm_rsh_agent "oarsh" \
        --prefix $HOME/.nix-profile \
        apptainer instance stop nom_instance

Si vous utilisez une session Guix, il faut remplacer la ligne source /applis/site/nix.sh par source /applis/site/guix-start.sh, et les lignes --prefix $HOME/.nix-profile par --prefix $HOME/.guix-profile

Si vous voulez monter des dossiers spécifiques avec le paramètre --bind, il faut le faire à la création de l’instance

mpirun -npernode 1 \
        --machinefile $OAR_NODE_FILE \
        --mca plm_rsh_agent "oarsh" \
        --prefix $HOME/.nix-profile \
        apptainer instance start \
        --bind chemin_dossier_machine_hote:chemin_dossier_conteneur \
        image_apptainer.sif nom_instance

Conseils et bonnes pratiques

Bien que l’image Apptainer s’exécutent, cela ne veut pas dire que vous tirez avantage des installations HPC.

Si OpenMPI n’est pas correctement configuré dans le conteneur Apptainer, cela peut se répercuter en des baisses de performances. Il faut idéalement que les deux OpenMPI (machine hôte et conteneur) soient identiques comme un “dialogue” s’opère entre eux. Si vous voulez conteneuriser vous-même votre code de calcul, il faut configurer OpenMPI comme suit :

Bootstrap: docker  
From: debian:stable-slim

%environment
  export PATH="$LIB_DIR/openmpi/bin:$WORK_DIR/.local/bin:$PATH"
  export LD_LIBRARY_PATH="$LIB_DIR/openmpi/lib:$LD_LIBRARY_PATH"
  
%post
##### Définit les variables d’environnement locales
  export LIB_DIR=/libraries
  mkdir -p $LIB_DIR
##### Installe les paquets nécessaires
  apt-get update -y
  apt-get install -y gfortran gcc g++ libgomp1
  apt-get install -y make wget oar-node
  apt-get install -y libinput-dev 
  ## Installe les paquets pour l'Infiniband
  apt-get install -y rdma-core libibverbs-dev libibverbs1
  apt-get install -y ibacm infiniband-diags librdmacm1 librdmacm-dev
  apt-get install -y libibnetdisc-dev 
  ## Installe les paquets l'Omnipath
  apt-get install -y libnuma-dev libfabric-dev libpsm2-2-compat libpsm2-dev numactl
  apt-get install -y libtool opensm libopensm-dev

##### Compile OpenMPI
  cd ${APPTAINER_CONTAINER}/tmp
  wget https://download.open-mpi.org/release/open-mpi/vX.X/openmpi-X.X.X.tar.gz
  tar -xvf openmpi-X.X.X.tar.gz
  cd openmpi-X.X.X
  mkdir -p $LIB_DIR/openmpi
  ./configure --prefix=$LIB_DIR/openmpi \
    ## Paramètres pour la configuration de l'Omnipath
    --with-libfabric --with-psm2 \
    ## Paramètres pour l'utilisation de l'Infiniband
    --enable-openib-control-hdr-padding \
    --enable-openib-dynamic-sl \
    --enable-openib-udcm \
    --enable-openib-rdmacm \
    --enable-openib-rdmacm-ibaddr \
    --with-pmix=internal \
    ## Paramètres de compilation utilisés sur les clusters GRICAD
    --disable-mca-dso --disable-static --disable-dependency-tracking --enable-mpi-cxx
  make -j4
  make install

Bien qu’il y existe une compatibilité OpenMPI inter-version, l’utilisation de versions différentes d’OpenMPI résulte en des baisses de performances. Il faut donc veiller à utiliser la même version d’OpenMPI dans le conteneur et dans le profil Nix (version d’OpenMPI disponible avec Nix) ou la session Guix (par exemple, via la commande guix install openmpi@4.1.6 pour installer la version 4.1.6 d’OpenMPI).

Pour aller plus loin

Pour toute question concernant ce sujet, ou toute demande d’aide, n’hésitez pas à envoyer un mail à sos-calcul-gricad@univ-grenoble-alpes.fr.