Vous arrivez sur un cluster de calcul. C'est nouveau (pour vous hein ! La plate-forme elle, n'a pas été mise à jour depuis 15 ans, elle en a donc vu d'autres...), mais ce n'est pas une raison pour y mettre le même bordel que celui qui règne dans votre ordinateur sous Windaube.

Voici un tuto complet sur un usage raisonné et raisonnable des ressources mises à votre disposition.

PS : Il ne s'agit pas d'un tuto sur le bruteforcing de clé WPA sur un cluster de calcul.

Sommaire

Règles de base

Connexion

Comme pour tous les serveurs, la connexion se fait via le protocole SSH. Ce protocole de connexion peut imposer un échange de clés de chiffrement en début de connexion empêchant l'espionnage des communications (chiffrement asymétrique). (Oui, c'est paradoxal : vous chiffrez vos scripts tout pourris et pas vos mails).

Vous devez donc générer votre couple de clés privée/publique ; Par défaut, l'outil ssh-keygen créera une paire de clés RSA de 2048 bits, parfaite pour la plupart des utilisations.

MValls@kali:/home/MValls/.ssh$ ssh-keygen -f ~/.ssh/id_rsa_mon_cluster
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Your identification has been saved with the new passphrase.
  • -t : spécifie le type d'algorithme utilisé
  • -f : spécifie le nom et le chemin des clés générées

PS 1 : Donnez une passphrase à vos clés ! Ne pas mettre de passphrase sur des clés d'identification est stupide et dangereux (autant ne pas en utiliser) !

PS 2 : Changement de mot de passe sur une clé ssh :

ssh-keygen -p -f ~/.ssh/id_rsa_mon_cluster

PS 3 : Il est possible que le cluster refuse les clés DSA (SSH2) pourtant plus sûres que les clés RSA (SSH1)*(cf note plus bas); Dans ce cas faites :

ssh-keygen -t rsa -f ~/.ssh/id_rsa_mon_cluster

À propos des clés DSA et RSA

Lors de la rédaction de cet article en 2016, les clés DSA étaient supposées plus sûres. Il s'avère que c'est toujours le cas par rapport aux clés RSA de tailles identiques. Or même si la norme DSA semble autoriser des clés dépassant cette robustesse (FIPS 186-3 vs FIPS 186-2 ?), ssh-keygen ne le permet pas et fige la taille de la clé à 1024 bits. Dès 2013 cette robustesse a été jugée trop faible et en passe d'être cassable.

Les autres algorithmes ECDSA et Ed25519 (dans une moindre mesure), sont suspectés d'être backdoorés par la NSA.

La robustesse de ECDSA, Ed25519 et DSA est de plus dépendante de la fiabilité du générateur de nombres aléatoires de la machine ; chose que vous n'avez aucun moyen d'auditer simplement.

Par conséquent l’utilisation des clés DSA a été dépréciée depuis OpenSSH 7 pour cause de faille de sécurité. ssh-keygen génère dorénavant une paire de clés RSA (SSH2) de 2048 bits.

Voir stackexchange - rsa-vs-dsa-for-ssh-authentication-keys.

Envoi de la clé publique

L'utilitaire ssh-keygen génère 2 fichiers :

 ~/.ssh/id_rsa_mon_cluster # clé privée SECRETE
 ~/.ssh/id_rsa_mon_cluster.pub # clé publique PUBLIQUE

La clé publique est celle que vous devez partager/renseigner sur l'interface web de votre service. Copiez TOUT le retour de cette commande :

cat ~/.ssh/id_rsa_mon_cluster.pub

Si le service n'est pas doté d'une interface en ligne, envoyez la clé via cette commande :

ssh-copy-id -i ~/.ssh/id_rsa_mon_cluster <username>@<hostname>

La clé sera ajoutée dans une entrée du fichier distant ~/.ssh/authorized_keys.

Connexion et usage de la clé SSH

La connexion proprement dite se fait comme ceci :

ssh -i ~/.ssh/id_rsa_mon_cluster login@mon.cluster.org
  • -i : Spécifie la clé à utiliser au cas où vous en auriez plusieurs

Envoi de fichiers

Renseignez vous sur la localisation de l'espace disque qui vous est réservé. En général vous avez au moins un dossier dédié aux calculs et un dossier dédié au stockage ! Ça parait anodin mais l'un supporte de nombreuses opérations d'I/O, tandis que l'autre offre un débit de transfert digne d'une disquette 3.5" ! En bref, faites attention à l'endroit où vous mettez vos cochonneries !

Ex: /home/genouest/<your-group>/<your-login> (backup) vs /omaha-beach/<your-login> (rapidité ++)


L'envoi se fait selon le protocole SCP.

scp -p -i ~/.ssh/id_rsa_mon_cluster login@mon.cluster.org:/home/genouest/<your-group>/<your-login>/scripts_R_degueulasses/20150420_results.tar.bz2 20150420_results.tar.bz2

Concrètement, au centre vous avez le serveur sur lequel vous vous connectez : login@mon.cluster.org; À sa droite immédiate vous avez les répertoires distants; À l'extrême droite vous mettez le répertoire local (où vos fichiers doivent arriver); À l'extrême gauche vous mettez le répertoire local (à partir duquel vos fichiers seront envoyés).

Résumé:

scp <départ> login@mon.cluster.org:<distant> <arrivée>

Oui, la commande est longue ! Voir le paragraphe suivant.


Note: La commande scp risque d'échouer si votre ~/.bashrc sur le cluster essaie d'initialiser des environnements automatiquement alors que c'est interdit sur le serveur frontal.

Afin d'éviter ce désagrément, veillez à faire apparaitre les lignes suivantes en haut de votre ~/.bashrc:

# If not running interactively, don't do anything
[[ $- == *i* ]] || return

Une alternative serait de déplacer les lignes problématiques dans le fichier ~/.bash_profileSource: Stackoverflow

Simplifiez vous la vie !

Faites des alias dans votre ~/.bashrc !

Ex:

alias 'cluster'='ssh -i ~/.ssh/id_rsa_mon_cluster login@mon.cluster.org'

Utilisez un agent ssh pour stocker la passphrase en mémoire, et éviter de l'entrer 10 fois par heure :

$ ssh-add ~/.ssh/id_rsa_mon_cluster
Identity added: /home/MValls/.ssh/id_rsa_mon_cluster (/home/MValls/.ssh/id_rsa_mon_cluster)

En cas d'erreur Could not open a connection to your authentication agent.:

$ eval `ssh-agent -s`
$ #ou
$ eval $(ssh-agent)

Explications :

The idea of this problem is that ssh-add needs SSH_AUTH_SOCK and SSH_AGENT_PID environment variables to be set with current ssh-agent sock file path and pid number.Source: Stackoverflow. (Le socket se trouve dans un dossier du type /tmp/ssh-kjmxRb2764/agent.2764.)

Les nœuds et les queues

Lorsque vous vous connectez à un serveur de type cluster vous êtes accueillis par un serveur frontal. Ce serveur n'est pas dédié aux calculs; il permet juste aux utilisateurs de rejoindre un nœud de la grappe ! Si vous balancez vos tâches sur le frontal, la DSI va vite vous envoyer des mots d'amour !

Le cluster sur lequel je prends exemple utilise l'outil Sun Grid Engine (SGE) (Encore un de ces nombreux outils développés par Sun et phagocytés par l'ogre Oracle).

Rejoignez donc instantanément un nœud :

qrsh

Note : Exécuter qrsh ou qsub sur une tâche, vous libère du frontal.

  • -l h=<nom_du_noeud> : Rejoindre un nœud particulier.

Lister les nœuds, leurs caractéristiques et utilisations

Listons les nœuds avec qhost:

$ qhost
HOSTNAME                ARCH         NCPU NSOC NCOR NTHR  LOAD  MEMTOT  MEMUSE  SWAPTO  SWAPUS
----------------------------------------------------------------------------------------------
global                  -               -    -    -    -     -       -       -       -       -
cl1n022                 lx-amd64       80    4   40   80 272.3  504.7G   26.6G    4.0G   23.9M
cl1n023                 lx-amd64       80    4   40   80 331.8  504.7G   33.2G    4.0G   33.1M
cl1n027                 lx-amd64       40    2   20   40  3.69  252.0G   48.7G   15.6G   34.7M
cl1n028                 lx-amd64       40    2   20   40 11.67  252.0G    5.9G   15.6G  169.2M
cl1n030                 lx-amd64       56    2   28   56 72.56  757.0G   30.1G    6.8G    8.3M
cl1n031                 lx-amd64       48    2   24   48 42.70  504.5G   12.4G   11.7G   52.7M
cl1n032                 lx-amd64       48    2   24   48 102.9  504.5G   30.3G   11.7G   16.9M

Ici, le nœud cl1n030 possède 757Go de mémoire (dont 30.1Go utilisés), 28 processeurs physiques "donc" 56 coeurs virtuels. Sa charge CPU LOAD est de 72.56.

Comment interpréter la charge d'un processeur ?

La charge représente le nombre de processus en train d'utiliser ou en train d'attendre le processeur plus, sous la majorité des systèmes, le nombre de processus bloqués.
Source : Wikipédia.*

Par conséquent sur notre nœud une charge supérieure à 56 peut être considérée comme problématique. Toutefois, une charge élevée ne veut pas forcément dire que le processeur est occupé à 100%; il peut tout à fait attendre des processus faisant gérant beaucoup d'entrées/sorties bloquantes (accès disque/requêtes sur le réseau).

Pour savoir vraiment ce qu'il en est, la commande top peut être utilisée.


Utilisation de la commande top.

$ top
top - 17:02:32 up 96 days, 23:23,  0 users,  load average: 81.70, 79.39, 77.59
Tasks: 1119 total,  10 running, 1109 sleeping,   0 stopped,   0 zombie
Cpu(s): 93.1%us,  1.8%sy,  0.0%ni,  5.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

Signification des abréviations :

us: user cpu time (or) % CPU time spent in user space
sy: system cpu time (or) % CPU time spent in kernel space
ni: user nice cpu time (or) % CPU time spent on low priority processes
id: idle cpu time (or) % CPU time spent idle
wa: io wait cpu time (or) % CPU time spent in wait (on disk)
hi: hardware irq (or) % CPU time spent servicing/handling hardware interrupts
si: software irq (or) % CPU time spent servicing/handling software interrupts
st: steal time - - % CPU time in involuntary wait by virtual cpu while hypervisor is servicing another processor (or) % CPU time stolen from a virtual machine

Voir les explications sur la signification des temps user/sys: optimiser son code python.

Signification des status des processus (extrait de man ps) :

D    uninterruptible sleep (usually IO)
R    running or runnable (on run queue)
S    interruptible sleep (waiting for an event to complete)
T    stopped by job control signal
t    stopped by debugger during the tracing
W    paging (not valid since the 2.6.xx kernel)
X    dead (should never be seen)
Z    defunct ("zombie") process, terminated but not reaped by its parent

Source: Stackexchange.

Listons les tâches en cours sur chaque nœud qhost -u '*' ou qstat -u '*':

$ qhost -u '*'
HOSTNAME                ARCH         NCPU NSOC NCOR NTHR  LOAD  MEMTOT  MEMUSE  SWAPTO  SWAPUS
----------------------------------------------------------------------------------------------
    job-ID  prior   name       user         state submit/start at     queue      master ja-task-ID
    ----------------------------------------------------------------------------------------------
cl1n030                 lx-amd64       56    2   28   56 76.97  757.0G   26.3G    6.8G    8.3M
    5314480 0.50034 evilDocker mvalls       r     01/26/2017 18:38:08 all.q@cl1n MASTER
    5314482 0.50034 useleStask esnowden     r     01/26/2017 18:38:53 all.q@cl1n MASTER
    5315793 0.61000 wallBuild  dtrump       r     01/31/2017 11:31:19 all.q@cl1n MASTER

Ici, 3 utilisateurs (mvalls, esnowden, dtrump) se partagent le noeud cl1n016; leurs tâches respectives se nomment 5314480, 5314482, 5315793; elles ont pour priorité 0.50034, 0.50034 et 0.61000.

On remarque que la tâche de dtrump a une priorité supérieure; cela signifie que les ressources lui sont allouées en priorité (les autres tâches sont probablement en suspens).

Lister les queues

Les queues :

  • sont des files où les jobs s'exécutent, pas là où ils attendent,
  • regroupent plusieurs hosts => des grappes,
  • fournissent des slots; en général 1 slot = 1 coeur de CPU,
  • peuvent être explicitement sélectionnées ou non (auquel cas la queue par défaut sera utilisée).

Source : Resource and queues.

$ qconf -sql
all.q
galaxy.q
web

Afficher les caractéristiques d'une queue :

$ qconf -sq all.q
qname                 all.q
pe_list               make mpi
slots                 1,[cl1n023.genouest.org=80],[cl1n022.genouest.org=80]...

Lister les environnements parallèles

Les environnements parallèles sont des environnements de programmation adaptés aux clusters de calcul. On peut citer Message Passing Interface (MPI) pour les machines à mémoire distribuée, et OpenMP pour les machines à mémoire partagée.

Source : Environnements parallèles.

$ qconf -spl
make
mpi

La commande suivante affichera plus d'information pour chaque environnement (dont le nombre de slots disponibles) :

$ qconf -sp make
pe_name            make
slots              150
user_lists         NONE
xuser_lists        NONE
start_proc_args    /data1/sge/mpi/startmpi.sh $pe_hostfile
stop_proc_args     /data1/sge/mpi/stopmpi.sh
allocation_rule    $pe_slots
control_slaves     TRUE
job_is_first_task  FALSE
urgency_slots      min
accounting_summary TRUE

Utiliser un environnement parallèle se fait via le paramètre -pe make <nb_cpu> de qsub vu plus loin.

PS:

  • Multicores jobs : Jobs using several cores on a single machine
  • Parallel Jobs : Jobs composed of cooperating tasks that must all be executed at the same time, often with requirements about how the tasks are distributed across the resources. Note that not just any program can run in parallel, it must be programmed as such and compiled against a particular MPI library.

Les tâches

Charger un environnement de travail

Pour utiliser le moindre outil, le moindre interpréteur Python, vous devez charger l'environnement pré-configuré sur le serveur. Là encore, consultez la doc de la plate-forme. C'est cet environnement que vous devrez mentionner dans votre script (cf plus bas).

Note : Le système d'environnement n'est pas universel; renseignez-vous !

Exemple de localisation des envs :

/softs/local/env/env*

Exemple du chargement d'un environnement dinopython :

.  /softs/local/env/envpython-2.7.sh # bash syntax

Le script

Les tâches sont configurées/encapsulées dans un script .sh. Voici sa syntaxe. Vous y remarquerez un en-tête avec des directives débutant par #$ (préfixe modifiable via l'option -C prefix); il s'agit de la configuration de la tâche. Les paramètres sont décrits ci-après. Notez que ces paramètres peuvent être spécifiés dans un script comme ci-dessous ou directement après la commande qsub décrite plus bas.

#! /bin/bash
#$ -S /bin/bash
#$ -M <votre-adresse>@mail.fr
#$ -N nom_du_script
#$ -m bea

# Initialisation environnement
. /local/env/envncbi.sh
# Le calcul proprement dit
blastall -p blastx -d genbank -i seq.fasta

Paramètres :

  • -S : Le shell à utiliser
  • -M : Adresse mail
  • -m b|e|a|s|n : Expédition d'un mail
    • au début (b),
    • à la fin (e),
    • à l’arrêt (a),
    • à la suspension (s),
    • pas du tout (n),
    • à la folie.
  • -N : Nom de la tâche
  • -cwd :Lancement du script dans le répertoire courant
  • -i : Fichier d’entrée de la tâche
  • -j y[es]|n[o] : Fusion de la sortie d’erreur avec la sortie standard
  • -o : Fichier de sortie de la tâche (cf -j)
  • -verify : Contrôle la tâche qui sera soumise


  • -pe make <nb_cpu> : Chargement d'un environnement parallèle en spécifiant le nombre de CPU requis/demandés.
  • -q <file_attente> : Soumission en spécifiant le nom de la file d'attente censée exécuter le script.

Options de -l (à séparer par une , :

  • h=<nom_du_noeud> : Rejoindre un nœud particulier.
  • h_vmem=10G : Maximum de mémoire allouée à la tâche par slot demandé (le programme sera tué en cas de dépassement).
  • mem_free=5G : Demander 5Go de mémoire immédiatement.

Note : Exécuter qrsh ou qsub sur une tâche, vous libère du frontal. Inutile donc d'appeler qrsh dans le script.

Cas particulier des environnements virtuels Python

Pour charger un environnement virtuel Python (vous devriez toujours en utiliser pour assurer entre autres la reproductibilité de votre travail et vous éviter des prises de têtes), (voir Environnements virtuels Python), vous devrez inclure ces lignes dans le script avant d'appeler votre programme :

# Chargement de python 2
.  /softs/local/env/envpython-2.7.sh

WORKON_HOME=~/.virtualenvs
source ~/.local/bin/virtualenvwrapper.sh

workon my_virtual_env

my_command

Soumission d'une tâche

$ qsub script.sh
your job <jobid> (script.sh) has been submitted

Suppression d'une tâche

qdel <identifiant_tache>
  • -u <login> : Suppression de toutes les tâches d'un utilisateur

Obtenir des informations sur les tâches lancées

$ qstat -j <jobid>
==============================================================
job_number:                 5314477
exec_file:                  job_scripts/5314477
submission_time:            Thu Jan 26 18:37:06 2017
owner:                      dtrump
sge_o_home:                 /home/dtrump
sge_o_log_name:             dtrump
sge_o_path:                 /local/python/2.7/bin:/usr/local/sge/bin/lx-amd64:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin
sge_o_shell:                /bin/bash
sge_o_workdir:              /home/dtrump
sge_o_host:                 cl1n017
account:                    sge
cwd:                        /home/dtrump
merge:                      y
mail_options:               abes
mail_list:                  donald.trump@elysee.fr
notify:                     FALSE
job_name:                   wallBuild
jobshare:                   0
shell_list:                 NONE:/bin/bash
env_list:
script_file:                script_cluster_temp.sh
usage    1:                 cpu=22:01:25:18, mem=9951300.14892 GBs, io=0.00743, vmem=17.301G, maxvmem=18.127G

Paramètres :

  • -u <login> : Affichage des tâches lancées par un utilisateur.
  • -j job_identifier_list : Affichage de la totalité des informations relatives à une tâche.

À vérifier:

  • job_number : Identifiant unique affiché lors de la confirmation de la soumission,
  • job_name : Nom du script de la tâche,
  • owner: Propriétaire de la tâche,
  • État de la tâche (r = running, etc.),
  • submission_time : Heure de soumission,
  • Nom de la file faisant tourner la tâche,

Obtenir des informations sur les tâches arrêtées

$ qacct -j <jobid>
hostname     cl1n024
exit_status  137
cpu          1067.180
mem          543.745
io           0.007
iow          0.000
maxvmem      11.137G

À vérifier :

  • maxvmem < limite spécifiée
  • exit_status != 137 (SIGKILL)
  • hostname : état du noeud

Références