Page suivante Page précédente Table des matières
2. CHAOS deuxième partie: Préparer les outils système
Par Alex Vrenios
2.1 Introduction
Mon premier article, "CHAOS: CHeap Array of Obsolete Systems" (voir Linux Gazette 30, Juillet 1998), décrivait l'enchaînement d'imprévus qui m'a conduit à construire un petit réseau de PCs obsolètes tournant sous Linux (Red Hat). Suite à cet article, de nombreux lecteurs m'ont contacté pour me demander la tournure que cela prenait et si il y aurait un autre article sur ce thème: le voici !
Quelques PCs, un système d'exploitation et un peu de matériel réseau constituent la base indispensable à la réalisation du type de systèmes que je désire bâtir, mais cette base n'est pas suffisante pour autant. Un peu d'administration, quelques scripts de commandes et autres petits utilitaires vont permettre d'en faire ce que je veux obtenir: un système distribué.
Les algorithmes distribués ne sont souvent que plusieurs copies identiques du même programme s'exécutant sur différentes machines du même réseau. Je peux bien sûr écrire et débugger un exemplaire d'un programme sur mon gros 486, nommé Omission, mais ce n'est que la première étape. Debugger le produit final, celui qui s'exécute sur sept machines simultanément, demande de pouvoir démarrer à distance un processus sur chacune des machines, regarder comment il s'exécute, puis le tuer si nécessaire, en centralisant les fichiers de trace en un endroit unique pour me permettre de comprendre ce qui n'a pas fonctionné.
Cet article décrit ce que j'ai ajouté à mon système pour permettre tout cela.
2.2 Administration système
J'ai travaillé avec de nombreux réseaux Unix par le passé. Je n'ai jamais envisagé d'utiliser la "commande en ligne à distance", rsh, pour me connecter à une autre machine du réseau et accéder à ses données locales. Jusqu'à ce que je décide de l'utiliser sur mon propre réseau.
Depuis Omission, je vois trois façons de me connecter à l'un des six autres 386. La première est d'utiliser telnet, qui demande un login et un mot de passe. La deuxième est d'utiliser rlogin, qui me demande un nom d'utilisateur, mais pas de mot de passe, pour peu que les systèmes de fichiers soient convenablement installés. La troisième, rsh, me laisse changer de machine sans même me demander de nom d'utilisateur, si tant est que l'on ait tout configuré comme il faut. D'après moi, tout configurer comme il faut rélève cependant de la magie noire.
Je savais que mon répertoire presonnel (home), /home/alex, avait besoin d'un fichier .rhosts contenant mon nom d'utilisateur: une unique ligne "alex". Je savais aussi que le fichier /etc/hosts.equiv jouait un rôle important, mais je ne savais pas exactement lequel. J'ai donc commencé à me documenter et à me poser beaucoup de questions. La plupart des articles sur le sujet s'intéressaient plus à "comment empêcher les autres de rentrer", que "comment les acueillir".
Je ne suis pas du genre à renier l'utilisation de méthodes que je qualifierais de "force brute" pour résoudre certains problèmes. Je suis prêt à parier qu'un administrateur système compétent qui lira cet article pourra être atterré par ma méthode, mais elle fonctionne, et je m'en suis contenté.
Mon nom de domaine, comme les lecteurs du premier article s'en souviennent peut-être, est "chaos.org" et mes sept 386 portent les noms des sept péchês capitaux. L'utilisateur alex a un répertoire personnel sur Omission, qui est monté par NFS sur chacune des sept autres machines. Les fichiers /home/alex/.rhosts et /etc/hosts.equiv ont exactement le même contenu, à savoir:
omission.chaos.org alex greed.chaos.org alex lust.chaos.org alex anger.chaos.org alex pride.chaos.org alex gluttony.chaos.org alex envy.chaos.org alex sloth.chaos.org alex
Je ne suis plus très sûr de l'endroit où j'ai trouvé le modèle de cette configuration, mais ça fonctionne, ce qui me suffit pour le moment.
Je voulais aussi assurer un minimum de synchronisation entre les horloges des différents systèmes. J'ai donc ajouté une commande lors du démarrage de chaque machine qui règle son horloge sur celle d'Omission. Voici ce qu'on trouve dans le fichier rc.local de chacun des sept 386:
# reset date and time from server date `rsh omission "date +%m%d%H%M"`
Je démarre toujours Omission en premier et attend la fin de son initialisation avant de démarrer les autres, car il partage le répertoire /home que doivent monter les autres machines. La précision de la synchronisation des horloges est inférieure à la minute.
2.3 Système de fichiers distribué
Il n'existe qu'un exemplaire de /home/alex/.rhosts, mais chaque système dispose de sa propre copie de /etc/hosts.equiv. Maintenir huit copies identiques de quelque chose n'est jamais plaisant, surtout lorsqu'on procède à de subtils changements supposés résoudre votre problème si toutes les copies sont cohérentes.
Une première solution est de copier le fichier en question sur une disquette pour le charger sur chacune des machines, mais cela demande trop d'efforts. Les plus sophistiqués pourront créer une partition spéciale pour ce type de fichiers sur la machine principale et la monter depuis chacune des autres machines. Comme je suis à la fois l'administrateur système et la communauté d'utilisateur à moi tout seul, j'en ai profité pour entremêler les deux.
J'ai créé un répertoire /home/alex/root, appartenant à root, et copié tous les fichiers de configuration que je voulais partager dans ce répertoire. Comme /home/alex est monté par toutes les machines, je peux aisément modifier ces fichiers en une seule fois. J'ai mis dans ce répertoire /etc/hosts, rc.local et tous les scripts que l'administrateur est susceptible d'utiliser sur l'une ou l'autre des machines.
2.4 Utilitaires et scripts d'administration
Je peux avoir besoin de régler l'horloge manuellement, j'ai donc repris la commande vue plus haut dans un script "settime":
#!/bin/csh -f # # settime - synchronise l'horloge avec celle d'omission # date `rsh omission "date +%m%d%H%M"`
Je peux avoir besoin de suivre l'exécution de tests assez long, et comme je suis craintif, je peux vouloir visualiser les performances globales du système. Voici mon script "ruptime" :
#!/bin/csh -f # # ruptime - affiche les performances du système # cat /etc/hosts \ | grep -v localhost \ | awk '{ print $3": ";system("rsh "$3" uptime") }'
Il affiche la charge de chacune de mes machines. Je l'utilise comme un indicateur global de performances. Le mot charge désigne le nombre de processus dans la file "prête", en attente du processeur. Le processeur est en général occupé par l'exécution de la tâche active. Les trois nombres qu'affiche uptime, sont les charges moyennes sur 1, 5 et 15 minutes (voir la page du manuel pour plus d'informations: man uptime). Si je découvre ce qui peut être un problème, par exemple un triplet de zéros, je peux lancer d'autres commandes pour obtenir plus d'informations.
La commande ps affiche l'état de tous les processus du système. L'utilisation de grep permet ensuite de limiter l'affichage pour n'obtenir que ceux que je désire voir apparaître. Voici donc le script rps:
#!/bin/csh -f # # rps - remote process status # ps -aux | grep alex \ | grep -v rps \ | grep -v aux \ | sed -e "s/alex\ \ \ \ \ /`hostname -s`/" \ | grep -v sed \ | grep -v hostname \ | grep -v grep
Ci-dessus, la commande sed permet de remplacer mon nom d'utilisateur par le nom de la machine qui exécute le processus. J'utilise alors ce script pour connaître l'état des processus d'une machine donnée:
omission:/home/alex> rsh pride rps pride 218 0.4 7.0 1156 820 1 S 13:34 0:02 /bin/login -- alex pride 240 0.7 6.6 1296 776 1 S 13:37 0:01 -csh pride 309 0.3 1.8 856 212 1 S 13:41 0:00 ser pride 341 0.0 4.4 1188 524 ? R 13:41 0:00 /bin/sh /home/alex/bin
Les lecteurs attentifs auront remarqué que le script ruptime affiche la charge de toutes les machines du réseau, alors que rps ne s'applique qu'à une seule machine. Une seconde version de rps est donc utilisée: le script rpsm, qui fait appel à rstart et psm.
#!/bin/csh -f # # rpsm - remote process status for my userid # rstart psm
Le program rstart.c accepte en argument le nom d'un exécutable:
#include <stdio.h> #include <chaos.h> /* une liste de toutes les machines de chaos.org */ main(argc, argv) char *argv[]; int argc; /* ** rstart.c - lance le processus nommé argv[1] sur chacune des machines */ { int i, j, pids[NUM]; char command[64]; /* ** demande au moins deux arguments */ if(argc < 2) { printf("\n\tUsage: %s <process> [<parameters>]\n\n", argv[0]); exit(-1); } close(0); /* évite des problèmes avec stdin si on tourne en tâche de fond */ /* ** initialise le nom du processus distant */ strcpy(command, argv[1]); if(command[0] != '/') /* ajoute le chemin si nécessaire */ sprintf(command, "%s%s", Bin, argv[1]); /* ** ajoute les autres arguments */ for(i=2; i<argc; i++) { strcat(command, " "); /* ajoute un espace */ strcat(command, argv[i]); /* ajoute un paramètre */ } /* ** lance les processus distants */ for(i=0; i<NUM; i++) { if(i) /* petite pause entre chaque lancement */ sleep(1); if((pids[i] = fork()) == 0) { if(execl("/usr/bin/rsh", "rsh", Hosts[i], command, NULL) == -1) { perror("execl()"); exit(-1); } } } /* ** attend la fin de chacun des processus */ for(i=1; i<NUM; i++) waitpid(pids[i]); return(0); }
Le script rpsm ci-dessus utilise rstart, qui utilise psm, que voici:
#include <string.h> #include <stdio.h> main() /* ** psm.c - affiche l'état de mes processus */ { FILE *fp; int len, pid1, pid2; char host[32], *p; char line[128]; /* nom de notre hote ? */ gethostname(host, sizeof(host)); if((p = strchr(host, '.')) != NULL) *p = '\0'; /* coupe nom de domaine */ len = strlen(host); /* le pid de notre proc */ pid1 = getpid(); /* liste de l'état de tous les processus */ fp = popen("ps -aux", "r"); while(fgets(line, sizeof(line), fp) != NULL) { if(strstr(line, "alex ") == NULL) continue; /* pas à nous */ if(strstr(line, "psm") != NULL) continue; /* ne pas s'auto-afficher */ sscanf(line, "%*s %d", &pid2); if(pid2 >= pid1) continue; /* oublie les plus grands */ /* remplace utilisateur par hote */ strncpy(line, host, len); printf("%s", line); } return(0); }
Voici un exemple:
> rpsm pride 218 0.0 7.0 1156 820 1 S 13:34 0:02 /bin/login -- alex pride 240 0.0 6.6 1296 776 1 S 13:37 0:01 -csh pride 309 0.0 1.8 856 212 1 S 13:41 0:00 ser pride 487 38.3 5.4 1240 636 ? S 14:17 0:01 csh -c /home/alex/bin greed 222 35.8 7.3 1240 636 ? S 14:17 0:01 csh -c /home/alex/bin . . . sloth 201 36.5 7.1 1240 636 ? S 14:17 0:01 csh -c /home/alex/bin
Le principe du programme rstart peut être étendu pour obtenir bien plus que l'état des processus. J'ai créé des paires de programmes et de scripts pour obtenir le fichier trace et le fichier log d'une machine. Je peux aussi tuer un processus distant en d'après son nom en appelant le programme suivant (k.c) avec rstart:
#include <string.h> #include <stdio.h> main(argc, argv) int argc; char *argv[]; /* ** k.c - tuer le processus nommé xxx */ { FILE *fp; int pid1, pid2; char line[128]; char shell[32]; char host[32]; char proc[16]; if(argc < 2 || argc > 3) { printf("\tUsage: k <process_name> [noconf]\n\n"); exit(-1); } /* obtienir le nom du processus */ sprintf(proc, "%s ", argv[1]); /* ajoute espace */ sprintf(shell, "-c k %s", proc); /* notre parent */ pid1 = getpid(); /* hote = ? utile pour affichage */ gethostname(host, sizeof(host)); /* liste de l'état des processus */ fp = popen("ps -aux", "r"); while(fgets(line, sizeof(line), fp) != NULL) { if(strstr(line, "alex ") == NULL) continue; /* pas nous */ if(strstr(line, shell) != NULL) continue; /* évite shell */ if(strstr(line, proc) == NULL) continue; /* doit correspondre */ sscanf(line, "%*s %d", &pid2); if(pid2 >= pid1) continue; /* évite les plus grands */ /* tue le processus */ system(line); sprintf(line, "kill -9 %d", pid2); if(argc != 3) printf("%s: %s\n", host, line); } return(0); }
Tous les programmes ci-dessus ont été copiés et collés dans cet article à partir du code source "testé et approuvé", mais j'ai supprimé des lignes blanches et modifié quelque peu la présentation pour les rendre plus lisibles. Pardonnez-moi par avance des éventuelles difficultés que vous pourrez rencontrer en les utilisant. Sachez d'autre part que je ne suis responsable en aucune sorte de l'utilisation que vous en ferez, agissez-donc à vos risques et périls.
2.5 Conclusion
Je crois que je suis maintenant prêt à développer des logiciels conformément à mon idée de départ. J'espère que quelques-unes de mes solutions pourront vous aider, au cas ou vous décideriez d'essayer tout ceci par vous-même.
La prochaine étape sera de développer un programme de contrôle centralisé des tâches, s'exécutant sur Omission, qui pourra afficher en temps-réel l'état et le comportement du système de processus distribués s'exécutant sur les autres machines. Je voudrais être capable de diriger le système en envoyant des requêtes à l'un des processus sur l'une quelconque des machines, avant d'observer comment les différents processus interagissent pour formuler leur réponse. Chaque processus distant devra interagir avec un "agent", un processus local, qui s'exécutera en parallèle. Chaque agent enverra en retour des messages au programme de contrôle central, lui indiquant l'état courant de cette partie du système. Lequel programme pourra combiner les différentes réponses pour présenter une synthèse de l'état du système distribué. Si le sujet vous intéresse, retez à l'écoute.
Ce projet m'a beaucoup appris. Je suis fier de ce que j'ai bâti et j'espère que ces outils très simples encouragerons certains d'entre vous à tenter l'expérience, avec peut-être trois ou quatre machines, ou même plus que les huit que j'ai réunies. Les réseaux "à la maison" sont à la mode, en ce moment, et développer des logiciels qui tirent parti des avantages d'un réseau ne peut qu'en découler. Essayez-donc si vous l'osez et préparez-vous à entrer dans le futur.
Traduction française Nicolas Chauvat <nico@caesium.fr>
Page suivante Page précédente Table des matières