Aller au contenu

Administration Unix - CM séance 16

16. Temporisation et démarrage du système

Les systèmes Unix disposent de mécanismes qui leur permet de lancer des commandes automatiquement à des dates choisies.

16.1. Exécution différée de tâches avec at

Les commandes de la famille at permettent à un utilisateur de demander un lancement différé d'une série de commandes, appelée un job.

La commande at

L'usage général est :

at [options] timespec

timespec indique l'heure et la date, qui peuvent être relative.

Dans cette forme, at est interactive, et demande de rentrer les commandes du job, la saisie se terminant par Ctrl+D :

$ date
lundi 15 février 2021, 13:30:50 (UTC+0100)

$ at 13:36 feb 15
warning: commands will be executed using /bin/sh
at> touch Riri                          # commandes saisies par l'utilisateur
at> mv -f Riri Fifi
at> <EOT>                               # l'utilisateur a tapé Ctrl-D
job 35 at Mon Feb 15 13:36:00 2021

On peut aussi spécifier la date relative par rapport à now ou today :

at now + 5 minutes
at 13:00 today
at 13:30 tomorrow
at 14:00 + 1 days
at now + 2 days

(voir /usr/share/doc/at/timespec)

La commande atq

La commande atq affiche la queue (liste numéro a) des jobs en attente :

$ atq
36  Mon Feb 15 13:36:00 2021 a thiel
35  Mon Feb 15 13:36:00 2021 a thiel
37  Tue Feb 16 14:00:00 2021 a thiel

Les jobs disparaissent dès qu'ils sont terminés.

On peut supprimer un job :

$ atrm 37
$ atq
36  Mon Feb 15 13:36:00 2021 a thiel
35  Mon Feb 15 13:36:00 2021 a thiel

Regroupement

On peut appeler at avec un here-document, ce qui est utile dans les scripts :

$ at now + 4 hours << FIN
touch Pluto
FIN

ou encore

$ at now + 4 hours <<< "touch Pluto"

Il est également possible de passer un fichier de commandes à at :

$ cat todo.txt 
touch Mickey
$ at -f todo.txt now + 10 minutes
warning: commands will be executed using /bin/sh
job 39 at Mon Feb 15 13:51:00 2021

Comme l'indique le warning, les commandes seront interprétées pas sh ; on ne peut donc pas utiliser des construction bash telles que $(), (()), [[]], ... Par contre on peut appeler dans ces commandes tout type de script.

Interrogation

On peut demander à at les commandes qu'il va lancer pour un job :

$ at -c 39
#!/bin/sh
# atrun uid=1000 gid=1000
# mail thiel 0
...
umask 2
PATH=/home/thiel/bin:/usr/bin:/usr/local/bin; export PATH
OLDPWD=/home/thiel; export OLDPWD
cd /home/thiel/Bureau || {
    echo 'Execution directory inaccessible' >&2
    exit 1
}
touch Mickey

La commande at possède le setuid ; elle choisira l'uid et le gid effectifs d'après le commentaire # atrun uid=... gid=...

À la suite, at enregistre toutes les variables d'environnement, puis une commande cd pour aller dans le répertoire courant au moment du at, et enfin la ou les commandes données par l'utilisateur.

Au niveau système, l'administrateur peut autoriser ou interdire aux utilisateurs l'emploi de at via les fichiers de configuration /etc/at.allow et /etc/at.deny (man at.allow).

Envoi d'un mail

À la fin du job, les affichages produits sont envoyés automatiquement par mail à l'utilisateur (voir options -m ou -M de at). Pour que cela fonctionne, il faut qu'un service MTA (Mail Transport Agent) soit configuré sur la machine, sinon les affichages sont perdus.

S'il n'y a pas de MTA (situation par défaut), on peut rediriger explicitement les affichages dans un fichier :

$ at now + 2 minutes
warning: commands will be executed using /bin/sh
at> {                                
at>   echo "L'espace disponible est"
at>   df   
at> } >> trace.txt
at> <EOT>
job 43 at Mon Feb 15 18:02:00 2021

Pour installer un MTA dans une VM xubuntu, il suffit de faire

$ sudo apt install mailutils

configuration :

  • choisir envoi local uniquement, tout le reste par défaut
  • pour reconfigurer : sudo dpkg-reconfigure postfix
  • pour supprimer : sudo apt purge mailutils postfix ; sudo apt autoremove

Test :

$ at now + 2 minutes
warning: commands will be executed using /bin/sh
at> echo "Gloubi boulga"
at> <EOT>
job 2 at Mon Feb 15 18:50:00 2021

$ mail
"/var/mail/thiel": 1 message 1 nouveau
>N   1 Edouard Thiel      lun. févr. 15 1  12/371   Output from your job   2
? 1
Subject: Output from your job        2
To: thiel@xubu1
From: Edouard Thiel <thiel@xubu1>

Gloubi boulga
? q
1 message sauvegardé dans /home/thiel/mbox
0 message conservé dans /var/mail/thiel

$ mail
Pas de courrier pour thiel
$ mail -f ~/mbox
"/home/thiel/mbox": 1 message 1 nouveau
>N   1 Edouard Thiel      lun. févr. 15 1  12/371   Output from your job   2
? q

Compléments

Dans le man de at, il est aussi question de

  • choix parmi plusieurs queues (a, b, ...) ; la queue a par défaut est prioritaire sur les autres ;
  • une commande batch qui attend que le système ne soit pas surchargé avant de lancer un job.

16.2. Exécution périodique avec cron

Les commandes de la famille cron permettent de programmer des tâches qui seront exécutées périodiquement. Par exemple :

  • sauvegardes
  • test si un service est actif
  • nettoyage

En réalité, cron est le nom du démon qui lance les tâches, et la commande pour les gérer est crontab ; les tâches programmées sont stockées dans /var/spool/cron/crontabs/<user>.

L'usage de crontab est :

  • crontab -l   affiche la liste des tâches programmées ;
  • crontab -e   édite la liste (avec le choix d'un éditeur).

Le fichier édité par crontab est sous la forme :

# Edit this file to introduce tasks to be run by cron.
# ...
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
<tâche>
...

chaque ligne représente une tâche périodique sous la forme (man 5 crontab) :

m h  dom mon dow   command

avec :

m           minutes 0..59
h           heures 0-23
dom         day of the month 1..31
mon         month 1..12
dow         day of week 0..7 (0 = 7 = dimanche)
command     une commande avec de préférence un chemin absolu

Les informations de date (m, h, dom, mon, dow) peuvent être données sous la forme de :

a,b     liste de valeurs
a-b     intervalle, équivalent à a,a+1,a+2,...,b
*       n'importe quelle occurrence
*/n     une occurrence sur n

Par exemple :

# m   h        dom  mon  dow  command
  15  8,12,16  *    *    1-5  /home/thiel/monscript.sh

exécutera monscript.sh du lundi au vendredi à 8h15, 12h15 et 16h15.

Pour annuler une tâche il suffit de rappeler crontab -e et de commenter ou supprimer la ligne correspondante.

L'administrateur peut autoriser ou interdire aux utilisateurs l'emploi de crontab via les fichiers de configuration /etc/cron.allow et /etc/cron.deny (man crontab).

Au niveau du système, il y a de nombreuses tâches programmées pour cron, qui n'apparaissent pas dans des crontab ; elles sont stockées dans  :

/etc/crontab            fichier crontab avec en plus un champ user
/etc/cron.d/            fichiers au même format

/etc/cron.hourly/       contient des scripts à exécuter ; ils sont lancés
/etc/cron.daily/        par anacron, lui-même appelé dans /etc/crontab
/etc/cron.weekly/
/etc/cron.monthly/

Ainsi, lorsqu'on installe un paquet, son script postinst pourra éventuellement rajouter des scripts dans /etc/cron.{d,hourly,daily,weekly,monthly}.

16.3. Démarrage traditionnel du système

Lors du démarrage, le chargeur d'amorçage exécute le noyau, qui à son tour créé le premier processus du système : /sbin/init, de PID 1. Si le noyau n'y arrive pas, il fait un kernel panic et s'arrête.

init est un processus démon, qui continue à tourner jusqu'à l'arrêt du système. C'est l'ancêtre de tous les processus, et l'un de ses rôle est d'adopter les orphelins (puis de supprimer les zombies de la table de processus).

Dès qu'il est lancé, init va lancer une série de scripts pour démarrer les différents services (réseau, impression, serveur web, ...), et éventuellement un système de fenêtrage graphique (serveur X11, Wayland, ...).

Il existe différentes familles de démons init et scripts de démarrage :

  • BSD init, inspiré des Unix BSD, depuis 1986
  • SysV init, inspiré de Unix System V (lire 5) de AT&T, depuis 1988
  • launchd sur MacOS, depuis 2005
  • upstart, sur Ubuntu, 2006 à 2014 (<= 14.10)
  • systemd, depuis 2010 (sur ubuntu >= 15.04)
  • ...

Ces familles sont apparues car il y a eu de plus en plus de services à démarrer, et on voulait à la fois en faciliter la gestion, et accélérer le démarrage.

BSD init

Inspiré du init historique, capable en plus de démarrer le mode graphique.

init exécute les scripts d'initialisation /etc/rc puis /etc/rc.local. Ces scripts sont souvent longs, "monolithiques", et doivent être édités à la main.

Par la suite ont été rajoutés des scripts dans /etc/rc.d/, mais leur ordre d'appel devait être "codé en dur".

SysV init

Ce système introduit un état, appelé le runlevel (un chiffre entre 0 et 9 ou une lettre, par exemple S ou K). Sa signification varie selon le système ; par exemple pour Linux Standard Base :

0   halt                        éteint le système
1   mode single-user            pour administration
2   multi-user sans réseau
3   multi-user avec réseau      mode console
4   réservé
5   3 + display manager         mode graphique, par défaut
6   reboot

Voir aussi : https://en.wikipedia.org/wiki/Runlevel

Le fichier de configuration des runlevels est /etc/inittab ; le runlevel par défaut y est indiqué dans la ligne

id:<runlevel>:initdefault:

Voir :

L'administrateur peut changer immédiatement le runlevel avec la commande :

$ telinit <runlevel>

Pour connaître le runlevel précédent et courant :

$ runlevel
N 5                     (N : inconnu)

Historiquement, les scripts de démarrages étaient répartis selon le runlevel dans les fichiers /etc/rc.<runlevel>. Par la suite est apparue une organisation plus fine des scripts :

  • les scripts sont placés dans /etc/init.d/ ; ils peuvent recevoir en argument : start, stop, restart, reload, status, probe, ... ;
  • pour chaque runlevel il y a un répertoire /etc/rc<runlevel>.d : /etc/rc1.d/, /etc/rc2.d/, ...
  • pour activer un script de /etc/init.d/ lors d'un runlevel, il suffit de placer dans /etc/rc<runlevel>.d/ un lien vers le script.
  • ces liens ont le format de nom suivant :

    <K ou S><numéro sur 2 chiffres><nom du script>
    

Les numéros servent à fixer un ordre d'exécution (ordre lexicographique). Les liens commençant par S(tart) sont pour les services activés (enabled), ceux commençant par K(ill) sont pour les services désactivés (disabled).

Lors qu'on passe d'un runlevel <a> à un runlevel <b>, init appelle

  • tous les scripts de /etc/rc<a>.d/S* avec l'argument stop, puis
  • tous les scripts de /etc/rc<b>.d/S* avec l'argument start.

Il est également possible de faire exécuter par init un script de init.d/ avec la commande :

$ service <nom script> <ordre> [options]

<ordre> est start, stop, etc.

Extension LSB

Le projet Linux Standard Base a travaillé sur une méthode pour gérer les dépendances entre les scripts de démarrage dans /etc/init.d ;

cela consiste à rajouter un entête dans chaque script :

### BEGIN INIT INFO
# Provides:             fonctionnalité apportée par ce script
# Required-Start:       éléments devant être lancés avant le démarrage
# Required-Stop:        éléments devant être arrêtés avant l'arrêt
# Default-Start:        runlevels de démarrage par défaut
# Default-Stop:         runlevels d'arrêt par défaut
# Short-Description:    description courte
# Description:          description
### END INIT INFO

La commande update-rc.d gère ensuite automatiquement les liens pour les différents runlevels, en les renumérotant selon les dépendances (man update-rc.d).

Upstart

Spécifique à Ubuntu, et abandonné depuis 2015 au profit de systemd ; compatible avec SysV init et l'extension LSB.

Remarque :

le démon init (de PID 1) crée pour chaque user connecté un fils par init --user.
Ce fils adoptera tous les orphelins du user.
Avantage : répartit les orphelins sur plusieurs processus ;
Inconvénient : un processus ne peut plus savoir s'il est orphelin en testant si PPID est 1.

16.4. Démarrage moderne du système avec systemd

systemd a été créé en 2010 par Lennart Poettering (employé par Redhat, auteur également de pulseaudio et avahi).

Il permet un démarrage très rapide des services en parallèle, avec un système de dépendances, et plein d'autres choses.

De nombreux Unix sont passés à systemd. Pour savoir si votre système l'utilise :

$ ls -gG /sbin/init
lrwxrwxrwx 1 20 sept.  4 11:31 /sbin/init -> /lib/systemd/systemd*

systemd est compatible avec SysV init et l'extension LSB :

  • init de PID 1, init --user pour chaque user, runlevel et telinit,
  • scripts dans /etc/inid.d et /etc/rc<runlevel>.d/
  • pour exécuter les scripts : commande service
  • pour gérer/renuméroter les liens /etc/rc*.d/ : commande update-rc.d

systemd remplace non seulement le init SysV, mais offre tout un écosystème de commandes : man -k systemd

systemd                systemd-detect-virt   systemd-resolve
systemd-analyze        systemd-escape        systemd-run
systemd-ask-password   systemd-hwdb          systemd-stdio-bridge
systemd-cat            systemd-inhibit       systemd-machine-id-setup
systemd-cgls           systemd-tmpfiles      systemd-tty-ask-password-agent
systemctl              ...

On peut par exemple rejouer le démarrage :

$ systemd-analyze

afficher les durées triées :

$ systemd-analyze blame

ou afficher les durées dans le navigateur :

$ systemd-analyze plot > durees.svg
$ firefox durees.svg &

Principe de fonctionnement

la gestion du système, pour systemd, ne se réduit pas à démarrer ou arrêter des démons ou changer d'état ; elle comprend aussi la gestion des périphériques, et de différents éléments comme la date, les locales, et un journal des logs.

La philosophie est basée sur des unités, qui ont un nom et un type. Par exemple :

  • target   ce sont des cibles, l'équivalent des runlevels (dépréciés)
  • service   ce sont les démons
  • socket   points de communication entre programmes (sockets)
  • timer   actions temporelles (peuvent remplacer les cron)

Les unités sont configurés dans des fichiers, sous la forme name.type, dans les répertoires :

/lib/systemd/system/        tous les fichiers de configuration

/etc/systemd/system/        liens vers les fichiers pour dire leur état
                            dans le target courant

L'état des unités peut être :

enabled     actif dans le target courant : il y a un lien 
            /etc/systemd/system/unit.type -> /lib/systemd/system/unit.type

disabled    inactif dans le target courant : pas de lien
            /etc/systemd/system/unit.type

masked      impossible à démarrer dans le target courant : lien
            /etc/systemd/system/unit.type -> /dev/null

static      impossible à démarrer par lui-même, c'est une dépendance 
            (on ne peut peut pas le faire passer à enable ou disable).

Exemple :

$ ls -l /lib/systemd/system/ssh.service         # serveur ssh installé ?
Aucun fichier ou dossier de ce type

$ sudo apt install openssh-server               # installation + enable
...
Created symlink /etc/systemd/system/sshd.service 
             -> /lib/systemd/system/ssh.service
Created symlink /etc/systemd/system/multi-user.target.wants/ssh.service
             -> /lib/systemd/system/ssh.service
...

$ sudo systemctl disable ssh.service
Synchronizing state of ssh.service with SysV service script with 
  /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable ssh
Removed /etc/systemd/system/sshd.service.
Removed /etc/systemd/system/multi-user.target.wants/ssh.service.

$ sudo systemctl mask ssh.service
Created symlink /etc/systemd/system/ssh.service -> /dev/null.

$ sudo systemctl enable ssh.service             # essai enable
Synchronizing state of ssh.service with SysV service script with 
  /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable ssh
Failed to enable unit: Unit file /etc/systemd/system/ssh.service is masked.

$ sudo systemctl unmask ssh.service             # enlève le mask
Removed /etc/systemd/system/ssh.service.

$ sudo systemctl enable ssh.service             # nouvel essai enable
Synchronizing state of ssh.service with SysV service script with 
  /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable ssh
Created symlink /etc/systemd/system/sshd.service 
             -> /lib/systemd/system/ssh.service.
Created symlink /etc/systemd/system/multi-user.target.wants/ssh.service 
             -> /lib/systemd/system/ssh.service.

Fichiers de description

  • Fichier de description d'un target :

    $ more /usr/lib/systemd/system/multi-user.target
    #  This file is part of systemd. (+ licence)
    
    [Unit]
    Description=Multi-User System
    Documentation=man:systemd.special(7)
    Requires=basic.target
    Conflicts=rescue.service rescue.target
    After=basic.target rescue.service rescue.target
    AllowIsolate=yes
    
    $ more /usr/lib/systemd/system/graphical.target
    #  This file is part of systemd. (+ licence)
    
    [Unit]
    Description=Graphical Interface
    Documentation=man:systemd.special(7)
    Requires=multi-user.target                      # dépend du target précédent
    Wants=display-manager.service
    Conflicts=rescue.service rescue.target
    After=multi-user.target rescue.service rescue.target display-manager.service
    AllowIsolate=yes
    
  • Fichier de description d'un service :

    $ more /usr/lib/systemd/system/ssh.service
    [Unit]
    Description=OpenBSD Secure Shell server
    Documentation=man:sshd(8) man:sshd_config(5)
    After=network.target auditd.service
    ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
    
    [Service]
    EnvironmentFile=-/etc/default/ssh
    ExecStartPre=/usr/sbin/sshd -t
    ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
    ExecReload=/usr/sbin/sshd -t
    ExecReload=/bin/kill -HUP $MAINPID
    KillMode=process
    Restart=on-failure
    RestartPreventExitStatus=255
    Type=notify
    RuntimeDirectory=sshd
    RuntimeDirectoryMode=0755
    
    [Install]
    WantedBy=multi-user.target
    Alias=sshd.service
    

Manipulations

  • Manipulation des états (équivalent runlevels) :

    systemctl list-unit-files --type=target     affiche l'état des targets
    
    systemctl get-default                       affiche le target du boot
                                                (graphical.target)
    
    systemctl set-default level[.target]        change le target pour le boot
                                                (équivalent inittab:initdefault)
    
    systemctl isolate level[.target]            change immédiatement le target
                                                (équivalent telinit)
    systemctl halt|poweroff
    systemctl reboot
    systemctl suspend|hibernate
    
  • Manipulation des services :

    systemctl list-unit-files --type=service    affiche l'état des services
    
    systemctl enable|disable service            change l'état du service au boot
    systemctl start|stop|status service         change l'état actuel du service
    systemctl mask|unmask service               empêche démarrage, même en manuel
    
    systemctl is-enabled service                activé dans le target courant ?
    systemctl is-active service                 actif en ce moment ?
    systemctl is-failed service                 a démarré sur une erreur ?
    

Exemple :

```
$ systemctl list-unit-files --type=service
UNIT FILE                               STATE           VENDOR PRESET
...
bluetooth.service                       enabled         enabled      
...

$ sudo systemctl stop bluetooth
$ sudo systemctl status bluetooth
  bluetooth.service - Bluetooth service
     Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; 
                     vendor preset: enabled)
     Active: inactive (dead)
       Docs: man:bluetoothd(8)

$ systemctl is-enabled bluetooth            # au boot
enabled
$ systemctl is-active bluetooth             # état actuel
inactive
```

Compléments

  • Interrogation des services :

    systemctl cat service                       affiche le fichier du service
    systemctl show service                      affiche les propriétés du service
    systemctl list-dependencies service         liste les dépendances du service
    ...
    
  • Confinement des processus :

    Les processus sont étroitement confinés par l'utilisation massive de cgroups (control groups). Par exemple, tous les processus qui dépendront d'un démon ssh seront dans le même cgroup. On pourra lui allouer un quota d'espace mémoire ou le limiter en nombre de cœurs (de CPU) utilisés.

    Voir la hiérarchie des cgroups, ou les cgroups racines triés par ressource :

    systemd-cgls
    systemd-cgtop
    

    Voir :

  • Autres commandes relative à systemd :

    localectl       lister et manipuler les locales, le layout clavier, etc
    hostnamectl     afficher ou manipuler le hostname
    timedatectl     afficher ou manipuler la date et l'heure
    journalctl      afficher ou manipuler le journal maintenu par systemd
    
  • Documentation :