如何为您的 Discourse 社区配置 Cloudflare R2

Cloudflare R2 存储桶可用于存储 Discourse 社群的静态资源(如图片和 GIF),但不能用于存储社群备份!

简介:

Cloudflare R2 对象存储可用作 Amazon S3 的替代方案,用于存储 Discourse 论坛的上传文件。以下步骤概述了如何进行配置。

配置步骤:

  1. 启用 S3 上传: 在 Discourse 设置中勾选启用 S3 上传的复选框。
  2. S3 访问密钥 ID: 输入 R2 存储桶的 API 密钥 ID。这是创建存储桶 API 令牌时提供的 ID。
  3. S3 密钥: 输入创建授予对存储桶访问权限的 API 令牌时提供的密钥。重要提示: 此密钥仅显示一次,请务必安全备份。
  4. S3 区域: 可以输入任何区域,对于 R2 来说无关紧要。
  5. S3 上传存储桶: 输入 R2 存储桶的名称。
  6. S3 端点: 输入 R2 存储桶的 S3 API 链接,格式为 https://xxxxxx.com。请参阅 Cloudflare R2 控制面板查找此链接。
  7. S3 CDN URL: 输入存储桶的公共 R2.dev 存储桶 URL。此链接也将在 Cloudflare R2 控制面板中找到。

完成:

配置完这些设置后,您的 Discourse 论坛将设置为使用 Cloudflare R2 进行存储。

免费套餐信息:

Cloudflare 的 R2 服务提供免费套餐,包括每月 10 GB 存储空间、100 万次上传和 100 万次读取操作。

5 个赞

我建议您遵循为上传配置 S3 兼容对象存储提供商中的示例,并将设置放在 yml 文件中,而不是数据库中。

感谢您的反馈。我之前仔细阅读了该指南,并且我认为关于 Cloudflare R2 的建议是不正确的。文章建议 Discourse 社区不支持 Cloudflare R2 存储桶。然而,实际上 Cloudflare R2 与 S3 高度兼容,并且可以完美地处理 Discourse 社区的图片和文件上传和下载。这已经通过我在我的社区 (starorigin.net) 上的实际应用得到了验证。

1 个赞

我怀疑文章写的时候确实是这样。

将 S3 设置放在 yml 文件中比通过用户界面配置并将它们存储在数据库中要好得多。您是否尝试过将数据库恢复到新服务器?

设置好推荐的配置后,您可以编辑该主题或发表评论并请其他人来编辑。

1 个赞

你说得对,我使用 Cloudflare R2 存储桶来存储社区的图片、GIF 和其他资源。这大大减轻了社区服务器的负载,并加快了页面加载速度。

我还没有为我的社区设置自动备份以存储在 Cloudflare R2 存储桶中,因为 Cloudflare R2 存储桶不支持存储压缩文件。但是,Cloudflare R2 存储可以存储社区的 PDF、图片、GIF 和其他静态资源,这也非常好。

有趣。我以为我以前用 R2 做过备份。但也许我记错了。

你仍然可以遵循推荐的说明,并注意不要把备份放在那里。

谢谢提醒,我会重点关注这部分。

Cloudflare R2 存储桶可用于存储 Discourse 社区的静态资源(如图片和 GIF),但不能用于存储社区备份!

为了更新此帖子,我需要包含一些在 Cloudflare 为我工作之前必须解决的陷阱。


1. 区域


事实并非如此,我必须使用“auto”或我选择的区域,“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 不起作用。

3 个赞

运行 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 密钥是账户 API 密钥,而不是仅限于存储桶的密钥(如帖子中所述)。此外,我的 Discourse 实例显示如下:

运行 sudo -E -u discourse bundle exec rake s3:upload_assets 后显示:

`/root` 不可写。
Bundler 将暂时使用 `/tmp/bundler20250410-2363-zj2g6x2363` 作为您的主目录。
正在安装 CORS 规则...
跳过
rake 中止!
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: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>'
Tasks: 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,并且(假设可以备份到 Cloudflare R2)是否有可能同时进行本地备份和 Cloudflare R2 备份?

另外,上传所有资源的脚本是否意味着它会删除本地资源(以释放存储空间)?还是我需要单独执行此操作?

感谢您花时间帮助我 :slight_smile:

我个人还没试过。
我的论坛属于“不受支持”类别,因为我的数据库是外部的,并且我的备份策略与论坛使用的 pg_dumps 不同。
据我所知,备份在 Cloudflare 上不起作用,但没有什么能阻止你尝试一下。

1 个赞