Une épreuve de migration de Scaleway vers un Raspberry Pi 4

Voici la documentation de mes déboires et de mon succès éventuel à migrer une instance discourse de scaleway vers un raspberry pi 4, avec cloudflare devant.

Créé une sauvegarde de l’instance discourse de scaleway, et ./launcher stop app, et arrêté la machine.

Installé Ubuntu Server 23.10 sur un SSD SATA connecté en USB alimentant le Raspberry Pi 4
Installé LXD, créé un pool de stockage loopback btrfs de 100 Gio.
Mis à jour le profil default pour :

config:
  cloud-init.user-data: |
    #cloud-config
    ssh_pwauth: false
    package_update: true
    package_upgrade: true
    packages:
      - openssh-server
      - vim
      - git
      - rsync
    users:
      - name: root
        lock_passwd: true
        ssh_import_id: gh:balupton
description: Profil LXD par défaut
devices:
  eth0:
    name: eth0
    network: lxdbr0
    type: nic
  root:
    path: /
    pool: default
    type: disk
name: default

Ajouté un profil discourse avec :

config:
  limits.memory: 1GiB
  limits.memory.enforce: soft
  security.nesting: 'true'
description: Configuration pour les instances Discourse
devices: {}
name: discourse

Créé une image Ubuntu 23.10 Minimal Server avec ces profils. Accédé via la configuration suivante dans mon ~/.ssh/config :

Host LXD_DISCOURCE_INSTANCE

	ProxyJump LXD_HOST
	User REDACTED
	IdentityFile ~/.ssh/REDACTED.pub

Suivi les instructions d’installation cloud de discourse et restauré ma configuration discourse depuis l’instance scaleway :

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/cloudflare.template.yml"
  - "templates/web.ssl.template.yml"  # https
  - "templates/web.letsencrypt.ssl.template.yml"  # https
  # - "templates/web.ratelimited.template.yml" # pas nécessaire avec cloudflare

expose:
  - "80:80"
  - "443:443"  # https

params:
  db_default_text_search_config: "pg_catalog.english"

env:
  LANG: en_US.UTF-8

  ## Configuration HTTPS pour : templates/web.letsencrypt.ssl.template.yml
  LETSENCRYPT_ACCOUNT_EMAIL: "redacted"  # https

  ## Le nom de domaine auquel cette instance Discourse répondra
  DISCOURSE_HOSTNAME: "redacted"

  ## Liste des emails délimités par des virgules qui seront faits administrateurs et développeurs
  ## lors de la première inscription exemple 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: "redacted"

  ## Le serveur de messagerie que cette instance Discourse utilisera
  DISCOURSE_SMTP_ADDRESS: "redacted"
  DISCOURSE_SMTP_PORT: redacted
  DISCOURSE_SMTP_USER_NAME: "redacted"
  DISCOURSE_SMTP_PASSWORD: "redacted"
  #DISCOURSE_SMTP_DOMAIN: discourse.example.com    # (requis par certains fournisseurs)
  #DISCOURSE_NOTIFICATION_EMAIL: nobody@discourse.example.com    # (adresse pour envoyer les notifications depuis)
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

## Toutes commandes personnalisées à exécuter après la construction
run:
  - exec: rails r "SiteSetting.contact_email='redacted'"
  - exec: rails r "SiteSetting.notification_email='redacted'"

## Le conteneur Docker est sans état ; toutes les données sont stockées dans /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Plugins
## https://meta.discourse.org/t/19157
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/discourse-adplugin.git
          - git clone https://github.com/discourse/discourse-affiliate.git
          - git clone https://github.com/discourse/discourse-assign.git
          - git clone https://github.com/discourse/discourse-docs.git
          - git clone https://github.com/discourse/discourse-topic-voting.git
          - git clone https://github.com/discourse/discourse-github.git
          - git clone https://github.com/discourse/discourse-saved-searches.git
          - git clone https://github.com/discourse/discourse-shared-edits.git
          - git clone https://github.com/discourse/discourse-solved.git
          # - git clone https://github.com/discourse/discourse-encrypt.git
          # - git clone https://github.com/discourse/discourse-reactions.git
          # - git clone https://github.com/discourse/discourse-subscriptions.git

Avant de pouvoir restaurer la sauvegarde, j’ai dû la reconstruire. Malheureusement, le pool de stockage btrfs se bloquait à l’étape d’installation de yarn pendant des heures, puis finissait par expirer, avec une charge quasi nulle sur la machine.

Après quelques recherches, j’ai décidé d’essayer d’utiliser un pool de stockage zfs à la place, cela a permis d’aller plus loin, mais bloquait toujours indéfiniment après “Sauvegarde en arrière-plan terminée avec succès”, avec une charge quasi nulle sur la machine.

(J’ai des captures d’écran, mais leur téléchargement ici échoue.)

J’ai ensuite décidé d’abandonner LXD et d’essayer directement sur l’instance Ubuntu Server sur le Raspberry Pi 4.
Pour la première fois, j’ai eu une reconstruction réussie, cependant toutes les tentatives d’accès redirigeaient vers lui-même, dans une boucle de redirection.

La boucle de redirection avait deux causes…

Si j’avais ceci dans ma configuration discourse :

expose:
  - "8080:80"
  - "8081:443"  # https

Cela redirigeait sans fin, voulant toujours rediriger vers https://hostname.
Pour résoudre cela, il fallait revenir à :

expose:
  - "80:80"
  - "443:443"  # https

Deuxièmement, tout ce qui était accédé via le tunnel cloudflare redirigeait également sans fin vers lui-même. La cause s’est avérée être la présence d’un tunnel pour HTTP et d’un tunnel pour HTTPS. Changer le tunnel pour HTTPS uniquement a résolu le problème.

Autres choses que j’ai faites mais dont je ne suis pas sûr si elles ont eu un impact à ce stade :

  1. J’ai supprimé letsencrypt car j’utilisais un certificat d’origine Cloudflare à la place.
  2. J’ai configuré le Nom du serveur d'origine dans le tunnel HTTPS pour qu’il soit le nom d’hôte prévu.

Points à améliorer :

  1. Le HTTPS de l’origine vers Cloudflare pourrait être évité si je verrouille la machine pour n’autoriser que les connexions de Cloudflare, et que je configure un tunnel SSH. Cependant, je ne suis pas sûr si Discourse fonctionne mieux avec le HTTPS activé lui-même (par exemple, http2, etc.).
  2. Si letsencrypt fonctionne avec le tunnel cloudflare (je n’ai pas pu le tester car lorsque j’utilisais letsencrypt, j’avais les boucles de redirection).

Comment j’ai débogué les boucles de redirection :

  • Pour déboguer la boucle de redirection discourse : j’ai défini /etc/hosts pour pointer mon nom d’hôte discourse directement vers l’adresse IP, puis j’ai utilisé curl -k --head 'https://hostname:8081 etc pour le tester.
  • Pour déboguer la boucle de redirection du tunnel cloudflare : j’ai retiré cela de /etc/hosts afin que le nom d’hôte soit résolu via DNS, puis j’ai utilisé curl -k --head 'https://hostname etc pour le tester.

Il y a un tas d’autres choses astucieuses et d’apprentissages en cours de route, mais cela peut attendre.

Commentaires pour discourse :

  • La reconstruction doit être plus claire sur ce qu’elle fait. Trop souvent, il y a de longs délais sans action évidente.
  • Découvrir pourquoi l’exposition de différents ports causerait une boucle de redirection.
  • Depuis que letsencrypt est devenu une chose, la documentation sur la façon de spécifier son propre certificat SSL est fastidieuse à trouver. De plus, il semble qu’un seul certificat puisse être utilisé car il est fixé à /var/discourse/shared/standalone/ssl/ssl.key au lieu de, par exemple, /var/discourse/shared/standalone/ssl/CONTAINER_ID.key, par exemple /var/discourse/shared/standalone/ssl/app.key — cloudflare fournit des certificats d’origine, c’est donc une bonne option pour les utilisateurs de cloudflare.
  • La publication d’un guide complet, étape par étape et tout compris pour cloudflare + raspberry pi 4 m’aurait énormément aidé, actuellement de tels guides délèguent trop d’informations à des tiers qui n’ont aucune conscience les uns des autres, et toute la complexité et le débogage résident dans la façon dont les différentes parties fonctionnent ensemble, pas dans la façon dont elles fonctionnent seules.

Autres tâches à faire un jour :

  • Découvrir pourquoi cela se bloquait dans LXD.
  • Voir si cela fonctionne dans LXD sur un Raspberry Pi 5, ou sur Multipass sur macOS, ou LXD avec le pool de stockage étant une partition/disque au lieu d’un fichier loopback : car alors je n’ai pas à gaspiller une machine entière pour cela.
  • Voir si je peux faire en sorte que docker et launcher n’aient pas besoin de sudo.
3 « J'aime »