目录

从“Read-Only File System” 说到 Linux 文件系统结构

背景

某天深夜我们 k8s 生产环境集群发出了一条报警 “某一个节点 unavailable”。问题出现后立马查看节点 CPU, Memory, Disk 状态,发现 CPU 状态正常,但是 Memory 和 Disk 的信息收集不到了(crontab 定时执行的脚本上报),接着登录机器,没问题能登录进去(一般测试环境出现节点不可用,是由于负载长时间内太高打垮了机器,机器也会登录不了)。随便执行一个命令 tab 补全报错 “Cannot Create File: Read-Only File System”,显然文件系统变成了只读状态,写操作都会失败,意味着这个实例就是不可用的状态。 Cannot Create File: Read-Only File System 可能的原因是什么呢?
在回答这个问题之前我们先得了解一下 Linux 文件系统是怎样的一个构成。

Linux 文件系统结构

目前大多Linux发行版本默认使用的文件系统一般是ext4, 从名字我们也能看出肯定还有 ext3, ext2, ext。每一代都是上一代的改进版,从 ext2 到 ext4根本设计上是没有太大变化的。 首先我们都知道文件最终是保存在硬盘上的, 磁盘是由若干扇区组成,若干个扇区组成一个块(ext2默认是由8个扇区组成一个块,即4kb), 磁盘分成若干个分区,每个分区由若干个块组成。

  • Boot Block EXT2 的头 1024 Bytes 称为 Boot Block,引导块,预留给 VBR,即分区的引导扇区,不受 EXT2 文件系统的管理。

  • Block Group 其余的空间被划分为多个块,这些块被编成 块组,即 Block Groups(BG)。任何给定文件的数据通常都尽可能地包含在一个块组中,这样,在读取连续大文件时,会减少磁盘寻道次数。
    每个块组包含:

    • 超级块:1 个块
    • 块组描述符表:N 个块
    • 块位图:1 个块
    • Inode 位图:1 个块
    • Inode 表:N 个块
    • 数据块:N 个块
  • 超级块
    超级块 用于记录文件系统的 整体 信息:

    • 块与 Inode 的总数
    • 块与 Inode 已用、可用数量
    • 块与 Inode 的大小
    • 文件系统的挂载时间、最近写入数据的时间、最近校验的时间
    • 文件系统当前挂载状态:称 Valid Bit,0 表示已挂载,1 表示未挂载
      超级块 保存在整个系统第一个 BG 中,后续的每个块组都会在开头保存超级块的复本,以备不时之需。 通常在系统启动中使用的是文件系统中第一个超级块。
  • 块组描述符表 Group Descriptor Table
    这张表包含了 整个系统所有块组的信息,由一个接一个的块组描述符组成。每个块组都包含这张表的复本,紧跟超级块,作用也一样,用于修复系统。
    正常工作时 EXT2 只使用第一个复本,即块组0 中的。
    块组描述符包含:
    该块组的块位图、Inode 位图、Inode 表的位置,可用块数量,可用 Inode 数量,已用目录数量。

  • 块位图 Block Bitmap
    该块组中块的分配位图,每一位代表一个块的当前状态,1 为已用,0 为可用。据此位图来分配或回收块。

  • Inode 位图
    该块组中 Inode 的分配位图,工作方式同块位图。据此位图来分配或回收 Inode。

  • Inode 表
    Inode 表是当前块组中所有 Inode 的集合,用于追踪块组中所有文件的元数据。
    该表会被操作系统频繁地访问,因此应尽可能减少读取该表所用的时间。

ext-fs

Inode 是一个非常重要的概念
inode 就是index-node(索引节点)的简写。 inode 是位于磁盘上的一个 256 字节的块,用于存储和该 inode 对应的文件的相关数据。这些数据包含了文件的大小、文件的所有者和所属组的用户 ID、文件模式(即访问权限)以及三个时间戳用于指定:该文件最后的访问时间、该文件的最后修改时间和该 inode 中的数据的最后修改时间。 同时,这个 inode 还包含了位置数据,指向了其所对应的文件数据在硬盘中的位置。当查看某个文件时,会先从 inode table 中查出文件属性及数据存放点,再从数据块中读取数据。

inode-structure

解决问题

有了以上对文件系统的了解以后,我们再来分析出现的问题:文件系统变成只读状态
磁盘出现“read only file system”的原因有很多种,可能是文件系统数据块出现不一致导致的,也有可能是磁盘故障造成的。主流的ext3、ext4文件系统都有很强的自我修复机制,对于简单的错误,文件系统一般可自行修复,当遇到致命错误无法修复时,文件系统为了保证数据一致性和安全,会暂时屏蔽文件系统的写操作,将文件系统变为只读,进而出现了上面的“read only file system”现象。 那么关键就在文件系统的一致性,因为每时每刻系统都在创建、修改和删除文件。每次修改文件时,操作系统都会执行一系列文件系统更新。如果这些更新被可靠地写入磁盘,便会产生一致的文件系统。
用户程序执行更改文件系统的操作(例如写入操作)时,会先将要写入的数据复制到内核中的核心缓冲区。通常,以异步方式处理磁盘更新。尽管在写入系统调用返回很长时间之后才会写入数据,但是允许用户进程继续执行。这样,在任何给定时间,由于文件系统驻留在磁盘上,因此它将滞后于核心信息所表示的文件系统状态。
当缓冲区需要用于其他用途时,或者内核自动运行 fsflush 守护进程时,将更新磁盘信息以反映核心信息。 如果在未写出核心信息的情况下停止系统,则磁盘上的文件系统可能会处于不一致状态。

  • 确认一下 EBS volume 的状态
1
2
$ mount -l | grep nvme
/dev/nvme0n1p1 on / type ext4 (ro,relatime,data=ordered) [cloudimg-rootfs]

我们看到文件系统状态确实是 ro, 正常应该是 rw

  • 修复一致性错误
    FSCK( File System Consistency Check) 是一个很重要的 Linux/Unix 工具,它用于检测并修复文件系统中的错误
    它可以进行三种模式的操作,
    • 查错并在发现错误时由用户决定如何处理,
    • 查错并自动修复,
    • 查错但在发现错误时只显示错误而不进行修复 fsck 支持的选项有,
      -p 自动修复(不询问)
      -n 不对文件系统做出改动
      -y 对所有问题都回答 “yes”
      -c 检查所有的坏块并将之添加到坏块列表中
      -f 即使文件系统标记为 clean 也强制进行检查
      -v 输出详细信息
      -b superblock 使用替代的超级块
      -B blocksize 指定超级块的块大小
      -j external_journal 指定外部日志的位置
      -l bad_blocks_file 添加到指定的坏块列表(文件)
      -L bad_blocks_file 指定坏块列表(文件)
      我们文件系统类型是 ext4,使用 fsck.ext4 fsck.ext4 -y /dev/nvme0n1p1
      修复完成,重启
      sync; reboot (sync 是用来强制将内存中的文件缓冲写入磁盘,更新块信息) 如果不是硬件问题,绝大部分文件系统的一致性错误都能被 fsck 检查出来并修复。 我们使用了 fsck 自动修复后,文件系统恢复成正常状态,实例得以恢复。