Douze constructions

Déplacer deux téraoctets entre deux de nos sites sans interruption

Une pré-copie, une réplique en streaming et une bascule par procuration, avec le réglage TCP qui décide si un transfert longue distance prend une heure ou une journée.

Ce que cela permet de réaliser

Deux téraoctets de fichiers et une base de données Postgres en production sont déplacés d'un site à un autre, tandis que l'application continue de répondre aux requêtes. La méthode repose sur une pré-copie massive pendant que la source continue de servir, une réplication en continu qui reste synchronisée, et une bascule mesurée en secondes, l'ancien hôte agissant comme proxy vers le nouveau jusqu'à ce que le DNS ait convergé.

La distance est le point intéressant. Entre AMS-01 et FRA-01, l'aller-retour est de sept millisecondes et un seul flux suffit à saturer le port. Pour la même copie vers GRU-01, où l'aller-retour approche deux cents millisecondes, un transfert non optimisé rampe à une fraction de ce que vous payez, tandis que les deux machines sont presque inactives. Cet écart dépend entièrement de la quantité de données qu'une seule connexion peut avoir en vol sans acquittement.

Avant de commencer

  • Des instances source et cible, un tunnel entre elles, et un accès root sur les deux.
  • Un DNS que vous contrôlez, avec le TTL du enregistrement réduit à soixante secondes au moins un jour à l'avance. C'est l'étape que l'on saute souvent, pour ensuite attendre six heures.
  • Assez d'espace disque sur la cible pour l'ensemble du jeu de données plus la réplique.

1. Évaluer les capacités de la liaison

Le produit bande-passante-délai détermine tout. Multipliez le temps aller-retour par le débit souhaité et vous obtenez la quantité de données qui doit être en vol sans acquittement à tout moment :

ping -c20 target.example.com | tail -1
CheminRTTEn vol pour 1 Gbit/sFenêtre par défaut du noyau
AMS-01 vers FRA-017 ms0,9 MoSuffisante
AMS-01 vers NYC-0176 ms9,5 MoInsuffisante
AMS-01 vers SIN-01168 ms21 MoTrès insuffisante
AMS-01 vers GRU-01196 ms24,5 MoTrès insuffisante

Sur les deux machines :

cat > /etc/sysctl.d/75-transfer.conf <<EOF
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 262144 134217728
net.ipv4.tcp_wmem = 4096 262144 134217728
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
net.ipv4.tcp_mtu_probing = 1
EOF
sysctl --system

BBR plutôt que le contrôle de congestion par défaut est important sur les longs chemins dès qu'il y a un peu de perte, car les algorithmes basés sur la perte interprètent un paquet perdu unique comme une congestion et réduisent leur fenêtre de moitié. Sur deux cents millisecondes, la récupération prend des secondes, et cela se répète.

Mesurez avant de confier deux téraoctets à une hypothèse :

# target
apt install -y iperf3 && iperf3 -s
# source
iperf3 -c target.example.com -P 8 -t 30

Huit flux parallèles devraient approcher la vitesse du port. Si un flux atteint cent mégabits et que huit atteignent huit cents, c'est que les réglages de fenêtre n'ont pas pris effet. Une fois ceux-ci actifs, un flux unique devrait obtenir l'essentiel du débit à lui seul.

2. Pré-copie en masse

Exécutez cela sur une source en production. Cela prendra des heures, et rien n'est interrompu pendant ce temps :

apt install -y rsync
rsync -aHAX --numeric-ids --partial --inplace --info=progress2 \
  -e "ssh -c [email protected] -o Compression=no" \
  /srv/data/ [email protected]:/srv/data/

Le choix du chiffrement n'est pas une superstition. Sur ces processeurs, le mode GCM accéléré par le matériel déplace plusieurs gigaoctets par seconde et par cœur, alors que le chiffrement par défaut négocié par certains clients est nettement plus lent et transforme votre transfert en un test monocœur. La compression est désactivée car les données sont déjà compressées et, si elles ne l'étaient pas, le processeur passerait son temps à compresser au lieu de déplacer des octets.

Pour des millions de petits fichiers, un seul flux n'est pas adapté. Divisez par répertoire de premier niveau et lancez plusieurs flux :

ls /srv/data | xargs -P 8 -I{} rsync -aHAX --numeric-ids \
  -e "ssh -c [email protected] -o Compression=no" \
  /srv/data/{}/ [email protected]:/srv/data/{}/

Huit copies parallèles sur huit cœurs dédiés est le bon nombre ici ; seize n'est pas deux fois plus rapide et rendra la machine source désagréable pour ce qu'elle continue de servir.

3. La base de données, répliquée plutôt que copiée

Dumper et restaurer deux cents gigaoctets de Postgres implique une longue fenêtre de perte d'écritures. Diffusez plutôt. Sur la source :

sudo -u postgres psql -c "CREATE USER repl WITH REPLICATION PASSWORD '<a long password>';"
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('target_site');"
echo "host replication repl 10.9.0.2/32 scram-sha-256" >> /etc/postgresql/17/main/pg_hba.conf
systemctl reload postgresql

Sur la cible :

systemctl stop postgresql
rm -rf /var/lib/postgresql/17/main/*
sudo -u postgres pg_basebackup -h 10.9.0.1 -U repl -D /var/lib/postgresql/17/main \
  -S target_site -X stream -R -P -c fast
systemctl start postgresql

L'option -R écrit les paramètres de connexion et le fichier signal standby, ainsi la cible se lance en tant que réplique et commence à suivre. À partir de là, elle reste à une ou deux secondes de la source indéfiniment, ce qui signifie que la bascule n'implique plus du tout de déplacer la base de données.

4. Bascule

Lancez une sonde depuis une troisième machine et laissez-la tourner. C'est à la fois votre vérification et votre enregistrement du coût de la bascule :

while true; do curl -s -o /dev/null -w "%{http_code} %{time_total}\n" https://app.example.com/health; sleep 1; done | tee cutover.log

Ensuite, dans cet ordre et sans pause :

# 1. final delta, source still live
rsync -aHAX --numeric-ids --delete -e "ssh -c [email protected]" /srv/data/ [email protected]:/srv/data/

# 2. stop writes on the source
systemctl stop app

# 3. one more delta, now tiny
rsync -aHAX --numeric-ids --delete -e "ssh -c [email protected]" /srv/data/ [email protected]:/srv/data/

# 4. promote the replica
ssh [email protected] "sudo -u postgres pg_ctl promote -D /var/lib/postgresql/17/main"

# 5. start the application on the target
ssh [email protected] "systemctl start app"

Les étapes de deux à cinq prennent quelques secondes. Maintenant, empêchez l'ancien hôte de servir un contenu périmé pendant la propagation DNS, en le transformant en proxy vers le nouveau :

server {
  listen 443 ssl;
  server_name app.example.com;
  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  location / {
    proxy_pass https://10.9.0.2;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}

C'est ce qui rend toute l'opération non perturbante. Toute personne qui résout encore l'ancienne adresse obtient une réponse correcte via le tunnel plutôt qu'une erreur ou, pire, une copie des données qui a cessé d'être mise à jour lors de la bascule. Maintenant, modifiez l'enregistrement DNS.

Vérification

Commencez par le journal de la sonde que vous avez collecté :

sort cutover.log | uniq -c | sort -rn | head

Chaque ligne doit afficher 200. Une poignée de réponses plus lentes pendant la bascule est attendue ; tout code non-200 signifie que l'étape cinq a démarré après l'étape deux avec trop d'écart, et le proxy de la section précédente permet de réduire cet écart à néant.

Ensuite, prouvez que les données sont identiques et non simplement présentes :

rsync -aHAXn --delete --itemize-changes /srv/data/ [email protected]:/srv/data/ | head
du -s --bytes /srv/data
ssh [email protected] "du -s --bytes /srv/data"

Un essai à vide qui ne liste rien signifie que les deux arborescences concordent sur le contenu, les permissions, la propriété et les attributs étendus. Les totaux d'octets doivent correspondre exactement.

Base de données ensuite :

ssh [email protected] "sudo -u postgres psql -c 'SELECT pg_is_in_recovery();'"
ssh [email protected] "sudo -u postgres psql app -c 'SELECT count(*) FROM orders;'"
sudo -u postgres psql app -c "SELECT count(*) FROM orders;"

La récupération doit maintenant être désactivée sur la cible, et les comptes de lignes doivent concorder. Enfin, confirmez que le trafic a réellement été déplacé plutôt que d'être silencieusement proxyé pour toujours :

dig +short app.example.com
tail -f /var/log/nginx/access.log | wc -l   # on the old host, an hour later

Une fois l'ancien hôte inactif pendant un TTL complet plus une marge confortable, retirez le proxy et détruisez l'instance. Ne le détruisez pas le même après-midi ; l'assurance la moins chère de toute cette procédure est de laisser la machine source intacte et éteinte pendant une semaine.

Après

Deux téraoctets sur un chemin longue distance bien réglé représentent environ une heure à dix gigabits et plutôt plus en pratique, car la source continue de servir. Entre des sites européens voisins, prévoyez un après-midi ; entre continents, lancez le transfert le soir et effectuez la bascule le lendemain. La page des emplacements liste les temps aller-retour que nous mesurons, et ce sont ces chiffres à utiliser dans le tableau de l'étape un.

Prêt quand vous l'êtes

Choisissez une ville. Choisissez une taille. Payez en crypto.

Aucun formulaire sur votre identité, pas d'attente d'approbation humaine, pas d'appel téléphonique pour vérifier quoi que ce soit. La facture est réglée et les identifiants arrivent dans votre boîte mail.