在Ubuntu或SUSE这类默认启用AppArmor的发行版上运行PostgreSQL时,一个很常见的问题是自己明明用chown和chmod把目录权限调好了,PostgreSQL却依然报Permission denied。罪魁祸首往往是AppArmor——它工作在内核层面,优先级高于传统的文件系统权限。只要进程的行为不在它的配置文件允许范围内,操作就会被拦截。这篇文章围绕PostgreSQL的AppArmor配置文件调整展开,从定位问题到修改规则、验证生效,完整走一遍流程。

一、如何确认是AppArmor在拦截
排查的第一步是搞清楚拒绝访问到底是谁造成的。先看AppArmor的服务状态,确认它是否处于启用状态:
sudo aa-status # 或者 sudo apparmor_status</code>
输出中会列出所有已加载的配置文件,重点找类似usr.sbin.postgres这样的条目,看它处于enforce还是complain模式。enforce表示违规操作会被真正拦截,complain则只记录日志不拦截。
接着查看内核审计日志,AppArmor的拒绝记录都在这里:
sudo dmesg | grep -i apparmor | grep DENIED # 或者查看审计日志 sudo journalctl -k | grep apparmor | grep -i denied sudo tail -f /var/log/kern.log | grep apparmor
如果日志里出现类似apparmor="DENIED" operation="open" profile="usr.sbin.postgres" name="/data/pgdata/base/16384/2619"这样的记录,基本可以确定是AppArmor配置文件没有放行对应路径。注意记录中的profile字段就是肇事配置文件的名字,name字段是被拒绝的路径,这两个信息是后续修改规则的直接依据。
还有一个容易被忽略的点:如果你自己编译了PostgreSQL或者二进制路径和发行版默认路径不同,AppArmor可能根本没有为它加载配置文件。可以用cat /proc/<pid>/attr/current查看某个PostgreSQL进程当前套用的profile,输出为unconfined则表示该进程不受限制,问题就出在别处。
二、理解配置文件的路径规则语法
PostgreSQL的AppArmor配置文件通常位于/etc/apparmor.d/目录下,Ubuntu的包命名一般是usr.sbin.postgres。打开后可以看到大量类似下面的规则:
/var/lib/postgresql/ r, /var/lib/postgresql/** rwk, /var/log/postgresql/ r, /var/log/postgresql/** rw, /etc/postgresql/ r, /etc/postgresql/** r,
规则由路径加权限组成。路径部分支持通配符:*匹配任意字符但不跨越目录分隔符,**匹配任意层级(包括子目录),?匹配单个字符。路径末尾带/表示目录本身,不带则表示文件。权限方面,r是读,w是写(包含创建和删除),k是文件锁定,a是追加写入,m是内存映射可执行文件,ix、px等是执行权限。
有一个细节值得注意:w权限要求文件已存在才能打开写入,若进程需要创建新文件,路径匹配的规则里必须同时覆盖目录的写权限。很多失败案例就是只给日志文件加了w,却没给所在目录加写权限,导致轮转日志时创建新文件被拒绝。另外l权限对应软链接访问,如果数据目录里有符号链接,也需要考虑这个权限。
配置文件末尾通常会看到#include <tunables/global>和abstractions/下的公共规则引用。这些共享片段里包含了读取locale、访问共享库等通用规则,自己改配置时不要轻易删掉这些include,否则可能引发一堆看起来毫无关联的拒绝。
三、修改配置并重新加载
假设你把数据目录迁移到了/data/pgdata,WAL归档放到了/data/archive,需要在配置文件中追加对应规则。编辑/etc/apparmor.d/usr.sbin.postgres,在原有规则附近加入:
/data/pgdata/ r, /data/pgdata/** rwk, /data/archive/ r, /data/archive/** rw,
保存后不要直接重启数据库,先用AppArmor自带的语法检查工具验证配置是否有拼写错误:
sudo apparmor_parser -r -T /etc/apparmor.d/usr.sbin.postgres
其中-r表示替换重载,-T只做语法缓存处理。如果没有报错,再正式加载:
# 重载单个配置文件(enforce模式) sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.postgres # 重新加载全部配置 sudo systemctl reload apparmor
一个更稳妥的做法是先用complain模式(也叫审计模式或学习模式)跑一段时间。处于该模式时,所有违规只记录不拦截,你可以通过日志观察PostgreSQL实际需要访问哪些路径,确认没有遗漏后再切回enforce:
# 切换到complain模式 sudo aa-complain /etc/apparmor.d/usr.sbin.postgres # 观察日志确认规则完整后,切回enforce sudo aa-enforce /etc/apparmor.d/usr.sbin.postgres
这两个命令属于apparmor-utils包,如果提示找不到命令,先执行sudo apt install apparmor-utils安装。complain模式下积累的日志还可以配合aa-logprof工具自动生成规则建议,比自己手工猜测路径靠谱得多。
四、验证生效与常见坑
规则加载完成后,验证方法很简单:切换到postgres用户,尝试在被放行的路径下写一个测试文件,或者直接执行pg_basebackup、pg_dump等实际操作,再回头看dmesg里有没有新的DENIED记录。没有新记录且操作成功,说明配置生效了。
实践中常见的坑有几个。第一,改完配置忘了重载,AppArmor不会自动感知文件变化,必须执行apparmor_parser或reload服务。第二,规则写的是目录却没有加**,导致只有目录本身可读,子目录下的文件全部被拒。第三,PostgreSQL执行外部程序(比如归档命令archive_command调用cp或rsync)时,子进程会继承或切换profile,外部程序自身的AppArmor配置可能成为新的拦截点,排查时要把子进程的profile也纳入检查范围。
第四个坑是SELinux和AppArmor的混淆。两者都是强制访问控制,但机制完全不同,AppArmor基于路径,SELinux基于标签。如果你的系统装了SELinux相关的工具但发行版实际跑的是AppArmor,用semanage调整不会起任何作用,先确认aa-status和sestatus哪个真正在生效,再决定用哪套工具去改。
最后建议把调整过的配置文件纳入配置管理(比如Ansible或Git),并在注释里写清楚每条规则对应的业务用途。AppArmor配置文件在系统升级PostgreSQL大版本时可能被包管理器覆盖或产生冲突提示,有完整记录的规则清单能让你在升级后快速恢复,避免数据库因为一条丢失的路径规则而启动失败。
PostgreSQLAppArmor配置文件修改时间:2026-09-14 05:56:37