网站优化01
将 Halo 博客从内置 H2 数据库迁移到独立 MySQL 8.0 容器,以及后续维护运行
1. YAML 语法与解析错误
问题现象:
go-yaml load error ... did not find expected key。根本原因:编辑
docker-compose.yml时混用了制表符(Tab)与空格,或键值对缩进层级不一致、冒号后漏加空格。解决方式:规范 YAML 缩进,全量替换为统一 2 空格缩进的配置。
2. 容器生命周期竞争(Race Condition)
问题现象:Halo 启动直接抛出
Connection refused: halo-mysql/172.20.0.2:3306。根本原因:MySQL 8.0 首次挂载空卷时,需要 20~40 秒在后台初始化系统表与数据目录,此时 3306 端口未对外开放监听。Compose 默认的
depends_on仅等待容器创建即拉起 Halo,Halo 抢先连接未就绪的端口而崩溃退出。解决方式:在
halo-mysql服务中配置healthcheck(mysqladmin ping),并将 Halo 的依赖条件设定为condition: service_healthy,确保 MySQL 真正可用后再启动博客。
3. MySQL 容器权限与 NUMA 警告刷屏
问题现象:MySQL 启动日志中大量刷出
mbind: Operation not permitted。根本原因:Docker 默认的
seccomp安全沙箱拦截了 MySQL 尝试调用底层内存策略(set_mempolicy/mbind)的系统调用。解决方式:在配置中加入
security_opt: [seccomp:unconfined],放行系统调用,避免初始化停滞。
4. 数据库迁移方言冲突(最核心深层根因)
问题现象:MySQL 连接成功后,Halo 依然报错崩溃:
io.r2dbc.spi.R2dbcBadGrammarException: [1064] You have an error in your SQL syntax ... near '"migrations"'。根本原因:
旧的工作目录挂载(
./data)残留了之前的 H2 运行配置与状态。启动命令行(
command:)传参优先级问题,导致底层r2dbc-migrate组件依然锁定了dialect=H2,生成了 H2 双引号语法并向 MySQL 执行,直接引发 1064 语法错误。
解决方式:
将旧的
data目录改名隔离为纯净目录。弃用
command传参,改用标准环境变量SPRING_SQL_INIT_PLATFORM=mysql与SPRING_R2DBC_*显式声明方言与连接平台。
5. 云服务器内存击穿(OOM)导致 SSH 失联
问题现象:执行重启与数据导入过程中,服务器终端彻底断开且无法重新连入。
根本原因:在 2GB 内存的云主机上,MySQL 8.0 与 Java 21 (Spring Boot) 在数据恢复/启动时产生瞬时内存峰值,物理内存耗尽触发内核保护,导致 SSH 守护进程无响应或假死。
解决方式:
通过云控制台完成实例重启恢复连通。
为系统创建并挂载 2GB Swap 虚拟内存(
/swapfile),提供了充足的突发内存缓冲空间。
6. 网关 502 与大文件反代拦截
问题现象:
浏览器提示
502 Bad Gateway (openresty)。后台上传备份文件连续失败。
根本原因:
502 是因为后端 Halo 容器在崩溃退出或还在初始化的空档期,反代网关找不到上游服务。
上传失败是因为反代网关(OpenResty/Nginx)默认限制了客户端请求体大小(
client_max_body_size默认 1M~10M)。
解决方式:绕过前端 HTTP 上传,直接将备份包移入挂载的
/opt/halo/data/backups/目录,在控制台直接触发本地恢复。
梳理下来,这次排查涵盖了容器编排、数据库引擎初始化、框架方言适配、Linux 内存管理以及网络反代的完整排障闭环。目前整套架构运行在健康检查与持久化卷之上,整体已经处于非常健壮的状态。
docker-compose.yml文件的配置:
Halo 启动得比 MySQL 快。depends_on 只会等待 MySQL 的容器「启动」,并不会等 MySQL 真正「就绪」(尤其第一次启动时 MySQL 8.0 要初始化数据目录,需要几十秒)。于是 Halo 第一次启动时连不上 MySQL,直接崩溃:
2026-10-05T20:49:31 ... Application run failed
Failed to obtain R2DBC Connection → Connection refused
然后因为 restart: unless-stopped,它重启一次,这次 MySQL 已经好了,于是第二次就正常启动了(日志 788 行:StartedApplication in 30.275 seconds、777 行:Netty started on port 8090)。
502 就是这段崩溃窗口里(以及每次重启时都会复现)反代(nginx/Caddy)找不到后端 8090 端口导致的。
#停止当前容器:
docker compose down
#编辑配置文件:
nano docker-compose.yml
docker-compose.yml:
services:
halo-mysql:
image: mysql:8.0
container_name: halo-mysql
restart: unless-stopped
security_opt:
- seccomp:unconfined
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: "HaloUser123456"
MYSQL_DATABASE: halo
MYSQL_USER: halo
MYSQL_PASSWORD: "HaloUser123456"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 30
start_period: 30s
volumes:
- ./mysql-data:/var/lib/mysql
networks:
- default
halo:
image: ghcr.io/halo-dev/halo:2.24.2
container_name: halo
restart: unless-stopped
depends_on:
halo-mysql:
condition: service_healthy
ports:
- "8090:8090"
volumes:
- ./data:/root/.halo2
environment:
- SPRING_R2DBC_URL=r2dbc:pool:mysql://halo-mysql:3306/halo
- SPRING_R2DBC_USERNAME=halo
- SPRING_R2DBC_PASSWORD=HaloUser123456
- SPRING_SQL_INIT_PLATFORM=mysql
- HALO_EXTERNAL_URL=https://originspace.work
networks:
- default
networks:
default:
name: web-net
external: true
#等待重启
docker compose up -d
其中docker镜像加速使用阿里云官方提供的加速源:
#编辑配置文件
nano /etc/docker/daemon.json
{
"registry-mirrors": [
"https://e1bn0vyt.mirror.aliyuncs.com"
]
}
systemctl daemon-reload
systemctl restart docker
评论