Boot NFS

Pour diminuer au maximum les sources de bruit de Mimosa, je n'utilise plus de disque dur, ni clés USB, ni flash. Il boote sur le réseau en PXE en récupérant une image d'une Debian customisée pour démarrer directement l'interface de Mythtv directement.

Le processus de démarrage d'un ordinateur sur le réseau est le suivant :

  1. au cours du boot du bios, celui ci envoie une requête DHCP;
  2. le serveur DHCP lui fournit une adresse IP, masque + passerelle ainsi que les options BOOTP contenant le nom du fichier d'amorce ainsi que l'adresse d'un serveur sur lequel le récupérer;
  3. le bios le télécharge alors via le protocole TFTP (Trivial FTP) puis exécute ce programme d'amorçage qui lit un fichier de description de menu pour choisir l'image Linux à démarrer;
  4. Le noyau linux choisi est démarré avec des options permettant de monter un dossier spécifique partagé par le serveur NFS en tant que /;
  5. Le noyau lance un init comme habituellement sur une installation standard qui lance à son tour les processus/démons traditionnels.

La mise en oeuvre n'est pas simple et requiert beaucoup de configuration/modifications que je vais tenter de détailler :

  • Configuration du serveur DHCP + TFTP
  • Paramétrage du serveur NFS
  • Installation d'une Debian capable de démarrer sur du NFS

Plusieurs documentations sont disponibles : ici et .

Dnsmasq est un serveur DHCP que j'utilise depuis longtemps et qui est capable de répondre aux requêtes PXE et intègre un serveur TFTP.

Dans mon installation, le dossier contenant tout l'arborescence nécessaire au TFTP + NFS se trouve dans /mnt/nfsroot.

Pour activer les options BOOTP dans les réponses DHCP et activer le serveur TFTP intégré à Dnsmasq, ajouter les lignes suivantes :

/etc/dnsmasq.conf
enable-tftp
dhcp-boot=pxelinux.0
tftp-root=/mnt/nfsroot/tftp
  • enable-tftp : active le serveur TFTP intégré;
  • dhcp-boot : nom du fichier que le client doit demander pour démarrer en PXE;
  • tftp-root : dossier dans le lequel se trouve les fichiers à servir.

Le dossier exposé via TFTP doit contenir au minimum des fichiers pxelinux.0 et pxelinux.cfg/default, le contenu est le suivant :

├── initrd.img
├── ldlinux.c32 -> /usr/lib/syslinux/modules/bios/ldlinux.c32
├── pxelinux.0 -> /usr/lib/PXELINUX/pxelinux.0
├── pxelinux.cfg
│   └── default
└── vmlinuz

Description des fichiers :

  • pxelinux.0 + pxelinux.cfg/default : programme d'amorçage + fichier de listing des images disponibles que le bios va lancer, équivalent à /boot/grub/grub.cfg;
  • ldlinux.c32 : loader de noyau linux, équivalent à lilo ou grub mais dédié au boot réseau;
  • vmlinuz + initrd.img : image du noyau linux accompagné de son “archive” pour avoir le minimum vitale pour démarrer : module noyau, quelques commandes… voir ci dessous pour le contenu.

Pour récupérer les fichiers d'amorçage, il faut installer les paquets pxelinux et syslinux-common

sudo apt-get install -y pxelinux syslinux-common

Copier le programme pxelinux.0 dans l'arborescence cible. C'est un équivalent de grub ou lilo :

sudo ln -s /usr/lib/PXELINUX/pxelinux.0 /mnt/nfsroot/tftp/

Idem que le chargeur de menu ldlinux.c32 :

sudo ln -s /usr/lib/syslinux/modules/bios/ldlinux.c32 /mnt/nfsroot/tftp/
Les distributions récentes netboot de Debian intègre déjà ces fichiers, cette opération n'est plus nécessaire.

Le programme pxelinux est en mesure d'afficher un menu permettant de choisir l'image à démarrer ainsi que les options à passer au noyau. Voici mon fichier de menu pxelinux.cfg/default :

pxelinux.cfg/default
# image par défaut
DEFAULT mythtv
 
# déclaration d'une image
LABEL mythtv
# noyau à utiliser
KERNEL vmlinuz
# options du noyau
APPEND initrd=initrd nfsroot=/mnt/nfsroot/mythtv.0.26,v3 root=/dev/nfs panic=10 ro quiet ipv6.disable=1
 
# pas d'attente
PROMPT 0
TIMEOUT 60

Les options importantes transmises au noyau sont :

  • nfsroot : précise où se trouve la racine de l'image à démarrer. Comme il n'est pas précisé de serveur NFS particulier, le noyau le cherchera sur la même IP que celui qui a servit les fichiers PXE. Ce dossier sera monté en que / sur la machine cliente;
  • root : nom du block device référençant le périphérique contenant /.

La configuration du serveur NFS est traditionnelle. La ligne suivante précise l'arborescence à exporter, l'option no_root_squash est importante pour conserver l'appartenance de l'utilisateur root aux fichiers :

/etc/exports
/mnt/nfsroot/mythtv.0.26  *(rw,no_subtree_check,no_root_squash,fsid=8)

La documentation officielle décrit précisément comment initialiser une arborescence à partir d'une distribution netboot de Debian.

Il faut juste noter que lorsque le noyau de la machine cliente démarre, le dossier /mnt/nfsroot/mythtv.0.26 partagé par le serveur NFS doit être monté en tant que /. Pour se faire, en plus d'ajouter l'option nfsroot au noyau comme indiqué ci dessus, il faut modifier le fichier /etc/fstab pour qu'il référence alors le device spécifique /dev/nfs :

/etc/fstab
# <file system> <mount point>    <type>  <options>                       <d><p>
/dev/nfs        /                nfs     ro,hard                          1  1

Le lecteur attentif aura noté que / est monté en *lecture seule* (option ro) : mon objectif est de pouvoir démarrer 2 machines clientes sur la même arborescence de fichiers côté serveur. Ce partage fonctionnera alors comme les live-cd Knoppix ou Ubuntu : il n'est pas possible de modifier les fichiers sur une machine cliente. Une procédure spéciale permet de réaliser cette opération depuis l'hôte.

Spécialisation par host

Pour démarrer 2 machines (Mimosa + Wimpy) sur ce boot PXE en même temps, il faut être en mesure de partager l'arborescence / tout en évitant les effets de bord (écriture des fichiers temporaires dans /var/… ou dans /tmp/… par exemple). Si des précautions ne sont pas prises, le démarrage d'une machine influera sur la 2e.

Je veux pouvoir en plus spécialiser un minimum les séquences de démarrage des 2 machines notamment pour la conf de X / alsa / lirc :

  • Wimpy est équipé une carte graphique Intel en affichant l'image sur la sortie VGA et le son en analogique;
  • Mimosa avec une carte Nvidia en passant l'image et le son sur la sortie HDMI.

Enfin, chaque machine possède un /home différent lié à leurs utilisations :

  • Wimpy étant dans la cuisine, il sert à la fois pour les recettes (navigation internet) et Mythtv;
  • Mimosa dans le salon pour Mythtv et jouer à Stepmania.

Pour mettre en place l'ensemble, j'utilise plusieurs types de filesystem :

  • tmpfs : filesystem entièrement en mémoire limité en taille. En le montant dans un dossier, il est alors possible d'écrire dedans comme un dossier “normal” sauf qu'à l'arrêt de la machine, toutes les écritures seront perdues.
  • aufs : permettant de superposer différents répertoires les un sur les autres tout en spécifiant ceux en lecture seule ou lecture / écriture. Voici un exemple de superposition d'un /dossierA sur un /dossierB monté dans /dossierC :
mount -t aufs nom /dossierC -o br:/dossierA=ro:/dossierB=rw
Arborescence initiale
├── dossierA
│   ├── fichier1
│   └── fichier2
└── dossierB
    ├── fichier2
    └── fichier3


Contenu de /dossierC après le montage
└── dossierC
    ├── fichier1
    ├── fichier2 (celui du dossierA)
    └── fichier3

Lors des écritures dans /dossierC, celles ci s'effectueront dans /dossierB (spécifié via le flag rw).

Il existe également ramfs qui est très similaire au tmpfs : les fichiers sont stockés en RAM donc volatiles. Il a la particularité d'occuper la mémoire proportionnellement aux fichiers écrit dedans. Par contre, aufs refuse de manipuler ce filesystem :'(.

En combinant ces 2 systèmes de fichiers, il est possible alors de partir d'une image de base en lecture seule et de lui superposer un filesystem volatile autorisant les écritures.

J'ai créé un dossier /target contenant 2 dossiers normalisés host-<nom de machine> dans lesquels se trouvent les fichiers de configurations spécifiques à chaque machine. Voici le contenu de ces dossiers :

├── host-mimosa
│   └── etc
│       ├── asound.conf
│       ├── asound.state
│       ├── hostname
│       ├── lirc
│       │   ├── hardware.conf
│       │   └── lircd.conf
│       ├── motd
│       ├── rc.local
│       ├── rc.target
│       ├── udev
│       │   └── rules.d
│       │       └── 70-persistent-net.rules
│       └── X11
│           ├── edid.bin
│           └── xorg.conf
└── host-wimpy
    └── etc
        ├── hostname
        ├── lirc
        │   ├── hardware.conf
        │   ├── lircd.conf
        │   └── lircmd.conf
        ├── motd
        └── X11
            └── xorg.conf

J'ai donc 1 dossier pour Mimosa et Wimpy et avec principalement le paramétrage de la carte son, de la télécommande, un script de démarrage spécifique, un fichier de règles udev et la configuration de X.

Ainsi, les opérations réalisées lors du boot de la machine sont les suivantes :

  1. le noyau démarre à partir du serveur TFTP et charge le / monté en lecture seule à partir du NFS via le /etc/fstab présenté ci dessus
  2. dans la séquence de boot, un script maison est lancé le plus tôt possible /etc/rcS.d/S04aufs_ramfs montant plusieurs filesystem :
    • un tmpfs sur /tmp
    • un aufs qui superpose un dossier de /tmp sur /var avec /var est en lecture seule, /tmp est en lecture / écriture. Les programmes démarrant un peu plus tard pourront donc écrire sur /var.
    • un 2nd aufs en vérifiant au préalable si un dossier /target/host-<nom de machine>/etc existe.
      • Si c'est le cas, il superpose /etc en lecture seule, /target/host-<nom de machine>/etc en lecture seule et un dossier de /tmp en lecture écriture.
      • Sinon, il superpose /etc en lecture seule avec un dossier de /tmp en lecture écriture.

Schéma d'empilement des filesystems

J'ai développé un script SysVInit pour être lancé au démarrage de la machine et réalisant les opérations décrites précédemment pour monter sur /etc et /var le ramfs avec aufs.

/etc/rcS.d/S04aufs_ramfs
### BEGIN INIT INFO
# Provides:          aufs_ramfs checkroot-bootclean checkfs
# Required-Start:    hostname mountkernfs
# Required-Stop:     
# Should-Start:      
# Should-Stop:       
# Default-Start:     S
# Default-Stop:      
# Short-Description: Aufs Ramfs and specific hostname stuff
### END INIT INFO
 
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Aufs Ramfs and specific hostname stuff"
NAME=AufsRamfs
 
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
 
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
 
HOSTNAME=$(hostname)
 
#
# Function that starts the daemon/service
#
do_start()
{
  /bin/mount -t tmpfs ramfsrwtmp /tmp -o noatime,nodev,nosuid,noexec,size=20M,mode=1777
  touch /tmp/.clean
 
  mkdir /tmp/varfs /tmp/etcfs
  /bin/mount -t aufs var_aufs /var -o br:/tmp/varfs=rw:/var=ro
  if [ -d /target/host-$HOSTNAME ];
  then
    /bin/mount -t aufs etc_aufs /etc -o br:/tmp/etcfs=rw:/target/host-$HOSTNAME/etc=ro:/etc=ro
  else
    /bin/mount -t aufs etc_aufs /etc -o br:/tmp/etcfs=rw:/etc=ro
  fi
}
 
case "$1" in
  'start')
    log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    log_end_msg 0
    ;;
  *)
    echo "Usage: $SCRIPTNAME {start}" >&2
    exit 3
    ;;
esac

J'utilise le même principe pour le montage du $HOME du user mythtv. J'ai créé plusieurs dossiers :

  • /mnt/nfsroot/mythtv.0.26/home/mythtv : contient la configuration commune à toutes les machines, sera monté en lecture seule;
  • autant de /mnt/nfsroot/mythtv.0.26/home/mythtv-<nom de machine> que de machines à démarrer : contient la configuration spécifique de l'ordinateur (mapping de touche de la télécommande, cache Mythtv…, sera monté en lecture écriture;

Lors de la séquence de boot avant le démarrage de X, un autre script maison est lancé pour superposer un dossier spécifique à l'hôte :

  1. le dossier /mnt/nfsroot/mythtv.0.26/home est monté à partir du NFS dans /home
  2. si un dossier /home/mythtv-<nom de machine> existe alors celui si sera monté au dessus du /home/mythtv déjà existant.

Ici, pas de système de fichier en mémoire, les programmes écrivant dans leur /home/mythtv écriront directement dans /home/mythtv-<nom de machine> qui est sur le NFS.

Ce script monte le /home/mythtv-<nom de machine> sur /home/mythtv comme décrit précédemment :

/etc/rcS.d/S17mount_home
#!/bin/sh
### BEGIN INIT INFO
# Provides:          aufs_home
# Required-Start:    x11-common nfs-common
# Required-Stop:     $time
# Default-Start:     S
# Default-Stop:
# Short-Description: Aufs specific home
### END INIT INFO
 
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Aufs specific home"
NAME=AufsHome
 
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
 
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
 
HOSTNAME=$(hostname)
 
#
# Function that starts the daemon/service
#
do_start()
{
  # automaticaly find / mount source
  ROOT_DIR=$(mount | grep "on / type nfs" | cut -d' ' -f 1 | cut -d"/" -f1-3 )
  /bin/mount -t nfs -o noatime,rsize=65536,wsize=65536,hard,nolock,async,intr,sec=sys ${ROOT_DIR}/home /home
 
  if [ -d /home/mythtv-$HOSTNAME ];
  then
    /bin/mount -t aufs home_aufs /home/mythtv -o br:/home/mythtv-$HOSTNAME=rw:/home/mythtv=ro
  fi
}
 
do_stop()
{
  umount /home/mythtv
  umount /home
}
 
case "$1" in
  'start')
    log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    log_end_msg 0
    ;;
  'stop')
    log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    log_end_msg 0
    ;;
  *)
    echo "Usage: $SCRIPTNAME {start|stop}" >&2
    exit 3
    ;;
esac

Sur une distribution linux standard taillée pour le bureau, le serveur X est lancé avec un gestionnaire de login (xdm, gdm, lightdm…). C'est bien mais pas top car dans mon cas, cela augmente le temps de démarrage et l'occupation mémoire même si ce gestionnaire est le plus léger possible.

Pour répondre à mes besoins, j'ai écrit un script SysVInit de lancement de X avec le user mythtv via la commande startx :

/etc/rc2.d/S02startx
#!/bin/sh
### BEGIN INIT INFO
# Provides:          startx
# Required-Start:    $remote_fs $time nvidia-kernel rc.target
# Required-Stop:     
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO
 
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/startx
NAME=startx
DESC="X server"
 
. /lib/init/vars.sh
. /lib/lsb/init-functions
 
case "$1" in
  start)
    log_begin_msg "Starting $DESC: "
    rm -f /home/mythtv/.Xauthority*
    /bin/su - mythtv -c /usr/bin/startx 2>/dev/null 1>&2 &
    log_end_msg 0
    ;;
  stop)
    echo -n "Stopping $DESC: "
    chvt 1
    echo "$NAME."
    ;;
  restart)
    $0 stop
    sleep 1
    $0 start
    ;;
  *)
    N=/etc/init.d/$NAME
    echo "Usage: $N {start|stop|restart}" >&2
    exit 1
    ;;
esac
 
exit 0

startx démarre un serveur X et invoque le script $HOME/.xinitrc. Voici la configuration pour Mimosa :

/home/mythtv/.xinitrc
# permet de faire disparaitre le curseur
unclutter -idle 0.1 -root -regex -notclass 'Iceweasel|XTerm|Gcalctool|Gthumb' &
 
# charge la configuration utilisateur du driver de carte graphique
nvidia-settings -l 
 
# désactive l'extinction automatique de l'écran pour l'économie d'énergie
xset -dpms
 
# écran de veille
xscreensaver &
 
# fond d'écran
xloadimage -onroot -center ~/bg.png 
 
# mapping des touches de la télécommande
(sleep 30 ; irexec -d ) &
 
# IHM de mythtv
mythfrontend --logpath /var/log/mythtv &
 
# gestionnaire de fenêtre ultra minimaliste
ratpoison -f ~/.ratpoisonrc

Au final, entre l'allumage de l'ordinateur et la fin de démarrage de Mythtv, il s'écoule 1 minute environ.

Ce modèle peut s'appliquer à plus de 2 machines, je l'utilise également avec une VM associée un nom d'hôte supplémentaire pour effectuer la mise au point.

Maintenance

Pour modifier la configuration, installer des paquets il n'est pas possible de le faire sur une machine cliente bootée car / est montée en lecture seule. Ces opérations doivent être réalisées directement sur le serveur sur l'arborescence partagée par le NFS en lançant un chroot dans /mnt/nfsroot/mythtv.0.26.

user@popeye $ cd /mnt/nfsroot/mythtv.0.26
user@popeye $ sudo chroot .
user@chroot # 

Il faut également monter /dev, /dev/pts et /proc dans ce chroot pour que tous les programmes s'exécutent correctement. Pour me faciliter le travail, j'ai écrit un script de lancement de montage de ces dossiers et de lancement du chroot :

chroot_setup.sh
#!/bin/sh -e
# récupération du chemin absolu du dossier dossier courant
CHROOT_DIR=$( cd -P -- "$(dirname -- "$0")" && pwd -P)
 
echo "Montage en cours"
mount /dev/ $CHROOT_DIR/dev -o bind
mount -t devpts devpts $CHROOT_DIR/dev/pts
mount -t proc proc $CHROOT_DIR/proc -o noexec,nosuid,nodev
 
echo "Montage OK"
 
chroot .

Lancement :

user@popeye $ cd /mnt/nfsroot/mythtv.0.26
user@popeye $ sudo ./chroot_setup.sh
Montage en cours
Montage OK
user@chroot # apt-get install ...

Ainsi un apt-get install zsh s'installera bien dans cet environnement plutôt que sur la machine hôte.

Pour démonter les filesystems, il faut le faire dans le sens inverse de montage. Un autre script fait ce travail :

chroot_de-setup.sh
#!/bin/sh
 
CHROOT_DIR=$( cd -P -- "$(dirname -- "$0")" && pwd -P)
 
echo "demontage en cours"
 
umount $CHROOT_DIR/dev/pts
umount $CHROOT_DIR/dev
umount $CHROOT_DIR/proc

Lancement :

user@popeye $ cd /mnt/nfsroot/mythtv.0.26
user@popeye $ sudo ./chroot_de-setup.sh

Les filesystemes sont démontés et laissent la machine hôte toute propre ;).