Bookwyrm由0.7.5升级至Production (e217a17) 完整过程及疑难解答
引言
本教程旨在帮助Bookwyrm用户如何将其部署从v0.7.5升级到(笔者执笔时)最新的production
分支(具体到e217a17
版本)。截至该版本提交(commit)的诸多代码带来了许多改进,例如对搜索词汇权重的优化设计(能更精准地根据标题、作者、系列等信息进行排序),以及数据导出功能的增强(排除已删除内容)等,这些都会提升用户体验和系统稳定性。
然而,本次升级过程中,笔者遇到了一些特殊情况,这些情况并非普遍存在,但对于拥有类似部署环境的用户来说至关重要。如果您是——
使用外部(包括宿主机上的)数据库服务,并依赖Docker的host
网络模式进行通信;同时,
通过容器外的服务进行HTTPS管理(而非在容器内部自签)
——那么本教程中的“特殊情况”部分将为您提供经验与解决方案。
如果您没有这些特殊需求,通常可以直接遵循Bookwyrm官方的升级指南,那将是更简便的路径。
特殊情况概述:为何我的升级之路如此“曲折”?
在本次升级案例中,笔者面临的主要挑战源于以下几个相互关联的部署选择:
外部数据库与host
网络模式: 为了与宿主机上运行的外部PostgreSQL数据库服务进行通信,笔者的Docker Compose配置中将db
服务注释掉了,并将web
、celery
等服务设置为 network_mode: "host"
。
- 影响: 在
host
网络模式下,Docker容器直接共享宿主机的网络堆栈,容器之间无法通过服务名称进行内部解析。它们必须通过宿主机的IP地址(通常宿主机实际的局域网IP;或者都是host
了,干脆127.0.0.1
亦可)进行通信。这直接影响了nginx与Web服务之间的通信配置。
Cloudflare Zero Trust与HTTPS管理: 笔者用 Cloudflare Zero Trust来处理HTTPS加密。这意味着服务器端无需强制进行HTTPS,而是由Cloudflare在边缘网络提供SSL/TLS保护。
- 影响: 如果在服务器端启用自签名或其他免费的HTTPS证书,并与Cloudflare的“严格”HTTPS策略冲突,可能导致Cloudflare阻止流量,认为存在不安全的端到端连接。因此,我们的nginx配置必须避免在服务器端强制HTTPS,而是通过HTTP监听,并由Cloudflare Zero Trust负责转发和加密。这与Bookwyrm官方版本(无论是截至v0.7.5抑是截至本文所提之“最新”版)中可能集成的Certbot自动化HTTPS方案有所冲突,需要进行定制化。
nginx端口冲突: 由于采用host
网络模式,nginx容器会尝试直接监听宿主机的端口。在升级过程中,我们发现宿主机的80端口已被一个别的(就诸如nginx这样的)Web服务器占用,导致nginx无法启动。这需要沉下心诊断并解决端口冲突问题。
这些特殊因素叠加,使得简单的拉取(pull)或者复制粘贴新版本docker-compose.yml
配置变得不可行,需要对网络、nginx代理和端口管理进行精细调整。
升级教程:从0.7.5到Production (e217a17)
本教程假设您已经有了一个运行正常的Bookwyrm 0.7.5部署,并且熟悉基本的Git和Docker Compose命令。
步骤1:备份数据和配置
这是最关键的第一步!在进行任何重大升级之前,务必备份所有重要数据。
备份数据库:如果数据库运行在Docker容器内(假设容器名为bookwyrm-db-1
),可以使用docker exec
命令进行备份:
docker exec -t bookwyrm-db-1 pg_dump -U your_db_user your_db_name > db_backup.sql
如果数据库是外部服务,则使用其提供的备份机制。
备份 Docker Compose 文件和自定义配置:复制当前的docker-compose.yml
文件和所有自定义的配置文件(例如.env
文件、nginx配置目录 nginx/
等)。
cp docker-compose.yml docker-compose.yml.bakcp .env .env.bakcp -r nginx/ nginx.bak/# 确保备份所有可能包含定制化信息的目录和文件
步骤2:更新Bookwyrm代码库
停止当前运行的Bookwyrm服务:
docker compose down
拉取最新的production
分支代码并回滚:
(请确保您明白本节在说什么之后,再继续操作!如于不熟悉Git之情况下贸然照搬,会导致可怕后果😱)
由于过去站点的运行可能会生成很多不需要的变更甚至提交(例如,nginx静态文件目录的权限变更,或者是Windows/Linux之间的换行符差异,皆会被Git一一记录);故此,先回滚到干净的远程production
状态,然后只应用您所需的修改。
git fetch origin # 获取远程仓库的最新状态git reset --hard origin/production # 强制重置本地分支到远程 production 的最新提交
这将清除您本地所有未推送到origin/production
的修改。(或者可能是upstream/production
,根据实际情况来。)
步骤3:应用有价值的修改
(本节同样也是,希望您明白下述文字在说什么以后,再考虑要否采纳。)
现在,在干净的origin/production
基础上,重新应用希望保留修改:
重命名docker-compose.yml
为docker-compose.example.yml
:如果origin/production
包含docker-compose.yml
文件,执行:
mv docker-compose.yml docker-compose.example.yml
兹解释一下笔者为什么会需要这么做。笔者以为官方Git库直接提供docker-compose.yml
并于每回释出新版本时累次将变更付诸此文件是不优雅的。这样做没有考虑到(包括笔者在内)魔改docker-compose.yml
以取消掉部分原生服务(例如容器内的db
)的可能性——每次拉取官方更新,若不备份好自己的docker-compose.yml
,就会被官方发布的.yml
一整个覆盖掉。
笔者以为,较为优雅的实践,是官方提供一个docker-compose.example.yml
,并指导用户去:
cp docker-compose.example.yml docker-compose.yml
岂不美哉?不过,反馈给官方后,项目的积极维护者所分享的考量也不无道理——
对于像BookWyrm这样(笔者瞎按:由于处于内测阶段所以会急遽更迭)的项目来说,使用标准并有助于保持一致的环境是非常典型的。一般来说,自定义应该是环境变量。
(笔者中译)
官方倒也有提供docker-compose.override.yml
这样的维护方式。记入在docker-compose.override.yml
的配置如与docker-compose.yml
冲突,优先执行的是override
的。且override.yml
也在.gitignore
内,就不会在每次更新时被覆盖了。但是,笔者这种需要注释掉部分服务的做法,override
就支持不到了。
所以,截至本文执笔时,还是只能自行重命名。可以考虑fork一份Git库,然后把重命名操作应用过去。在这种情况下,我们就可以将upstream
指定为官方Git库,origin
指定为自己的Git库。
在.gitignore
中增加临时文件的忽略规则:打开本地的.gitignore
文件,并在文件末尾或其他合适位置添加增加临时文件的忽略规则(笔者例)。如果您像笔者一样,宿主机用的是魔改版的Linux系统,而该系统恰会以自己的方式为一些图片文件生成临时缩略图的话,就需要忽略之。否则,nginx静态文件目录(./static
)下面会出现很多临时缩略图待提交——或者在哪次直接不慎提交掉了。
提交这些更改:
git add docker-compose.example.yml .gitignore # 根据实际重命名情况调整git commit -m "Rename docker-compose.yml to docker-compose.example.yml and add temp files ignore to .gitignore"
步骤4:调整docker-compose.yml
和nginx配置以适应特殊情况
这是本次升级教程的核心和关键,将根据特殊需求定制docker-compose.yml
和nginx配置。
手动更新 docker-compose.yml
:基于docker-compose.example.yml
或production
提供的docker-compose.yml
文件,进行以下修改:
为nginx
, web
, celery_worker
, celery_beat
等实际有启用的服务设置 network_mode: "host"
:
services: nginx: # ... network_mode: "host" # ... web: # ... network_mode: "host" # ... celery_worker: # ... network_mode: "host" # ... celery_beat: # ... network_mode: "host" # ...
nginx服务volumes
调整 (核心):笔者将放弃官方的default.conf.template
模板机制,而是直接挂载一个包含所有自定义nginx配置的文件,并让它直接覆盖容器内nginx的默认配置。
首先,在本地./nginx/
目录中,准备一个名为my-bookwyrm-nginx.conf
的文件(或者选择用https.conf
也可以),将所有自定的nginx配置内容都放入其中。
nginx
服务volumes
部分应类似于:
volumes: # 直接将你的主Nginx配置文件挂载到容器的 /etc/nginx/conf.d/default.conf # 确保这个文件包含了所有你需要的Nginx指令,并且是最终版本 - ./nginx/my-bookwyrm-nginx.conf:/etc/nginx/conf.d/default.conf # 如果你的 server_config, server_name, locations 是独立的Nginx include文件 # 并且你希望它们继续被加载,你仍然可以挂载它们。但要确保它们不包含监听指令, # 而是被 my-bookwyrm-nginx.conf 文件通过 `include` 指令引用。 # 示例: # - ./nginx/locations:/etc/nginx/conf.d/locations # - ./nginx/server_config:/etc/nginx/conf.d/server_config # - ./nginx/server_name:/etc/nginx/conf.d/server_name # 99-autoreload.sh 脚本的挂载 - ./nginx/99-autoreload.sh:/docker-entrypoint.d/99-autoreload.sh # 静态文件和媒体文件卷 (改为直接挂载本地目录) - ./static:/app/static - ./images:/app/images - ./exports:/app/exports # 用于导出数据
重要: 在你的docker-compose.yml
中,如果Nginx服务仍然有ports
部分,请确保它被移除或注释掉,因为network_mode: "host"
会让Nginx直接监听宿主机端口,ports
映射将是多余的。
按需添加redis_activity
和redis_broker
、flower
(用于监控Celery)服务。(笔者用了外部的服务,就没开这些个。)
web
服务的depends_on
调整:
web: # ... depends_on: celery_worker: condition: service_started redis_activity: condition: service_started # 以及其他你实际有开启的
定义具名卷 (volumes) 在docker-compose.yml
底部:所有在服务中使用的具名卷(例如 redis_activity_data
,redis_broker_data
,pgdata
,backups
)都需要在这里定义。
volumes: pgdata: backups: static_volume: # 如果你用具名卷而不是直接挂载目录 media_volume: # 如果你用具名卷而不是直接挂载目录 exports_volume: redis_broker_data: redis_activity_data:
重要: 如果你的web
、celery
和nginx
已经使用network_mode: "host"
并直接挂载本地目录static
, images
, exports
,那么static_volume
, media_volume
等具名卷就不需要了。保持一致性,如果挂载本地目录,就不要定义具名卷,反之亦然。
创建或更新nginx主配置文件 (./nginx/my-bookwyrm-nginx.conf
):这个文件将是nginx容器实际加载的/etc/nginx/conf.d/default.conf
。
确保upstream web
指向127.0.0.1:8000
:
upstream web { server 127.0.0.1:8000; # 因为web容器在宿主机上也监听这个地址}
配置nginx监听的端口 (关键步骤):由于Apache占用80端口,且打算通过Cloudflare Zero Trust处理HTTPS,Nginx应该监听一个未被占用的端口(例如8001),并只提供HTTP服务。
# 定义一个 Nginx 缓存区域 (可选,但推荐)proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=bookwyrm_cache:10m inactive=60m;server { listen [::]:8001; # Nginx 监听宿主机的 8001 端口 listen 8001; server_name sanguok.com; # 示例。换成你的域名。下同 # 最大请求体大小,根据需要调整 client_max_body_size 10M; # 确保只通过主域名访问 if ($host != "sanguok.com") { return 301 $scheme://sanguok.com$request_uri; } # 主要的反向代理到 web 服务 location / { proxy_pass http://web; # upstream web 已经指向了 127.0.0.1:8000 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; # 缓存设置 (与上面 proxy_cache_path 配合使用) proxy_cache bookwyrm_cache; proxy_cache_valid any 1m; add_header X-Cache-Status $upstream_cache_status; proxy_ignore_headers Cache-Control Set-Cookie Expires; proxy_cache_methods GET HEAD; proxy_no_cache $cookie_sessionid; proxy_cache_bypass $cookie_sessionid; } # 专门处理登录、密码重置等,通常有限制请求 location ~ ^/(login[^-/]|password-reset|resend-link|2fa-check) { # limit_req zone=loginlimit; # 如果定义了限速区域 proxy_pass http://web; } # 静态文件服务 location /static/ { root /app; # 对应 docker-compose.yml 中的 ./static:/app/static try_files $uri =404; add_header X-Cache-Status STATIC; access_log off; } # 图像文件服务 location /images/ { location ~ \.(bmp|ico|jpg|jpeg|png|svg|tif|tiff|webp)$ { root /app; # 对应 docker-compose.yml 中的 ./images:/app/images try_files $uri =404; add_header X-Cache-Status STATIC; access_log off; } return 403; # 阻止非图片文件访问 } # favicon.ico 处理 location /favicon.ico { root /app/images/logos; # 假设你的 favicon 在这个路径 try_files /IMG_5682.ico =404; # 确保指向实际的图标文件 } # Flower 监控界面 (如果启用了 flower 服务) location /flower/ { proxy_pass http://127.0.0.1:8888; # 如果flower也在host模式下监听,并且监听8888端口 proxy_cache_bypass 1; } # 其他常用的 Nginx 优化设置 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; gzip on; gzip_disable "msie6"; proxy_read_timeout 1800s; chunked_transfer_encoding on; # 错误日志 error_log /var/log/nginx/error.log debug; access_log /var/log/nginx/access.log; # 可以改为 cache_log}# 如果有其他 Nginx 配置 include,确保其内容正确且不会监听 80 端口# 例如:# include /etc/nginx/conf.d/server_config;# include /etc/nginx/conf.d/server_name;# include /etc/nginx/conf.d/locations;
关于99-autoreload.sh
的权限: 再次确认nginx/99-autoreload.sh
文件具有执行权限 (chmod +x ./nginx/99-autoreload.sh
)。
步骤5:更新.env
文件
Bookwyrm和Redis服务会引入新的环境变量。打开.env
文件并更新:
步骤6:构建和启动服务
构建Docker镜像:
docker compose build
这会根据更新的Dockerfile
和docker-compose.yml
来构建所有服务的镜像。
启动所有服务:
docker compose up -d
这将以后台模式启动所有容器。
检查日志以确认服务是否正常运行:
docker compose logs -f
密切关注nginx、Web和Celery服务的日志,确保它们没有错误并且正常监听。
步骤7:执行数据库迁移和初始化
首次启动新版本时,可能需要执行数据库迁移。
执行数据库迁移:
docker compose exec web python manage.py migrate
创建超级用户 (如果还没有):
docker compose exec web python manage.py createsuperuser
收集静态文件:
docker compose exec web python manage.py collectstatic --noinput
步骤8:配置nginx和Cloudflare Zero Trust
确保宿主机上没有其他服务占用nginx计划监听的端口。检查你的宿主机,确保其他服务没有占用8001端口 (如果你按照本教程将其配置为nginx的监听端口)。
sudo lsof -i :8001 # 检查 8001 端口是否被占用
如果被占用,需要停止占用8001端口的服务,或者将nginx配置为监听另一个空闲端口。
Cloudflare Zero Trust配置:在Cloudflare Zero Trust控制台,为域名(按前面的示例就是sanguok.com
)配置应用程序,使其通过HTTP转发到你的服务器IP地址的8001端口。Cloudflare将负责从客户端到Cloudflare的HTTPS连接。
恭喜!
经过这些详细的步骤和针对特殊情况的调整,您的Bookwyrm实例应该能够成功从0.7.5升级到production
分支的e217a17
版本,并且在特定的Docker Host网络模式和Cloudflare Zero Trust环境下正常运行。
如果在过程中遇到任何问题,请再次查看日志输出,并对照本教程中的排查步骤。祝君顺利!
#Bookwyrm #自託管