Introduction

Lors de mon dernier travail sur Cockpit, je me suis rendu compte que ce dernier reposait beaucoup sur systemd. En effet, en essayant l'interface Web de Cockpit, je me suis rendu compte qu'on pouvait ajouter pas mal de renseignements sur la machine via systemd. Vu la quantité d'informations disponibles, un article s'imposait pour présenter l'ensemble des modifications de mon système.

De même, ayant adopté systemd, il m'a semblé plus intéressant d'essayer de gérer la configuration de mes machines en utilisant les mécanismes systemd qui semblent maintenant assez bien éprouvés.

Bon, je sais que systemd est un sujet encore épineux en 2017 mais, je crois qu'en utilisant ses mécanismes de configuration le plus possible, on arrive à une méthode de configuration un peu plus universelle. En effet, sous réserve que d'autres distributions reposent sur les services systemd présentés dans cet article, un administrateur système qui changerait d'environnement serait moins perdu (il y a de grandes différences entre un système RedHat et un système Debian) et, de fait, serait beaucoup plus efficace.

Mais voyons plutôt ce que nous pouvons faire avec systemd.

L'ensemble de ce qui suit a été réalisé sur plusieurs machines sous Debian Stable (Stretch au moment de la rédaction de cet article).

Informations sur la machine via /etc/machine-info

hostnamectl est une commande systemd qui permet de customiser plusieurs éléments:

  • l'emplacement physique de la machine
  • l'environnement de la machine (production/dev/test/etc).
  • le type de machine (chassis).
  • Un nom élégant.
  • l'icône de la machine (dont le nom est au format XDG).

Ces informations sont stockées dans le fichier /etc/machine-info. On peut également les modifier avec hostnamectl.

Gestion du temps via systemd-timesyncd

J'utilisais depuis des temps immémoriaux l'antique ntpdate pour synchroniser l'heure de mon serveur public. Attention, cette option est ultra importante car mon serveur public n'a pas d'horloge temps réel indépendante. Donc, dès qu'il boote, il lui faut récupérer l'heure via le réseau, histoire d'avoir les bonnes dates dans les logs.

Mais avec systemd, il y a un outil pour faire ça: timedatectl. Si je peux virer un paquet de mon système, autant le faire non ? Par ailleurs, la documentation de timedatectl indique que ce dernier stocke la date et l'heure sur disque ce qui permet de reprendre à une date moins vieille que epoch lors du reboot de la machine.

La configuration de timedatectl est assez simple, vous devez simplement renseigner le fichier /etc/systemd/timesyncd.conf avec la bonne liste de serveurs, dans le cadre d'une machine à configuration statique.

#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See timesyncd.conf(5) for details.

[Time]
NTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org

Dans le cas, d'une machine servie par DHCPv4, je vous conseille de ne pas configurer ce fichier mais d'utiliser la directive UseNTP dans la configuration du service networkd et de servir la liste des serveurs NTP via votre serveur DHCP: votre configuration sera alors centralisée correctement.

Ensuite, n'oubliez pas de supprimer le paquet ntpdate, ce qui supprime aussi l'unité systemd associée. Enfin, vous devrez activer le service systemd-timesyncd.service tout en ayant pris soin de recharger le démon systemd.

Gestion des interfaces réseaux via systemd-networkd

Bon ça fait des années que j'utilise le système ifup de Debian qui marche assez bien finalement. Donc pour moi, NetworkManager est un peu "overkill". Néanmoins, systemd présente un mécanisme intéressant pour la gestion des interfaces réseau: il s'agit de systemd-networkd.

Globalement la logique est un peu la même que celle de ifup:

  • On définit un fichier .network de définition d'un périphérique réseau.
  • On y indique les différentes options disponibles dedans.
  • Au démarrage de la bécane, systemd se charge "d'allumer" les différents périphériques réseau.

Voici quelques fichiers que j'utilise au boulot et à la maison, pour vous montrer différentes utilisations possibles et que networkd sait être versatile...

Connexion de l'interface filaire ethernet de base via DHCP

Dans ce qui suit, l'interface réseau ethernet se nomme eno1. Pour savoir ce que vous avez, un simple ip link vous donnera la liste des interfaces repérées par le noyau.

Vous devez créer un fichier se terminant par .network dans le répertoire /etc/systemd/network/ (ex: /etc/systemd/network/ethernet.network) et contenant les lignes à suivre:

[Match]
Name=eno1

[Network]
Description="Primary Ethernet card"
DHCP=yes

Comme vous le voyez, c'est très simple ! Le principe est d'inscrire une directive de match qui permettra de repérerer la carte réseau concernée par le fichier .network. Puis, on indique qu'on souhaite utiliser le DHCP.

Connexion de l'interface filaire ethernet de base via DHCP avec IPv6

Dans le cas qui suit, on souhaite récupérer une adresse IPv4 via DHCP mais utiliser les mécanismes IPv6 d'allocation automatique d'adresses.

[Match]
Name=eno1

[Network]
Description="Primary Ethernet card"
DHCP=ipv4
LinkLocalAddressing=ipv6
IPv6AcceptRA=true

[DHCP]
UseDNS=true
UseDomains=true
UseNTP=true
UseRoutes=true
UseTimezone=true
ClientIdentifier=mac

Pour la directive DHCP, on souhaite:

  • Configurer le DNS via DHCP.
  • Configurer le domaine par défaut via DHCP.
  • Configurer les serveurs NTP utilisés par DHCP.
  • L'authentification DHCP se fera par adresse mac.

Connexion de l'interface filaire ethernet de base via DHCP filtrant

Au bureau, j'ai un serveur DHCP filtrant sur les adresses MAC et qui s'attend à avoir des machines Windows. Pour cela, on va indiquer qu'on souhaite identifier le client DHCP via l'adresse MAC de la carte réseau et on va ajouter un identifiant de vendeur qui aura pour contenu MSFT 5.0 (un truc qui dit que c'est un MS-Windows):

[Match]
Name=eno1

[Network]
Description="Primary Ethernet card"
DHCP=ipv4
LinkLocalAddressing=no

[DHCP]
UseDNS=true
ClientIdentifier=mac
VendorClassIdentifier="MSFT 5.0"

Connexion Wifi avec WPA

C'est la connexion classique sur une borne wifi. Ici, il y a une petite astuce: nous allons utiliser wpa_supplicant (le classique) et créer une "fausse" unité systemd pour indiquer à systemd-networkd qu'il faut utiliser wpa_supplicant.

D'abord, la configuration de la carte réseau Wifi (wlp1s0 chez moi) dans /etc/systemd/network/wlp1s0.network:

[Match]
Name=wlp1s0

[Network]
Description="Primary WiFi card"
DHCP=ipv4
LinkLocalAddressing=ipv6
IPv6PrivacyExtensions=true
IPv6AcceptRA=true

[DHCP]
UseDNS=true
UseNTP=true
UseDomains=true
ClientIdentifier=mac

Dans cette configuration, on configure IPv4 par DHCP, IPv6 par les mécanismes d'annonce de routeur et, bien sûr, on active les extensions de vie privée sur IPv6. Rien que du classique.

Ensuite, il vous faut un fichier de configuration pour wpa_supplicant, nommé de la bonne manière, c'est-à-dire en tenant compte du nom de l'interface. Dans notre cas, ce sera /etc/wpa_supplicant/wpa_supplicant-wlp1s0.conf:

ctrl_interface=/var/run/wpa_supplicant
eapol_version=1
ap_scan=1
fast_reauth=1

network={
    ssid="Foobar1"
    psk="password1"
    priority=1
}
network={
    ssid="Foobar2"
    psk="password2"
    priority=2
}

Enfin, il reste à créer une "fausse" unité systemd et à l'activer:

# systemctl enable wpa_supplicant@wlp1s0.service
# systemctl start wpa_supplicant@wlp1s0.service

Normalement, ça devrait bien fonctionner...

Connexion filaire statique avec une route supplémentaire

Pour la maison, j'ai une bécane qui sert de pont "Wifi" pour le réseau 192.168.1.0. Voici la configuration spécifique de sa carte ethernet:

[Match]
Name=eno1

[Network]
Description="Primary Ethernet card"
DHCP=no
LinkLocalAddressing=no
IPForward=ipv4

[Address]
Address=192.168.1.7/24
Broadcast=192.168.1.255

Bien entendu, vous devez utiliser un mécanisme de forwarding IPv4 via nftables pour que ça fonctionne complètement (mais ça, c'est hors-sujet pour aujourd'hui).

Création d'un pont pour des machines virtuelles sur DHCP filtrant

Au boulot, j'ai besoin d'avoir un pont ethernet pour mes machines virtuelles sous KVM. Pour faire simple, j'utilise un pont via networkd. Mais il y a un peu de magie dans l'histoire: n'oublions pas que mon serveur DHCP est filtrant sur l'adresse MAC. Du coup, je suis obligé d'affecter l'adresse MAC de la carte réseau ethernet au pont et de mettre une fausse adresse MAC pour la carte réseau. C'est chiant mais sinon, ça ne fonctionne pas du tout !

Vous devez créer 3 fichiers:

  • Un fichier de périphérique réseau virtuel pour le pont (br0.netdev).
  • Un fichier de configuration réseau pour le pont (br0.network).
  • Un fichier qui va inscrire la carte ethernet physique dans le pont.

Contenu de /etc/systemd/network/br0.netdev:

[NetDev]
Description="Network Bridge for KVM"
Name=br0
Kind=bridge

Contenu de /etc/systemd/network/br0.network:

[Match]
Name=br0

[Link]
MACAddress=88:51:fc:4e:8a:91

[Network]
Description="Network configuration for Bridge"
DHCP=ipv4
LinkLocalAddressing=no

[DHCP]
UseDNS=true
UseMTU=true
UseDomains=true
ClientIdentifier=mac
VendorClassIdentifier="MSFT 5.0"

Contenu de /etc/systemd/network/ethernet-slave.network:

[Match]
Name=eno1

[Link]
MACAddress=88:51:fc:4e:8a:92

[Network]
Description="Primary Ethernet card (bridged)"
Bridge=br0

Ensuite, relancez le service:

# systemctl restart systemd-networkd.service

Un peu de ménage

Une fois que votre configuration est effective, vous pouvez supprimer les anciens paquets (oui, je sais, ça fait bizarre de supprimer un paquet aussi essentiel que ifupdown mais, vous n'en avez plus besoin):

# apt purge isc-dhcp-client isc-dhcp-common bridge-utils ifupdown
# apt autoremove

Gestion du résolveur DNS via systemd-resolved

Bon, c'est une opération assez simple qui consiste simplement à activer le service systemd-resolved et à remplacer le fichier /etc/resolv.conf par un lien symbolique vers le fichier maintenu par systemd:

# systemctl enable systemd-resolved.service
# systemctl start systemd-resolved.service
# ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

Une fois opérationnel, le service se charge de gérer la résolution de noms. Plus besoin de gérer le resolv.conf à la main ou via ifupdown.

Gestion des logs

Après quelques mois de fonctionnement correct, je me suis résolu à passer à journald en totalité. En effet, je trouve que ce dernier, qui est installé si vous utilisez systemd remplit bien son rôle. En maitrisant les commandes de consultation, on arrive finalement à s'en sortir assez facilement.

J'en ai donc profité pour configurer son stockage permanent et virer les résidus de syslog. Voici le contenu de mon fichier /etc/systemd/journald.conf:

[Journal]
Storage=persistent
SystemMaxUse=200M
MaxFileSec=1month
ForwardToSyslog=no

Suivi de systemctl restart systemd-journald. Pour virer rsyslog:

# apt purge rsyslog

Ça fait toujours un paquet en moins à gérer.

Abandonner le traditionnel cron

Oui, systemd gère un peu l'équivalent de cron via les "timers". C'est une approche qui me séduit depuis le début même si, à l'usage, une simple crontab est plus simple à gérer. Car dans le cas de systemd, vous aurez deux choses à faire:

  • créer un service à lancer.
  • créer un timer pour le service.

Néanmoins, d'un point de vue de sysadmin, cela fait sens car de toute manière, le script que vous lancez par le démon cron doit bien se situer quelquepart. Avec systemd, vous avez un peu de travail supplémentaire mais vous pouvez lire assez facilement l'emplacement du service/script. Ce référencement est quasiment obligatoire (il est indiqué dans la définition du service), c'est donc plus beaucoup plus propre, aucun risque de se demander où est situé le script à lancer.

Néanmoins, se débarrasser de cron n'est pas chose aisée. En effet, certains paquets appellent explicitement cron ou au moins le paquet cron-daemon. Mais, bonne nouvelle, il reste un subterfuge: il existe un "cron like" basé sur systemd: systemd-cron. Ce paquet installe le paquet virtuel cron-daemon.

Donc, un simple:

# apt install --no-install-recommends systemd-cron

fera l'affaire. Bon, vous me direz: quel est l'intérêt de remplacer cron par un autre paquet ? Moi, je vous répondrais que c'est pour la beauté du geste !

Conclusions

En fin de l'année 2017, systemd a bien progressé. Il fait beaucoup de choses de facto. Ce n'est pas parfait, notamment pour cron mais c'est surtout lié à la manière d'empaqueter les logiciels sous Debian.

Dans mon objectif de réduire le nombre de paquets à administrer sur une machine aux capacités limitées, systemd remplit parfaitement son rôle. Je ne peux que vous recommander de l'utiliser. Certes parfois, vous allez rencontrer des bugs car tout n'est pas parfait. Mais je trouve que le concept d'un système de configuration déclaratif est quand même beaucoup plus clair qu'un système mélangeant code Shell et fichier de conf.

Par ailleurs, et pour relancer une énième polémique, je trouve que systemd est finalement assez KISS et correspond bien à la philosophie Unix qui implique d'utiliser des outils dédiés pour chaque action. Car, c'est bien ce qu'on retrouve dans systemd: les informations sur la machine se gèrent avec hostnamectl, le réseau avec networkd, la gestion du temps avec timesynd, la gestion de la résolution DNS avec resolved. Systemd propose donc bien pleins d'outils dédiés pour chaque action d'administration système.

Dans tous les cas, pour moi, il n'y a plus de débats possibles: mon serveur public démarre bien plus vite et sans accroc depuis que j'utilise systemd. Auparavant, j'avais toujours mon service dovecot qui ne fonctionnait pas (simplement parcequ'il s'activait au mauvais moment par rapport à l'état de configuration de la carte réseau). J'ai passé des journées entières pour débugguer cette situation sans jamais y parvenir. La migration systemd a tout simplement réglé mon problème après quelques heures de recherche dans les logs.