Конфигурация развертывания Discourse по мнению MKJ

Майкл,

После того как я им воспользовался, я согласен, что restic — «лучший» вариант для резервного копирования, но меня интересует, не стали ли вы использовать совместимую альтернативу, например Garage, в свете недавнего скандала вокруг Minio? Или ответ таков: «это не имеет значения, они все используют одни и те же команды, так что можете использовать любой, который вам нравится»? Эти инструменты я только сейчас начинаю изучать, поэтому не знаю подробных различий, знаю лишь, что текущая рекомендация — использовать Garage вместо Minio.

1 лайк

О, да, я уверен, что сегодня начал бы с гаража. Я просто ещё не переехал.

1 лайк

Также спасибо @raykholo за то, что он указал мне вне очереди, что мои инструкции по отключению прозрачных больших страниц были тихо неправильными.

Те, кто следит за этим, пожалуйста, используйте вместо этого:

echo 'w /sys/kernel/mm/transparent_hugepage/enabled - - - - never
w /sys/kernel/mm/transparent_hugepage/defrag  - - - - never' > /etc/tmpfiles.d/thp.conf
systemd-tmpfiles --create

Они не настраиваются через sysctl, и если вы ранее создали /etc/sysctl.d/10-huge-pages.conf следуя моим более ранним инструкциям, вы можете удалить его.

Я не проверил это правильно, когда писал, и с тех пор не заметил, что это не работало правильно. Хорошая находка, и спасибо!

3 лайка

Хм. У меня есть две системы, я только что их проверил, и могу предположить, что для Ubuntu 22 вы правы, и ваше исправление помогло, но для Ubuntu 24 оно не требуется. Написав это, я понял, что ваше исправление может быть эффективным и в Ubuntu 24, даже если оно не нужно. С какой версией ОС вы работаете?

Речь идёт о конфигурации по умолчанию. Отключать её безопасно, если она уже отключена (ничего не происходит), и описанный ниже метод широко поддерживается.

Я использую AlmaLinux, как указано в начале. Как только Docker наконец добавил поддержку пространств имён v2, я воспользовался первой возможностью перейти на операционную систему, основанную на Red Hat.

1 лайк

Возможно, отвечая на свой же вопрос, вот несколько заметок из опыта использования Garage с вашим руководством:


1. Garage не поддерживает “теги разрешений” для файлов (это реальное ограничение Garage)

Amazon позволяет индивидуально помечать каждый файл как “публичный” или “частный”. Garage это не реализует.

Discourse по умолчанию пытается использовать эту функцию. Хитрость заключается в следующем: когда Discourse говорил “сохраните этот файл, пометьте его как публичный”, Garage принимал запрос и тихо игнорировал тег. Поэтому базовые загрузки выглядели нормально, и проблемы возникли бы гораздо позже, в первый раз, когда кто-то сделал загрузку частной.

Решение: указать Discourse прекратить использование тегов (одна настройка). У Cloudflare R2 есть такое же ограничение, и решение такое же. Garage обрабатывает разрешения по-своему, на уровне бакетов, чего нам достаточно.


2. Discourse настаивает на определенном формате именования бакетов (не является ошибкой Garage)

Discourse не скажет “сервер garage, бакет uploads”. Он настаивает на uploads.garage — имя бакета приклеено спереди, как поддомен. И нет возможности это отключить.

Ничто в нашей сети не знало это имя, поэтому Discourse вообще не мог подключиться — установка прервалась на полпути.

Решение: дал Garage такой стиль именования и зарегистрировал имена. Две строки конфигурации.

Это известная особенность Discourse, а не Garage — именно поэтому хранилище Oracle находится в официальном списке Discourse “не работает”.


3. Изображениям нужен публичный веб-адрес (не имеет отношения к Garage)

Discourse навсегда записывает адрес каждого изображения в пост. Если не указать ему публичный адрес заранее, он сохранил внутренние адреса, доступные только нашему серверу, — поэтому каждое изображение было бы сломано для посетителей навсегда, если бы мы не пересобрали каждый пост.

Решение: установить адрес CDN перед первой загрузкой. Это было бы идентично на Amazon, R2 или любом другом.


Примечание: каждый из этих случаев был тихим. Ничего не сообщало “не поддерживается”. Один принял запрос и проигнорировал его, один выглядел как сетевая ошибка, третий выглядел так, будто все работает нормально.

Так что это просто несколько первоначальных моментов, которые стоит иметь в виду новому пользователю, пробующему это впервые, и/или тому, кто пытается следовать руководству Майкла с использованием Garage.

Вот полные заметки ИИ о нашем опыте теперь, когда форум развернут:


Запуск Discourse на самохостинге Garage (S3) за туннелем Cloudflare — заметки

Garage нет в таблице совместимости “Настройка совместимого с S3 поставщика объектного хранилища”, поэтому вот точка данных. Настройка: Garage v2.3.0, двухконтейнерный Discourse, Debian 13,
один узел, туннель Cloudflare вместо внешнего nginx. Предупреждение заранее: это одно
небольшое развертывание без производственного трафика, поэтому относитесь к этому как к “работает”, а не как к “проверено в бою на масштабе Maker Forums”.

  1. Это работает. Проверено против собственных путей кода Discourse, а не CLI: UploadCreator,
    OptimizedImage, ListObjectsV2, HeadObject/GetObject с побайтово точным круговым циклом,
    многочастные загрузки, удаление, remove_upload, плюс BackupRestore::Backuper, записывающий в бакет
    резервных копий, и BackupStore#files / #download_file, читающий оттуда. Настройка жизненного цикла
    работает, поэтому s3_configure_tombstone_policy действительно вступает в силу, а не
    тихо игнорируется. PutBucketCors работает, поэтому ручная настройка CORS в порядке.

  2. Вы должны настроить адресацию в стиле виртуального хоста, и это сломает db:migrate, если вы
    этого не сделаете. Discourse превращает конечную точку http://garage:3900 + бакет “uploads” в хост
    uploads.garage:3900, и нигде в s3_helper.rb или
    site_settings.yml нет опции стиля пути. Требуются две части: root_domain в [s3_api] в garage.toml,
    И разрешаемое имя DNS для каждого бакета (псевдоним сети Docker для каждого бакета, так как в DNS Docker нет подстановочных знаков). Симптом, если вы это упустите, — ошибка Aws::Waiters о
    getaddrinfo во время SiteIconManager.ensure_optimized! — выглядит как сетевая неисправность, а не
    хранилищная. Каждый новый бакет нуждается в новом имени.

  3. Garage не реализует PutObjectAcl / GetObjectAcl, поэтому установите s3_use_acls в false —
    то же самое, что и для R2. Здесь две ловушки. Первая: PutObject с --acl public-read тихо
    ПРИНИМАЕТСЯ, и заголовок игнорируется, поэтому базовые тесты загрузки проходят, и это ломается только позже при
    переходе на безопасную загрузку. Вторая: DISCOURSE_S3_USE_ACLS — это не скрытая глобальная переменная —
    помещение её в app.yml ничего не делает. Она стала true при моей первой загрузке, несмотря на то, что была в
    окружении. Это должно быть настройкой сайта, и её нужно перепроверять после каждой сборки,
    потому что ничто вас не предупреждает.

  4. Если вы ставите CDN спереди, направьте его на WEB-конечную точку Garage (:3902), а не на S3 API
    (:3900). Анонимные чтения работают только на веб-конечной точке; S3 API правильно возвращает 403
    для неаутентифицированных запросов, поэтому CDN, направленный на :3900, будет терпеть неудачу для каждого изображения, пока ваши
    учетные данные абсолютно валидны. Вам также нужно выполнить garage bucket website --allow для
    бакета uploads и root_domain в [s3_web]. Включите это только для uploads — бакет
    резервных копий никогда не должен быть анонимно читаемым.

  5. ListObjectVersions возвращает NotImplemented в Garage. Это не имеет значения: grep
    s3_helper.rb и file_store/s3_store.rb по list_object_versions / object_versions
    возвращает нулевые совпадения. Discourse не использует версионирование объектов S3; истечение срока действия надгробий — это
    правила жизненного цикла плюс префикс.

  6. Тестирование с aws-cli или mc НЕ доказывает совместимость. Оба по умолчанию используют адресацию в стиле пути
    против пользовательской конечной точки; Discourse использует только стиль виртуального хоста. Мой CLI
    тест вернул 15/16 и выглядел как зеленый свет, затем реальная установка
    сразу же потерпела неудачу из-за разницы в адресации. То же хранилище, те же учетные данные, те же
    операции. Если вы оцениваете непроверенный S3-бэкенд, запускайте реальный Discourse — достаточно
    одноразового одноконтейнерного экземпляра, и делать это до создания какого-либо контента — вот в чем суть, поскольку переход от S3 к локальному — это дорога в один конец.

  7. В частности, для туннеля Cloudflare: раздел external-nginx-for-SSL не применяется
    (TLS завершается на краю, нет certbot, нет входящих 80/443), но конфигурация выхода реального IP
    по-прежнему необходима и, возможно, даже более важна. Используйте real_ip_header CF-Connecting-IP
    вместо X-Forwarded-For — когда туннель является единственным путем входа, это одно
    недвусмысленное значение набора краевых узлов. Проверьте это, опубликовав из известного публичного IP и проверив,
    что записал nginx; без этого Discourse записывает адрес коннектора Docker для
    каждого запроса и ограничивает частоту всего интернета как одного клиента. То, что вы теряете по сравнению
    с подходом external-nginx, — это страница обслуживания во время сборок — cloudflared
    не может её обслуживать.

  8. Небольшое исправление чего-то широко (неправильно) заявленного, в том числе мной: DISCOURSE_S3_CDN_URL
    не вписывается необратимо в сохраненные URL. upload.url действительно сохраняет сырой URL S3 с
    внутренним хостом, но Discourse подменяет хост CDN во время рендеринга через
    Discourse.store.cdn_url / UrlHelper.cook_url. Поздняя настройка исправима —
    цена rake posts:rebake, потому что posts.cooked кэширует HTML от времени приготовления. Все равно
    установите это перед первой загрузкой; просто не паникуйте, если вы этого не сделали.

  9. Две операционные заметки, не связанные с Garage. ./launcher выводит полную строку docker run,
    включая DISCOURSE_S3_SECRET_ACCESS_KEY в открытом виде, поэтому логи инициализации содержат
    секреты — стоит ротировать ключи после шумной установки. И одиночный узел Garage
    по-прежнему требует layout assign + layout apply, прежде чем он начнет что-либо хранить, с
    replication_factor = 1, что означает полное отсутствие репликации, поэтому ваша задача резервного копирования — это единственная
    долговечность, у вас есть.

Я не использую объектное хранилище для публичного доступа в Discourse, отчасти потому, что миграция является односторонней, а также потому, что в этом нет необходимости; в моём случае эти данные обслуживались бы из того же хранилища, просто с помощью другого виртуального сервера для управления. Я использую объектное хранилище только в качестве цели для резервного копирования restic, которое выполняется вне Discourse.

Резервные копии Discourse, содержащие миниатюры, но не изображения, в сочетании с резервными копиями уровня хоста для загруженных файлов, которые сохраняются с помощью restic на хосте, означают, что эти ограничения, насколько мне известно, не имеют значения.