Le software de base
Choix du système software et installation
Centos 8
J’ai choisi CentOS (8.2) pour deux raisons:
- c’est le système choisi dans le livre “Administration Linux par la pratique”;
- c’est basé sur du RedHat 8, et je croise tous les jours du redHat au boulot (en temps que simple utilisateur), donc ça me fait la main.
Il y a toute la documentation de RedHat 8 disponible.
On part sur une installation minimale, avec juste un serveur SSH. Installation mode poule, sur le petit disque de 120GO, on s’occupe du reste après.
Le disque multimedia
Ce disque va contenir une partition, pour musique et films. On utilise l’intégralité du SSD 1TB. Pour les partitions GPT, utiliser gdisk et pas fdisk.
> yum install gdisk
Et on attaque:
> gdisk /dev/sdb
GPT fdisk (gdisk) version 1.0.3
Partition table scan:
MBR: not present
BSD: not present
APM: not present
GPT: not present
Creating new GPT entries.
Command (? for help): n
Partition number (1-128, default 1):
First sector (34-2930277134, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-2930277134, default = 2930277134) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sdb.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.
n pour new, tout valider (une seule partition), p pour print et vérifier, w pour write et valider.
> lsblk
sdb 8:16 0 931.5G 0 disk
└─sdb1 8:17 0 931.5G 0 part
On formate, on donne un label et on monte:
> mkfs -t ext4 /dev/sdb1
> e2label /dev/sdb1 multimedia
> restorecon -R /multimedia/
> mount /dev/sdb1 /multimedia
Note: le restorecon est une subtilité de SELinux, que je tire du livre “Administration de Linuxpar la pratique”. En fait, j’ai constaté plus tard que MinimServer ne pouvait pas tourner avec SELinux, donc j’ai désactivé SELinux, qui me posait beaucoup de problèmes.
Pour que ce soit permanent on édite le /etc/fstab
UUID=604a3ea7-3338-458f-ab95-39029e81a5dc /multimedia ext4 defaults 0 2
Le UUID peut être trouvé dans /dev/disk/by-uuid
Le disque de backup
Quel système ?
On dispose de deux disques de 1.5TB. Ces deux disque sont assez anciens, donc avec un risque de défaillance non négligeable. J’estime que le volume à sauvegarder (surtout si on utilise de la déduplication) sera en dessous du TB. Donc on peut faire de la redondance.
Je vois deux moyens de faire de la redondance:
- le RAID1 (mdadm): du RAID logiciel, qui met en miroir les disques;
- ZFS (via zfsonlinux): un système de storage, qui va au delà du concept de partition et de système de fichiers.
ZFS a de sérieux avantages: c’est à la fois un système ancien (donc bien testé) avec des features très avancées (snapshot, raid, checksums, pools…). Il y a deux aspects qui m’intéressent ici:
- data integrity : ZFS s’occupent de vérifier l’intégrité des données (par somme de contrôle);
- mirroring: ZFS gère les disques, donc peut mettre deux disques en miroir, pour la redondance matérielle;
Vu l’utilisation visée (une zone de backup), je ne vais pas utiliser des features importantes comme les snapshots ou la compression. La compression des données est déjà gérée par le software de backup, et ce software est incrémental donc ce je n’aurai pas besoin de garder des snapshots.
Pas de suspense, je choisi ZFS, ce qui flatte ma geekitude, et me permet d’apprendre un truc nouveau.
L’inconvénient de ZFS c’est qu’il peut être gournmand en ressources, particulièrement en mémoire. Sur ma configuration minimaliste (en nombre de disques) ça ne va pas trop poser de problème, surtout que j’ai prévu 16GO de mémoire
ZFS
La documentation de référence pour ZFS sur RHEL et CentOS: https://openzfs.github.io/openzfs-docs/Getting%20Started/RHEL%20and%20CentOS.html
Installation de ZFS sur CentOS:
> su -
> yum install http://download.zfsonlinux.org/epel/zfs-release.el8_2.noarch.rpm
> yum install zfs
On charge le module kernel:
> modprobe zfs
Et on crée un pool en miroir, avec les deux disques dev/sda et dev/sdc:
> zpool create -f poolbackup mirror /dev/sda /dev/sdc
> zpool status
pool: poolbackup
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
poolbackup ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
sda ONLINE 0 0 0
sdc ONLINE 0 0 0
errors: No known data errors
Ensuite on crée un dataset nommé borg, qu’on va monter sur /backup (quelle inventivité):
> zfs create poolbackup/borg
> zfs set mountpoint=/backup poolbackup/borg
Et c’est fait, j’ai maintenant une partition /backup accessible, gérée par ZFS. C’est une zone avec redondance matérielle (deux disques), et le software vérifie l’intégrité des données.
Je n’en suis qu’à la découverte de ZFS, mais pour vérifier il y a zfs scrub qui va se charger de vérifier l’intégrité des données.
> zfs scrub
... quelques heures plus tard ...
> zpool status
pool: poolbackup
state: ONLINE
scan: scrub repaired 0B in 0 days 01:50:50 with 0 errors on Fri Nov 13 09:27:49 2020
config:
NAME STATE READ WRITE CKSUM
poolbackup ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
sda ONLINE 0 0 0
sdc ONLINE 0 0 0
errors: No known data errors
Donc un scrub a tourné et n’a pas trouvé d’erreurs.
Et puis iostat, pour quelques statistiques:
> zpool iostat -v poolbackup
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
poolbackup 487G 905G 10 8 7.78M 993K
mirror 487G 905G 10 8 7.78M 993K
sda - - 6 4 3.89M 497K
sdc - - 3 4 3.89M 497K
---------- ----- ----- ----- ----- ----- -----
Mes disques ne sont pas bien rapides…
Pour le monitoring, j’utilise un script similaire à celui proposé ici couplé à une crontab (au reboot, un check, et une fois par jour. Le check ne consomme pas de ressource). Envoi automatique par email.
La mise à jour de trop
Une mise à jour qui update le kernel, mais pas de module ZFS disponible pour ce kernel, et c’est le drame. Plus de ZFS. Donc, on liste ce qui est disponible:
grubby --info ALL
index=0
kernel="/boot/vmlinuz-4.18.0-240.el8.x86_64"
args="ro resume=/dev/mapper/cl_nestor-swap rd.lvm.lv=cl_nestor/root rd.lvm.lv=cl_nestor/swap rhgb quiet $tuned_params"
root="/dev/mapper/cl_nestor-root"
initrd="/boot/initramfs-4.18.0-240.el8.x86_64.img $tuned_initrd"
title="CentOS Stream (4.18.0-240.el8.x86_64) 8"
id="be1d49915e9f4e45a746790fb45ca583-4.18.0-240.el8.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64"
args="ro resume=/dev/mapper/cl_nestor-swap rd.lvm.lv=cl_nestor/root rd.lvm.lv=cl_nestor/swap rhgb quiet $tuned_params"
root="/dev/mapper/cl_nestor-root"
initrd="/boot/initramfs-4.18.0-193.28.1.el8_2.x86_64.img $tuned_initrd"
title="CentOS Linux (4.18.0-193.28.1.el8_2.x86_64) 8 (Core)"
id="be1d49915e9f4e45a746790fb45ca583-4.18.0-193.28.1.el8_2.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-be1d49915e9f4e45a746790fb45ca583"
args="ro resume=/dev/mapper/cl_nestor-swap rd.lvm.lv=cl_nestor/root rd.lvm.lv=cl_nestor/swap rhgb quiet"
root="/dev/mapper/cl_nestor-root"
initrd="/boot/initramfs-0-rescue-be1d49915e9f4e45a746790fb45ca583.img"
title="CentOS Linux (0-rescue-be1d49915e9f4e45a746790fb45ca583) 8 (Core)"
id="be1d49915e9f4e45a746790fb45ca583-0-rescue"
On positionne la version du kernel qui fonctionnait en valeur par défaut au démarrage - en attendant le bon module:
grubby --set-default /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64
The default is /boot/loader/entries/be1d49915e9f4e45a746790fb45ca583-4.18.0-193.28.1.el8_2.x86_64.conf with index 1 and kernel /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64
Un reboot, une vérification:
lsmod | grep zfs
lsmod | grep zfs
zfs 4202496 8
...
zpool status
pool: poolbackup
state: ONLINE
scan: scrub repaired 0B in 0 days 01:51:34 with 0 errors on Sun Nov 22 12:12:58 2020
config:
NAME STATE READ WRITE CKSUM
poolbackup ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
sda ONLINE 0 0 0
sdc ONLINE 0 0 0
errors: No known data errors
Youhou, c’est reparti.
Les services
Configuration réseau
Insérer ici la configuration réseau pour du statique
Interface : /etc/sysconfig/network-scripts/ifcfg-enp8s0
NetworkManager interface: nmcli
Installation du gestionnaire NetworkManager en mode texte:
dnf install NetworkManager-tui
nmcli device status
DEVICE TYPE STATE CONNECTION
enp8s0 ethernet connected enp8s0
lo loopback unmanaged --
wlp7s0 wifi unmanaged --
Serveur SSH
On commence par créer un user (fabien), qui sur un poste client génère une paire de clés publique/privée. On fait ensuite les choix suivants:
- transfert de clé publique avec ssh-copy-id pour l’utilisateur
- suppression du ssh en root
- pas de passsword
- reload
Sur le poste client:
ssh-keygen -t ed25519
Configuration dans /etc/ssh/sshd_config:
PasswordAuthentication no
PermitRootLogin no
Pour BorgBackup, il vaut mieux garder la connection alive:
ClientAliveInterval 30
ClientAliveCountMax 3
On recharge le serveur ssh:
systemctl reload sshd
Depuis le poste client, vérification que la connection à SSH fonctionne toujours, et sans mot de passe (c’est la paire de clés qui gère l’authentification).
Firewall
Ce n’est pas vraiment un service, mais il ne faut pas se tromper sinon sur une machine sans clavier et sans écran, on va avoir l’impression que plus rien ne fonctionne.
Le premier choix à faire c’est firewalld ou nftables ? En lisant la documentation officielle RHEL8 je choisis nftables parce que firewalld est en fin de vie.
Arrêt de firewalld:
> systemctl stop firewalld
> systemctl disable firewalld
> systemctl mask firewalld
La stratégie est simple (voire simpliste):
- on ferme tout: drop des paquets en input par défaut
- on ouvre les services qui sont nécessaires (par exemple TCP/22 pour SSH)
- on conserve les paquets qui font partie d’une connection en cours
Le fichier de règles est très fortement inspiré de celui proposé par le wiki nftables, avec quelques modifications pour la partie UpNP.
#!/usr/sbin/nft -f
flush ruleset
# List all IPs and IP ranges of your traffic filtering proxy source.
define SAFE_TRAFFIC_IPS = {
192.168.0.0/24
}
table inet firewall {
chain inbound {
# By default, drop all traffic unless it meets a filter
# criteria specified by the rules that follow below.
type filter hook input priority 0; policy drop;
# Allow traffic from established and related packets.
ct state established,related accept
# Drop invalid packets.
ct state invalid drop
# Allow loopback traffic.
iifname lo accept
iif lo accept comment "Accept any localhost traffic"
# Allow all ICMP and IGMP traffic, but enforce a rate limit
# to help prevent some types of flood attacks.
ip protocol icmp limit rate 4/second accept
ip6 nexthdr ipv6-icmp limit rate 4/second accept
ip protocol igmp limit rate 4/second accept
# Allow SSH on port 22.
tcp dport 22 accept
# Allow HTTP(S).
# -- From anywhere
# tcp dport { http, https } accept
# udp dport { http, https } accept
# -- From approved IP ranges only
tcp dport { http, https } ip saddr $SAFE_TRAFFIC_IPS accept
udp dport { http, https } ip saddr $SAFE_TRAFFIC_IPS accept
# Uncomment to allow incoming traffic on other ports.
# -- Allow Jekyll dev traffic on port 4000.
# tcp dport 4000 accept
# -- Allow Hugo dev traffic on port 1313.
# tcp dport 1313 accept
# adding for UPnP
tcp dport 58051 ip saddr $SAFE_TRAFFIC_IPS accept comment "Accept connection on BubbleUPnP server"
tcp dport 9790 ip saddr $SAFE_TRAFFIC_IPS accept comment "Minimserver connection"
tcp dport 9791 ip saddr $SAFE_TRAFFIC_IPS accept comment "Minimserver connection"
udp dport 1900 ip saddr $SAFE_TRAFFIC_IPS accept comment "Minimserver connection"
udp sport 1900 udp dport >= 1024 ip6 saddr { fd00::/8, fe80::/10 } meta pkttype host limit rate 4/second burst 20 packets accept comment "Accept UPnP IGD port mapping reply"
udp sport 1900 udp dport >= 1024 ip saddr { 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16 } meta pkttype host limit rate 4/second burst 20 packets accept comment "Accept UPnP IGD port mapping reply"
# add MPD port
tcp dport 6600 ip saddr $SAFE_TRAFFIC_IPS accept comment "MPD connection"
# add upmpdcli interface port
tcp dport 49152 ip saddr $SAFE_TRAFFIC_IPS accept comment "upmpdcli connection"
udp dport mdns ip6 daddr ff02::fb accept comment "Accept mDNS"
udp dport mdns ip daddr 224.0.0.251 accept comment "Accept mDNS"
# Uncomment to enable logging of denied inbound traffic
log prefix "[nftables] Inbound Denied: " flags all counter drop
}
chain forward {
# Drop everything (assumes this device is not a router)
type filter hook forward priority 0; policy drop;
# Uncomment to enable logging of denied forwards
# log prefix "[nftables] Forward Denied: " flags all counter drop
}
chain outbound {
# Allow all outbound traffic
type filter hook output priority 0; policy accept;
}
}
Donc on drope tout sauf SSH et BubbleUpNp. On conserve tout les paquets “établis” (established,related). Et puis au fur et à mesure de l’analyse des logs, on ouvre ceux qui semblent nécessaires (par exemple pour le ping).
Chargement:
> nft -f /etc/nftables/active.nft
Vérifications:
- la connection SSH est toujours possible, ouf …
- on peut accéder à l’interface du serveur BubbleUpNp. Cool.
- il n’y a rien d’autre qui marche.
Activation au démarrage:
> echo 'include "/etc/nftables/active.nft"' > /etc/sysconfig/nftables.conf
> systemctl enable nftables
Backup
Borg backup répond aux demandes:
- backup incrémental
- contenu du backup chiffré
- déduplication, donc gain de place
Documentation officielle : https://borgbackup.readthedocs.io/en/stable/
Des notes en français ici et là sur linuxfr.
Serveur
Côté serveur l’installation est simplissime:
> sudo yum install borgbackup
> borg --version
borg 1.1.14
Ensuite, j’ajoute un groupe borg, je crée un répertoire borg accessible en écriture à ce groupe, et j’ajoute les utilisateurs de borg à ce groupe:
> sudo groupadd borg
> sudo mkdir /backup/borg
> sudo chgrp borg /backup/borg
> chmod g+w /backup/borg
> sudo usermod -a -G borg fabien
Voilà. Rien d’autre.
Client
Côté client, on va faire attention à avoir exactement la même version que cell installée sur le serveur. Donc quel que soit l’OS (Mac ou Linux) on utilise le binaire proposé dans la version idoine:
> wget https://github.com/borgbackup/borg/releases/download/1.1.14/borg-linux64
> sudo mv borg-linux64 /usr/local/bin/borg
Note janvier 2021: mise à jour sur toutes les machines en version 1.1.15.
Pour un Mac, le plus simple est de passer par pip:
pip3 install "borgbackup==1.1.15"
L’installation est finie. Ensuite on initialise un repository sur le serveur de backup:
borg init --encryption=repokey-blake2 fabien@192.168.0.100:/backup/borg/fabien/T530
Cette commande demande un mot de passe. A conserver soigneusement, il est nécessaire à chaque connection. Il faut aussi une clé, qui a été générée lors de l’initialisation. Cette clé est pour l’instant présente côté serveur, on va l’exporter et la sauvegarder.
borg key export fabien@192.168.0.100:/backup/borg/fabien/T530 borg.key
La clé et le mot de passe sont à sauvegarder sur une autre machine: si le client crashe complètement et qu’on a besoin de restaurer une sauvegarde, il ne faut pas que la clé et le mot de passe soient sauvés uniquement sur le client qui a crashé. Bien évidemment en lieu sûr (par exemple un gestionnaire de mot de passe à la Bitwarden).
Ensuite on teste avec un répertoire la création d’un backup:
> borg create --stats fabien@192.168.0.100:/backup/borg/fabien/T530::{hostname}-{user}-{now} 'Calibre Library'/
Enter passphrase for key ssh://fabien@192.168.0.100/backup/borg/fabien/T530:
------------------------------------------------------------------------------
Archive name: fabien-T530-fabien-2020-11-04T20:23:10
Archive fingerprint: 6847a9adb71de8c9995ce5c624ca61c8123a7d3fe9cbc957ad91f5f1aa734473
Time (start): Wed, 2020-11-04 20:23:32
Time (end): Wed, 2020-11-04 20:24:34
Duration: 1 minutes 1.89 seconds
Number of files: 644
Utilization of max. archive size: 0%
------------------------------------------------------------------------------
Original size Compressed size Deduplicated size
This archive: 294.16 MB 270.14 MB 236.19 MB
All archives: 294.16 MB 270.14 MB 236.19 MB
Unique chunks Total chunks
Chunk index: 669 727
------------------------------------------------------------------------------
Donc ça a l’air de fonctionner, les data sauvegardées sont (un peu) compressées, et surtout dédupliquées. Mais la commande nécessite un mot de passe. Il y a plusieurs méthodes sur la doc officielle, j’utilise la plus simple, qui est l’export du mot de passe dans une vriable d’envirronement.
> vi ~/configuration/borg/passphrase
# insérer son mot de passe sans espace espace ni retour à la ligne
> chmod 400 ~/configuration/borg/passphrase
> export BORG_PASSPHRASE=`cat ~/configuration/borg/passphrase`
> borg create --stats fabien@192.168.0.100:/backup/borg/fabien/T530::{hostname}-{user}-{now} 'Calibre Library'/ configuration/
Cette fois plus besoin de mot de passe. On peut aussi simplifier l’adresse du serveur avec la variable BORG_REPO.
A mettre dans le .bashrc:
export BORG_PASSPHRASE=`cat ~/configuration/borg/passphrase`
export BORG_REPO='ssh://fabien@192.168.0.100/backup/borg/fabien/T530'
Plus besoin de mot de passe, plus besoin de path vers l’archive, il suffit de remplacer par ::
> borg list ::
fabien-T530-fabien-2020-11-11T21:29:34 Wed, 2020-11-11 21:29:35 [628dd2c190028d450a0f88fa383fc217862900cc2a37b9fee813adc25464b4e1]
fabien-T530-fabien-2020-11-13T23:04:49 Fri, 2020-11-13 23:04:50 [0001b4461c3e4b14d1ee37a6e203d200381ee79da7624f10f1398821788d9395]
Script de backup
Insérer ici un exemple de script de backup.
Récupérer ses fichiers
On peut déjà lister l’ensemble des backups dans un repository. Les prérequis sont:
- une connection SSH vers la machine de backup (user@machine)
- la variable BORG_REPO (ou est le repository ?)
- la variable BORG_PASSPHRASE (le mot de passe)
> export BORG_REPO=ssh://user@machine:/path/to/borg/repo
> export BORG_PASSPHRASE=`cat ~/configuration/borg/passphrase`
> borg list ::
fabien-T530-fabien-2020-11-11T14:27:06 Wed, 2020-11-11 14:27:07 [30c7ca4d537b466489ebdc3c8111c4c4fab4cb68bf8e6c720aca20afc32de191]
fabien-T530-fabien-2020-11-11T15:24:20 Wed, 2020-11-11 15:24:21 [483640a5b69dff5f31510f74dfb1357584f68b2657564499482027eb320a35ef]
fabien-T530-fabien-2020-11-11T16:11:31 Wed, 2020-11-11 16:11:32 [baa21cdbc36527aa0a49c2027d975a9b46fbb446f693ffc5d0aa0445beab71f3]
(la syntaxe :: est un raccourci d’accès, remplacé par $BORG_REPO).
On peut monter le backup comme un disque, avec un accès à un backup particulier, ou à l’ensemble des dates.
Montage de tous les backups:
> mkdir /home/fabien/tmp
> borg mount :: /home/fabien/tmp
> ls -l /home/fabien/tmp
total 0
drwxr-xr-x 1 fabien fabien 0 nov. 11 14:27 fabien-T530-fabien-2020-11-11T14:27:06
drwxr-xr-x 1 fabien fabien 0 nov. 11 15:24 fabien-T530-fabien-2020-11-11T15:24:20
drwxr-xr-x 1 fabien fabien 0 nov. 11 16:11 fabien-T530-fabien-2020-11-11T16:11:31
# do some action: find the missing file, copy back this file to the regular directory tree.
> borg umount /home/fabien/tmp
Premier backup des des photos
Un répertoire d’à peu près 500GO, avec des photos et des metadata. On envoie le backup:
------------------------------------------------------------------------------
Archive name: fabien-T530-fabien-2020-11-11T21:29:34
Archive fingerprint: 628dd2c190028d450a0f88fa383fc217862900cc2a37b9fee813adc25464b4e1
Time (start): Wed, 2020-11-11 21:29:35
Time (end): Thu, 2020-11-12 01:20:11
Duration: 3 hours 50 minutes 36.11 seconds
Number of files: 115645
Utilization of max. archive size: 0%
------------------------------------------------------------------------------
Original size Compressed size Deduplicated size
This archive: 497.84 GB 489.61 GB 438.09 GB
All archives: 564.48 GB 554.81 GB 461.37 GB
Unique chunks Total chunks
Chunk index: 271530 399147
------------------------------------------------------------------------------
Donc ça compresse peu (j’utilise lz4, l’algorithme par défaut), et ce n’est pas très rapide. Le facteur limitant est sûrement la vitesse d’écriture sur ces disques.
Backup : mission accomplie.