Uma provação migrando do Scaleway para um Raspberry Pi 4

Aqui estão as anotações sobre meus percalços e meu sucesso eventual em migrar uma instância do Discourse de Scaleway para um Raspberry Pi 4, com Cloudflare na frente.

Criei um backup da instância do Discourse da Scaleway, e ./launcher stop app, e desliguei a máquina.

Instalei o Ubuntu Server 23.10 em um SSD SATA conectado via USB alimentando o Raspberry Pi 4.
Instalei o LXD, criei um pool de armazenamento loopback btrfs de 100GiB.
Atualizei o perfil default para:

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 padrão
devices:
  eth0:
    name: eth0
    network: lxdbr0
    type: nic
  root:
    path: /
    pool: default
    type: disk
name: default

Adicionei um perfil discourse com:

config:
  limits.memory: 1GiB
  limits.memory.enforce: soft
  security.nesting: 'true'
description: Configuração para instâncias Discourse
devices: {}
name: discourse

Criei uma imagem mínima do Ubuntu 23.10 Server com esses perfis. Acessei-a através do seguinte em meu ~/.ssh/config:

Host LXD_DISCOURCE_INSTANCE

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

Segui as instruções de instalação na nuvem do Discourse e restaurei minha configuração do Discourse da instância 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" # não necessário com cloudflare

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

params:
  db_default_text_search_config: "pg_catalog.english"

env:
  LANG: en_US.UTF-8

  ## Configuração HTTPS para: templates/web.letsencrypt.ssl.template.yml
  LETSENCRYPT_ACCOUNT_EMAIL: "redacted"  # https

  ## O nome de domínio que esta instância Discourse responderá
  DISCOURSE_HOSTNAME: "redacted"

  ## Lista de e-mails separados por vírgula que se tornarão administradores e desenvolvedores
  ## na primeira inscrição, exemplo 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: "redacted"

  ## O servidor de e-mail que esta instância Discourse usará
  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 alguns provedores)
  #DISCOURSE_NOTIFICATION_EMAIL: nobody@discourse.example.com    # (endereço para enviar notificações)
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

## Quaisquer comandos personalizados para executar após a compilação
run:
  - exec: rails r "SiteSetting.contact_email='redacted'"
  - exec: rails r "SiteSetting.notification_email='redacted'"

## O contêiner Docker é sem estado; todos os dados são armazenados em /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 o backup, no entanto, precisei reconstruí-lo. Infelizmente, o pool de armazenamento btrfs travava na etapa do yarn install por horas e, eventualmente, expirava, com carga quase zero na máquina.

Pesquisando um pouco, decidi tentar usar um pool de armazenamento zfs em vez disso, o que avançou mais, mas ainda travava indefinidamente após Background saving terminated with sucess, com carga quase zero na máquina.

(Tenho capturas de tela, mas o upload delas aqui falha.)

Então decidi abandonar o LXD e tentar diretamente na instância do Ubuntu Server no Raspberry Pi 4.
Pela primeira vez, tive uma reconstrução bem-sucedida, no entanto, todas as tentativas de acessá-la redirecionavam para si mesma, em um loop de redirecionamento.

O loop de redirecionamento tinha duas causas…

Se eu tivesse o seguinte em minha configuração do Discourse:

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

Ele redirecionaria infinitamente, sempre querendo redirecionar para https://hostname.
Resolver isso foi voltar para:

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

Em segundo lugar, qualquer coisa acessada através do túnel Cloudflare também redirecionava infinitamente para si mesma. A causa, ao que parece, foi ter um túnel para HTTP e um túnel para HTTPS. Mudar o túnel para apenas HTTPS resolveu.

Outras coisas que fiz, mas neste ponto não tenho certeza se importaram:

  1. Removi o letsencrypt, pois usei um Certificado de Origem Cloudflare em vez disso.
  2. Configurei o Origin Server Name no túnel HTTPS para ser o hostname pretendido.

Coisas que podem ser melhoradas:

  1. O HTTPS da Origem para o Cloudflare poderia ser evitado se eu bloqueasse a máquina para permitir apenas conexões do Cloudflare e configurasse um túnel SSH. No entanto, não tenho certeza se o Discourse roda melhor tendo HTTPS em si mesmo (por exemplo, http2, etc.).
  2. Se o letsencrypt funciona com o túnel Cloudflare (não consegui testar, pois quando estava usando letsencrypt, estava recebendo os loops de redirecionamento).

Como depurei os loops de redirecionamento:

  • Para depurar o loop de redirecionamento do Discourse: configurei /etc/hosts para apontar meu hostname do Discourse diretamente para o endereço IP, então usei curl -k --head 'https://hostname:8081 etc. para testar.
  • Para depurar o loop de redirecionamento do túnel Cloudflare: removi isso de /etc/hosts para que o hostname fosse resolvido via DNS, então usei curl -k --head 'https://hostname etc. para testar.

Há um monte de outras coisas úteis e aprendizados ao longo do caminho, no entanto, isso pode esperar.

Feedback para o Discourse:

  • A reconstrução precisa ser mais clara sobre o que está fazendo. Muitas vezes há longos atrasos sem ação óbvia sendo realizada.
  • Descobrir por que expor portas diferentes causaria um loop de redirecionamento.
  • Desde que o letsencrypt se tornou uma coisa, a documentação sobre como especificar seu próprio certificado SSL é tediosa de descobrir. Além disso, parece que apenas um certificado pode ser usado, pois está fixo em /var/discourse/shared/standalone/ssl/ssl.key em vez de, digamos, /var/discourse/shared/standalone/ssl/CONTAINER_ID.key, por exemplo, /var/discourse/shared/standalone/ssl/app.key — o Cloudflare fornece certificados de origem, então esta é uma boa opção para usuários do Cloudflare.
  • Publicar um guia completo passo a passo para Cloudflare + Raspberry Pi 4 teria ajudado enormemente, atualmente tais guias delegam muita informação a terceiros que não têm conhecimento um do outro, e toda a complexidade e depuração está em como as diferentes partes funcionam juntas, não em como funcionam sozinhas.

Outros “algum dia” para fazer:

  • Descobrir por que travava no LXD.
  • Ver se funciona no LXD em um Raspberry Pi 5, ou no Multipass no macOS, ou LXD com o pool de armazenamento sendo uma partição/disco em vez de um arquivo loopback: pois assim não preciso desperdiçar uma máquina inteira com isso.
  • Ver se consigo fazer com que o docker e o launcher não precisem de sudo.
3 curtidas