Le blog technique d’Ineat (ce blog-là même, donc), tourne sur une infrastructure des plus simple. Il est hébergé chez AWS et utilise le service de VPS (Virtual Private Server) nommé Lightsail.

Nous voulions un hébergement avec peu de maintenance et un coût fixe (idéalement faible). La solution proposée par AWS, Lightsail, répondait à ces critères : tarifs bas et fixes, serveur disponible en quelques dizaines de secondes avec la distribution de notre choix et, cerise sur le gâteau, WordPress d’installé.

Cela fait maintenant plusieurs années que le blog est hébergé grâce à cette solution sans aucune maintenance de notre part, excepté lors des indispensables mises à jour logicielles, notamment celle de PHP. Le processus est alors plus lourd que si nous gérions nous même notre serveur, puisque la distribution installée, Bitnami, n’offre pas la possibilité de mettre à jour PHP. 

La procédure consiste à créer une nouvelle instance Lightsail et utiliser le plugin WordPress de migration « All-in-One WP Migration ». A première vue cette solution parait optimale puisqu’elle se réalise à l’aide de l’interface graphique de WordPress. Pour l’avoir faite plusieurs fois, elle nécessite des actions supplémentaires, non documentées. 

Parmi ces actions on citera : 

  • La modification de la configuration de PHP pour augmenter la taille maximale des fichiers uploadés (il est fort probable que votre archive de migration dépasse la limite par défaut de 80 Mo) ;
  • La migration des fichiers de configuration Apache des virtual hosts ;
  • La migration des fichiers concernant le certificat SSL.

En plus d’être fastidieux, toute action manuelle est source d’erreur. Comme chez Ineat, on aime bien tout automatiser, j’ai décidé de rédiger un playbook Ansible qui automatise toute la procédure de migration.

Dans la suite de cette article nous allons voir la construction du playbook et le raisonnement associé. Nous partons du principe que vous êtes déjà familier avec l’environnement AWS et avez déjà manipulé des VPS Lightsail.

Spécifications de la migration WordPress

Je veux pouvoir migrer facilement une instance Lightsail de WordPress avec le moins d’actions possible. 

Les étapes de l’automatisation complète de cette migration sont les suivantes :

  1. Créer la nouvelle instance ;
  2. Migrer l’installation de la précédente sur la nouvelle ;
  3. Détacher l’IP statique de l’ancienne instance (si vous n’utilisez pas d’IP statique c’est le moment d’en créer une) ;
  4. Attacher l’IP statique sur la nouvelle ;
  5. Supprimer l’ancienne instance.

Avant d’aller plus loin et de débuter la rédaction du playbook il faut s’assurer qu’il existe des modules Ansible permettant de réaliser les actions précédentes. Heureusement pour nous, la collection AWS dispose d’un module de gestion des instances Lightsail ainsi qu’un module de gestion des IP statiques.

Leurs documentations nous apprend que le module de gestion des IP statiques n’offre pas la possibilité d’attacher ou détacher une IP à une instance. Est-ce que ce manque de fonctionnalités nous bloque ? Il faut se demander si nous voulons vraiment tout automatiser.

Illustration par Storyset

En suivant la procédure décrite précédemment, si une étape de la migration ne se déroule pas correctement, il y a un risque de se retrouver avec une version non fonctionnelle en production, et pas de moyen de revenir rapidement à la version précédente. Par exemple, nous ne sommes pas à l’abri de voir sur la nouvelle instance des changements de chemins ou de permissions avec une version plus récente de Bitnami.

Préférant contrôler le bon fonctionnement de la nouvelle instance, le playbook Ansible prendra en charge les deux premières étapes de la procédure.

Une fois son exécution terminée, je veux m’assurer que la migration s’est effectuée correctement. Pour ça je modifie le fichier hosts (/etc/hosts) pour y associer l’IP du nouveau serveur au nom de domaine du blog. Après cette vérification, j’associe manuellement l’IP statique à la nouvelle instance et supprime (ou éteint) la précédente.

Notez que si vous voulez automatiser entièrement la procédure, le client python d’AWS, boto3 le permet. Une implémentation de ces deux actions dans le module ne demanderait pas énormément de travail.

Rédaction du playbook Ansible

Le playbook sera découpé logiquement en trois « plays » :

  1. La gestion des instances Lightsail à partir de notre contrôleur (la machine qui exécute le playbook) ;
  2. La récupération des fichiers et données sur l’instance précédente ;
  3. Le transfert des fichiers sur la nouvelle instance.

La commande ansible-playbook aura pour arguments supplémentaires les variables du nom de la précédente instance et du chemin d’accès à la clé SSH :

ansible-playbook install.yml --extra-vars "lightsail_private_key_file=/path/to/.ssh/lightsail-key.pem previous_instance_name=blog.ineat-group.com"

Avant de passer à la rédaction du playbook, il faut disposer d’une machine configurée pour utiliser l’environnement AWS.

Configuration de l’environnement AWS

Afin que le playbook Ansible puisse manipuler l’environnement Lightsail, il est nécessaire que celui-ci s’authentifie auprès de l’API. Les modules de la collection AWS offrent plusieurs façons d’y parvenir. J’ai opté pour la configuration du CLI AWS (dont nous aurons besoin plus tard). Cela évite d’avoir à spécifier les propriétés « access_key » et « secret_key » à chaque appel à un module AWS.

Des moyens différents pour gérer l’authentification existent tels qu’un rôle IAM temporaire. La clé d’API me semblait appropriée et facile à configurer.

Pour créer une clé d’accès et configurer le CLI AWS avec, référez vous à la documentation « Créer une clé d’accès pour utiliser l’API Amazon Lightsail ou l’AWS Command Line Interface ».

Maintenant que la connexion à l’environnement Lightsail est configurée, il faut pouvoir se connecter en SSH à l’instance existante. À la première création d’un serveur, une clé SSH par défaut est créée, elle se trouve dans votre compte, onglet « SSH keys ».

Interface de gestions des clés SSH d’un compte AWS Lightsail

Si vous n’avez pas de clé SSH, créez-en une maintenant.

La gestion de l’authentification est terminée.

Création des instances Lightsail WordPress

Dans ce premier play, nous allons récupérer l’IP de l’instance existante pour pouvoir s’y connecter ultérieurement, puis nous créons la nouvelle instance.

- hosts: localhost
  name: Manage lightsail servers
  vars:
    new_instance_name: blog.ineat-group.com_{{ ansible_date_time.date }}

  tasks:
  - name: Get previous instance IP
    community.aws.lightsail:
      state: present
      name: '{{ previous_instance_name }}'
      region: eu-west-3
      zone: eu-west-3a
      blueprint_id: wordpress
      bundle_id: micro_2_0
    register: previous_instance

  - name: Add previous_instance to hosts list
    ansible.builtin.add_host:
      name: '{{ previous_instance.instance.public_ip_address }}'
      groups: previous_lightsail
      ansible_ssh_private_key_file: '{{ lightsail_private_key_file }}'
      ansible_user: bitnami

  - name: Create a new blog instance
    community.aws.lightsail:
      state: present
      name: '{{ new_instance_name }}'
      region: eu-west-3
      zone: eu-west-3a
      blueprint_id: wordpress
      bundle_id: micro_2_0
    register: new_instance

  - name: Add new_instance to hosts list
    ansible.builtin.add_host:
      name: '{{ new_instance.instance.public_ip_address }}'
      groups: new_lightsail
      ansible_ssh_private_key_file: '{{ lightsail_private_key_file }}'
      ansible_user: bitnami

  - name: Wait for VM to become available
    ansible.builtin.wait_for_connection:
      timeout: 120
    delegate_to: '{{ new_instance.instance.public_ip_address }}'

Attardons-nous tout d’abord sur les deux premières tâches relatives au serveur existant.

Il est obligatoire de paramétrer le module « community.aws.lightsail » avec les valeurs actuelles du serveur sinon un nouveau serveur sera créé. Pour connaitre la valeur de la propriété « bundle_id » qui correspond à la taille de l’instance, vous pouvez exécuter la commande aws lightsail get-bundles --region eu-west-3.

Nous devons ensuite ajouter dynamiquement l’adresse IP de l’instance à l’inventaire Ansible pour pouvoir s’y connecter dans le second play. C’est le module « ansible.builtin.add_host » qui s’en charge. Nous rattachons le nouvel host à un groupe. Sans ça il ne serait pas possible de s’y connecter dans un autre play, il faudrait assigner l’adresse IP à une variable via le module « ansible_facts » et appeler la variable magique « hostvars ».

Les deux étapes suivantes sont en tout point similaires.

Avant de se connecter à la nouvelle instance dans le second play, il est nécessaire d’attendre qu’elle soit disponible. C’est le but du module « wait_for_connection ».

Récupération des données et des fichiers à migrer

Pour migrer une instance de WordPress, nous avons besoin : 

  • Du répertoire wp-content ;
  • Du fichier de configuration wp-config.php ;
  • Du fichier de configuration d’Apache .htaccess ;
  • D’un dump de la base de données ;
  • Des fichiers du certificat SSL ;
  • Des fichier de la configuration Apache relative à WordPress.

Tous ces dossiers et fichiers vont être compressés dans une archive qui sera ensuite récupérée pour être transférée sur la nouvelle instance.

- hosts: previous_lightsail
  name: Fetch files and data on current server

  tasks:
    - name: Get database password
      ansible.builtin.shell: 'cat /home/bitnami/bitnami_application_password'
      register: db_password

    - name: Dump database
      ansible.builtin.shell: 'mysqldump -uroot -p{{ db_password.stdout }} bitnami_wordpress > /home/bitnami/wordpress.sql'

    - name: Gzip files
      ansible.builtin.shell: >
        tar czf /tmp/prev_ligthsail_wp_files.gz 
        /bitnami/wordpress
        /opt/bitnami/wordpress/.htaccess
        /opt/bitnami/apache/conf/bitnami/certs/ineat-group.com.crt
        /opt/bitnami/apache/conf/bitnami/certs/ineat-group.com.key
        /opt/bitnami/apache/conf/bitnami/certs/GandiStandardSSLCA2.pem
        /opt/bitnami/apache/conf/vhosts/wordpress-vhost.conf
        /opt/bitnami/apache/conf/vhosts/wordpress-https-vhost.conf
        /home/bitnami/wordpress.sql
      become: true

    - name: Fetch the compressed file
      ansible.builtin.fetch:
        src: /tmp/prev_ligthsail_wp_files.gz
        dest: /tmp/
        flat: true

Pour effectuer un dump de la base de données le mot de passe root est requis. Avec bitnami celui-ci se trouve dans le fichier /home/bitnami/bitnami_application_password.

Le dump est exécuté via le module « shell ». Un module MySQL dédié existe : « community.mysql.mysql_db ». Il offre plus de contrôle, mais requiert d’installer des dépendances supplémentaires.

La tâche de compression des fichiers utilise, elle aussi, le module « shell » bien qu’il existe un module dédié « community.general.archive ». Dans le cas du blog, l’archive générée pèse plus de 4 Go. Le module d’archive étant une surcouche Python, la commande consomme trop de mémoire et se termine par un sigkill système (pendant ce temps le serveur ne répond plus). C’est pourquoi je passe par le module « shell » pour utiliser directement le binaire tar.

Transfert de l’archive sur la nouvelle instance

Il ne reste plus qu’à transférer l’archive précédemment créée et à l’extraire. Dans le fichier de configuration WordPress wp-config.php, il faut penser à remplacer le mot de passe de la base de données de la précédente instance par le nouveau. 

- hosts: new_lightsail
  name: Transfer files and data to the new server
  vars:
    wp_dir: /opt/bitnami/wordpress
  
  tasks:
    - name: Get current bitnami_wordpress db password
      ansible.builtin.shell: "sed -n \"s/^define( 'DB_PASSWORD', '\\(.*\\)' );$/\\1/p\" {{ wp_dir }}/wp-config.php"
      register: current_db_pwd

    - name: "Delete bitnami's wordpress content directory"
      ansible.builtin.file:
        path: '/bitnami/wordpress'
        state: absent
      become: true

    - name: Extract compress file
      ansible.builtin.unarchive:
        src: /tmp/prev_ligthsail_wp_files.gz
        dest: /
      become: true

    - name: Get database password
      ansible.builtin.shell: 'cat /home/bitnami/bitnami_application_password'
      register: db_password

    - name: Import database dump
      ansible.builtin.shell: 'mysql -uroot -p{{ db_password.stdout }} bitnami_wordpress < /home/bitnami/wordpress.sql'

    - name: Replace password in copied config file
      ansible.builtin.lineinfile:
        path: '{{ wp_dir }}/wp-config.php'
        regexp: "^define\\( 'DB_PASSWORD', '(.*)' \\);$"
        line: "define( 'DB_PASSWORD', '{{ current_db_pwd.stdout }}' );"
      become: true

    - name: Restart apache
      ansible.builtin.shell: '/opt/bitnami/ctlscript.sh restart apache'
      become: true

La première tâche fait appel à la commande « sed » pour obtenir le mot de passe de la nouvelle base de données. Les expressions régulières utilisées par sed dans des fichiers YAML ne sont pas évidentes à écrire à cause des caractères à échapper. Le YAML étant un sous-ensemble de JSON l’astuce consiste à enregistrer l’expression à échapper dans un fichier et d’utiliser l’utilitaire « jq » pour l’échapper : cat /tmp/sed-ansible | jq -R 

Lorsque les fichiers et la base de données sont importés, nous faisons appel au module « ansible.builtin.lineinfile » pour remplacer le mot de passe de la base de données par celui stocké dans la variable « current_db_pwd ».

Il ne reste plus qu’à redémarrer Apache dans le but de prendre en compte le changement de configuration.

Vérification du bon fonctionnement du WordPress à jour

Comme indiqué au début de cet article, une fois le playbook Ansible exécuté, je m’assure du bon fonctionnement de la nouvelle instance en modifiant le fichier hosts de manière à ce que le nom de domaine du blog pointe sur la nouvelle IP.

Une fois que l’installation me semble fonctionnelle, j’attache l’IP statique (les DNS du blog pointent sur cette IP, comme ça lors d’un changement de machine, il n’y a pas de propagation DNS à attendre) à la nouvelle instance et je supprime l’ancienne (en cas de soucis, j’ai toujours à ma disposition un snapshot de celle-ci).

La migration est terminée !

Si vous désirez en apprendre davantage sur Ansible, Ineat Academy dispense une formation à son sujet.