Minikube 本地部署挂载方案升级:解决 PostgreSQL 重启丢数据
在 Minikube 上做全流程本地部署时,用 minikube mount 把开发机目录共享进节点是常见做法。但把它直接当成数据库存储用,就会踩到"Pod 一重建、PostgreSQL 数据全丢"的坑。这次改动的目标只有一个:让 PostgreSQL 在 Pod 重建后不丢数据,并且能够正常初始化。
旧方案中,/data 用于从开发机共享模型、配置和 SQL 文件;但 PostgreSQL 的真实数据目录(PGDATA)没有独立的持久化存储。新方案为 PGDATA 增加了独立的 pgdata-pvc,并将其放在 Minikube 节点本地的 /pgdata。
背景与问题
项目把 PostgreSQL 以 Deployment 跑在 Minikube 集群里,开发机的 data 目录通过 minikube mount 映射到节点,再由 PVC 挂入容器,用于共享模型、配置文件和初始化 SQL。
问题表现为:Pod 被删除重建后,数据库运行期间写入的数据全部丢失,而且每次重建数据库都会重新执行一遍初始化 SQL。
先理解两个目录
PostgreSQL 会用到两类完全不同的文件:
| 文件 | 位置 | 作用 |
|---|---|---|
| 初始化 SQL | /docker-entrypoint-initdb.d | 仅在数据库为空时,用来创建初始表和导入初始数据 |
| 真实数据库数据(PGDATA) | /var/lib/postgresql/data | 保存日常写入的表、索引、事务日志和业务数据 |
挂载了初始化 SQL,不等于持久化了真实数据库数据。 这是排查时最容易忽略的一点:看到初始化 SQL 每次都正常执行,就误以为数据"有备份",其实数据恰恰是在每次重新初始化时被清掉的。
排查过程
复现路径很直接:kubectl delete pod 删除 PostgreSQL 的 Pod,等 Deployment 重新拉起,再查询之前写入的数据。
旧方案将开发机的 data 目录通过 minikube mount 映射到 Minikube 节点的 /data,再由 modelengine-pvc 挂入容器。它很适合放模型文件、配置文件和初始化 SQL。但 PostgreSQL 的 PGDATA 没有独立 PVC,而是留在容器的临时可写层中。因此只要 Pod 被删除并重新创建,原有 PGDATA 就会消失:
Pod 重建
↓
旧的 PGDATA 消失,数据库看到空目录
↓
PostgreSQL 再次初始化
↓
再次执行初始化 SQL
↓
此前运行期间写入的数据丢失
一个看似直接的补救办法是:把 PGDATA 也放到 /data。实测发现这条路走不通——见下面的根因分析。
根因分析
问题本质是"数据放错了地方":
- PGDATA 没有独立持久化存储,留在容器临时可写层,Pod 一重建就归零;
- 想借道
/data持久化,但/data来自minikube mount的 9P 共享目录,不能可靠支持 PostgreSQL 初始化时所需的chown、chmod等权限操作。结果就是initdb报Permission denied或Operation not permitted,数据库根本无法启动。
注意,权限失败的对象不是 SQL 文件。SQL 文件通常只需要被读取;真正需要创建文件、修改属主和收紧权限的是 PGDATA。所以"共享目录能放 SQL"和"共享目录能放数据库"完全是两回事。
解决方案
保留 /data 的共享用途,同时新增一条专门给 PostgreSQL 的数据通路:
开发机 ./data
↓ minikube mount(9P)
Minikube 节点 /data
↓ modelengine-pvc
模型、配置、WebDAV 文件、初始化 SQL
Minikube 节点本地 ext4 /pgdata
↓ pgdata-pvc
PostgreSQL 的 /var/lib/postgresql/data(PGDATA)
pgdata-pvc 对应的节点路径是 /pgdata,它不受 /data 的 9P 挂载影响,支持标准 Linux 文件权限,核心 PVC 定义如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pgdata-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
# 数据库专用存储:放在 VM 本地 ext4 磁盘,刻意避开 /data。
# 9P 不允许 postgres chown/chmod,导致 initdb 失败;
# ext4 路径不受 mount 影响且跨 minikube stop/start 持久。
hostPath:
path: /pgdata
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pgdata-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
volumeName: pgdata-pv
Deployment 侧的关键点有两个:initContainer 预创建目录并设置属主权限,主容器用 subPath 把子目录挂到真实的 PGDATA 位置:
initContainers:
- name: prepare-pgdata
image: postgres:14.12-alpine
command:
- /bin/sh
- -ec
- |
mkdir -p /pg/pgdb
chown 70:999 /pg/pgdb # postgres:postgres
chmod 700 /pg/pgdb
volumeMounts:
- name: pgdata-pvc
mountPath: /pg
containers:
- name: pgdb
image: postgres:14.12-alpine
securityContext:
runAsUser: 70
runAsGroup: 999
volumeMounts:
# PostgreSQL 的真实数据目录;Pod 重建后不会再从空目录启动。
- mountPath: /var/lib/postgresql/data
name: pgdata-pvc
subPath: pgdb
部署中的初始化容器会预先创建 pgdb、geoagent 等子目录并设置权限;PostgreSQL 主容器再将它们挂到真实的 PGDATA 位置。
验证结果
Pod 重建后的流程从"重新初始化 + 丢数据"变为:
Pod 重建
↓
重新挂载同一个 pgdata-pvc
↓
PostgreSQL 读到已有 PGDATA
↓
直接启动,不再执行初始化 SQL
↓
此前业务数据保留
重建 Pod 后查询此前写入的数据仍在,数据库也不再重复执行初始化 SQL。
注意事项
新方案不是简单地"多加了一个 PVC",而是同时解决了两个相互关联的问题:
- 数据持久化: PGDATA 位于独立 PVC 中,Pod 重建不会丢失数据库数据,也不会每次重新初始化。
- 权限兼容: PGDATA 位于节点本地 ext4,而不是 9P 共享目录;PostgreSQL 可以正常执行初始化所需的权限操作。
- 开发便利不受影响: 模型、配置和初始化 SQL 仍保留在
/data,可继续从开发机直接共享和维护。
一句话总结:旧方案只有"初始化脚本可共享",没有"真实数据可持久化";新方案将 PGDATA 放入独立 PVC,让 PostgreSQL 既能正常启动,也能在 Pod 重建后保留数据。
后续遇到同类问题可以直接按这个路径判断:数据库反复重新初始化、重建后丢数据 → 先确认 PGDATA 是否落在持久卷上;initdb 报权限错误 → 再确认存储介质是不是 9P 这类不支持完整 POSIX 权限的共享文件系统。