Здесь документация о моих ошибках и о том, как я в итоге успешно мигрировал экземпляр Discourse с Scaleway на Raspberry Pi 4 с Cloudflare перед ним.
Создал резервную копию экземпляра Discourse на Scaleway, выполнил ./launcher stop app и выключил машину.
Установил Ubuntu Server 23.10 на подключённый через USB SATA SSD, который питает Raspberry Pi 4.
Установил LXD, создал пул хранения btrfs с файлом loopback объёмом 100 ГиБ.
Обновил профиль 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 и восстановил конфигурацию моего экземпляра Discourse с машины 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" # 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 на несколько часов и в итоге истекало время ожидания, при этом нагрузка на машину была практически нулевой.
Прочитав несколько статей, я решил попробовать использовать пул хранения ZFS. Это позволило продвинуться дальше, но процесс всё равно зависал бесконечно после сообщения Background saving terminated with success, при этом нагрузка на машину оставалась практически нулевой.
(У меня есть скриншоты, но загрузить их сюда не удаётся.)
Тогда я решил отказаться от LXD и попробовать запустить всё напрямую на экземпляре Ubuntu Server на Raspberry Pi 4.
Впервые мне удалось успешно пересобрать контейнер, однако все попытки доступа к нему приводили к бесконечному циклу перенаправлений (redirect loop).
Цикл перенаправлений был вызван двумя причинами…
Если в конфигурации Discourse было указано следующее:
expose:
- "8080:80"
- "8081:443" # https
то происходило бесконечное перенаправление, всегда стремящееся перенаправить на https://hostname.
Решением было возвращение к следующему:
expose:
- "80:80"
- "443:443" # https
Во-вторых, любой доступ через туннель Cloudflare также приводил к бесконечному перенаправлению на самого себя. Оказалось, что причиной было наличие туннеля как для HTTP, так и для HTTPS. Решение состояло в изменении туннеля так, чтобы он использовался только для HTTPS.
Другие действия, которые я предпринял, но сейчас не уверен, повлияли ли они:
- Я отказался от Let’s Encrypt, используя вместо этого сертификат Origin от Cloudflare.
- В туннеле HTTPS я настроил
Origin Server Nameравным ожидаемому имени хоста.
Возможные улучшения:
- HTTPS от Origin до Cloudflare можно было бы избежать, если ограничить доступ к машине только подключениями от Cloudflare и настроить SSH-туннель. Однако я не уверен, работает ли Discourse лучше при наличии собственного HTTPS (например, HTTP/2 и т. д.).
- Работает ли Let’s Encrypt с туннелем Cloudflare (мне не удалось это проверить, так как при использовании Let’s Encrypt я сталкивался с циклами перенаправлений).
Как я отлаживал циклы перенаправлений:
- Для отладки цикла перенаправлений в Discourse: я добавил запись в
/etc/hosts, чтобы имя хоста Discourse указывало напрямую на IP-адрес, затем использовалcurl -k --head 'https://hostname:8081и т. д. для тестирования. - Для отладки цикла перенаправлений в туннеле Cloudflare: я удалил эту запись из
/etc/hosts, чтобы имя хоста разрешалось через DNS, затем использовалcurl -k --head 'https://hostnameи т. д. для тестирования.
Есть ещё много интересных вещей и уроков, полученных по пути, но это можно отложить на потом.
Обратная связь для проекта Discourse:
- Процесс пересборки должен быть более прозрачным в том, что именно происходит. Слишком часто наблюдаются длительные задержки без видимых действий.
- Выяснить, почему указание разных портов вызывает цикл перенаправлений.
- С момента появления Let’s Encrypt документация о том, как указать собственный SSL-сертификат, стала труднодоступной. Кроме того, похоже, что можно использовать только один сертификат, так как он зафиксирован по пути
/var/discourse/shared/standalone/ssl/ssl.keyвместо, например,/var/discourse/shared/standalone/ssl/CONTAINER_ID.key, скажем,/var/discourse/shared/standalone/ssl/app.key. Cloudflare предоставляет сертификаты Origin, что является хорошим вариантом для пользователей Cloudflare. - Публикация исчерпывающего пошагового руководства по связке Cloudflare + Raspberry Pi 4 очень помогла бы. В настоящее время такие руководства перекладывают слишком много информации на третьи стороны, которые не знают о существовании друг друга, а вся сложность и отладка заключаются в том, как работают разные части вместе, а не по отдельности.
Другие задачи на будущее:
- Выяснить, почему процесс зависал в LXD.
- Проверить, работает ли это в LXD на Raspberry Pi 5, или в Multipass на macOS, или в LXD, если пул хранения — это раздел/диск, а не файл loopback: тогда мне не нужно выделять под это целую машину.
- Посмотреть, можно ли заставить Docker и launcher работать без sudo.