Una odisea migrando desde Scaleway a Raspberry Pi 4

Aquí la documentación de mis percances y mi eventual éxito al migrar una instancia de Discourse de Scaleway a una Raspberry Pi 4, con Cloudflare delante.

Creé una copia de seguridad de la instancia de Discourse de Scaleway, y ./launcher stop app, y apagué la máquina.

Instalé Ubuntu Server 23.10 en un SSD SATA conectado por USB alimentando la Raspberry Pi 4.
Instalé LXD, creé un grupo de almacenamiento de loopback btrfs de 100GiB.
Actualicé el perfil default a:

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: Perfil LXD predeterminado
devices:
  eth0:
    name: eth0
    network: lxdbr0
    type: nic
  root:
    path: /
    pool: default
    type: disk
name: default

Añadí un perfil discourse con:

config:
  limits.memory: 1GiB
  limits.memory.enforce: soft
  security.nesting: 'true'
description: Configuración para instancias de Discourse
devices: {}
name: discourse

Creé una imagen de Ubuntu 23.10 Minimal Server con esos perfiles. Accedí a ella a través de lo siguiente en mi ~/.ssh/config:

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

Seguí las instrucciones de instalación en la nube de Discourse y restauré mi configuración de Discourse desde la instancia de 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" # no necesario con cloudflare

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

params:
  db_default_text_search_config: "pg_catalog.english"

env:
  LANG: en_US.UTF-8

  ## Configuración HTTPS para: templates/web.letsencrypt.ssl.template.yml
  LETSENCRYPT_ACCOUNT_EMAIL: "redacted"  # https

  ## El nombre de dominio al que responderá esta instancia de Discourse
  DISCOURSE_HOSTNAME: "redacted"

  ## Lista de correos electrónicos separados por comas que se convertirán en administradores y desarrolladores
  ## en el registro inicial, por ejemplo, 'usuario1@ejemplo.com,usuario2@ejemplo.com'
  DISCOURSE_DEVELOPER_EMAILS: "redacted"

  ## El servidor de correo que utilizará esta instancia de Discourse
  DISCOURSE_SMTP_ADDRESS: "redacted"
  DISCOURSE_SMTP_PORT: redacted
  DISCOURSE_SMTP_USER_NAME: "redacted"
  DISCOURSE_SMTP_PASSWORD: "redacted"
  #DISCOURSE_SMTP_DOMAIN: discourse.example.com    # (requerido por algunos proveedores)
  #DISCOURSE_NOTIFICATION_EMAIL: nobody@discourse.example.com    # (dirección desde la que se enviarán las notificaciones)
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

## Cualquier comando personalizado para ejecutar después de la compilación
run:
  - exec: rails r "SiteSetting.contact_email='redacted'"
  - exec: rails r "SiteSetting.notification_email='redacted'"

## El contenedor Docker es sin estado; todos los datos se almacenan en /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

Antes de poder restaurar la copia de seguridad, sin embargo, necesitaba reconstruirla. Desafortunadamente, el grupo de almacenamiento btrfs se colgaba en el paso de instalación de yarn durante horas y, finalmente, se agotaba el tiempo de espera, con una carga casi nula en la máquina.

Investigando un poco, decidí probar a usar un grupo de almacenamiento zfs en su lugar, esto avanzaría más, pero aún así se colgaría indefinidamente después de Background saving terminated with sucess, con una carga casi nula en la máquina.

(Tengo capturas de pantalla, sin embargo, subirlas aquí falla.)

Entonces decidí abandonar LXD y probarlo directamente en la instancia de Ubuntu Server en la Raspberry Pi 4.
Por primera vez tuve una reconstrucción exitosa, sin embargo, todos los intentos de acceder a ella redirigían a sí misma, en un bucle de redirección.

El bucle de redirección tenía dos causas…

Si tenía lo siguiente en mi configuración de Discourse:

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

Redirigiría sin fin, queriendo siempre redirigir a https://hostname.
Resolver esto fue volver a:

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

En segundo lugar, cualquier cosa accedida a través del túnel de Cloudflare también redirigiría sin fin a sí misma. La causa resultó ser tener un túnel tanto para HTTP como para HTTPS. Cambiar el túnel a solo HTTPS lo resolvió.

Otras cosas que hice pero en este punto no estoy seguro si importaron:

  1. Eliminé letsencrypt ya que usé un Certificado de Origen de Cloudflare en su lugar.
  2. He configurado el Nombre del Servidor de Origen en el túnel HTTPS para que sea el nombre de host deseado.

Cosas que podrían mejorarse:

  1. Se podría evitar el HTTPS desde el Origen a Cloudflare si bloqueo la máquina para que solo permita conexiones desde Cloudflare y configuro un túnel SSH. Sin embargo, no estoy seguro si Discourse funciona mejor teniendo HTTPS en sí mismo (por ejemplo, http2, etc.).
  2. Si letsencrypt funciona con el túnel de Cloudflare (no pude probarlo ya que cuando usaba letsencrypt estaba obteniendo los bucles de redirección).

Cómo depuré los bucles de redirección:

  • Para depurar el bucle de redirección de Discourse: configuré /etc/hosts para que apunte mi nombre de host de Discourse directamente a la dirección IP, luego usé curl -k --head 'https://hostname:8081 etc para probarlo.
  • Para depurar el bucle de redirección del túnel de Cloudflare: eliminé eso de /etc/hosts para que el nombre de host se resuelva a través de DNS, luego usé curl -k --head 'https://hostname etc para probarlo.

Hay un montón de otras cosas ingeniosas y aprendizajes en el camino, sin embargo, eso puede esperar.

Comentarios para Discourse:

  • La reconstrucción necesita ser más clara sobre lo que está haciendo. Con demasiada frecuencia hay largos retrasos sin una acción obvia que se esté realizando.
  • Averiguar por qué exponer diferentes puertos causaría un bucle de redirección.
  • Dado que letsencrypt se convirtió en una cosa, la documentación sobre cómo especificar el propio certificado SSL es tediosa de descubrir. Además, parece que solo se puede usar un certificado ya que está fijado a /var/discourse/shared/standalone/ssl/ssl.key en lugar de, por ejemplo, /var/discourse/shared/standalone/ssl/CONTAINER_ID.key, por ejemplo, /var/discourse/shared/standalone/ssl/app.key — Cloudflare proporciona certificados de origen, por lo que esta es una buena opción para los usuarios de Cloudflare.
  • Publicar una guía completa paso a paso e inclusiva para Cloudflare + Raspberry Pi 4 habría ayudado enormemente, actualmente tales guías delegan demasiada información a terceros que no tienen conocimiento mutuo, y toda la complejidad y depuración está en cómo funcionan juntas las diferentes partes, no en cómo funcionan solas.

Otras tareas pendientes para algún día:

  • Averiguar por qué se colgaba en LXD.
  • Ver si funciona en LXD en una Raspberry Pi 5, o en Multipass en macOS, o LXD con el grupo de almacenamiento siendo una partición/disco en lugar de un archivo de loopback: ya que entonces no tengo que desperdiciar una máquina entera en ello.
  • Ver si puedo hacer que docker y launcher no necesiten sudo.
3 Me gusta