Как настроить Cloudflare R2 для вашего сообщества Discourse

Корзины Cloudflare R2 можно использовать для хранения статических ресурсов, таких как изображения и GIF-файлы, для сообщества Discourse, но их нельзя использовать для хранения резервных копий сообщества!

Введение:

Объектное хранилище Cloudflare R2 можно использовать как альтернативу Amazon S3 для хранения загружаемых файлов на вашем форуме Discourse. Ниже приведены шаги по настройке этого.

Шаги настройки:

  1. Включить загрузку через S3: Установите флажок для включения загрузки через S3 в настройках Discourse.
  2. ID ключа доступа S3: Введите ID ключа API для вашей корзины R2. Это идентификатор, предоставленный при создании токена API для вашей корзины.
  3. Секретный ключ доступа: Введите секретный ключ, предоставленный при создании токена API, предоставляющего доступ к вашей корзине. Важно: Этот секретный ключ отображается только один раз, поэтому обязательно сохраните его в надежном месте.
  4. Регион S3: Вы можете ввести любой регион, для R2 это не имеет значения.
  5. Корзина для загрузки S3: Введите имя вашей корзины хранения R2.
  6. Конечная точка S3: Введите ссылку API S3 для вашей корзины R2, которая имеет формат https://xxxxxx.com. Найдите эту ссылку в панели управления Cloudflare R2.
  7. URL CDN S3: Введите публичный URL корзины хранения R2.dev для вашей корзины. Он также доступен в панели управления Cloudflare R2.

Завершение:

После настройки этих параметров ваш форум Discourse будет настроен на использование Cloudflare R2 для хранения.

Информация о бесплатном тарифном плане:

Услуга R2 от Cloudflare предоставляет бесплатный тарифный план, включающий 10 ГБ хранилища, 1 миллион загрузок и 1 миллион операций чтения в месяц.

Я рекомендую следовать примерам в статье Настройка провайдера объектного хранилища, совместимого с S3, для загрузки файлов и размещать настройки в вашем YAML-файле, а не в базе данных.

Спасибо за ваш отзыв. Я внимательно ознакомился с руководством ранее и считаю, что информация о Cloudflare R2 неверна. В статье утверждается, что сообщество Discourse не поддерживает бакеты Cloudflare R2. Однако на самом деле Cloudflare R2 полностью совместим с S3 и отлично справляется с загрузкой и скачиванием изображений и файлов для сообщества Discourse. Это было подтверждено на практике в моём сообществе (starorigin.net).

И, полагаю, это было верно на момент написания статьи.

Гораздо лучше хранить настройки S3 в файле yml, чем настраивать их через интерфейс и сохранять в базе данных. Пробовали ли вы восстановить базу данных на новом сервере?

После того как вы настроите всё рекомендуемым способом, вы можете отредактировать эту тему или оставить комментарий, попросив кого-то другого сделать это.

Вы правы, я использую хранилище Cloudflare R2 для хранения изображений, GIF-файлов и других ресурсов сообщества. Это значительно снижает нагрузку на сервер сообщества и ускоряет загрузку страниц.

Я не настроил автоматическое резервное копирование сообщества в хранилище Cloudflare R2, поскольку бакеты Cloudflare R2 не поддерживают хранение сжатых файлов. Однако хранилище Cloudflare R2 может хранить PDF-файлы, изображения, GIF-файлы и другие статические ресурсы сообщества, что также очень хорошо.

Забавно. Мне казалось, я уже использовал R2 для резервных копий. Но, возможно, я ошибаюсь.

Вы всё ещё можете следовать рекомендуемым инструкциям и просто отметить, что не стоит размещать там резервные копии.

Спасибо за напоминание, я выделю эту часть.

Ведро Cloudflare R2 можно использовать для хранения статических ресурсов, таких как изображения и GIF-файлы для сообщества Discourse, но оно не подходит для хранения резервных копий сообщества!

Просто обновлю этот пост: у меня возникли некоторые нюансы, которые нужно было учесть, прежде чем Cloudflare заработал.


1. Регион


Это было неверно: мне пришлось использовать “auto” или выбранный регион. “Auto” проще, поэтому используйте его.
Если вам нужно узнать, какие варианты доступны, попробуйте ввести любую случайную строку в поле региона и выполните:

sudo -E -u discourse bundle exec rake s3:upload_assets

Если вы используете NixOS

sudo discourse-rake s3:upload_assets

Это выведет ошибку с перечислением допустимых вариантов.


2. Разрешения API


Также важно знать, что ограниченные токены API не работают. Необходимо использовать Admin Read & Write.
Object Read & Write не сработал.

Ошибка при выполнении sudo -E -u discourse bundle exec rake s3:upload_assets @Eviepayne

Установите регион в режим «автоматически».
Возможно, вам также потребуется установить:
DISCOURSE_S3_INSTALL_CORS_RULE: false

Я выполнил обе эти команды и пересобрал app.yml:

  ## Настройка S3
  DISCOURSE_USE_S3: true
  DISCOURSE_S3_REGION: auto
  DISCOURSE_S3_ACCESS_KEY_ID: XXX
  DISCOURSE_S3_SECRET_ACCESS_KEY: XXX
  DISCOURSE_S3_CDN_URL: https://pub-XXX.r2.dev
  DISCOURSE_S3_ENDPOINT: https://XXX.r2.cloudflarestorage.com/XXX
  DISCOURSE_S3_BUCKET: XXX
  DISCOURSE_S3_INSTALL_CORS_RULE: false

Также я подтвердил, что ключи API являются ключами аккаунта, а не только ключами, специфичными для бакета (как упоминалось в посте). Кроме того, мой экземпляр Discourse показывает следующее:

После запуска sudo -E -u discourse bundle exec rake s3:upload_assets отображается:

`/root` не доступен для записи.
Bundler временно использует `/tmp/bundler20250410-2363-zj2g6x2363` в качестве вашего домашнего каталога.
Установка правил CORS...
пропуск
rake aborted!
Seahorse::Client::NetworkingError: Пустое или неполное тело ответа (Seahorse::Client::NetworkingError)
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/sse_cpk.rb:24:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/dualstack.rb:21:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/accelerate.rb:43:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/checksum_algorithm.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:16:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/invocation_id.rb:16:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/request_callback.rb:89:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/response_target.rb:24:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:39:in `block in call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/telemetry/no_op.rb:29:in `in_span'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:53:in `span_wrapper'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:39:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/request.rb:72:in `send_request'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/client.rb:12654:in `list_objects_v2'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/bucket.rb:1513:in `block (2 levels) in objects'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/bucket.rb:1512:in `block in objects'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `block in non_empty_batches'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `block in each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/lib/tasks/s3.rake:14:in `map'
/var/www/discourse/lib/tasks/s3.rake:14:in `existing_assets'
/var/www/discourse/lib/tasks/s3.rake:24:in `should_skip?'
/var/www/discourse/lib/tasks/s3.rake:36:in `upload'
/var/www/discourse/lib/tasks/s3.rake:197:in `block (2 levels) in <main>'
/var/www/discourse/lib/tasks/s3.rake:197:in `each'
/var/www/discourse/lib/tasks/s3.rake:197:in `block in <main>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'
Задачи: TOP => s3:upload_assets
(Полный трассировочный отчет можно получить, запустив задачу с флагом --trace)

Думаю, вам придется удалить имя бакета из конечной точки. Нужно убрать завершающий /xxx, чтобы осталось только .com

Пересоздаю и снова выполню команду, спасибо за помощь!

Теперь мой app.yml выглядит так:

  ## Настройки S3
  DISCOURSE_USE_S3: true
  DISCOURSE_S3_REGION: auto
  DISCOURSE_S3_ACCESS_KEY_ID: XXX
  DISCOURSE_S3_SECRET_ACCESS_KEY: XXX
  DISCOURSE_S3_CDN_URL: https://pub-XXX.r2.dev
  DISCOURSE_S3_ENDPOINT: https://XXX.r2.cloudflarestorage.com
  DISCOURSE_S3_BUCKET: XXX
  DISCOURSE_S3_INSTALL_CORS_RULE: false

Я считаю, что всё это верно.
Убедитесь, что у CDN_URL (https://pub-xxx.r2.dev) есть публичный доступ на чтение, чтобы анонимные пользователи могли просматривать ресурсы.
Вы можете увидеть, что происходит, в инструментах разработчика браузера. Если права доступа настроены неправильно, во вкладке «Сеть» вы увидите множество ошибок 403 и красных запросов.

Да, я так считаю:

Правильно ли это настройка:

Это один из способов, но не рекомендуемый, и вы столкнетесь с проблемами.

Предполагая, что у вас уже есть домен и Cloudflare уже является вашим DNS:

Cloudflare автоматически настроит проксирование и сможет выполнять кэширование для этого домена.
Затем вы можете изменить параметр CDN_URL на этот пользовательский домен.

О, мне нужно подключить пользовательский домен к бакету?

В настройках ведра S3 есть параметр общего доступа.
Задайте для него уникальное поддоменное имя. (Cloudflare автоматически создаст для вас DNS-запись, а также настроит проксирование и кэширование)

Кажется, я разобрался?

Удалось ли вам настроить резервное копирование в Cloudflare R2? И возможно ли (при условии, что копирование в R2 поддерживается) настроить одновременное создание резервных копий и локально, и в Cloudflare R2?

Также, означает ли скрипт загрузки всех ресурсов, что они будут удалены с локального диска (для освобождения места)? Или мне нужно выполнить отдельную процедуру?

Спасибо за уделенное время и помощь! :slight_smile:

Лично я не пробовал.
Мой форум относится к категории «неподдерживаемых», так как моя база данных внешняя, а стратегия резервного копирования отличается от используемых форумом pg_dump.
Судя по тому, что я слышал, резервное копирование не работает на Cloudflare, но ничто не мешает вам попробовать.