Minikube 本地部署挂载方案升级:解决 PostgreSQL 重启丢数据

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。实测发现这条路走不通——见下面的根因分析。

根因分析

问题本质是"数据放错了地方":

  1. PGDATA 没有独立持久化存储,留在容器临时可写层,Pod 一重建就归零;
  2. 想借道 /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 权限的共享文件系统。