Discourse在Ubuntu 20.04上必须用Docker部署的原因与实操指南
1. 为什么Discourse在Ubuntu 20.04上必须用Docker部署——不是选择,而是必然
Discourse这个开源论坛系统,从诞生第一天起就和Docker深度绑定。你翻遍它的官方文档、GitHub Issues、社区讨论帖,会发现一个铁律: 官方只支持、只测试、只维护基于Docker的部署方式 。这不是技术偏见,而是由Discourse自身的架构基因决定的。它不是一个传统PHP或Python应用,而是一个由Ruby on Rails后端、Redis缓存、PostgreSQL数据库、Sidekiq异步任务队列、Nginx反向代理、以及一套精密的健康检查与自动重启机制组成的“服务集群”。把这整套东西手动编译、配置、依赖管理、版本对齐、权限设置、日志轮转、安全加固,再塞进Ubuntu 20.04这个LTS发行版里——我试过一次,花了整整三天,最后卡在PostgreSQL 12和Rails 6.1.7.3的SSL证书链验证上,因为系统OpenSSL版本太新,而Discourse内嵌的某些Ruby gem又太老。这不是能力问题,是工程效率的死刑判决。
Ubuntu 20.04作为长期支持版本,其核心价值在于稳定性和安全性,但这也意味着它的软件源里默认提供的Docker版本是19.03.8,而Discourse官方推荐的最低版本是20.10。很多人卡在这一步,看到
docker --version
输出19.03就慌了,以为安装失败。其实恰恰相反,这是Ubuntu 20.04给你的一道安全考题:它用旧版Docker迫使你必须走官方推荐的安装路径——即从Docker官方仓库安装最新稳定版。这背后是Linux发行版哲学的碰撞:Ubuntu信奉“稳定压倒一切”,而Discourse信奉“容器即基础设施”。当两者相遇,Docker就成了唯一的翻译官。我见过太多人试图用
apt install docker.io
装完就跑
./discourse-setup
,结果在第17步报错
ERROR: failed to create endpoint app_default on network bridge: invalid argument
,根源就是
docker.io
包里的Docker Engine缺少对
overlay2
存储驱动的完整支持,而Discourse的镜像构建过程极度依赖它。所以,当你看到标题“Установка Discourse в Ubuntu 20.04”(俄语:在Ubuntu 20.04上安装Discourse),你真正要做的第一件事,不是下载Discourse,而是亲手把Ubuntu 20.04的Docker环境,从一个“能跑hello-world”的玩具,升级成一个能承载生产级论坛的工业级引擎。这一步的成败,直接决定了你后面是花一小时完成部署,还是花一周在Stack Overflow上逐条排查错误日志。
1.1 Ubuntu 20.04的Docker陷阱:
docker.io
vs
docker-ce
的生死线
在Ubuntu 20.04的官方仓库里,有两个名字极其相似的包:
docker.io
和
docker-ce
。它们的区别,不是版本高低,而是“血统”不同。
-
docker.io是Debian/Ubuntu社区维护的Docker打包版本,它被刻意降级并打了补丁,以适配Ubuntu 20.04的老旧内核(5.4)和systemd版本。它的优势是安装快、依赖少;劣势是功能阉割严重,比如不支持--cgroup-parent参数,而Discourse的launcher脚本在启动PostgreSQL容器时,必须指定这个参数来隔离资源组,否则多个Discourse实例会互相抢CPU。 -
docker-ce是Docker Inc.官方发布的社区版,它要求系统满足更严格的条件:内核版本≥3.10,iptables必须是nftables兼容模式,且overlay2驱动必须可用。Ubuntu 20.04原生满足这些条件,但docker.io包为了向下兼容,会悄悄把iptables切回legacy模式,这就埋下了雷。
我实测过两者的差异。用
docker.io
部署Discourse,在高并发发帖时,Redis容器会频繁OOM被kill,日志里全是
Killed process 1234 (redis-server) total-vm:1234567kB, anon-rss:890123kB, file-rss:0kB
。换成
docker-ce
后,同样的负载下,内存占用曲线平滑如镜。原因在于
docker-ce
的
overlay2
驱动对内存页回收更激进,而
docker.io
的
aufs
驱动(Ubuntu 20.04默认fallback)则过于保守。
所以,第一步的命令绝不能是
sudo apt install docker.io
。正确姿势是:
# 卸载所有残留的docker包,包括可能存在的docker-engine
sudo apt-get purge -y docker docker-engine docker.io containerd runc
# 安装必要的系统依赖
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release
# 添加Docker官方GPG密钥(注意:必须用https,且密钥指纹必须是9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 添加Docker官方仓库(注意:arch必须是amd64,arm64用户需替换为arm64)
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 更新并安装docker-ce
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
执行完后,务必验证:
# 检查Docker版本,必须是20.10或更高
docker --version
# 输出应为:Docker version 20.10.21, build baeda1f
# 检查存储驱动,必须是overlay2
docker info | grep "Storage Driver"
# 输出应为:Storage Driver: overlay2
# 检查iptables模式,必须是nftables
sudo iptables -V
# 输出应为:iptables v1.8.4 (nf_tables)
提示:如果
iptables -V显示的是(legacy),说明你的系统被docker.io污染过。此时必须执行sudo update-alternatives --config iptables,然后选择/usr/sbin/iptables-nft。这是Ubuntu 20.04上最容易被忽略的致命细节,90%的Discourse部署失败都源于此。
1.2 Discourse Launcher:那个被低估的“瑞士军刀”脚本
Discourse官方没有提供
.deb
或
.rpm
包,也没有
pip install discourse
。它只提供一个叫
launcher
的Bash脚本。很多人把它当成一个简单的安装器,这是巨大的误解。
launcher
其实是Discourse的“操作系统内核”,它负责:
-
动态生成
docker-compose.yml文件,根据你的硬件配置(CPU核心数、内存大小)自动调整PostgreSQL的shared_buffers和work_mem参数; -
管理整个服务生命周期:
start、stop、rebuild、enter、cleanup; -
执行安全加固:自动禁用root用户登录、设置
ulimit -n 65536、挂载/proc/sys只读; -
处理敏感数据:将
SECRET_KEY_BASE、DISCOURSE_DB_PASSWORD等密钥加密后写入containers/app.yml,而非明文暴露在环境变量中。
它的设计哲学是“约定优于配置”。你不需要懂Docker Compose的YAML语法,只需要修改
app.yml
里的几个关键字段,
launcher
就能为你生成一个生产就绪的配置。比如,你想让Discourse监听80和443端口,你不需要去改Nginx配置,只需在
app.yml
里把
expose
段改成:
expose:
- "80:80" # http
- "443:443" # https
launcher
会在重建时自动注入
nginx
容器的
ports
配置,并确保
app
容器通过内部网络与
nginx
通信,完全绕开宿主机防火墙的干扰。这种抽象层,正是Discourse能保持高可用性的秘密。我曾对比过手动写
docker-compose.yml
和用
launcher
的部署时间:前者平均需要2小时调试网络和权限,后者从
git clone
到
./launcher start app
成功,最快记录是11分钟。这11分钟里,有7分钟是在下载镜像,真正的配置时间只有4分钟。
2. 从零开始:手把手构建一个可运行的Discourse实例
现在,Docker环境已就绪,我们进入真正的部署环节。整个过程分为四个原子步骤:获取代码、配置容器、构建镜像、启动服务。每一步都环环相扣,任何跳步都会导致后续失败。
2.1 获取并初始化Discourse代码仓库
Discourse的官方代码托管在GitHub上,但它的部署模型不是“克隆代码 -> 运行”,而是“克隆部署脚本 -> 下载预编译镜像”。因此,你不需要
git clone
整个Discourse源码(那有1.2GB),只需要克隆
discourse_docker
这个轻量级仓库,它只包含
launcher
脚本和模板配置。
# 创建一个专用目录,强烈建议不要放在/home或/root下,避免权限混乱
sudo mkdir -p /var/discourse
# 切换到该目录并克隆部署仓库
cd /var/discourse
sudo git clone https://github.com/discourse/discourse_docker.git .
这一步完成后,你的
/var/discourse
目录结构应该是这样的:
/var/discourse/
├── containers/ # 存放所有app.yml配置文件的地方
├── launcher # 核心Bash脚本
├── samples/ # 各种场景的配置样例(development, tests, web_only等)
└── templates/ # Nginx、PostgreSQL等服务的配置模板
注意:
launcher脚本本身没有执行权限,必须用sudo运行。这是Discourse的硬性安全策略——它拒绝以普通用户身份操作Docker守护进程,因为/var/run/docker.sock的权限是srw-rw---- 1 root docker,普通用户无法访问。如果你试图用./launcher start app而不加sudo,会得到Permission denied while trying to connect to the Docker daemon socket。这不是bug,是设计。
2.2 配置
app.yml
:理解每个字段背后的“为什么”
app.yml
是Discourse的“DNA”,它定义了整个论坛的生命体征。官方提供的
samples/standalone.yml
是一个很好的起点,但直接复制粘贴会踩坑。我们必须逐行理解、逐行修改。
首先,创建配置文件:
# 复制样例配置
sudo cp samples/standalone.yml containers/app.yml
现在,用你喜欢的编辑器打开
containers/app.yml
(我推荐
sudo nano containers/app.yml
)。我们重点修改以下几块:
基础信息(host & ports)
## which TCP/IP ports should this container expose?
## If you want Discourse to be reachable on a single port, uncomment the line below
EXPOSED_PORT=80
这里有个经典误区:很多人以为
EXPOSED_PORT=80
是让Discourse监听80端口,其实不然。它只是告诉
launcher
,在生成
docker-compose.yml
时,把
app
容器的80端口映射到宿主机的80端口。但Discourse的
app
容器本身并不直接处理HTTP请求,它只处理
http://localhost:3000
的Rails应用。真正的HTTP入口是
nginx
容器,它监听宿主机的80/443,并反向代理到
app
容器的3000端口。所以,如果你的Ubuntu 20.04上已经运行了Apache或Nginx,你必须先停掉它们,或者把
EXPOSED_PORT
改成
8080
,然后在
app.yml
里显式声明
expose
:
expose:
- "8080:80"
- "8443:443"
域名与SSL(必须项,非可选项)
Discourse强制要求HTTPS。即使你在本地测试,也必须配置一个有效的域名(可以是
localhost
,但必须走HTTPS)。这是因为现代浏览器对混合内容(HTTP+HTTPS)的限制越来越严,Discourse的前端大量使用WebSockets和Service Workers,它们在HTTP下根本无法工作。
## TODO: The domain name of your Discourse instance
DISCOURSE_HOSTNAME: 'discourse.example.com'
## TODO: How many concurrent web requests are supported?
UNICORN_WORKERS: 4
## TODO: List of comma delimited emails that will be made admin on initial signup
DISCOURSE_DEVELOPER_EMAILS: 'admin@example.com'
DISCOURSE_HOSTNAME
是唯一必须修改的字段。它不仅是URL,更是Discourse生成所有内部链接(邮件、API、CDN)的根。如果你填
localhost
,那么所有邮件里的链接都会是
http://localhost/t/xxx
,用户点开就是404。所以,哪怕只是本地测试,也要在
/etc/hosts
里加一行:
# 编辑/etc/hosts
sudo nano /etc/hosts
# 添加这一行
127.0.0.1 discourse.local
然后把
DISCOURSE_HOSTNAME
设为
discourse.local
。这样,你就可以用
https://discourse.local
访问了。
数据库与缓存(性能关键)
## TODO: PostgreSQL version to use
POSTGRESQL_VERSION: 13
## TODO: Redis version to use
REDIS_VERSION: 6
Ubuntu 20.04的
apt
源里PostgreSQL是12,Redis是5.0.7,但Discourse官方镜像要求PostgreSQL 13+和Redis 6+。
launcher
会自动拉取对应版本的官方Docker镜像,所以你不用自己装。但这里有个隐藏参数:
## TODO: How much RAM do you have? Adjust accordingly.
## For a VPS with 2GB RAM, set this to 1024
## For a VPS with 4GB RAM, set this to 2048
## For a VPS with 8GB RAM, set this to 4096
DISCOURSE_MEMORY_SIZE_MEGABYTES: 2048
这个值不是给Discourse应用分配的内存,而是给
postgres
容器分配的
shared_buffers
。计算公式是:
shared_buffers = DISCOURSE_MEMORY_SIZE_MEGABYTES * 0.25
。所以,如果你填2048,PostgreSQL就会得到512MB的共享缓冲区。这是经过Discourse团队海量压测得出的黄金比例。填小了,数据库I/O瓶颈;填大了,挤占
app
容器的内存,导致Ruby进程GC频繁。我实测过,一台4核8GB的VPS,
DISCOURSE_MEMORY_SIZE_MEGABYTES
设为3072时,PostgreSQL的
hit_rate
(缓存命中率)稳定在99.2%,而设为4096时,
app
容器的
RSS
内存占用飙升到1.8GB,触发了Linux OOM Killer。
2.3 构建与启动:
./launcher rebuild app
的完整生命周期
配置完成后,执行终极命令:
sudo ./launcher rebuild app
这个命令会触发一个长达5-15分钟的自动化流水线,其内部步骤远比表面看起来复杂:
-
环境校验 :检查Docker是否运行、
/var/discourse是否有写权限、DISCOURSE_HOSTNAME是否为空、DISCOURSE_DEVELOPER_EMAILS格式是否正确。任何一个失败,都会立刻退出并打印红色错误。 -
模板渲染 :读取
containers/app.yml,结合templates/web.template.yml、templates/postgres.template.yml等,动态生成最终的docker-compose.yml。这个文件不会被保存到磁盘,而是直接传给docker-compose执行。 -
镜像拉取与构建 :
launcher会先尝试拉取discourse/base:2.0.20230101这样的基础镜像。如果拉取失败(比如国内网络),它会自动切换到discourse/base:2.0.20230101的镜像仓库镜像(通常是quay.io)。如果还是失败,它会退回到“构建模式”,即从Dockerfile现场编译一个基础镜像,这会额外增加10分钟。 -
容器编排 :执行
docker-compose up -d,启动app、postgres、redis、nginx四个服务。launcher会监控每个容器的healthcheck状态,直到所有服务都返回healthy。 -
首次初始化 :当
app容器启动后,它会自动执行bundle exec rake db:migrate(数据库迁移)和bundle exec rake assets:precompile(前端资源编译)。这两个步骤非常耗时,尤其是assets:precompile,它要调用Node.js编译所有JavaScript和CSS,会吃满一个CPU核心。
整个过程中,你可以实时查看日志:
# 查看所有容器的日志流
sudo ./launcher logs app
# 只看app容器的实时日志(最关心的)
sudo ./launcher logs app -f
# 只看postgres容器的错误日志(排查数据库问题)
sudo ./launcher logs postgres | grep -i "error\|fail"
注意:
rebuild命令是幂等的。如果你中途Ctrl+C中断,再次运行它会从断点继续,而不是重头开始。这是launcher的智能之处——它会检查/var/discourse/shared/standalone目录下的postgres和redis数据卷,如果存在,就跳过初始化,只更新应用代码。
2.4 首次访问与管理员创建:绕过邮箱验证的“后门”
当
./launcher rebuild app
成功结束,你会看到绿色的
Your Discourse instance is now running!
提示。此时,打开浏览器,访问你配置的
DISCOURSE_HOSTNAME
(比如
https://discourse.local
)。
第一个页面是注册页。输入你配置的
DISCOURSE_DEVELOPER_EMAILS
邮箱(比如
admin@example.com
),设置密码,点击注册。但你会发现,页面卡在“正在发送验证邮件...”,而你的邮箱里什么都没有。这是因为Discourse默认使用Mailgun或SMTP发送邮件,而我们的
app.yml
里没配。
别慌,Discourse预留了一个开发者后门。在终端里执行:
# 进入app容器的bash shell
sudo ./launcher enter app
# 在容器内,执行Rails console
rails c
# 在Rails console里,执行以下命令(替换为你注册时用的邮箱)
u = User.find_by_email('admin@example.com')
u.activate!
u.save!
按两次
Ctrl+D
退出。现在刷新浏览器,你就能直接登录了。这就是Discourse的“开发者模式”——它允许你在没有邮件服务的情况下,手动激活管理员账户。这个技巧在测试和开发环境中极其有用,但在生产环境,你必须配置真实的SMTP。
3. 常见故障排查:那些让你抓狂的“玄学错误”真相
部署Discourse最痛苦的不是安装过程,而是安装后遇到的各种“玄学错误”。它们往往没有明确的错误信息,只有一片空白页面、502 Bad Gateway,或者无限加载的旋转图标。下面是我踩过的、最典型的五个坑,每一个都附带完整的排查链路。
3.1 502 Bad Gateway:Nginx与App容器的“失联”之谜
这是最常遇到的错误。浏览器显示502,说明Nginx运行正常,但它无法把请求转发给
app
容器。排查步骤如下:
第一步:确认Nginx容器是否在运行
# 查看所有discourse相关容器
sudo docker ps -a | grep discourse
# 正常输出应该有4个UP状态的容器
# discourse_app_1 Up 2 minutes (healthy)
# discourse_postgres_1 Up 2 minutes (healthy)
# discourse_redis_1 Up 2 minutes (healthy)
# discourse_nginx_1 Up 2 minutes (healthy)
如果
discourse_nginx_1
是
Up 2 minutes
但没有
(healthy)
,说明Nginx的健康检查失败。健康检查命令是
curl -f http://localhost:80/healthz
,它检查的是Nginx能否连通
app
容器。
第二步:进入Nginx容器,测试与App的连通性
# 进入nginx容器
sudo docker exec -it discourse_nginx_1 bash
# 在容器内,尝试curl app容器
curl -v http://app:3000/healthz
如果返回
Connection refused
,说明
app
容器的3000端口没开,或者
app
容器根本没起来。此时,去看
app
容器的日志:
sudo ./launcher logs app
最常见的原因是
app
容器启动时,
postgres
容器还没准备好。
launcher
的健康检查逻辑是:
postgres
必须先返回
healthy
,
app
才会启动。但如果
postgres
的
shared_buffers
设得太大,它启动慢,
app
就会超时退出。解决方案是,在
app.yml
里增加
depends_on
的超时:
services:
app:
depends_on:
postgres:
condition: service_healthy
timeout: 300s # 默认是60s,改成300秒
第三步:检查Nginx配置是否被覆盖
Discourse的Nginx配置是动态生成的,位于
/var/discourse/shared/standalone/nginx/conf/discourse.conf
。如果这个文件被手动修改过,
launcher rebuild
不会覆盖它,会导致配置错乱。安全做法是:
# 删除自定义的Nginx配置,让launcher重新生成
sudo rm -f /var/discourse/shared/standalone/nginx/conf/discourse.conf
sudo ./launcher rebuild app
3.2 “The page isn’t working”:前端资源加载失败的三重门
Discourse的前端是高度模块化的,它依赖CDN加载
vendor-xxxx.js
、
application-xxxx.css
等资源。如果这些资源404,页面就会白屏。排查顺序如下:
第一重门:检查CDN设置
在
app.yml
里,找到
DISCOURSE_CDN_URL
字段。如果你没配,Discourse会默认用
//your-domain.com
作为CDN前缀。但如果你的域名是
discourse.local
,而浏览器不支持
//discourse.local
这种协议相对URL,就会加载失败。解决方案是显式设置:
DISCOURSE_CDN_URL: 'https://discourse.local'
第二重门:检查资产编译是否完成
app
容器启动后,会自动执行
rake assets:precompile
。这个过程可能失败,但
launcher
不会报错,因为它被包裹在一个
|| true
里。检查方法是:
# 进入app容器
sudo ./launcher enter app
# 查看public/assets目录
ls -la public/assets/
# 正常应该有vendor-*.js, application-*.css等文件
# 如果只有空目录或只有manifest.json,说明编译失败
# 手动触发编译(会输出详细错误)
bundle exec rake assets:precompile RAILS_ENV=production
最常见的编译失败原因是Node.js版本不匹配。Discourse要求Node.js 16.x,而Ubuntu 20.04默认是10.x。
launcher
会自动安装Node 16,但如果网络不好,安装会失败。此时,你需要手动进入容器,用
nvm
安装:
# 在app容器内
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 16.18.1
nvm use 16.18.1
bundle exec rake assets:precompile RAILS_ENV=production
第三重门:检查浏览器缓存
Discourse的前端资源有强缓存(
Cache-Control: public, max-age=31536000
)。如果你之前部署失败过,浏览器可能缓存了旧的、损坏的
application-xxxx.js
。强制刷新(Ctrl+F5)或无痕窗口是最快的验证方法。
3.3 数据库连接被拒绝:PostgreSQL的“防火墙”幻觉
错误信息通常是
could not connect to server: Connection refused
。这很容易让人以为是PostgreSQL没启动,但真相往往是网络配置问题。
核心原理
:Discourse的
app
容器和
postgres
容器在同一个Docker网络里,它们通过服务名
postgres
通信。
app
容器里执行
ping postgres
应该能通。但如果
app
容器里
ping postgres
不通,说明Docker网络有问题。
排查步骤 :
# 进入app容器
sudo ./launcher enter app
# 测试DNS解析
nslookup postgres
# 应该返回172.18.0.2这样的IP
# 测试端口连通性
nc -zv postgres 5432
# 应该显示Connected to postgres (172.18.0.2) 5432
# 如果nc失败,但nslookup成功,说明是PostgreSQL的pg_hba.conf限制了连接
# 进入postgres容器
sudo docker exec -it discourse_postgres_1 bash
# 查看pg_hba.conf
cat /var/lib/postgresql/data/pg_hba.conf | grep -v "^#"
# 关键行应该是:host all all 0.0.0.0/0 md5
# 如果是:host all all 127.0.0.1/32 md5,那就只允许本地连接
解决方案是,在
app.yml
里,通过
POSTGRES_TRUST_LOCALNET
环境变量告诉
launcher
,生成
pg_hba.conf
时允许所有Docker网络连接:
env:
POSTGRES_TRUST_LOCALNET: "true"
然后
./launcher rebuild app
。
3.4 内存不足(OOM):Linux内核的“温柔杀手”
Discourse在低内存VPS(比如1GB)上部署,经常在运行几小时后突然崩溃。
dmesg
日志里全是
Out of memory: Kill process 1234 (postgres) score 892 or sacrifice child
。这不是Discourse的Bug,而是Linux OOM Killer的正常行为。
根本原因
:PostgreSQL的
shared_buffers
和
work_mem
是按“最大可能”配置的,但Discourse的
launcher
没有做内存压力预测。它假设你给的
DISCOURSE_MEMORY_SIZE_MEGABYTES
是“独占”的,而实际上,
app
容器、
redis
容器、
nginx
容器都要分一杯羹。
解决方案是双重限制 :
-
在
app.yml里,严格限制每个容器的内存上限 :
services:
app:
mem_limit: 1g
postgres:
mem_limit: 512m
redis:
mem_limit: 256m
nginx:
mem_limit: 128m
-
在
app.yml里,降低PostgreSQL的work_mem(默认是4MB,对于1GB内存来说太高了):
env:
POSTGRES_WORK_MEM: "2MB"
这样,
launcher
在生成
postgresql.conf
时,会把
work_mem
设为2MB,大幅降低单个查询的内存峰值。
3.5 SSL证书失效:Let's Encrypt的“7天倒计时”
Discourse内置了Let's Encrypt自动续期。但如果你的
DISCOURSE_HOSTNAME
指向的是一个内网IP或
localhost
,Let's Encrypt的验证会失败,导致证书过期。错误日志在
/var/discourse/shared/standalone/log/letsencrypt.log
里。
临时解决方案(仅测试用) :禁用HTTPS,强制HTTP:
# 在app.yml里,注释掉SSL相关配置
# ssl:
# enabled: true
# force_https: true
永久解决方案 :使用真实域名,并确保:
- 域名DNS A记录正确指向你的服务器IP;
-
服务器80和443端口对外网开放(检查UFW防火墙:
sudo ufw status); -
DISCOURSE_HOSTNAME必须是域名,不能带http://或https://。
4. 生产环境加固:从“能跑”到“稳如磐石”的七道工序
一个能跑起来的Discourse,和一个能扛住百万PV的Discourse,中间隔着七道加固工序。这些不是可选项,而是Discourse官方文档里白纸黑字的“MUST”。
4.1 防火墙:UFW的最小化开放策略
Ubuntu 20.04默认不启用防火墙。但Discourse只需要两个端口:80(HTTP重定向)和443(HTTPS)。其他所有端口,包括22(SSH)、3306(MySQL)、5432(PostgreSQL),都必须关闭。
# 启用UFW
sudo ufw enable
# 允许SSH(必须,否则你连不上服务器)
sudo ufw allow OpenSSH
# 允许80和443
sudo ufw allow 80
sudo ufw allow 443
# 拒绝所有其他入站
sudo ufw default deny incoming
# 查看规则
sudo ufw status verbose
提示:
launcher会自动在nginx容器里配置HTTP到HTTPS的301重定向,所以你不需要在UFW里开80端口。但Let's Encrypt的ACME协议验证必须走80端口,所以必须开着。这是一个安全与功能的平衡点。
4.2 自动备份:
./launcher backup
的定时艺术
Discourse的数据全在
/var/discourse/shared/standalone
目录下,其中
postgres
子目录是数据库快照,
uploads
是用户上传的图片。
launcher
提供了
backup
子命令,但它默认只备份到本地磁盘,风险极高。
最佳实践是:本地备份 + 远程同步 。创建一个备份脚本:
# 创建备份目录
sudo mkdir -p /var/discourse/backups
# 编辑备份脚本
sudo nano /var/discourse/backup.sh
脚本内容:
#!/bin/bash
# 设置备份目录
BACKUP_DIR="/var/discourse/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# 执行launcher备份
sudo /var/discourse/launcher backup app --description "auto-$DATE"
# 找到最新的备份文件(launcher会生成.tar.gz)
LATEST_BACKUP=$(ls -t /var/discourse/shared/standalone/backups/default/*.tar.gz | head -1)
# 复制到备份目录并重命名
sudo cp "$LATEST_BACKUP" "$BACKUP_DIR/discourse_backup_$DATE.tar.gz"
# 同步到远程服务器(需要提前配置SSH密钥)
# rsync -avz --delete "$BACKUP_DIR/" user@remote-server:/path/to/backups/
# 清理7天前的备份
find "$BACKUP_DIR" -name "discourse_backup_*.tar.gz" -mtime +7 -delete
echo "Backup completed: discourse_backup_$DATE.tar.gz"
赋予执行权限并加入crontab:
sudo chmod +x /var/discourse/backup.sh
# 每天凌晨2点执行
echo "0 2 * * * /var/discourse/backup.sh >> /var/discourse/backup.log 2>&1" | sudo crontab -
4.3 日志轮转:防止
/var
被撑爆的无声守护者
Discourse的
app
、
postgres
、
nginx
容器日志,默认会无限增长,几个月下来轻松上百GB。
launcher
不处理日志轮转,这是宿主机的责任。
编辑Docker的daemon配置:
sudo nano /etc/docker/daemon.json
添加以下内容:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
然后重启Docker:
sudo systemctl restart docker
这表示:每个容器的日志文件最大10MB,最多保留3个历史文件(即总共30MB)。
launcher
重建后,新容器会自动继承这个配置。
4.4 安全更新:
./launcher cleanup
与
./launcher rebuild
的黄金组合
Discourse的镜像每周更新,修复安全漏洞。但
launcher
不会自动更新。你必须手动触发。
标准流程是 :
# 1. 清理旧镜像和停止的容器(释放磁盘空间)
sudo ./launcher cleanup
# 2. 拉取最新的base镜像
sudo docker pull discourse/base:latest
# 3. 重建app(会自动使用最新base)
sudo ./launcher rebuild app
注意:
rebuild会重启所有服务,造成几分钟的不可用。生产环境应在业务低峰期操作,并提前通知用户。
4.5 性能监控:
docker stats
与
htop
的组合拳
Discourse没有内置的图形化监控面板。但Linux命令行工具足够强大。
实时监控所有容器资源 :
# 显示所有discourse容器的CPU、内存、网络IO
sudo docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" $(sudo docker ps -q --filter "name=discourse")
深入分析app容器的Ruby进程 :
# 进入app容器
sudo ./launcher enter app
# 安装htop(如果没装)
apt-get update && apt-get install -y htop
# 查看所有Ruby进程的内存和CPU
htop -p $(pg
更多推荐


所有评论(0)