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.

Précédent
Suivant