最近帮团队把一套业务系统的PostgreSQL从虚拟机迁到了K8S上整个集群平时是用Rancher来管理的。从最开始纠结数据库到底该不该容器化到最终稳定跑起来中间踩了不少坑也积累了一些文档里不会明说的经验。这篇文章就把整个过程的思路和操作拆开讲清楚——如何通过Rancher在K8S上运行PostgreSQL数据库从环境准备、存储设计、Helm部署到备份恢复每一步都会给出实际用过的配置和选择理由。如果你正准备把有状态服务搬上K8S或者已经在用Rancher但不确定数据库这块怎么落地这篇应该能帮你省掉不少弯路。1. 为什么选择在Rancher上跑PostgreSQL而不是直接用裸机或云托管数据库1.1 Rancher真正解决的是K8S的使用门槛问题先说一个大前提K8S本身足够强大但使用门槛不低。特别是团队里没有专门的平台运维时光是把多套集群、权限、监控、日志这些基础设施盘清楚就得花很多精力。Rancher这类管理平台的核心价值就是把这些事情包了一层可视化界面和统一入口。我个人的体感是Rancher最大的好处不是炫而是让开发和运维之间的协作变简单了。开发人员不需要记一堆kubectl命令也不用理解ServiceAccount、RBAC、NetworkPolicy这些概念在UI上就能完成应用部署、日志查看、版本回滚。而运维这边可以同时管理多套集群从一个控制台切换生产、测试环境权限也能按项目或命名空间隔离好用程度比想象中高不少。回到PostgreSQL这个话题。既然团队已经在用Rancher管理K8S那么数据库放进来就是水到渠成的事。我们当时的判断很简单业务量属于中小规模数据量在几十GB级别对性能没有那么变态的要求但迭代速度和环境一致性要求很高。把数据库放进K8S能和业务应用统一编排、统一更新、统一监控这套一套集群打天下的运维模型比单独维护几台数据库虚拟机更贴合我们的现状。1.2 K8S上跑数据库的三大痛点需要先正视不过数据库确实是有状态服务和普通无状态应用不一样直接上个Deployment就完事的话后面会发现问题很多。我总结下来K8S上跑PostgreSQL主要卡在三个地方第一是Pod重建后的身份和网络变化。普通Deployment的Pod死了就换一个新的IP也变了对外用Service访问问题不大但数据库自身的主从关系、会话保持、业务连接池配置都需要考虑到这个动态性。第二是数据持久化。Pod本身是临时的容器一删里面的数据就没了。必须把数据写到外挂存储上而且这个存储的生命周期要独立于Pod。第三是性能和资源隔离。容器化之后CPU、内存的限制如果设置得不合理数据库性能波动会非常明显甚至出现节点内存被打爆的情况。这三件事想明白了后面的一系列设计就都有方向了。数据库放不放K8S不是能不能跑的问题而是有没有把状态管理、存储、资源配置设计好的问题。1.3 我最终选定的部署路线综合考虑了团队现状和维护成本我最终敲定的路线是这样的集群层沿用团队已有的Rancher 2.7.x管理K8S 1.26集群不做迁移只做资源规划和标签整理。部署层不手写裸YAML选用Helm Chart来安装PostgreSQL。Chart里已经把StatefulSet、Service、Secret、PVC这些对象都打包好了可配置项多安装和升级都方便。存储层使用Rancher生态内推荐的Longhorn作为StorageClass底层是云主机本地磁盘支持快照和备份够用且可控。运维层用K8S CronJob做每日逻辑备份定期做一次恢复演练监控走Rancher自带的监控组件。这条路线的核心思路是优先选有社区生态、有维护方、升级路径清晰的方案而不是所有东西都自己造轮子。后面我会详细展开每一步的关键操作和参数下面先从环境准备说起。2. 环境准备Rancher部署方式和集群接入的关键选择2.1 Rancher自身的部署方式单节点还是高可用Rancher本身也是一个应用它的部署方式直接影响后续集群管理的稳定性。我用过两种方式简单对比一下单节点Docker启动适合测试环境。一条docker run命令就能把Rancher跑起来自带嵌入式K3S集群操作最简单。缺点是Rancher服务本身挂了整个管理面就不可用了虽然底层业务集群不受影响但那时候你想用UI做任何操作都做不了。高可用模式生产环境推荐。通过RKE2或K3S部署一套Rancher集群使用外部数据库存储Rancher的数据配置负载均衡入口。这样Rancher本身的故障不会影响整个管理能力。如果你的环境允许生产环境建议直接上高可用模式这属于早期多花半天后期省心半年的投入。我当时在团队环境里用的就是单节点Rancher配合定期快照测试阶段够用了但上线前还是补了一套高可用。2.2 导入已有集群还是让Rancher新建集群Rancher支持两种接入方式一种是新建集群由Rancher调用云平台API或通过节点模板创建节点并安装K8S组件另一种是导入集群把已有的K8S集群不管是kubeadm建的还是云平台自带的注册到Rancher上管理。我们当时已有了一套跑业务的K8S集群所以选的是导入方式。导入过程不复杂核心就三步在Rancher UI中选择添加集群 → 导入现有集群给集群起个名字。Rancher会生成一段Agent安装命令本质是一个Deployment ClusterRole把这段命令复制到目标K8S集群上执行。等待Rancher Agent运行起来集群状态变成Active就能在Rancher里管理了。这里有一个值得注意的点导入集群时Rancher默认会给集群打上管理标签并且创建一组ClusterRole来获得管理权限。如果你的K8S集群安全要求比较高记得在导入前检查一下Rancher Agent所需的权限范围避免给了过大权限。在内部测试环境问题不大但生产环境还是建议按需配置权限。2.3 节点标签和命名空间规划给数据库一个明确的家集群接入Rancher之后我做的第一件事不是急着部署数据库而是规划命名空间和节点标签。这一步看似简单但直接影响后面Pod调度和资源隔离。命名空间方面我单独建了一个叫database的命名空间和业务应用所在的namespace比如app-web、app-api分开。好处是权限控制更清晰一般开发人员只需要业务命名空间权限监控和日志过滤也更方便后期如果要迁移或重建数据库影响面也被隔离在这个命名空间内。节点标签方面我给计划运行数据库的几台节点打上了专用标签kubectl label node node-1 database-nodetrue kubectl label node node-2 database-nodetrue然后在部署PostgreSQL时通过NodeSelector把数据库Pod固定到带这个标签的节点上。为什么要这么做因为数据库对I/O稳定性有要求如果Pod被调度到一台负载很重的节点上和其他业务Pod抢磁盘和网络性能波动会很厉害。给数据库单独划节点相当于在物理层面做了一层隔离虽然节省不了太多成本但排查问题时思路会清楚很多。3. 存储先行持久化方案与PVC设计这一步最容易翻车3.1 StorageClass、PV、PVC到底是怎么回事在K8S里存储相关有三个概念经常被混在一起StorageClass、PV、PVC。我用一个生活化的类比解释一下新手听一遍就能懂。StorageClass相当于硬盘工厂它定义了生产硬盘的方式比如底层用的是本地盘还是云盘、是否支持动态扩容。PVC相当于下订单你说我要一块10GB的硬盘访问模式是读写一次。PV则是工厂实际交付给你的硬盘一个PVC匹配一个PV。在没有StorageClass的旧式K8S环境里你得手动创建PV相当于自己造硬盘很费劲。而有了StorageClass之后PVC下订单K8S自动通过StorageClass创建对应的PV整个过程无需人工干预。对于数据库这种需要持久化的应用来说这基本是标配。在Rancher界面上创建PVC时你会看到需要选择StorageClass、填写容量和访问模式。数据库场景下访问模式务必选择ReadWriteOnceRWO意思是同一时刻只能被一个节点读写。不要选ReadWriteManyRWXPostgreSQL的默认单实例架构用不上多节点同时读写而且RWX在部分文件系统上的表现并不理想。3.2 底层存储选型本地盘、NFS还是Longhorn这是整个部署过程中最需要慎重的一环。我把常见的几种存储方案拉了个对比表列出我实测下来的体感存储方案性能数据安全运维复杂度适用场景节点本地目录无持久化最好极差Pod漂移即丢数据最低临时测试手动挂载云盘如云厂商数据盘较好中等依赖快照策略中等单节点固定调度NFS中等偏下中上取决于NFS自身高可用中等多节点共享但对性能要求低Longhorn中上较高支持快照和备份中高但UI友好有状态服务的首选之一我个人的建议是如果你的K8S集群节点本身就是云主机且云厂商已经提供了CSI存储插件直接用云盘类型的StorageClass最省事。但如果你和我一样集群跑在自建的虚拟机或物理机上那Longhorn是更合适的选择。Longhorn是Rancher生态里的开源分布式存储部署之后会变成集群内的一个StorageClass它会自动把数据分块复制到多个节点、支持定期快照和一键备份还自带了恢复功能。官方的Helm Chart也能直接装Longhorn装完就能在StorageClass列表里看到longhorn这个类。前面提到的PVC下订单以后就会由Longhorn自动创建一个副本数为2的卷数据被同步写到两台不同节点上一定程度避免了单节点故障导致的数据丢失。3.3 PVC的资源规划与关键参数确定存储方案后PVC的容量规划也不建议拍脑袋。我先说结论数据库的PVC容量要留出至少两倍余量——当前数据量的一半空间给数据本身余下的一半给WAL日志、临时文件、未来一段时间的增长以及快照操作预留空间。我当时给PostgreSQL申请了20Gi的PVC实际当时数据量只有不到6Gi。看起来浪费了但数据库这种存储一旦满了后果不只是写入失败还可能把整个Pod的日志目录填爆最终Pod挤掉导致服务不可用。容量大一点的PVC多用不了多少成本却能让故障概率降低一个数量级。在Rancher的PVC创建页面里几个关键参数这样填名称postgresql-data命名空间databaseStorageClasslonghorn访问模式ReadWriteOnce请求容量20Gi注意PVC创建之后并非立刻绑定PV只有被StatefulSet或其他Pod引用时才会真正发生绑定和创建底层卷的动作。所以创建完PVC发现状态是Pending很正常等部署Helm Chart之后再看通常会变成Bound。4. 正式部署通过Helm Chart在Rancher上拉起PostgreSQL4.1 为什么不手写YAML而是用Helm Chart在Rancher上部署应用你可以直接上传YAML也可以从应用商店安装Chart。我强烈建议数据库这类有状态服务走Helm路线理由很实际第一Chart把StatefulSet、Service、Secret、PVC、ConfigMap这些对象都自动编排好了。你不需要自己纠结Pod模板怎么写、端口怎么暴露、健康检查探针怎么配这些都已经有成熟的默认值。第二升级和回滚方便。后面要调整PostgreSQL配置参数、升级镜像版本只要修改values.yaml一条命令就能完成升级Helm会自动处理新版StatefulSet的滚动策略比手动改YAML然后再apply省心得多。第三社区维护的Chart会跟随PostgreSQL版本持续迭代安全补丁和bug fix。你用Chart等于自动接住了上游的修复更新这比自己维护一套YAML要安全不少。4.2 添加Chart仓库并准备values.yamlRancher 2.x的应用商店本身就内置了一批常用Chart仓库包括Bitnami等社区的仓库。如果你在Rancher UI的市场里没看到想要的PostgreSQL Chart也可以手动添加仓库应用商店 → 添加Chart仓库填写仓库地址和名称保存之后等待索引同步就能在市场里搜到。添加好仓库后接下来说values.yaml。下面是我当时的核心配置去掉了业务敏感部分保留了最关键的内容# values.yaml 核心配置 auth: postgresPassword: 你的强密码 database: myapp username: myapp_user primary: persistence: enabled: true existingClaim: postgresql-data size: 20Gi resources: requests: cpu: 500m memory: 1Gi limits: cpu: 2 memory: 4Gi nodeSelector: database-node: true service: type: ClusterIP ports: postgresql: 5432这里有几个参数我想专门解释一下因为它们直接影响数据库的可用性primary.persistence.existingClaim指定的是我们前面创建好的PVC。如果Chart没有指定existingClaim它会自己申请一块新PVC那样反而容易出现一块卷上同时创建了多个PVC实例数据目录就乱套了。resources部分requests是给调度器看的最低需求limits是对Pod的封顶限制。我们直接在生产环境吃的第一个亏就是没有设置limits后面我会在踩坑章节详细说。primary.nodeSelector对应前面打的节点标签确保数据库Pod只会被调度到指定的数据库节点上。4.3 在Rancher UI上安装Chart的操作轨迹如果你习惯在UI上操作流程是这样的在Rancher顶部分别选择目标集群和database命名空间。进入应用商店Apps页面搜索PostgreSQL。选择对应的Chart点击安装Install。在配置页面里可以切换YAML视图直接粘贴上面准备好的values.yaml然后选择目标命名空间。点击创建Create等待工作负载状态变为Running。这个过程里还有几个UI上容易踩的坑我提一下第一Rancher的安装界面默认会把你当前选择的命名空间带进Chart的安装请求中但有些Chart模板会忽略这个设置默认装到default命名空间。装完一定要去确认Pod的命名空间别在错误的地方找资源。第二Chart的默认版本可能和你的K8S版本不兼容。如果创建后Pod一直CrashLoopBackOff并且错误信息里出现类似no matches for kind的提示十有八九是CRD版本不匹配需要在应用商店里选择一个兼容版本的Chart或者升级Rancher/K8S。4.4 部署后的验证清单不是Pod起来就算完事Pod状态变成Running只说明容器被拉起来了不代表数据库真的可用。我每次部署完都会按下面这个清单排查一遍查看Pod状态和事件确认没有ImagePullBackOff、CrashLoopBackOff等异常。查看PVC状态应该变成Bound。检查日志用kubectl logs看PostgreSQL是否正常完成初始化有没有权限错误或目录失败。进入Pod执行SQL验证kubectl exec -it postgresql-0 -n database -- psql -U myapp_user -d myapp -c select version();如果能正常返回版本信息说明数据库本体是通的。接下来还要验证网络层kubectl get svc -n database确认Service存在并能被集群内其他应用通过服务名访问。如果你需要从集群外连接数据库做数据迁移临时把Service类型改成NodePort也可以但生产环境建议用集群内访问尽量别把5432端口直接暴露出去。到这里一个单实例PostgreSQL已经在Rancher管理的K8S集群里跑起来了。但跑起来和稳定运行之间还有高可用和备份这两道功课要做。5. 高可用与备份数据库上线后必须补上的两件事5.1 单实例的局限以及我为什么不一开始就上搭建主从很多教程讲到K8S上部署数据库都会建议直接上主从集群。但结合团队实际情况我一开始并没有上主从而是把单实例的可用性先做到了一个合理程度。原因很简单PostgreSQL高可用方案无论是Patroni还是传统的流复制repmgr都涉及选举、同步复制、脑裂保护等复杂逻辑在K8S环境下还需要处理Service的漂移、存储的跨节点复制这些内容本身就是一个不小的项目。如果团队里没有专人负责贸然上主从反而可能在故障切换时引入新的问题。所以我的策略是先保证单实例的韧性具体做了三件事使用StatefulSetPod意外被杀后能自动重建并且PVC会重新挂载到新Pod上数据不丢。配置了合理的存活探针和就绪探针Pod内部PostgreSQL没起来之前服务不会分发流量。在节点层面给数据库Pod绑定专用节点减少被其他应用拖垮的概率。等业务稳定、团队有空余精力之后再考虑引入真正的主从或Operator方案。这是我认为比较务实的一个推进路径。5.2 备份策略pg_dump CronJob 远端对象存储不管单实例还是主从备份都是一条底线。我当时选用的备份方案是每天定时用pg_dump做逻辑备份上传到远端S3兼容存储同时保留最近7天的备份文件。思路不复杂实现方式用K8S CronJob就足够了。下面是一个简化的备份Job模板你可以直接照着改apiVersion: batch/v1 kind: CronJob metadata: name: postgres-backup namespace: database spec: schedule: 0 2 * * * jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: pg-backup image: postgres:15-alpine env: - name: PGPASSWORD valueFrom: secretKeyRef: name: postgresql key: password command: - /bin/sh - -c - | pg_dump -h postgresql.database.svc.cluster.local -U myapp_user -d myapp | gzip /backup/myapp-$(date %Y%m%d-%H%M%S).sql.gz # 上传到S3存储的命令可以用aws cli或mc这里省略CronJob每天凌晨两点执行pg_dump导出的SQL压缩后上传到对象存储。这里我特别想提醒一件事备份最重要的不是备份动作本身而是定期做恢复验证。我见过太多环境备份任务天天跑真到出故障那天才发现备份文件是坏的或者S3密钥过期了。所以每隔一段时间我会手动拿一次最近的备份在测试环境里恢复一遍PostgreSQL确认导入没有任何报错。5.3 一次真实的故障恢复演练为了心里有底我在测试环境做了一次完整的故障模拟过程大致如下场景模拟假设数据库节点磁盘损坏Pod被调度到一台新节点而PVC数据也损坏了。在测试环境手动删除PostgreSQL的StatefulSet但保留PVC生产环境遇到节点故障时系统会做类似的操作。等待新Pod被创建确认它挂载了原PVC但数据目录为空。这里注意如果PVC数据完好PostgreSQL会自动读取原数据如果数据目录为空说明需要恢复。把最近的备份文件从小对象存储下载下来解压得到SQL文件用psql导入kubectl exec -it postgresql-0 -n database -- psql -U myapp_user -d myapp myapp-latest.sql导入完成后重新启动应用验证业务接口返回值正常。这次演练得到的核心结论是CronJob 对象存储的方案虽然土但非常可靠只要备份文件能正常下载和导入恢复时间基本就是文件下载时间加上SQL导入时间。对于中小业务量这完全在可接受范围内。6. 过程里踩过的坑以及最终留下的几条建议6.1 资源限制不设置的惨痛教训前面提到过我们早期部署时没有给数据库Pod设置resources.limits想着容器里的数据库缺资源了就抢一下呗。结果某次业务流量上涨PostgreSQL进程把节点内存吃满了导致整台节点的Kubelet响应超时节点状态变成NotReady上面所有Pod全部被驱逐重建。那一次的影响范围远超数据库本身。后面我们把limits加上并且根据业务情况做了调整。参考值如下shared_buffers建议设置为容器内存总量的1/4。比如limits为4Gishared_buffers设为1GB左右。effective_cache_size建议设为内存总量的3/4这个参数影响优化器对索引扫描的倾向性。work_mem排序操作的内存建议从16MB起步不要直接调太高高并发场景下work_mem会乘并发数计算。max_connectionsK8S环境下建议控制在100以内太多连接会把CPU拖垮。结合应用侧连接池合理设置。这些参数可以在values.yaml里通过PostgreSQL的配置项覆盖例如primary: extendedConf: shared_buffers: 1GB effective_cache_size: 3GB work_mem: 32MB max_connections: 100改完参数记得通过Helm升级应用然后观察连接数和慢查询指标的变化。6.2 Rancher UI操作中容易忽略的权限与命名空间问题Rancher的权限模型基于集群和命名空间两层UI上操作时普遍容易忽略几个问题一是创建资源时没有确认当前上下文。Rancher页面顶部有两个下拉框一个选集群一个选命名空间如果停留在默认集群或default命名空间很可能把数据库装到错误环境里尤其是同时管理开发和生产集群的时候选错环境的影响会非常大。二是给普通用户授权时注意命名空间级别的RoleBinding。数据库所在命名空间最好只分配给负责数据库维护的人员不要给所有人只读权限避免误操作。三是使用Rancher的应用商店安装Chart时Rancher会在你的命名空间里创建额外的自定义资源删除Chart时有时不会连带清理CRD实例。如果你发现卸载应用后依然残留了PostgreSQL Operator相关的CR对象手动清理就行不用担心。6.3 日常运维清单和最终体会这套环境稳定运行了几个季度之后我沉淀下来的日常运维动作基本是这几项每天检查备份CronJob的执行状态确认最近一次job是Successful。每周看一次数据库Pod的资源使用曲线确认requests和limits的设置仍然合理。每逢K8S或Rancher有版本升级先在测试集群验证PostgreSQL Chart的兼容性再升生产。每季度做一次备份恢复演练确保恢复流程不是纸上谈兵。最后想说的是在Rancher上运行PostgreSQL这件事真正难的不是安装那一步而是你愿不愿意把存储、备份、监控、权限这些周边都规划好。很多人以为用Helm一键装完就结束了恰恰是那些没规划的细节会在某个深夜变成让你爬起来的故障。如果你也在规划这件事建议先把存储方案和备份恢复这两块理清楚再动手装数据库真的能少走很多弯路。