MKJ 的观点性话语部署配置

我运行了一个 Discourse 论坛,里面包含了大量的内容和图片,已经好几年了。Maker Forums 拥有超过 100GB 的图片以及超过 400,000 个帖子,其中相当一部分是从 Google+ 导入的,其余的则是在网站上创建的。这篇文章描述了我最终配置 Maker Forums 以及后来其他几个 Discourse 实例时所涉及的一些要素。这是我当初起步时希望知道的事情,我也用这些经验帮助其他人避免在自己部署 Discourse 实例时犯同样的错误。

面向更广泛的受众。

:warning: 警告: 如果你不熟悉 Linux 系统管理员的工作,这份指南可能不适合你。我甚至可能没有意识到这份指南中预设了多少关于 Linux 的知识。如果读起来让你豁然开朗,那你可能就是目标受众。如果读起来让你感到困惑,那你可能不是目标受众。如果这看起来像是一项繁重的工作,请考虑付费请 CDCK 或 @pfaffman 为你运行 Discourse;他们非常专业。或者先从 免费的 discourse.group 站点 开始,然后随着规模增长再付费升级。:warning:

:warning: 这还不够:我的 Linux 专业知识比 Discourse 专业知识更丰富。我的观点不承担任何保证责任。如果尝试遵循我的建议导致你的任何东西损坏(你的 Discourse 论坛、你的主机系统,或者你的心),损坏的碎片归你所有,而且全是锋利的边缘。我没有任何计划为本文中的内容提供任何形式的支持。:warning:

我计划(但不承诺)根据我参与维护的 Discourse 实例的实践情况,随时更新这份文档。这份文档以建议的形式撰写,但主要旨在作为对我自己以及任何继承了我负责的 Discourse 部署的管理员的建议。除此之外,你应该将其视为你自行研究的一个起点,以确定你想要如何部署 Discourse。

系统设置

使用基于 CentOS 或 Ubuntu LTS 的操作系统。任何支持 Docker 的系统可能都能工作,但我使用的是这两个。

Docker

我是 Fedora 用户。我曾担任 Red Hat 的首位 Fedora 项目主管,我更倾向于在 Podman 上运行 Discourse,因为我认为其安全模型优于 Docker。然而,Discourse 部署仅支持 Docker,如果你尝试在其他平台上运行,你将是一位相当先锋的探索者。(也许将来,如果 docker-compose 得到支持,可以通过 podman-compose 在 Podman 上运行。)

现在 Docker 支持 cgroups v2,你可以在基于 CentOS 的系统上安装官方 Docker 构建版:

dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install --allowerasing docker-ce docker-ce-cli

(包含 --allowerasing 是因为可能与已安装的 podmanruncbuildah 存在冲突;需要删除它们才能安装 docker。)

systemctl enable --now docker

我已在 AlmaLinux 9 上测试过此操作。

安全

这一部分与 Discourse 本身没有直接关系,但这是我常规安全实践的一部分。不要允许仅凭密码通过 shell 访问网络上的任何系统,包括运行 Discourse 的虚拟机。 设置基于 SSH 密钥(使用密码短语加密)的访问,并配置虚拟机上的 ssh 服务器,禁止密码访问。

laptop$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/.../.ssh/id_rsa):
Enter passphrase (empty for no passphrase): SOME LONG PHRASE
Enter same passphrase again: SOME LONG PHRASE
Your identification has been saved in .ssh/id_rsa
Your public key has been saved in .ssh/id_rsa.pub

Linux 发行版通常设置为在内存中记住密码短语,因此每次启动只需输入一次。Windows 没有这么方便;你可以考虑使用 Pageant 配合 PuTTY 来实现同样的功能。

首先,验证无需密码的传入 SSH 连接是否正常工作。只有在完成此操作后,在服务器上修改 /etc/ssh/sshd_config 文件,找到 PasswordAuthentication 行。将其设置为 no 以禁用传入的密码访问。

PasswordAuthentication no

防火墙

你需要保持 80 和 443 端口通常开放,以便 letsencrypt 生成和更新你的 SSL 证书,即使你的 Discourse 尚未对公众开放。

如果你使用 firewalld,以下命令可以实现此目的:

firewall-cmd --add-service http --add-service https --zone public
firewall-cmd --runtime-to-permanent

独立的设备和文件系统

将 /var/discourse/shared 设置为一个独立的设备,拥有自己的文件系统,空间为 20GB 加上至少两倍于你所需图片存储的空间;如果你将使用 prometheus,请增加更多空间。如果该设备以后易于扩展(例如 LVM 或任何云块存储,如 AWS 弹性块存储),你可以监控并根据需要增长它;否则,请在开始时预留充足的空间。如果你使用的是网络存储块设备,不要在其上创建分区表。不使用分区表将使扩展更容易;你无需修改分区表。在许多情况下,你可以在没有任何系统停机时间的情况下进行扩展。

在 Maker Forums 上,这是一个连接到运行 Maker Forums 的虚拟机的网络存储块设备。在另一个 Discourse 论坛上,它是 Digital Ocean 块存储卷。在 Amazon 上,这将是 AWS 弹性块存储。在我运行于 Fedora 上 libvirt 下的 KVM 虚拟机的测试系统上,它是 Fedora 主机上的 LVM 卷,作为虚拟磁盘导出给 AlmaLinux 虚拟机。在每种情况下,我都可以创建一个新的虚拟机,将关键文件复制过去,停止旧虚拟机,将 /var/discourse/shared 卷附加到新虚拟机,并在几分钟内恢复运行。这使得虚拟机上的操作系统升级风险相对较低。

确保你的虚拟机根文件系统至少有 25GB 空间,不包括 /var/discourse/shared 的任何空间。这将用于所有 docker 容器,如果任何时候可用空间少于 5GB,discourse launcher 将会失败。你还需要为系统更新预留充足的空间。如果磁盘空间不足,很难恢复。

在站点配置中,请设置 force_https,但注意警告。在将 Discourse 站点公开之前,先在测试环境中设置它。请注意,即使使用 force_https,你也需要开放 80 端口,以便重定向到 443 端口的 SSL 并更新你的 letsencrypt SSL 证书。(但是,如果你使用 cloudflare,请使用其功能;据报道它与 Discourse 中的 force_https 不兼容。)

内核配置

Redis(Discourse 构建的关键组件之一)强烈建议在使用基于磁盘的持久化时禁用透明大页面(Discourse 正是这样做的),我也允许内存超额分配。

echo 'sys.kernel.mm.transparent_hugepage.enabled=never' > /etc/sysctl.d/10-huge-pages.conf
echo 'vm.overcommit_memory=1' > /etc/sysctl.d/90-vm_overcommit_memory.conf
sysctl --system

Discourse 安装

虽然默认安装是单个容器,但这使得每次升级(建议每月进行)如果从命令行执行,通常需要 10-15 分钟的停机时间,这对于某些更新(包括更新 Discourse 构建基础工具的更新、安全或新功能更新,或者当 UI 实时更新因任何原因失败时)是必要的。你可以通过双容器安装在实际操作中减少停机时间。

双容器安装

从两个容器开始配置。

./discourse-setup --two-container --skip-rebuild
${EDITOR:-nano} containers/data.yml
./launcher rebuild data
# 我倾向于使用 app.yml,但你也可以坚持使用 web_only.yml,阅读相关文本
mv containers/web_only.yml containers/app.yml
${EDITOR:-nano} containers/app.yml
./launcher rebuild app

这使得每几个月必要的系统停机时间非常短,几乎察觉不到;如果用户在停机期间不点击或滚动浏览内容,许多用户根本不会注意到。这使得应用大多数安全更新变得更加容易;这只是一个短暂的停顿,而不是大约 15 分钟的重建所有东西。以下过程适用于大多数更新,通常会产生大约 30-90 秒的停机时间,主要取决于主机系统的性能和安装的插件集。

cd /var/discourse
git pull
./launcher bootstrap app
./launcher destroy app && ./launcher start app
./launcher cleanup

不要在 bootstrap 和 destroy/start 调用之间延迟。不经常(在实践中可能一年一两次),在 bootstrap 阶段末尾执行的数据库迁移会导致应用程序用户出现或多或少严重的错误,这是由于旧代码访问了更新的数据库。

确实意味着当你更新 Discourse 时,你还必须检查是否也需要更新数据容器,但这很少需要(通常预期每年一两次)。根据你的数据容器和应用程序容器的内容以及系统速度,这通常会导致 5 到 20 分钟的停机时间。

cd /var/discourse
git pull
./launcher stop app
./launcher rebuild data
./launcher rebuild app

关于何时更新数据容器的更多信息,请参阅:

(在我自己的部署中,我个人选择将 web_only 容器称为 app,既因为它更容易输入,也因为这使得大多数说明更容易遵循。这是非标准的,但我一直欣赏其易用性。然而,这是额外的工作,它对我有效是因为我知道发生了什么。如果这听起来不好,请在多容器部署中坚持使用默认的 web_only。)

请注意,在未来的某个时候,Docker 可能会强制你迁移到新的配置以连接你的容器:

如果你有 4GB 或更多内存,或多个 CPU,请阅读以下建议:

更新计划

关注 release-notes 标签(点击 release-notes 并点击右上角的铃铛;我使用“关注首帖”)和/或将 https://meta.discourse.org/tag/release-notes.rss 添加到你的 RSS 源,以了解何时发布新版本。在更新之前阅读发布说明。如果有数据库更改,发布说明会提及。它们还会指出包含安全更新的版本。即使你跳过实际更新到某些版本,也要阅读所有发布说明;如果你不阅读更新数据库的版本的发布说明,你可能会错过未阅读的发布说明中的数据库更新说明。

邮件

邮件仍然是保持人们联系的关键方式之一。设置传出和传入邮件,让邮件为你工作。如果有问题,请参阅:

保持联系

Maker Forums 曾见过一些长期离开后返回的偶尔访客。默认情况下,Discourse 在一年后停止发送摘要电子邮件。如果你想鼓励偶尔访客在看到新有趣内容时回来,请考虑将 suppress_digest_email_after_days 设置为比默认 365 天更长的时间。我为 Maker Forums 将其设置得长得多,以支持偶尔访客保持最新。阅读摘要电子邮件是“潜水”浏览论坛的有效方式,你永远不知道什么时候有什么东西会激发某人参与贡献的兴趣。

类似地,默认情况下,不活跃的特权用户(信任级别 0 且无帖子)在 730 天未登录后最终会被删除。将“清理不活跃用户天数”设置为 0 以禁用删除用户,如果你希望他们能够无限期地潜水阅读摘要电子邮件。

考虑添加 yearly review 插件,它每年会生成一个帖子,如 2020: The Year in Review 并最终将其电子邮件发送给你的不活跃用户,这可能会鼓励他们重新参与。

邮件接收容器

设置第三个容器作为邮件接收器。它确保处理退信,使你的退信处理独立于传出邮件提供商,并为你提供通过电子邮件回复的选项。

确保你设置了 SPF 以信任你的电子邮件发件人;至少,如果你通过同一个 MX 发送和接收,策略如 v=spf1 +mx -all,但更具体的可能作为垃圾邮件保护更受信任。也考虑 DKIM

如果你使用相同的主机名接收电子邮件,你真的应该在容器外部终止 SSL,就像“离线页面”一样(见下文),并且需要将你的 certbot 证书映射到容器中,并在运行 certbot 后重启容器。

在容器外部终止用户 SSL 连接

有两种选择在容器外部终止 SSL,任何一种都比在容器内部终止带来显著优势。在成功完成 discourse-setup 并引导你的论坛后,设置其中一种。

外部 nginx

在主机系统上运行 nginx,而不仅仅是在容器中,既用于托管维护页面,也用于支持 IPv6 地址日志记录(如果主机支持 IPv6)。(否则,所有 IPv6 连接都将记录为来自与你的本地 docker 虚拟网络接口相关的内部 RFC1918 地址。)此配置将在大多数维护操作期间显示临时维护页面,最终将重定向回用户查看的页面。

请注意,该页面上的说明(目前)建议安装名为 letsencrypt 的包,但现在通常称为 certbot。如果你按照该页面上的说明使用 --certonly,你将不需要 certbot 的 nginx 插件,但安装 nginx 插件是另一种机制。在 CentOS 衍生版上:

dnf config-manager --set-enabled crb
dnf install epel-release
dnf install certbot python3-certbot-nginx
systemctl enable --now certbot-renew.timer

确保 certbot 重启 nginx 和邮件接收容器,以免浏览器或电子邮件因继续使用旧的、过期的证书而阻止与你的站点的流量。

# systemctl edit certbot-renew

对于没有邮件接收器的系统,我添加了两行:

[Service]
ExecStartPost=/bin/systemctl reload nginx

在我使用单独邮件接收容器且共享系统证书的系统上:

[Service]
ExecStartPost=/bin/systemctl reload nginx
ExecStartPost=/bin/sh -c 'cd /var/discourse && ./launcher restart mail-receiver'

如果你使用 SELinux,Ubuntu 容器未设置为将 nginx.http.sock 文件标记为 httpd_sys_content_t 以便外部 nginx 能够访问它。你有两个选择。

第一种是以宽容模式运行 nginx,移除其 SELinux 保护:semanage permissive -a httpd_t

然而,这移除了可能最相关服务的 SELinux 保护!为了保持 SELinux 启用,你需要允许 nginx 访问错误页面,并从通过 unix 域套接字代理切换到端口(这慢几微秒,但你的用户应该察觉不到)。

首先,运行这些命令以允许 nginx 访问错误页面:

semanage fcontext -a -t httpd_sys_content_t /var/www
restorecon -R -v /var/www

然后在你的 app.yaml 中,注释掉或删除 - "templates/web.socketed.template.yml",将端口 80 暴露为本地机器上的不同端口,并重建容器。

expose:
  - "8008:80"   # http

不要在这里使用 https —— 你已经在外部 nginx 中终止了 SSL,X-Forwarded-Proto 头告诉 Discourse 请求是通过 https 进来的。确保端口 8008(或你选择的任何其他端口)没有通过防火墙设置公开暴露。

然后运行此命令以允许 nginx 通过网络连接到容器:

setsebool -P httpd_can_network_connect 1

然后修改你的外部 nginx 配置,从通过 nginx.http.sock 代理切换到 http://127.0.0.1:8008(或你选择的端口),并清除默认的 Connection: close 头,这样外部 nginx 就不必为每个请求建立新的 IP 连接。

...
  location / {
    proxy_pass http://127.0.0.1:8008;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    # Disable default "Connection: close"
    proxy_set_header "Connection" "";
...

删除 web.socketed.template.yml 也删除了 real_ip 调用,所以把它加回来。确保你使用的 IP 地址范围有意义;Docker 的默认是使用 172.16* RFC1918 地址空间,这些地址在公共互联网上策略上不路由。在你的 app.yml 文件中添加类似以下内容,在 run 部分中,选择一个或多个 RFC1918 地址空间或适合你部署的其他内容:

run:
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-recursive.conf
     chmod: 644
     contents: |
       real_ip_recursive on;
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-header.conf
     chmod: 644
     contents: |
       real_ip_header X-Forwarded-For;
  - file:
     path: /etc/nginx/conf.d/outlets/server/set-real-ip-from.conf
     chmod: 644
     contents: |
       set_real_ip_from 192.168.0.0/16;
       set_real_ip_from 172.16.0.0/12;
       set_real_ip_from 10.0.0.0/8;

这对于速率限制正常工作以及归因用户的注册和最后使用 IP 地址是必需的。

更多信息:

外部服务

我还没有在 Discourse 前面配置 Fastly 或 Cloudflare,但其他人已经配置了,与在主机上运行的外部 nginx 不同,它们允许你在主机系统完全关闭时(例如在主机上进行系统更新时重启)提供维护页面。如果这对你有价值,以下是操作方法:

不要急于使用 S3 上传

在启用 enable_s3_uploads 或在设置后迁移到它之前,非常确定你总是想使用 S3(或等效服务)上传图片。请注意,使用 S3 (s3_endpoint) 及其关联的 CDN (s3_cdn_url) 用于图片也会导致通过该 CDN 提供 javascript。从 S3 迁移回本地存储不受支持,目前也没有具体的实施计划。这是一扇“单向门”,甚至无法通过完整备份和恢复来撤销。如果你确实使用 S3 或类似服务,不要使用 Digital Ocean Spaces 代替 S3。meta 上有参考指出它不可靠。

我早期将我的站点迁移到通过 Digital Ocean Spaces 及其关联的 CDN 提供图片,我不得不编写数百行自定义代码以迁移回本地存储,在此过程中对我的 Discourse 实例造成了轻微损坏,因为“单向门”未被充分理解。

更多信息:

你不需要启用 S3 上传来为 Discourse 使用 CDN。考虑在管理自己图片的 Discourse 前面使用独立的 CDN(例如 Cloudflare、CloudFront、Fastly、GCS CDN)。据我二手了解,关于不建议使用 Cloudflare 的警告是由于“Rocket Loader”修改 JavaScript;并且目前,只要你不使用“Rocket Loader”,它就能正常工作。

Discourse 设置以进行 moderation

在任何 moderation 活跃的站点上,强烈考虑 enable_whispers 配置,它允许 moderation 员和管理员在行内讨论主题。此外,类别 moderation 员在最近的 Discourse 版本中被赋予了更多能力。如果你有不同主题的专家拥有自己的类别,或者你有功能上独立的类别(例如用于支持),值得了解 enable_category_group_moderation

地理位置在尝试确定账户是否合法时可能很有帮助。

Discourse Templates 功能对 moderation 员真的很有帮助。它让你协作处理常见回复。我们在 Maker Forums 有几十个。它比它取代的先前“Canned Responses”插件拥有更多功能。

User Notes 功能将帮助 moderation 员共享关于用户的笔记。你可以将这些用于:

  • “留意这个用户,他们可能是恶意的,因为……”
  • “虽然这种行为看起来可疑,但我已通过……验证这是一个合法用户。”
  • “我已经在这个用户那里进行对话以解决疑虑,其他 moderation 员不需要凑热闹。”

信息可访问性

Discourse Solved 插件不仅标记已解决的问题,以便站点访问者更容易识别,而且我理解它可能还会优先处理 google 搜索结果。

公共信息比私人信息更容易访问。在 Maker Forums 上,我们的 FAQ 强烈 discourage 个人消息,并提醒每个人个人消息并非真正私密。但是,默认情况下,用户可能会看到消息:

你已回复 user 3 次,你知道你可以发送个人消息给他们吗?

如果你真的想鼓励用户去个人消息,我建议你转到 Admin → Customize → Text 并更改 get_a_room 模板以修复逗号拼接。

如果像 Maker Forums 一样,你想保持对话公开以惠及所有人,Admin → Settings → Other → get_a_room_threshold 可以设置得更高,比如 1000000。

类似地,如果你有一个提供支持的论坛,max_replies_in_first_day 默认值 10 可能会在用户用完回复预算时,将寻求帮助的对话中的新用户推入个人消息。考虑增加此设置以避免将对话推入个人消息。

连接用户,建立社区

一些插件可以帮助用户相互连接。

如果你的论坛没有太多同时在线用户,考虑使用 Who’s Online 插件,让人们更有连接感。你可能想限制显示给登录用户,可能仅限于至少达到信任级别 1 的用户。你可以通过将 whose_online_minimum_display 设置得很高并将 whos_online_hide_below_minimum_display 设置为 true,仅用它来为头像添加存在标识 (whos_online_avatar_indicator)。这对于支持论坛以支持和鼓励快速问答并帮助用户解决问题可能很有用。

然而,存在感是一把双刃剑。如果用户在线时间与大多数论坛用户不同,他们可能会感到孤独,或者论坛对他们来说可能像“鬼城”。

如果你有很多国家的用户,并希望他们有关于彼此更可能可用的提示,考虑使用 National Flags 插件,并鼓励用户在他们的个人资料中设置国旗。

翻译是一个棘手的问题。当人们不说同一种语言时,帮助沟通会很方便,但目前(截至本文撰写时)没有提供免费服务层级的翻译服务。如果你选择付费翻译服务,你可以使用 Discourse Translator 插件启用翻译。

备份

对于系统文件,考虑至少备份:

  • /var/discourse/containers(用于 discourse 配置详情)
  • /var/www(用于错误页面)
  • /etc/ssl(用于 letsencrypt 配置,以避免在恢复备份时引导 certbot;否则你必须在引导时注释掉 nginx 配置中的 SSL 部分;这仅在你保持备份近期时有效,因为证书有效期短)
  • /etc/systemd/system/backup-uploads.service(用于将图片备份到 S3)
  • /usr/local/bin/mc(minio-client 作为图片备份工具,如果你选择使用)
  • /root/.mc(minio-client 图片备份配置)
  • /root/.ssh(传入 SSH 会话身份验证)

其中一些文件你可以通过将它们检入 Git 并推送到异地来备份。如果你检入 Git 的文件包含秘密(如数据库密码),绝对不要将它们推送到公共存储库。或者,你可以脚本化将它们复制出系统并检入到你控制的监督系统上的 Git。脚本化足够频繁以保持 /etc/ssl 的备份新鲜。

目标是在灾难情况下拥有备份,并在出错时拥有更改记录。

对于大多数这些文件,更好的替代方案是将规范副本保存在其他地方,并使用 Ansible 等工具维护系统上的配置,这使得备份后更新同样容易。但如果你要这样做,你可能不需要我告诉你就明白了!

Discourse 配置以进行备份

  • 使用 include_thumbnails_in_backups 备份缩略图。没有缩略图的恢复需要很长时间来重新生成它们。如果你的站点没有太多图形,缩略图占用的空间可以忽略不计。如果你的站点图形丰富,重新生成缩略图可能需要几天。在重新生成缩略图时,电子邮件通知将被禁用。无论如何,从备份中省略缩略图没有意义。

  • 如果你有很多图片,不要在备份中包含图片。这将使备份变得缓慢且笨重。单独备份它们。如果你在数据库备份后备份图片,你的备份将保持一致。

  • 安排以某种方式将备份发送到异地。

此页面显示如何设置数据库备份到 S3 或类似 S3 的服务:

使用 restic 备份

配置 discourse 备份到文件系统,然后使用 restic 从文件系统备份到远程备份目标。这是一个示例配方。

# dnf install restic
# mkdir /var/restic
# mkdir /opt/backup
# cd /opt/backup
# cat > backup <<EOF
#!/usr/bin/bash

set -e
. /opt/backup/backup-config

restic --cache-dir=/var/restic \
	backup \
	/etc \
	/root \
	/var/discourse \
	/opt/backup \
	--exclude /var/discourse/shared/data/postgres_data

restic --cache-dir=/var/restic forget \
	--prune --keep-hourly 24 --keep-daily 7 --keep-monthly 3
EOF
# chmod +x backup

这些细节会根据 restic 目标的不同而有所差异。并非所有目标都使用 AWS 环境变量,因此请阅读 restic 文档。

# cat > backup-config <<EOF
### 这些细节取决于你配置的 restic 目标,因此请修改它们
export AWS_ACCESS_KEY_ID=yours-here
export AWS_SECRET_ACCESS_KEY=same
export RESTIC_PASSWORD_FILE=/root/restic-password
export RESTIC_REPOSITORY=see-the-restic-documentation
EOF
# {$EDITOR:-nano} /root/restic-password

你需要保存一份 /root/restic-password 中内容的副本,否则你将无法读取备份!请使用你的密码保险箱。

最后,创建一些服务,初始化 restic 存储库,并设置计时器以启动备份。

# cat > /etc/systemd/system/backup.service <<EOF
[Unit]
Description=Back up to remote target
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
StandardOutput=file:/var/log/backup.out
StandardError=file:/var/log/backup.err
WorkingDirectory=/var/discourse
ExecStart=/opt/backup/backup

[Install]
WantedBy=multi-user.target
EOF
# cat > /etc/systemd/system/backup.timer <<EOF
[Unit]
Description=Regular system backups

[Timer]
Persistent=true
OnCalendar=00/4:30:00
Unit=backup.service

[Install]
WantedBy=timers.target
EOF

# systemctl daemon-reload
# . /opt/backup/backup-config
# restic init
# /opt/backup/backup
# systemctl enable backup.timer

完成此过程后,你应该能够看到已创建初始备份。

# restic snapshots

我成功地使用这种方式制作的 restic 备份,通过 restic restore 命令将 Discourse 服务器从一个系统迁移到另一个系统。

流式 minio 图像备份

作为 Restic 的替代方案,虽然数据库备份可以存储到 S3,但没有独立于从 S3 提供图像的 S3 图像备份。另一种方法是使用 minio-client 将图像复制到任何类似 S3 的存储。这可以是许多类似 S3 的目标,包括 S3 和 minio,但 不包括 DigitalOcean Spaces,因为它构建在 Ceph 文件系统之上,而 Ceph 文件系统以与 S3 不同的方式实现 ListObjectsV2 API。

在 S3 中,创建一个阻止公共访问的存储桶(权限阻止公共访问 是在 AWS 中正确设置的最简单方法)。

以某种方式安装 minio-client (mc)。这是一种方法。

curl https://dl.min.io/client/mc/release/linux-amd64/mc > /usr/local/bin/mc && chmod +x /usr/local/bin/mc

使用类似以下的命令配置名为 backup 的别名来设置 minio-client:

# mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
# mc mirror /var/discourse/shared/standalone/uploads backup/UPLOADS-BACKUP-BUCKET

然后创建服务 /etc/systemd/system/backup-uploads.service 如下

[Unit]
Description=Neartime remote backup sync of discourse uploads
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=600
User=root
ExecStart=/usr/local/bin/mc mirror --overwrite -a --watch /var/discourse/shared/app/uploads backup/UPLOADS-BACKUP-BUCKET

[Install]
WantedBy=multi-user.target

注意,这里的 UPLOADS-BACKUP-BUCKET 应该是一个与配置 discourse 上传数据库备份的 s3_backup_bucket 不同的存储桶。另外,请注意,如果你使用标准的多容器部署,路径将是 /var/discourse/shared/web_only/uploads

# systemctl enable backup-uploads
# systemctl start backup-uploads
# journalctl -fu backup-uploads

上传测试图像,并确保你看到成功备份原始和优化图像的行。在 journalctl 中按 Control-C 将退出跟随模式。

恢复

截至本文撰写时,我从未需要测试此计划。此摘要可能会遗漏某些内容。

  • 通常恢复所有备份文件
  • 启动 nginx(现在将显示你的维护页面)
  • 使用 /var/discourse/containers 中恢复的文件进行正常的 Discourse 部署
  • 如果你没有从备份中恢复,请在 /usr/local/bin/mc 中安装 minio-client
  • 如果你没有备份 /root/mc,设置备份别名 # mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
  • # mc cp backup/UPLOADS-BACKUP-BUCKET /var/discourse/shared/app/uploads
  • 恢复最新的数据库备份;我建议你 Restore a backup from the command line
  • 仅在确认站点运行正常后,按照上述文档重新配置上传到 S3 的备份。

流式 postgresql 备份

未来,我可能会创建、测试并提供一种配置,以启用使用 连续 WAL 归档 通过 postgresql 中的 archive-command 使用 minio-client 流式传输近乎实时的 Postgres 备份,类似于流式上传备份。

性能监控

至少有两种性能监控方法。

Prometheus 容器

设置 prometheus,如果你在同一系统上运行,将 prometheus 日志放在 /var/discourse/shared/prometheus。Prometheus 文件可能会变得很大,你不希望它们填满根文件系统;如果你迁移到更新的主机系统(无论是升级到更大的 VM 还是安装新操作系统的 VM),你可能还希望带上它们。

如果你在 discourse 系统(或公共互联网上的任何地方)部署 prometheus,请在其前面配置安全。以这种方式安装,一种选择是类似以下的 nginx 配置:

  location /prometheus/ {
    auth_basic "Prometheus";
    auth_basic_user_file /etc/nginx/prometheus-htpasswd;
    proxy_pass http://localhost:9090/;
  }

Sysstat

如果 Prometheus 太复杂,请考虑使用 sysstat 代替。

  • dnf install sysstat(或在 debian 及其衍生版本上 apt install sysstat
  • systemctl enable --now sysstat
  • systemctl enable --now sysstat-collect.timer
  • systemctl enable --now sysstat-summary.timer
  • systemctl edit sysstat-collect.timer 并将 OnCalendar=*:00/10 更改为 OnCalendar=*:00/2
  • 如果 /etc/default/sysstat 存在,将 false 更改为 true

此后,sar 命令可以告诉你是否偶尔资源不足。

其他资源

这里有一个补充(且更紧凑)的讨论,关于在内部使用 Discourse 作为内部通信的主要形式。

54 个赞

您好,感谢您提供这份操作指南

您目前的 CPU(核心数)和内存是多少?
您当前的设置如下:
\n\n db_shared_buffers: \"xGB\"\n db_work_mem: \"xMB\"\n UNICORN_WORKERS:\n

2 个 vCPU(L5640 Xeon),4GB 内存——不过,得益于大规模的数据导入,我们每个同时在线用户的内容量比典型的完全自然增长的 Discourse 站点都要大。我们很少同时有超过 5 个用户。

我目前未在 data.yml 中设置 db_work_mem,但看起来它在 /etc/postgresql/13/main/postgresql.conf 中被设置为 10MB。在我的 data.yml 中,我设置了 db_shared_buffers: "768MB",但现在我发现数据容器中的 /etc/postgresql/13/main/postgresql.conf 显示 shared_buffers = 512MB,这让我感到意外。显然,我在上次更改后没有重建数据容器。:roll_eyes: 我在添加 prometheus(占用更多内存)之前进行了配置更改,因此在我更改该设置之前,我可能会先将 prometheus 迁移到该服务器实例之外。

在应用中,我设置了 UNICORN_WORKERS: 4

1 个赞

感谢 @mcdanlj 提供的所有这些好东西。关于定期/计划维护有什么建议吗?比如每周/每月重启?或者带自动重启的上下行监控?还有其他定期手动或自动维护吗?

1 个赞

@jaffadog 请关注 release-notes 标签(位于右上角的铃铛图标;我使用的是“Watching First Post”)和/或将 https://meta.discourse.org/tag/release-notes.rss 添加到您的 RSS feed 中,以便了解发布信息。应用程序通常每月发布一次,这涵盖了每月重启。我不会按计划更频繁地重启。另外:

如果您使用 letsencrypt 和 mail_receiver,您可能应该将其设置为在获取新证书后重启 mail_receiver。

目前我想到的就这些。感谢您的提问,我已将如何关注 release-notes 和更多关于重启的细节纳入正文。我认为现在所有内容都已在原帖中完全涵盖。

我已将更新说明增强为在 /var/discourse 中拉取更新,以更改基础映像版本,因为在查看 Update base image for polkit vulnerability 后,我意识到不明确说明此步骤可能会产生误导。我之前认为这是基线文档的一部分。launcher 脚本包含对基础映像版本的特定引用,并且在您 git pull 之前,您将基于旧的基础映像进行构建,而不是运行已测试过的版本。(查找文件顶部的 image=。)

1 个赞

情况比这更糟:它默认配置为在一段时间后删除不活跃的 0 级账户。对我来说,这真的不是我想要的!请检查“几天后清理不活跃用户”并将其设置为零,或者一个非常大的数字。

2 个赞

我看过那个了,但据我所理解,这意味着他们从未响应过“确认您的电子邮件地址”流程,因此无论如何都不会收到电子邮件。我不认为这里的“不活跃”是指“未登录网站”,但我想知道我是否错了。

不,“inactive”在这里并不意味着 active=false

它指的是符合以下所有条件的已停用用户:

  • 信任等级 0
  • 没有帖子
  • 不是管理员,也不是版主
  • 上次登录时间超过 X 天。

是的,这种措辞确实令人困惑,尽管设置中对此有所解释(“信任等级为 0 且没有任何帖子”)。

8 个赞

实际上,我混淆了不同的设置,并且想到了“清理几天后未使用的暂存用户”。我很久以前就已经在 Maker Forums 中将“清理几天后不活跃的用户”设置为零,并且在这里未能提及;在我审计可能引起普遍兴趣的已更改站点设置时,我一定错过了它。感谢你们两位,@Ed_S@RGJ!我已经更新了帖子,增加了关于启用潜水的另一段。:smiling_face:

4 个赞

您好 @mcdanlj,感谢您在此分享的精彩见解。关于双容器安装,我不明白在执行升级时,它如何能减少停机时间。在我的测试安装中,通过 GUI /admin/upgrade#/upgrade/all 界面进行的升级确实需要几分钟时间,正如您所描述的那样,但在整个过程中,网站对用户来说仍然是可操作的。

当您必须从命令行重建两个容器安装时,可以让您在旧容器继续运行时引导新映像。

2 个赞

正如往常一样,@pfaffman 比我更快,而且他懂行。:smiley:

我从不通过 GUI 进行升级。这样,每次我更新时,都会包含 Discourse 运行所依赖的底层容器的任何系统安全更新。这并不是说使用 GUI 不好,也不是给其他所有人都推荐这样做。GUI 更新的停机时间稍短(重新启动 Web 容器会暂时中断服务,这是考虑将其与外部 nginx 结合使用的几个原因之一),所以这是一个权衡,我选择了人迹罕至的路。

如果定期更新以获取安全更新以及 Discourse 利用新 Postgresql 功能所需的偶尔数据库版本更新,那么单容器安装的停机时间会更长。在没有查看实际数据的情况下,我的直觉是,每年有 3-4 次重建的理由。如果从您的角度来看,这种停机时间是可以接受的,那么就没有太多理由去承担双容器部署的复杂性。

4 个赞

你太客气了,但这关乎的看法。 :wink:

我也是。除了我的仪表板,它在容器中有许多额外的东西(比如 Ansible,我记不清具体是什么了),如果有人在使用 dashboard.literatecomputing.com 进行升级,然后我销毁了那个容器,他们的重建就会被终止,这可能会有问题。所以我最近做了几次 docker_manager 升级,它们非常流畅。

不完全是。至少在大多数情况下,如果有新的基础镜像,docker_manager 会强制你获取它(至少它会尝试)。

差不多就是这样。供参考,我不推荐这样做,但我接触过很多人多年来零升级也没有遇到任何问题。

1 个赞

是的,毫无疑问,容器内更新的实现得非常好!

是的,这正是我所指的。:tada:

2 个赞

大家好,再次感谢你们的见解。我仍在考虑我的生产环境设置。我理论上理解为什么 2 容器安装可以减少停机时间。但即使是 GUI 更新机制,我也几乎没有遇到过停机时间。我刚刚计时了一下,我进行了一次 docker-manager 更新,Discourse 落后了 22 个提交。整个过程不到 5 分钟,在此期间论坛一直可以完全运行。诚然,这次没有 PostgreSQL 更新,但如果也需要更新它,那么即使使用 2 容器方法,我也会面临最长的停机时间,对吗?所以,如果我理解正确的话,2 容器方法只在需要 SSH 登录和重建应用程序容器以进行配置更改(添加/删除插件等)时才能减少停机时间?我预计我的生产环境配置不会经常更改,所以我觉得在这种情况下不会有任何停机时间减少。或者我也需要 SSH 登录并重建容器来应用某些类型的特性/安全更新?

好的。

是的,每次更新 PostgreSQL 时,您都会面临完整的停机时间(我猜是每隔一两年一次,外加 PostgreSQL 本身的安全更新,尽管它们并不频繁)。

在我更改之前,我在进行 GUI 更新时遇到过几次失败,但不再记得细节了;那是久远的历史了。

GUI 更新不会更新基础映像,因此提供的软件(如图像处理)中的所有安全更新都不会在那里应用。

我喜欢快速重建应用程序容器,这是我处理可用应用程序容器安全更新的常规模式,这样我就永远不会因为 10 分钟的停机时间而推迟重建所有东西。这就是为什么 git pull 在启动器中是我应用每次更新的第一部分;这样,如果像图像处理程序这样的基础映像已更新,它们就会被应用,而我甚至不必考虑是否有安全更新需要应用。:smiling_face:

但最终,我个人发现双容器方法对我来说更简单,而且我绝对不是想说服别人这样做。如果根据您的知识和经验,它对您来说并不简单,请不要仅仅因为我个人主观的指南在特定情况下将其标识为有用就这样做。 :grin:

2 个赞

明白了! :wink: 我对技术实现的细节也有自己的看法,但我很欣赏你的补充观点。

嗯,这是真的吗?

无论如何,根据我使用传统论坛在 LAMP/LEMP 堆栈上安装而没有容器化的经验,导致网站被真实泄露的典型漏洞/攻击向量几乎总是在 Web 应用程序的代码或其 Web 开发框架中。因此,我倾向于更紧迫地关注 Discourse 代码库的更新,这些更新似乎由 GUI 处理,所以我想我更倾向于后者。


顺便说一句,关于停机时间,快速推荐一下 Clear Linux:我开始测试它是因为它在纯数字运算方面的低级优化,试图为我庞大的论坛导入过程节省几个小时。在这种情况下可能确实有一些速度提升,但总的来说,我的天啊,它在重启时_速度快得离谱_,尤其是在 KVM 虚拟机中。在最便宜的 VPS 上,我可以不到 5 秒就重启并重新登录 SSH。所以我期待在重要的主机操作系统更新时使用它。

是的。但每年你必须进行几次命令行重建,因为容器中的某些组件已更新。然后你将会有 5-15 分钟的停机时间。我几乎只从命令行进行升级(除了在我的仪表板上,我可能一天会更新几次仪表板插件),并且我为许多客户进行这些必需的命令行更新(据推测他们是通过 Web 界面进行更新的,但他们通常不会)。

2 个赞

Discourse 仪表板是否会专门通知这类必需的更新?还是我应该留意 Debian 的 PSA?