Raspberry Pi 4への移行は大変な試練

Scaleway から Raspberry Pi 4 への Discourse インスタンスの移行、および Cloudflare を前面に配置した際の私の失敗談と最終的な成功についてのドキュメントです。

Scaleway の Discourse インスタンスからバックアップを作成し、./launcher stop app を実行してマシンをシャットダウンしました。

Raspberry Pi 4 に接続された USB SATA SSD から Ubuntu Server 23.10 をインストールしました。
LXD をインストールし、100GiB の btrfs ループバックストレージプールを作成しました。
default プロファイルを以下のように更新しました。

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

discourse プロファイルを以下を追加して作成しました。

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

これらのプロファイルを使用して Ubuntu 23.10 Minimal Server イメージを作成しました。~/.ssh/config で以下のようにアクセスしました。

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

Discourse のクラウドインストール手順に従い、Scaleway インスタンスから Discourse の設定を復元しました。

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" # not needed with cloudflare

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

params:
  db_default_text_search_config: "pg_catalog.english"

env:
  LANG: en_US.UTF-8

  ## HTTPS configuration for: templates/web.letsencrypt.ssl.template.yml
  LETSENCRYPT_ACCOUNT_EMAIL: "redacted"  # https

  ## The domain name this Discourse instance will respond to
  DISCOURSE_HOSTNAME: "redacted"

  ## List of comma delimited emails that will be made admin and developer
  ## on initial signup example 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: "redacted"

  ## The mailserver this Discourse instance will use
  DISCOURSE_SMTP_ADDRESS: "redacted"
  DISCOURSE_SMTP_PORT: redacted
  DISCOURSE_SMTP_USER_NAME: "redacted"
  DISCOURSE_SMTP_PASSWORD: "redacted"
  #DISCOURSE_SMTP_DOMAIN: discourse.example.com    # (required by some providers)
  #DISCOURSE_NOTIFICATION_EMAIL: nobody@discourse.example.com    # (address to send notifications from)
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

## Any custom commands to run after building
run:
  - exec: rails r "SiteSetting.contact_email='redacted'"
  - exec: rails r "SiteSetting.notification_email='redacted'"

## The Docker container is stateless; all data is stored in /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

バックアップを復元する前に、再構築する必要がありました。残念ながら、btrfs ストレージプールは yarn install のステップで数時間ハングし、最終的にタイムアウトしました。マシンの負荷はほとんどありませんでした。

調査の結果、zfs ストレージプールを使用することにしましたが、それでも Background saving terminated with sucess の後にハングし続け、マシンの負荷はほとんどありませんでした。

(スクリーンショットはありますが、ここにアップロードすると失敗します。)

LXD を断念し、Raspberry Pi 4 の Ubuntu Server インスタンスで直接試すことにしました。
初めて再構築が成功しましたが、アクセスを試みるとすべてリダイレクトループで自身にリダイレクトされました。

リダイレクトループの原因は 2 つありました…

Discourse の設定に以下が含まれている場合:

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

無限にリダイレクトされ、常に https://hostname にリダイレクトしようとしました。
これを解決するには、以下に戻しました。

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

次に、Cloudflare Tunnel 経由でアクセスされたものはすべて無限に自身にリダイレクトされました。原因は、HTTP 用のトンネルと HTTPS 用のトンネルの両方を持っていたことでした。トンネルを HTTPS のみに変更することで解決しました。

その他に行ったことですが、現時点では重要かどうか不明です。

  1. Let’s Encrypt を削除し、代わりに Cloudflare Origin Certificate を使用しました。
  2. HTTPS トンネルの Origin Sever Name を目的のホスト名に設定しました。

改善できる点:

  1. マシンを Cloudflare からの接続のみを許可するようにロックダウンし、SSH トンネルを設定すれば、オリジンから Cloudflare までの HTTPS を回避できます。しかし、Discourse が HTTPS を自身で実行する方がパフォーマンスが良いのか(例:http2 など)は不明です。
  2. Let’s Encrypt が Cloudflare Tunnel で機能するかどうか(Let’s Encrypt を使用していたときにリダイレクトループが発生していたため、テストできませんでした)。

リダイレクトループのデバッグ方法:

  • Discourse のリダイレクトループのデバッグ:/etc/hosts を設定して、Discourse ホスト名を直接 IP アドレスにマッピングし、curl -k --head 'https://hostname:8081 などを使用してテストしました。
  • Cloudflare Tunnel のリダイレクトループのデバッグ:ホスト名が DNS で解決されるように /etc/hosts から削除し、curl -k --head 'https://hostname などを使用してテストしました。

途中で他にも多くの便利なことや学習がありましたが、それは後回しにできます。

Discourse へのフィードバック:

  • 再構築が何をしているのかをより明確にする必要があります。多くの場合、明らかなアクションが実行されていないのに長時間遅延が発生します。
  • 異なるポートを公開するとリダイレクトループが発生する理由を調べる必要があります。
  • Let’s Encrypt が登場して以来、独自の SSL 証明書を指定する方法に関するドキュメントを見つけるのが困難です。また、/var/discourse/shared/standalone/ssl/ssl.key のように 1 つの証明書しか使用できないように見えますが、例えば /var/discourse/shared/standalone/ssl/CONTAINER_ID.key のように、/var/discourse/shared/standalone/ssl/app.key のように、Cloudflare はオリジン証明書を提供しているため、Cloudflare ユーザーにとっては良いオプションです。
  • Cloudflare + Raspberry Pi 4 の包括的なステップバイステップガイドを発行すれば、非常に役立ったでしょう。現在、そのようなガイドは、互いに認識していないサードパーティに多くの情報を委任しており、すべての複雑さとデバッグは、個々の部分がどのように機能するかではなく、それらがどのように連携して機能するかに関係しています。

その他のいつかやるべきこと:

  • LXD でハングした理由を調べる。
  • Raspberry Pi 5 の LXD、macOS の Multipass、またはループバックファイルではなくパーティション/ドライブをストレージプールとして使用する LXD で機能するかどうかを確認する。そうすれば、マシン全体を無駄にする必要がなくなります。
  • Docker と launcher が sudo を必要としないようにできるかどうかを確認する。
「いいね!」 3