Read Write Compute

PostgreSQL 的大版本之间,数据库的结构以及元数据保存方式可能会有一些区别,所以不能直接把数据拷过去用,直接把 volume 挂载到新的 container 里当然也是不行的。

官方提供了 pg_upgrade 命令,它要求你至少提供这四样东西:

  1. 旧版本的 psql 命令
  2. 新版本的 psql 命令
  3. 旧版数据库
  4. 用新版本命令初始化的数据库

但在 container 里,我们通常只有一个版本的 PostgresSQL 命令,也不方便临时安装其他版本的 PostgreSQL。软件工程师 Thomas Bandt 在他的博客里介绍了升级的方法,我按他的方法从 v16 升级到 v18 获得了成功。

我的环境是群晖的 NAS 系统,可以 SSH 进去使用 sudo docker 命令。下面的步骤假设使用官方/默认版本的镜像 postgres

备份数据库

我已经把数据和配置文件都备份到云上了,如果之前没有备份过的话,可以打开当前运行数据库的一个终端,用 pg_dumpall 备份。

建立新数据库的文件夹

PostgreSQL 17 之后,要求镜像的用户把数据挂载到容器的 /var/lib/postgresql 这个路径,PostgreSQL 在 ${VER}/docker 这个子路径里找数据库文件,也就是说数据库文件所在的完整路径是 /var/lib/postgresql/${VER}/docker。所以,先要给新版数据库建立对应的文件夹。

初始化新版数据库

拉取新版本的镜像,然后进入对应 /var/lib/postgresql 这个挂载点的本地路径,也就是如果你要升级到 v18 的话,当前路径下有 18/docker 这样的子路径。

docker run -it -v .:/var/lib/postgresql postgres:18 /bin/bash # 换成适合你的镜像 tag
$ su postgres # 切换用户
> cd /var/lib/postgresql/18/docker
> initdb -U wordpress -W # 假设数据库的 superuser 叫 wordpress
> pg_ctl -U wordpress start
# 可选,给数据库改名
> psql -U wordpress -d template1 # 不能给当前数据库改名
psql> ALTER DATABASE postgres RENAME TO wordpress;
psql> \q
> pg_ctl -U wordpress stop
> exit
$ exit # 退出和停止容器

导出旧版数据库

用旧版镜像启动一个容器,挂载原来对应 /var/lib/postgresql/data 也就是旧版数据库的路径。

docker run -it -v .:/var/lib/postgresql/data postgres:16 /bin/bash
$ su postgres
> cd /var/lib/postgresql/data
> pg_ctl -U wordpress start # 如果之前使用的容器已经停掉
> pg_dumpall -U wordpress > upgrade_backup.sql # 注意保存在对应持久化存储的位置
> pg_ctl -U wordpress stop
> exit
$ exit

处理导出文件

把下面脚本保存为 pg_extract.sh

#!/bin/bash
[ $# -lt 2 ] && { echo "Usage: $0 <postgresql dump> <dbname>"; exit 1; }
sed  "/connect.*$2/,\$!d" $1 | sed "/PostgreSQL database dump complete/,\$d"

在旧版或新版容器中运行,还是注意文件挂载位置和新文件保存位置:

./pg_extract.sh upgrade_backup.sql wordpress >> upgrade_backup_mydb.sql

导入数据

退出旧版容器,进入新版容器,还是切换成 postgres 用户,并启动数据库。

> cd /var/lib/postgresql # 假设导出 SQL 文件保存的位置对应这个路径
> cat upgrade_backup_mydb.sql | psql -U wordpress
...
> pg_ctl -U wordpress stop
> exit
$ exit

重启数据库和应用

cd /volume1/docker/wordpress
sudo docker-compose up -d

或者在 Web 管理界面用 Container Manager 重启项目也可。