Chez ZeGuigui

Le blog d'un geek chasseur de licornes au clair de lune

Chez ZeGuigui
Technique

Premières migrations

La première chose à faire était bien entendu un inventaire de ce qui était à migrer :

  • les mails… mais ça je n’allais pas m’y attaquer en premier car l’installation devait être entièrement repensée !
  • des sites plus ou moins à l’abandon, résultats d’expérimentations passées avec tel ou tel logiciel
    • un extranet qui servait surtout de point d’entrée vers des outils d’administration
    • mon blog wordpress
    • un site « old » qui pointait vers mon ancien site avant migration wordpress… à ne pas totalement oublier car certains billets référencent des visuels présents sur ce site !
    • une photothèque
  • une instance mastodon, et peut-être recrée mon instance personnelle
  • un serveur subversion qu’il faut archiver quelque part
  • un PHP Server Monitor qui me sert à surveiller des sites externes
  • du monitoring cacti
  • un serveur MQTT qui sert pour OwnTracks
  • un gros nextcloud

Afin de pouvoir gérer tout ça je sais que je vais avoir besoin – à un moment ou un autre – d’une base de données MariaDB. Je peux soit instancier une base par projet (sur l’host docker) soit décider de centraliser la partie MariaDB. C’est cette direction que je choisis. Pas de difficulté particulière, je crée un container LXC à partir du modèle debian-12-turnkey-mysql_18 et quelques instants plus tard j’ai une instance MariaDB opérationnelle.

Migration WordPress

Pour ma migration WordPress je décider d’utiliser l’image docker WordPress officielle. Mon installation n’a rien de particulier et l’image officielle doit suffire. Je commence donc par faire une recopie du site existant via un rsync de l’ancien serveur vers le nouveau. Au passage je recopie aussi l’ancien site (le old) et je fais un backup de la base de données.

Création d’une base wordpress dans l’instance MariaDB, création du user, import du backup, on est prêt à paramétrer l’image docker ! Comme pour tout ce que je mets dans Docker je fais un fichier docker-compose qui va ressembler à quelque chose comme ça :

secrets:
  db_password:
    file: ./secrets/db_password.txt
  wp_auth_key:
    file: ./secrets/wp_auth_key.txt
  wp_auth_salt:
    file: ./secrets/wp_auth_salt.txt
  wp_logged_in_key:
    file: ./secrets/wp_logged_in_key.txt
  wp_logged_in_salt:
    file: ./secrets/wp_logged_in_salt.txt
  wp_nonce_key:
    file: ./secrets/wp_nonce_key.txt
  wp_nonce_salt:
    file: ./secrets/wp_nonce_salt.txt
  wp_secure_auth_key:
    file: ./secrets/wp_secure_auth_key.txt
  wp_secure_auth_salt:
    file: ./secrets/wp_secure_auth_salt.txt

services:
  wordpress:
    image: wordpress:6-php8.1
    container_name: wordpress-zeguigui
    restart: always
    secrets:
      - db_password
      - wp_auth_key
      - wp_auth_salt
      - wp_logged_in_key
      - wp_logged_in_salt
      - wp_logged_in_salt
      - wp_nonce_key
      - wp_nonce_salt
      - wp_secure_auth_key
      - wp_secure_auth_salt
    environment:
      - "WORDPRESS_DB_HOST=192.168.6.2"
      - "WORDPRESS_DB_USER=wordpress"
      - "WORDPRESS_DB_NAME=wordpress"
      - "WORDPRESS_DB_PASSWORD_FILE=/run/secrets/db_password"
      - "WORDPRESS_AUTH_KEY_FILE=/run/secrets/wp_auth_key"
      - "WORDPRESS_SECURE_AUTH_KEY_FILE=/run/secrets/wp_secure_auth_key"
      - "WORDPRESS_LOGGED_IN_KEY_FILE=/run/secrets/wp_logged_in_key"
      - "WORDPRESS_NONCE_KEY_FILE=/run/secrets/wp_nonce_key"
      - "WORDPRESS_AUTH_SALT_FILE=/run/secrets/wp_auth_salt"
      - "WORDPRESS_SECURE_AUTH_SALT_FILE=/run/secrets/wp_secure_auth_salt"
      - "WORDPRESS_LOGGED_IN_SALT_FILE=/run/secrets/wp_logged_in_salt"
      - "WORDPRESS_NONCE_SALT_FILE=/run/secrets/wp_nonce_salt"
      - "WORDPRESS_CONFIG_EXTRA_FILE=/etc/wordpress/conf"
    extra_hosts:
      - "www.zeguigui.com:192.168.6.1"
    volumes:
      - ./data/wordpress:/var/www/html
      - ./conf/uploads.ini:/etc/php/conf.d/uploads.ini
      - ./conf/wordpress-extra.php:/etc/wordpress/conf:ro
      - ./data/filmotech:/var/www/filmotech
      - ./data/html/weblog/Images:/var/www/html/weblog/Images:ro
      - ./data/html/weblog/uploads:/var/www/html/weblog/uploads:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.wordpress-http.entrypoints=websecure,web"
      - "traefik.http.routers.wordpress-http.rule=Host(`www.zeguigui.com`,`wp.zeguigui.com`,`zeguigui.com`)"
      - "traefik.http.routers.wordpress-http.tls.certresolver=zeguigui"
      - "traefik.http.routers.wordpress-http.tls.domains[0].main=*.zeguigui.com"
      - "traefik.http.services.wordpress-service.loadbalancer.server.port=80"
    networks:
      - proxy

networks:
  proxy:
    external: true

Quelques explications s’imposent !

La première partie du fichier est une reprise des données de mon fichier de configuration wordpress initial. Ce sont des secrets et idéalement ils ne doivent pas apparaître en clair dans le fichier docker-compose. La partie service indique qu’on va utiliser ces secrets. Ils sont alors accessibles dans l’image sous forme de fichiers dans /run/secrets/nom_du_secret. C’est là où les gens de WordPress ont bien fait le travail car de nombreuses variables d’environnement peuvent-être suffixées par _FILE pour indiquer qu’on va aller chercher la valeur dans un fichier !

Côté variables d’environnement on indique le nécessaire et on ajoute une référence à un fichier de configuration complémentaire.

La partie extra_hosts est nécessaire pour que les appels de wordpress à lui-même (notamment pour les tâches planifiées) fonctionnent. En effet comme on est dans un réseau NATé on ne peut pas utiliser l’IP externe pour reboucler en interne. On indique donc au container que lorsqu’il veut joindre le site il faut utiliser cette adresse IP. Concrètement cela va ajouter une ligne dans le /etc/hosts du container au démarrage. L’autre option – pour éviter cette gymnastique – serait d’utiliser un DNS qui retourne les IP internes mais au moment de la migration je n’avais pas encore ajouté de DNS à l’infrastructure !

La partie volumes indique les mappings de base. J’ai ajouté un fichier de configuration php pour autoriser des uploads un peu plus gros. Le fichier de configuration wordpress-extra.php permet de référencer l’instance redis présente dans l’infra docker et utiliser le cache objets. Le reste c’est de l’accès aux données historiques.

Les labels permettent d’indiquer à Traefik que cette image docker va vouloir exposer ses services au travers du reverse proxy. Rien de bien sorcier ici : on indique qu’on accepte les connexions depuis les ports http et https (le http est redirigé vers https), les noms de domaines valides, le certResolver permettant de générer les certificats SSL et le type de certificat à utiliser (ici un wildcard). Enfin on indique que dans l’image le serveur http tourne sur le port 80.

Enfin on se positionne sur le réseau proxy pour que le reverse proxy communique facilement avec le wordpress.

Il restait néanmoins une dernière étape à réaliser ! En effet comme le /var/www/html existe au démarrage l’image ne recopie pas ses fichiers de base et on a donc pas le fichier wp-config.php qui permet de lire les variables d’environnement. J’ai donc repris ce fichier pour écraser celui de mon ancien site ! À noter que les montées de version de wordpress ne se font pas en mettant à jour l’image docker car le site est persisté en dehors de l’image. Il faut donc faire une mise à niveau depuis l’image elle-même, comme on le ferait avec un wordpress hébergé sur un serveur classique.

On lance via docker compose up -d et… tadaaam on a un wordpress qui fonctionne !

Migration Mastodon

La migration de l’instance mastodon était relativement aisée, il tournait déjà sous docker sur l’ancien serveur. Néanmoins je souhaiter mutualiser l’utilisation du redis existant dans l’infra docker. Au final j’ai juste modifier le docker-compose de mastodon. Voici à quoi ressemble le fichier docker-compose :

services:
  db:
    restart: unless-stopped
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - default
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - ./data/postgres14:/var/lib/postgresql/data
    environment:
      - 'POSTGRES_HOST_AUTH_METHOD=trust'

  web:
    image: ghcr.io/mastodon/mastodon:${MASTODON_VERSION}
    restart: unless-stopped
    env_file:
      - .env
      - .env.production
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    networks:
      - proxy
      - default
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    depends_on:
      - db
    volumes:
      - ./data/public/system:/mastodon/public/system
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mastodon-example.entrypoints=websecure"
      - "traefik.http.routers.mastodon-example.rule=Host(`mastodon.example.com`)"
      - "traefik.http.routers.mastodon-example.tls=true"
      - "traefik.http.routers.mastodon-example.tls.certresolver=zeguigui"
      - "traefik.http.routers.mastodon-example.tls.domains[0].main=mastodon.example.com"
      - "traefik.http.routers.mastodon-example.service=mastodon-example"
      - "traefik.http.services.mastodon-example.loadbalancer.server.port=3000"
      - "traefik.docker.network=proxy"
  streaming:
    image: ghcr.io/mastodon/mastodon:${MASTODON_VERSION}
    restart: unless-stopped
    env_file:
      - .env
      - .env.production
    command: node ./streaming
    networks:
      - default
      - proxy
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
    depends_on:
      - db
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mastodon-example-api.entrypoints=websecure"
      - "traefik.http.routers.mastodon-example-api.rule=(Host(`mastodon.example.com`) && PathPrefix(`/api/v1/streaming`))"
      - "traefik.http.routers.mastodon-example-api.tls=true"
      - "traefik.http.routers.mastodon-example-api.tls.certresolver=zeguigui"
      - "traefik.http.routers.mastodon-example-api.tls.domains[0].main=mastodon.example.com"
      - "traefik.http.routers.mastodon-example-api.service=mastodon-example-api"
      - "traefik.http.services.mastodon-example-api.loadbalancer.server.port=4000"
      - "traefik.docker.network=proxy"
  sidekiq:
    image: ghcr.io/mastodon/mastodon:${MASTODON_VERSION}
    restart: unless-stopped
    env_file:
      - .env
      - .env.production
    command: bundle exec sidekiq
    depends_on:
      - db
    networks:
      - default
    volumes:
      - ./data/public/system:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
networks:
  proxy:
    external: true

Contrairement à WordPress je n’ai pas instancié un PostgreSQL mutualisé pour l’ensemble des services. J’ai utilisé un fichier .env pour indiquer la version de mastodon à utiliser et le .env.production est le fichier habituel de mastodon qui contient les secrets. J’ai juste modifié le .env.production pour la partie redis en indiquant mon instance docker. J’ai aussi paramétré l’envoi des mails.

Côté Traefik on utilise les labels pour rendre le site et le streaming disponible. Là encore on se positionne sur le réseau proxy pour plus de simplicité.

Afin de simplifier la migration j’ai commencé par faire du ménage côté cache Mastodon ce qui a économisé pas mal d’octets au passage. Pour cela j’ai suivi ces instructions mais sur une base de 30 jours au lieu de 4. Un coup de rsync pour les fichiers statiques et – après arrêt de l’instance mastodon originale – pour les fichiers de la base de données. On lance mastodon sur le nouveau serveur, on vérifie que tout est OK… et c’est reparti !

Pièges et conclusion !

La migration des sites web semble simple si on respect bien toutes les étapes. La migration wordpress par exemple semblait s’être bien déroulée mais les actions planifiées, la mise à jour Woopress, etc. ne fonctionnaient pas. C’était parce qu’il me manquait la partie extra_hosts et que les requêtes internes échouaient. J’ai dans un premier temps « rusé » en ajoutant une crontab avant de comprendre que le problème venait du réseau NAT qui n’est pas réentrant. L’ajout du extra_hosts a résolu complètement le problème et WordPress était enfin opérationnel.

Côté Mastodon le principal enseignement est qu’il va consommer de plus en plus d’espace disque pour stocker en local les images, visuels, etc. Il faut donc penser à mettre en place une procédure de nettoyage et idéalement il faut l’automatiser ce que je n’ai pas encore fait !

Photo d’illustration de l’article par Lia Maaskant sur Unsplash