半夜两点被值班电话叫醒,某台服务器磁盘告警,登录上去看df -h,根分区已经 100%,du扫了一圈发现/var/log和/tmp各占了一大半,一时半会儿还不知道哪些文件能清。这种场景干运维的多少都经历过,而每次被这种问题困住,根子都在同一个地方:对 Linux 文件系统的目录结构理解停留在“大概知道”的层面,真到了关键时刻,没法靠目录树的逻辑快速定位问题。
很多人觉得目录结构就是考试题,记住/etc放配置、/var放日志就够了。但实际工作中你会发现,这套目录结构里面藏着 Unix 四十多年的设计思路,谁在什么场景下该往哪个目录放东西、为什么有些目录被拆出来单独挂载、为什么删了文件磁盘空间却不见少,全都能从目录结构中找到答案。这篇我就把自己这些年跟 Linux 目录结构打交道的理解、踩过的坑、以及排障时怎么用目录树的思维解题,一次讲透。
1. 目录结构从来不是随便画的:FHS 与 Unix 设计逻辑的来龙去脉
Linux 的目录结构不是某个团队拍脑袋定的,而是沿袭了一套名为 FHS(Filesystem Hierarchy Standard)的标准。FHS 本身又是从 Unix 早期目录布局演化过来的,理解这段历史,你就能明白“这个目录为什么存在”而不是死记硬背。
1.1 从一块硬盘到一棵树的演进:为什么根目录下要有这些固定目录
Unix 诞生于上世纪 70 年代的贝尔实验室,当时的计算机存储资源极其紧张,系统设计者把目录当成了一种隐形的“接口约定”。/bin放系统启动和执行基础命令所需的二进制,/etc放配置文件,/dev放设备文件,这套约定解决了什么问题?它让人和程序在没有任何数据库的情况下,也能大致猜到某个文件应该在哪,比如配置文件丢了就去/etc找,可执行文件不在PATH里就去/bin或/usr/bin翻。
Linux 沿用了这套约定,并在此基础上发展出 FHS,核心目标有两个:一个是让软件开发者知道“该把文件装到哪里”才能让不同发行版兼容;另一个是让系统管理员知道“该去哪里找文件、备份哪些目录、清理哪些目录”。所以你别小看根目录下那一排目录名,每一个都对应着一类文件的“职责归属”。比如/srv是给服务提供数据文件的,早期很多新手会把网站代码放到/home/www或/root/www,严格来说都不符合 FHS 的推荐,放到/srv/www才是规范的。
1.2 四个维度的分类:可共享、不可共享、可变、静态
FHS 背后真正的分类逻辑,其实是两个交叉的维度:文件和目录是否可共享(across hosts),以及是否可变(variable vs. static)。把目录往这四个象限里一放,就能看懂整个布局的深意。
- 可共享且静态:比如
/usr,它里面是安装的软件和库文件,基本不变,而且可以导出给同网络的其他机器用,也就是只读共享。 - 可共享且可变:比如
/var/mail、/var/spool,这些数据在机器之间同步有意义,但是内容会一直变。 - 不可共享且静态:比如
/etc,每台机器的配置都不一样,不能跨机器共享,但文件本身不经常变。 - 不可共享且可变:比如
/var/log、/var/run,这是本机运行时产生的内容,既不能共享又一直变化。
这个分类法解决了一个实际问题:做备份、做镜像、做集群扩容时,你不需要把整个根分区打包,只要按照这些维度去挑目录就行。比如要给一台新机器复刻环境,把/etc导出、把/usr镜像一份、再初始化/var里的业务数据,基本就完成了。我在实际迁移环境时就是这么干的,而不是拿着dd去整盘克隆,省下的时间不是一点半点。
强调一下,FHS 规定的其实是“目录应该装什么”,而不是“目录非长这样不可”。不同的发行版可以微调,但那棵大树的枝干是一致的,这也是为什么你在任何主流发行版上都能快速上手的原因。
2. 二级目录逐层拆解:每个目录真正在扛什么活儿
根目录下的二级目录是使用频率最高的,我打算把一个一个拆开讲,但不按教科书顺序,而是按它们在真实系统中的“数据生命周期”来讲:系统启动期、运行期、动态数据期、虚拟数据期、用户数据期。这样你脑子里会形成一条线,而不是零散的点。
2.1 /bin 与 /sbin:早期启动的最小可用环境
/bin在老的 Unix 系统里存放的是最基本的用户命令,/sbin存放的是系统管理命令,两者的边界主要体现在“谁在什么状态下需要它”。/bin里的ls、cp、cat是单用户模式、救援模式下也要能用的,/sbin里的fdisk、fsck、mkfs是系统维护和修复时才需要的。
现在大多数发行版里/bin和/sbin其实是指向/usr/bin和/usr/sbin的符号链接,这就是所谓的 usr merge(usr 合并),把根目录下的二进制都归拢到/usr下,根分区只需要承担启动那一小部分。这个改动让根分区变得更小、更稳定,也更容易做只读挂载。判断你的系统是不是这种布局,直接ls -ld /bin看有没有箭头就行。
实际排障中,我经常提醒自己:当系统进入救援模式时,PATH往往很短,很多命令都在/sbin下但不在默认PATH里。这时候你直接输入ifconfig(如果还在用的话)会提示找不到,但/sbin/ifconfig是能跑的。记住这一点,关键时刻能救命。
2.2 /usr:你以为是用户目录,实际是系统软件的大本营
/usr是个极其容易误解的目录,名字看起来像 user,所有人都以为它和“用户数据”有关。实际上/usr是 Unix System Resources 的缩写,里面装的是系统软件资源,而不是用户文件。用户文件在哪?在/home,这是两个完全不同的概念。
/usr/bin放的是发行版自带和通过包管理软件装进来的绝大部分可执行文件,/usr/lib放的是配套的库文件,/usr/share放的是架构无关的共享数据,比如帮助文档、字体、图标。一个典型的系统里,/usr 的体积可能占整个操作系统的七八成以上。
理解了这一点,你就明白为什么有些人会把/usr单独挂到一个比较大的分区上,根分区只给系统启动留出必要空间。这设计在目前大多数系统上实践下来很稳:即使系统日志把/var撑满了,也不会拖垮正在运行的二进制程序,登录还能进得来,排查就有抓手。
2.3 /var:所有“会长大”的数据都在这里
/var里全是动态变化的文件,这也是磁盘告警最先应该排查的目录。/var/log存各类日志、/var/tmp是比/tmp更“长寿”的临时文件(重启后可能依然存在)、/var/lib存软件包的运行状态数据,比如数据库文件、Docker 的 overlay 目录。可以说,整个系统运行起来之后,绝大部分“新增落盘的数据”都在/var下。
最重要的排障思路:磁盘满了先别rm扔着等,而是按照“先查/var/log、再查/var/tmp、再查/var/lib”的顺序层层逼近。很多日志默认不轮转或者轮转配置不合理,单个日志文件能涨到几十 GB 甚至上百 GB。我在某次处理一个广告服务集群的日志时,发现某个access.log已经 40GB 且还在跑,最后就是调整/etc/logrotate.d/下的配置把它“治住”的。
2.4 /etc:系统唯一的中心化配置目录
/etc大概是所有人最早记住的目录,但它的边界比很多人以为的更清晰。该目录只存放静态的配置文件,不允许存放可执行文件,更不允许保存动态生成的文件。系统的网络配置、用户账户信息(/etc/passwd)、软件服务的配置(比如 Nginx 的/etc/nginx/)都在这里。
这里有一个特别容易犯的错误:有些部署脚本喜欢把临时状态文件、PID 文件往/etc里塞。从 FHS 的角度这属于污染配置目录,PID 文件应该放到/run,状态文件应该放到/var/lib。我在项目规范里会明确指出这种边界,因为一旦把/etc当垃圾桶,备份和迁移配置时就会多出一堆不该有的内容。
2.5 /proc 与 /sys:不占磁盘的“虚拟文件系统”
/proc和/sys是新手最容易懵的两个目录,ls进去看到一大堆数字和文件,其实它们并不是磁盘上真实存在的文件,而是内核提供给用户态的一个“视图”。/proc主要通过进程号目录暴露每个进程的运行状态,比如/proc/loadavg看负载、/proc/meminfo看内存细节;/sys则用来暴露设备驱动、内核参数、电源管理等更底层的对象。
对系统管理来说,这两个虚拟目录的意义在于:很多原本需要写程序或调用系统调用的信息,直接用cat、echo就能读写。例如临时开启 IP 转发,echo 1 > /proc/sys/net/ipv4/ip_forward就能生效,而且不用重启。当然,这种直接改法重启后失效,生产环境还是建议通过/etc/sysctl.conf或sysctl.d配置文件持久化。
2.6 /home、/root 与 /tmp:用户空间和“临时停车场”
/home是普通用户的家目录集合,/root是管理员(root 用户)的家目录。这两个目录里的数据属于“用户产生数据”,需要和系统目录分开备份。有些公司会把整个/home单独挂一块盘,这样系统重装时用户数据不受影响,这是标准的实践。
/tmp是所有用户都能读写的共享临时代,它的特点是任何用户都能往里写,这既是便利也是风险。攻击者常常在/tmp里放临时提权工具;普通用户如果磁盘配额不限制,也可能把共享的临时空间写满,影响其他人。生产环境里我通常建议把/tmp独立挂载并加上noexec选项,禁止在这个目录里执行任何二进制,这是成本极低但收益很高的安全加固手段。
3. 磁盘爆满却找不到大文件:用目录树思维设计排障链路
理解了目录的职责,排障就不再是无头苍蝇。我这里把一个典型的“磁盘满但找不到大文件”案例拆出来,完整走一遍排查链路,你会发现每一步其实都对应着目录设计逻辑。
3.1 第一层排查:从 / 开始逐层 du,而不是靠 ls 猜
接到磁盘告警,第一件事当然是df -h看是哪个分区满了。假如告警的是/,那么接下来要用du -xhd1 /按一层目录统计根目录下的占用大小。这里的关键参数是-x(即--one-file-system),它告诉du不要跨文件系统,否则会把挂载在/下的其他独立分区(比如/home)也算进来,造成误判。
我看到有些人习惯直接du -sh /,这样扫全盘又慢又容易触发权限告警,扫出来的数值还因为跨文件系统而失真。正确姿势是一层层往下收窄,先看du -xhd1 /找到/var或某个目录占大头,再进到下一层接着du -xhd1 /var,把范围逐步缩小到具体文件或子目录上。这个过程快的话几十秒,慢的话几分钟,比写成脚本全盘扫要高效得多。
3.2 第二层排查:inode 用尽与“进程占用已删除文件”
有时候df -h显示空间还剩不少,但系统就是报告磁盘满,比如No space left on device,这时候要考虑df -i检查 inode。inode 是文件系统记录文件和目录元数据的结构,任何文件即使大小为 0 也要消耗一个 inode。如果某个目录下生成了海量小文件(比如/tmp里大量随机会话文件,或/var/spool里堆积的队列条目),就会先把 inode 耗尽,表现就是明明还有空间却写不进去任何文件。
这里还要提一个经典坑:用rm删除了文件,但df显示空间没变化。原因是有进程仍然持有这个文件的文件描述符(fd),文件没真正释放。排查方法是用lsof | grep deleted找出还在占用已删除文件的进程,确认后重启对应进程或让它重新打开日志文件。这个坑跟目录结构的关系在于:/var/log 下的日志文件如果被进程保持打开,运维轮转或删除日志时必须通知进程重新打开文件,否则磁盘空间会一直“错觉占满”。
3.3 第三层排查:合理的清理由本应避免的脏数据堆积
查出来大文件在哪之后,怎么清理也是门学问。以日志为例,你应该优先处理/etc/logrotate.d/下的轮转配置,而不是手动rm。手动删除日志文件容易导致正在写日志的进程找不到原来的文件句柄,行为变得难以预料。正确做法是让 logrotate 定时轮转,保留若干历史,再动态给进程发信号让它重新打开新日志文件。
还有/tmp里面的大文件,很多是程序运行时不规范把临时文件写到/tmp又没清理。除了定期用tmpwatch或systemd-tmpfiles清理(有些值班脚本也会顺手清),更重要的是根治程序行为,让临时数据写到规范的位置,比如应用自己目录下的tmp或/var/tmp。把这一层线理顺之后,你会发现磁盘排障从“每次都被动救火”变成“按目录树定期体检”。
4. 挂载、权限与安全加固:目录结构的运维进阶玩法
目录结构不仅决定文件放哪里,还决定你怎么挂盘、怎么控权、怎么防攻击者。这一节我会讲几个很具体的实操配置,都是生产环境里验证过很多年依然稳当的方案。
4.1 哪些目录值得独立挂载:/home、/tmp、/var 的取舍
把重要目录单独挂载到独立分区或磁盘,是 Linux 运维里非常重要的设计决策。好处主要有三个:一是隔离故障,某个分区写满不会连带拖垮根分区;二是方便备份,直接对挂载点做快照即可;三是能做不同的挂载选项,按目录的数据特征施加差异化安全策略。
/home:用户数据独立挂载,操作系统重装或崩溃时数据不受损,这是最标准的备份边界。/tmp:独立挂载并启用noexec,nosuid,禁止执行临时文件、禁止提权,这是最推荐的安全做法。/var:日志和数据增长大户,独立挂载避免日志涨满根分区。/usr:软件主体,如果能接受只读挂载,可以加ro提高安全性和稳定性。但前提是你足够清楚系统在正常运行时不会往/usr里写数据,否则会出现莫名奇妙的写入失败。
4.2 fstab 实战配置:用一行挂载选项体现安全策略
下面是一段典型的/etc/fstab配置示例,你可以看到目录与挂载逻辑是如何结合的:
# 设备 挂载点 类型 选项 dump pass /dev/sda3 / ext4 defaults 1 1 /dev/sda5 /home ext4 defaults,nodev,nosuid 1 2 /dev/sda6 /tmp ext4 defaults,nodev,nosuid,noexec 1 2 /dev/sda7 /var ext4 defaults,noatime 1 2这几行配置分别对/home加了禁止设备文件与禁止 SUID、对/tmp额外加禁止执行,noatime表示不记录文件访问时间戳,减少磁盘写放大。dump和pass列中,/为 1 表示最先做文件系统检查,其他分区为 2 表示随后检查。使用findmnt或mount -a可以验证配置是否生效。
强调一句:修改/etc/fstab前,建议先用mount -o remount方式验证挂载选项不存在语法错误,否则重启时因为 fstab 写错导致系统卡在维护模式,处理起来比较费劲。如果真把 fstab 写坏了,用单用户模式或引导盘把 fstab 改回来即可,但这个过程在无带外管理的远程服务器上会非常痛苦。
4.3 目录权限与默认掩码:把入口封到刚刚好
Linux 的目录结构是骨架,权限则是挂在骨架上的锁。一个常见的安全实践是:对/home下的用户目录设置为 700 或 750,阻止其他用户随意查看家目录;对/tmp这种共享目录,利用 sticky bit(粘贴位,用一个t表示)限制只有文件所有者才能删除自己创建的文件。Linux 的/tmp默认已经带上了这个位,ls -ld /tmp能看到权限末尾的t。
默认文件权限由umask决定,比如把umask 022改成umask 027,新创建文件/目录对“组内其他用户”就不再开放写权限。很多安全基线要求就是这个原因:尽量最小化新文件的默认开放面,然后按需追加。目录结构配合权限做下来,系统暴露面能少一大截。
5. 发行版差异与容器时代:这套结构还够用吗
很多人在学目录结构时有个疑惑:CentOS 和 Ubuntu 的目录为什么看起来不完全一样?Docker 容器里的目录结构是不是也按 FHS 来?这里我展开讲一下这两个问题,因为它们直接关系到你对目录结构知识的“可迁移性”。
5.1 CentOS/RHEL 系与 Ubuntu/Debian 系的主要目录差异
不同发行版对 FHS 的执行力度有差异,最典型的就是前面提到的 usr merge。RHEL/CentOS 7 之后、Ubuntu 较新的发行版,基本都把/bin、/sbin、/lib做成了指向/usr对应目录的符号链接。老的 Ubuntu 14.04 时代还不是这样,如果你在/bin找不到某个命令,先在/usr/bin搜一下,这个经验放到新旧系统上都适用。
另一个差异在软件包管理产生的目录,比如 Ubuntu 的/var/lib/dpkg,RHEL 系对应的是/var/lib/rpm,本质上都落在/var/lib这个“包管理状态数据库”的范畴里。遇到新系统时,只要你能识别出“这是/var/lib下的某种状态目录”,就不会因为具体子目录不同而迷路。
还有/opt的用法,FHS 推荐第三方闭源软件装到/opt下的独立子目录,比如/opt/oracle,避免污染系统目录;但开源软件习惯装进/usr体系。理解了这层约定,你在部署一些“解压即用”的软件包时,就知道该放到哪个位置最规范。
5.2 容器镜像里的目录结构:FHS 的“降级”与根文件系统设计
容器镜像通常也是基于标准的发行版目录骨架构建的,但有一个显著区别:容器里的根文件系统默认是只读层 + 可写层的叠加。镜像层里/usr、/etc这些目录来自基础镜像,而/var或/tmp在容器运行期产生的写入,可能只存在于该容器的可写层,容器删除后这些数据就没了。
正因为容器是“短生命周期”的,运维习惯跟传统机器完全不同:状态数据要么挂卷(volume)映射到宿主机,要么放到外部存储。你看到某个容器里的/var/lib/mysql,如果不挂卷,容器一删数据就蒸发,这本质上是在 FHS 的目录语义之上叠加了容器存储的生命周期语义。
这也解释了为什么容器时代目录结构知识依然重要:你排障时仍然需要顺着/var/log、/etc/nginx、/proc这些路径去判断问题,只是对“哪些数据要持久化到宿主机”多了层判断。理解了传统目录结构的“归属逻辑”,理解容器存储设计就是水到渠成的事。
5.3 从一个目录结构出发,建立你自己的系统地图
最后我想说一个观点:目录结构本质上是最好的系统地图。当你面对一台陌生的 Linux 机器时,先花五分钟大致浏览根目录下的二级目录,你就能快速推断出这台机器装了哪些软件、采用什么样的数据布局、日志和配置放在哪里。这种“地图感”是新老运维差距最直观的体现之一。
我在接手某台历史悠久的服务器时,习惯先跑一下ls /、ls /opt、ls /data(如果存在的话),配合mount看挂载点,脑子里立马就能浮现出这台机器的画像:根分区多大、哪些目录独立挂载、有没有人往根目录塞过自定义文件夹。这种画像能力只能靠对目录设计和默认约定的熟悉程度获得,任何监控工具都替代不了。
6. 目录结构实用自查清单与个人实践经验
技术内容讲得差不多了,这一节聊点更贴近日常操作的干货。我把这些年在目录结构应用上总结的自查清单和使用经验直接列出来,你可以存下来当工作手册用。
6.1 日常目录使用自查:三问法防止路径混乱
在写部署文档、写存储方案、写清理脚本时,建议每次都问自己三个问题:
- 这个文件是静态的还是动态的?静态的放
/usr、/opt体系,动态的放/var。这是目录选择的第一个分流依据。 - 这个文件是系统自己生成的还是用户产生的?系统运行状态放
/run、/var/lib,用户真实数据放/home、/srv。 - 这个文件重启后是否还需要?临时到极端的数据放
/tmp,重启后还要留一段时间的放/var/tmp,需要长期保留的绝不能只丢在/tmp。
这三问看着简单,但我在 code review 和脚本评审里抓出过不少把持久数据写到/tmp、把日志和代码混在一个目录下的案例。目录结构是系统设计的子结构,它混乱的根源通常是当初没人用这三问做过校验。
6.2 针对不同分区场景的挂载实践
根据我自己的实践,不同“角色”的机器,分区方案应有差异化倾向,不能一套配置通吃:
| 机器角色 | 核心考虑 | 推荐挂载策略 |
|---|---|---|
| Web 前端 | 日志增长快、代码更新频繁 | /var独立且空间给足,/usr跟随根分区,/tmp加 noexec |
| 数据库 | 数据文件持久性要求高 | /var/lib(具体看数据库)独立挂载到高性能盘,/home可以很小 |
| 大数据/对象存储 | 海量小文件与应用数据 | 独立数据盘挂在应用路径下,例如/data,同时注意 inode 用量 |
| 容器宿主机 | 镜像与容器层占用大 | /var/lib/docker(或容器存储目录)独立挂载,日志轮转要提前配好 |
这个表格只提供一个思路,具体还是要结合你所在团队的规范。但方向是确定的:每个分区都要有明确的“用途归属”,而不是稀里糊涂把所有东西塞进根分区。
6.3 看完目录结构对工作流的直接影响:备份与迁移策略
最后一点经验是关于备份迁移策略的。一旦你掌握了目录结构,其实就掌握了备份的第一性原理——你不需要备份整个系统,只需要备份“不可再生但必须保留的数据”。照这个思路:
- 系统配置文件:备份整个
/etc,这几乎是重建一台机器的“基因”。 - 用户数据:备份
/home(或远端挂载点对应的用户主目录)。 - 服务数据:按服务备份
/var/lib下对应子目录或/srv下的数据目录。 - 数据库数据:如果数据库装在默认路径,备份它对应的
/var/lib子目录,或直接使用数据库自带的 dump 工具;强烈不建议只靠文件拷贝备份正在运行的数据库文件。
我见过不少团队备份方案就是“把整块云盘做快照”,这当然是可行且有效的,但在追求精细恢复、按目录恢复、跨机器迁移时,目录级的备份策略有着不可替代的灵活度。目录结构给管理员带来的最大优势,就是让你能把系统拆成一个个逻辑上独立、边界清晰的“积木块”,备份、迁移、恢复都变得像拼图一样可控。
这套知识平时看起来不像框架、不像中间件那么有“技术感”,但它是所有排障、部署、安全加固的底层地基。我自己每次被某个目录问题折腾一遍后,再回头看都会发现,所有坑几乎都能绕回同一个结论:我当初要是对这个目录的“定位”再多想一步,这个事故大概率可以避免。希望这篇内容能帮你在心里也建立起这样一张 Linux 目录地图。