Construire un environnement de développement pour le deep learning avec NVIDIA Container Toolkit et Docker/Podman (2) - Configuration du runtime de conteneur pour l'utilisation du GPU, rédaction du Dockerfile et construction de l'image de conteneur
Cette série traite de la mise en place d'un environnement de développement pour le deep learning basé sur des conteneurs avec NVIDIA Container Toolkit en local, et de la configuration de SSH et Jupyter Lab pour l'utiliser comme serveur distant. Ce billet est le deuxième de la série et couvre le processus de rédaction d'un Dockerfile et de construction d'une image de conteneur.
Aperçu
Dans cette série, nous abordons le processus d’installation de NVIDIA Container Toolkit et Docker ou Podman, et la création d’un environnement de développement pour le deep learning en écrivant un Dockerfile basé sur les images CUDA et cuDNN fournies par le dépôt nvidia/cuda sur Docker Hub. Pour ceux qui en ont besoin, je partage le Dockerfile et l’image complétés via GitHub et Docker Hub, ainsi qu’un guide de configuration SSH et Jupyter Lab pour l’utilisation comme serveur distant.
La série se composera de 3 articles, et celui-ci est le deuxième de la série.
- Partie 1: Installation de NVIDIA Container Toolkit et du moteur de conteneur
- Partie 2: Configuration du runtime de conteneur pour l’utilisation du GPU, rédaction du Dockerfile et construction de l’image de conteneur (cet article)
- Partie 3 (à venir)
Je suppose que vous utilisez un système Linux x86_64 avec une carte graphique NVIDIA compatible CUDA. Bien que j’ai principalement testé sur Ubuntu et Fedora, certains détails peuvent varier légèrement sur d’autres distributions.
(Mis à jour le 18.02.12025)
Avis de correction d’erreur
Dans la version initiale de cet article publiée en août 12024 du calendrier holocène, il y avait quelques erreurs dans la section Rédaction du Dockerfile et dans l’image construite à partir de ce Dockerfile. Les problèmes étaient les suivants:
- La partie concernant la création du compte remote avait un problème dans la configuration du mot de passe, et il n’était pas possible de se connecter avec le mot de passe “000000” comme prévu
- Le démon SSH ne démarrait pas automatiquement au lancement du conteneur
J’ai récemment pris conscience de ces problèmes et, vers 2h du matin le 16 février 12025 (UTC+9), j’ai remplacé le Dockerfile problématique et les images Docker par des versions corrigées sur le dépôt GitHub et Docker Hub.
Si vous avez téléchargé le Dockerfile ou l’image Docker avant cette date, veuillez les remplacer par les versions corrigées.
Je présente mes excuses à tous ceux qui ont pu être confrontés à des difficultés en raison de ces erreurs.
Avant de commencer
Cet article fait suite à la première partie, donc si vous ne l’avez pas encore lue, je vous recommande de la lire d’abord.
4. Configuration du runtime de conteneur
Si vous utilisez Podman
Configurez en utilisant CDI (Container Device Interface).
Exécutez la commande suivante pour générer le fichier de spécification CDI dans le répertoire /etc/cdi
:
1
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
Si vous changez de carte graphique ou modifiez la configuration du pilote CUDA (y compris les mises à niveau de version), vous devrez régénérer le fichier de spécification CDI.
L’utilisation du hook NVIDIA Container Runtime avec CDI peut provoquer des conflits. Si le fichier
/usr/share/containers/oci/hooks.d/oci-nvidia-hook.json
existe, supprimez-le ou évitez d’exécuter des conteneurs avec la variable d’environnementNVIDIA_VISIBLE_DEVICES
définie.
Si vous utilisez Docker
Les explications sont basées sur le mode rootless.
4-Docker-1. Configurer le runtime de conteneur avec la commande nvidia-ctk
1
nvidia-ctk runtime configure --runtime=docker --config=$HOME/.config/docker/daemon.json
Cette commande modifie le fichier /etc/docker/daemon.json
pour permettre à Docker d’utiliser NVIDIA Container Runtime.
4-Docker-2. Redémarrer le démon Docker
Redémarrez le démon Docker pour appliquer les modifications:
1
systemctl --user restart docker
4-Docker-3. Configurer le fichier /etc/nvidia-container-runtime/config.toml
avec la commande sudo nvidia-ctk
1
sudo nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
Vérifier que tout est correctement configuré
Exécutez un conteneur CUDA d’exemple.
Si vous utilisez Podman:
1
podman run --rm --device nvidia.com/gpu=all --security-opt=label=disable ubuntu nvidia-smi
Si vous utilisez Docker:
1
docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
Si vous voyez un affichage similaire à celui ci-dessous, c’est un succès:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 555.58.02 Driver Version: 555.58.02 CUDA Version: 12.5 |
|-----------------------------------------+------------------------+----------------------+
| 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 NVIDIA GeForce RTX 3090 Off | 00000000:01:00.0 On | N/A |
| 0% 46C P8 29W / 350W | 460MiB / 24576MiB | 2% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
5. Rédaction du Dockerfile
Nous allons créer un Dockerfile pour notre environnement de développement basé sur les images CUDA et cuDNN fournies par le dépôt nvidia/cuda sur Docker Hub.
- Vous devez choisir une image en fonction des versions de CUDA et cuDNN dont vous avez besoin, ainsi que du type et de la version de distribution Linux.
À la date de rédaction de cet article (fin août 12024), la dernière version de PyTorch (2.4.0) prend en charge CUDA 12.4. Par conséquent, nous utiliserons l’image 12.4.1-cudnn-devel-ubuntu22.04. Vous pouvez vérifier la dernière version de PyTorch et les versions CUDA prises en charge sur le site web de PyTorch.
Le Dockerfile complet est disponible dans le dépôt GitHub yunseo-kim/dl-env-docker. Voici une explication étape par étape de sa création.
5-1. Spécifier l’image de base
1
FROM nvidia/cuda:12.4.1-cudnn-devel-ubuntu22.04
5-2. Installer les utilitaires de base et les prérequis Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Install basic utilities and Python-related packages, gosu, and SSH server
RUN apt-get update -y && apt-get install -y --no-install-recommends \
apt-utils \
curl \
gosu \
openssh-server \
python3 \
python-is-python3 \
python3-pip \
ssh \
tmux \
&& rm -rf /var/lib/apt/lists/* \
# verify that the binary works
&& gosu nobody true
5-3. Configurer le fuseau horaire (dans cet article, ‘Asia/Seoul’)
1
2
3
# Set up time zone
ARG TZ="Asia/Seoul" # If necessary, replace it with a value that works for you.
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
5-4. Configurer le serveur SSH pour l’accès à distance
Pour des raisons de sécurité, configurez SSH pour empêcher la connexion en tant que root:
1
2
3
4
# Set up SSH server
RUN mkdir /var/run/sshd
RUN echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
Créez un utilisateur non-root nommé ‘remote’ pour les connexions SSH:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Create remote user (password can be passed to --build-arg at build time)
#
# This default password is very weak. Make sure to change it to your own unique
# password string!
#
# This Dockerfile assumes that the built image will only be used by yourself or
# a small group of trusted insiders, and if you need to distribute the image
# without exposing sensitive information, using --build-arg is dangerous.
# See the official Docker documentation.
ARG USER_NAME="remote"
ARG USER_PASSWORD="000000"
ARG HOME_DIR="/home/$USER_NAME"
RUN useradd --create-home --home-dir $HOME_DIR --shell /bin/bash $USER_NAME \
&& echo "$USER_NAME:$USER_PASSWORD" | chpasswd
Lors de la construction de l’image Docker avec ce Dockerfile, si aucune option n’est spécifiée, le mot de passe initial du compte ‘remote’ sera 000000. C’est très vulnérable sur le plan de la sécurité, donc utilisez l’option
--build-arg
lors de la construction pour spécifier un mot de passe différent, ou changez-le immédiatement après le premier démarrage du conteneur. Pour une meilleure sécurité, il est recommandé de désactiver la connexion par mot de passe SSH et de n’autoriser que la connexion via des fichiers de clés, voire d’utiliser une clé matérielle comme Yubikey. La configuration du serveur SSH sera abordée dans la prochaine partie de cette série. Pour plus de détails, consultez les documents suivants:
De plus, ce Dockerfile suppose que l’image construite ne sera utilisée que par vous-même ou un petit groupe de personnes de confiance. Si vous devez distribuer l’image à l’extérieur, l’utilisation de
--build-arg
pour définir des mots de passe est dangereuse et d’autres méthodes devraient être utilisées. Consultez ce document pour plus d’informations.
5-5. Installer setuptools, pip et enregistrer la variable d’environnement PATH
1
2
3
4
5
6
7
8
# Switch to remote user
ENV USER_NAME="$USER_NAME"
USER $USER_NAME
WORKDIR $HOME_DIR
# Install pip and ml/dl related packages
RUN python3 -m pip install -U setuptools pip
ENV PATH="$HOME_DIR/.local/bin:$PATH"
5-6. Installer les packages de machine learning et deep learning
1
2
3
4
RUN python3 -m pip install -U \
jupyterlab numpy scipy pandas matplotlib seaborn[stats] scikit-learn tqdm \
&& python3 -m pip install -U torch torchvision torchaudio \
--index-url https://download.pytorch.org/whl/cu124
Si vous souhaitez utiliser Cupy, cuDF, cuML et DALI, ajoutez également ceci au Dockerfile:
1
2
3
RUN python3 -m pip install -U cupy-cuda12x \
&& python3 -m pip install -U --extra-index-url=https://pypi.nvidia.com \
cudf-cu12==24.8.* cuml-cu12==24.8.* nvidia-dali-cuda120
5-7. Créer un répertoire de travail
1
2
3
# Create a workspace directory to locate jupyter notebooks and .py files
ENV WORK_DIR="$HOME_DIR/workspace"
RUN mkdir -p $WORK_DIR
5-8. Exposer les ports et configurer l’ENTRYPOINT
à exécuter au démarrage du conteneur
Exposez les ports 22 et 8888 pour SSH et Jupyter Lab.
Pour exécuter automatiquement le démon SSH au démarrage du conteneur, nous avons besoin des privilèges root, donc nous utiliserons l’approche suivante:
- Le conteneur démarre en tant que root
- Le script
/entrypoint.sh
s’exécute juste après le démarrage - Ce script démarre le service SSH puis utilise
gosu
pour passer au compte remote - Si aucune commande n’est spécifiée lors du lancement du conteneur, Jupyter Lab démarre par défaut avec le compte remote (privilèges non-root)
En général, l’utilisation de
sudo
ousu
dans les conteneurs Docker ou Podman n’est pas recommandée. Si des privilèges root sont nécessaires, il est préférable de démarrer le conteneur en tant que root, d’effectuer les tâches nécessitant ces privilèges, puis de passer à un utilisateur non-root avecgosu
. Les raisons sont expliquées en détail dans les ressources suivantes:
D’abord, ajoutez ce qui suit à la fin de votre Dockerfile:
1
2
3
4
5
6
7
8
9
# Expose SSH and Jupyter Lab ports
EXPOSE 22 8888
# Switch to root
USER root
# Copy the entry point script and grant permission to run it
COPY --chmod=755 entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Ensuite, créez un fichier script nommé entrypoint.sh
dans le même répertoire que votre Dockerfile avec le contenu suivant:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
set -e
# Run SSH daemon in the background
service ssh start
# Move to the workspace directory and run Jupyter Lab
cd "$WORK_DIR"
if [ $# -gt 0 ];then
#su ${USER_NAME} -c "exec $@"
exec gosu ${USER_NAME} $@
else
#su ${USER_NAME} -c "exec jupyter lab --no-browser --autoreload --ip=0.0.0.0 --notebook-dir="${WORK_DIR}""
exec gosu ${USER_NAME} jupyter lab --no-browser --autoreload --ip=0.0.0.0 --notebook-dir="${WORK_DIR}"
fi
6. Construction de l’image Docker et exécution du conteneur
6-1. Construction de l’image
Ouvrez un terminal dans le répertoire contenant votre Dockerfile et exécutez:
1
2
docker build -t dl-env:cuda12.4.1-cudnn9.1.0-ubuntu22.04 -f ./Dockerfile . \
--build-arg USER_PASSWORD=<password>
Remplacez <password> par le mot de passe que vous souhaitez utiliser pour la connexion SSH.
6-2. Exécution d’une charge de travail d’exemple
Une fois la construction terminée, exécutez un conteneur jetable pour vérifier que tout fonctionne correctement.
Pour Podman:
1
2
3
podman run -itd --rm --name test-container --device nvidia.com/gpu=all \
--security-opt=label=disable -p 22:22 -p 88:8888 \
dl-env:cuda12.4.1-cudnn9.1.0-ubuntu22.04
Pour Docker:
1
2
3
docker run -itd --rm --name test-container \
--gpus all -p 22:22 -p 88:8888 \
dl-env:cuda12.4.1-cudnn9.1.0-ubuntu22.04
Cette commande exécute un conteneur nommé test-container
à partir de l’image dl-env:cuda12.4.1-cudnn9.1.0-ubuntu22.04
que vous venez de construire, en mappant le port 22 du conteneur au port 22 de l’hôte et le port 8888 du conteneur au port 88 de l’hôte. Si l’image Docker a été correctement construite et que le conteneur a démarré sans problème, JupyterLab devrait s’exécuter à l’adresse http:127.0.0.1:8888
à l’intérieur du conteneur. Vous devriez donc pouvoir ouvrir un navigateur sur le système hôte et accéder à http://127.0.0.1:88, qui se connectera à l’adresse http://127.0.0.1:8888
à l’intérieur du conteneur, affichant un écran similaire à celui-ci:
Ouvrez un terminal sur le système hôte et exécutez la commande ssh [email protected]
pour vous connecter à distance au compte remote du système Ubuntu exécuté dans le conteneur.
Lors de la première connexion, vous recevrez un avertissement indiquant que l’authenticité de l’hôte ne peut pas être établie et on vous demandera si vous souhaitez continuer la connexion. Tapez “yes” pour continuer.
Ensuite, entrez le mot de passe (si vous ne l’avez pas changé lors de la construction de l’image, ce sera la valeur par défaut “000000”).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ssh [email protected]
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ED25519 key fingerprint is {empreinte digitale (chaque clé a une valeur unique)}.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '127.0.0.1' (ED25519) to the list of known hosts.
[email protected]'s password:
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 6.12.11-200.fc41.x86_64 x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Si vous voyez un affichage similaire, vous avez réussi à vous connecter via SSH. Pour terminer la session, tapez la commande exit
.
6-3. (facultatif) Pousser l’image vers Docker Hub
Pour pouvoir utiliser votre image d’environnement de développement à tout moment en la récupérant, il est bon de la pousser vers Docker Hub.
Pour pousser votre image vers Docker Hub, vous avez besoin d’un compte Docker. Si vous n’en avez pas encore, inscrivez-vous d’abord sur https://app.docker.com/signup.
6-3-1. Connexion à Docker Hub
Pour Podman
1
podman login docker.io
Pour Docker
1
docker login
6-3-2. Attribution d’un tag à l’image
Remplacez <dockerhub_username>
, <repository_name>
et (optionnel) :TAG
par vos propres informations.
Par exemple: “yunseokim”, “dl-env”, “rapids-cuda12.4.1-cudnn9.1.0-ubuntu22.04”
Pour Podman
1
podman tag IMAGE_ID docker.io/<dockerhub_username>/<repository_name>[:TAG]
Pour Docker
1
docker tag IMAGE_ID <dockerhub_username>/<repository_name>[:TAG]
6-3-3. Pousser l’image
Enfin, exécutez la commande suivante pour pousser l’image vers Docker Hub:
Pour Podman
1
podman push docker.io/<dockerhub_username>/<repository_name>[:TAG]
Pour Docker
1
docker push <dockerhub_username>/<repository_name>[:TAG]
Vous pouvez vérifier que l’image a bien été poussée sur https://hub.docker.com/, comme illustré ci-dessous:
L’image complétée à travers ce processus est disponible publiquement dans le dépôt yunseokim/dl-env sur Docker Hub, et peut être utilisée librement par quiconque.
Pour récupérer l’image, il suffit de remplacer push
par pull
dans la commande utilisée précédemment pour la pousser.