删除文件为啥磁盘依然爆满
01
问题发现
最近在测试遇到一个问题,容器日志过大导致系统磁盘爆满,造成的影响就是该服务器的一些服务挂掉了。日志过大,解决方法就是删啊,直接定位到容器日志位置发现高达5G,三下五除二就rm了,但是仍然显示磁盘空间已满,但是du -sh命令也显示文件夹下为空。经过同事百度得知,容器日志不能直接rm,要通过cat /dev/null > {log文件}方式将日志删除。因为该文件被进程所引用,直接删除并不能擦除磁盘上的文件block信息,解决方式就是停止进程。今天就来复现一下并探究一下底层原理。
02
设计复现
由于容器也属于一种进程,引用文件的方式并没有不同于其他普通进程,所以就使用一个简单的demo复现。
首先进入一个目录,以/tmp/testfile目录为例,可以看到我的服务器还有2G的剩余空间
[centos@guozhao testfile]$ cd /tmp/testfile
[centos@guozhao testfile]$ df -h .
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda1 50G 49G 2.0G 97% /
然后生成一个随机文件,再来查看剩余空间,看到还剩余1014M空间
[centos@guozhao testfile]$ dd if=/dev/urandom of=/tmp/testfile/delfiletest bs=1M count=1000
记录了1000+0 的读入
记录了1000+0 的写出
1048576000字节(1.0 GB)已复制,8.31491 秒,126 MB/秒
[centos@guozhao testfile]$
[centos@guozhao testfile]$ df -h .
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda1 50G 49G 1014M 99% /
启动一个程序,引用该文件,不退出进程
func main() {
file, err := os.Open("/tmp/testfile/delfiletest")
defer file.Close()
if err != nil{
fmt.Println("open err :",err.Error())
return
}
time.Sleep(100*time.Minute)
}
接下来删除该文件文件,查看磁盘占用情况,看到虽然文件删除,但是磁盘空间并没有释放。
[centos@guozhao testfile]$ rm -rf delfiletest
[centos@guozhao testfile]$ df -h .
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda1 50G 49G 1015M 99% /
解决方法,找到引用该文件的进程,并停止该进程,可以Ctrl+C停止,也可以kill命令停止。
[centos@guozhao testfile]$ lsof | grep deleted|grep delfile
lsof: WARNING: can't stat() proc file system /run/docker/netns/default
Output information may be incomplete.
......
testdelet 29604 29608 centos 3r REG 253,1 1048576000 58925156 /tmp/testfile/delfiletest (deleted)
......
[centos@guozhao testfile]$ kill 29604
停止后再次查看磁盘空间,发现磁盘已经释放。
[centos@guozhao testfile]$ df -h .
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda1 50G 49G 2.0G 97% /
重做一次上面的步骤,这次我们使用正确的方式释放磁盘的空间,可以看到磁盘空间腾出来了。
[centos@guozhao testfile]$ cat /dev/null > /tmp/testfile/delfiletest
[centos@guozhao testfile]$
[centos@guozhao testfile]$ df -h .
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda1 50G 49G 2.0G 97% /
03
原理考究
继续深挖一下造成这种结果的原因。
在Linux上,每个文件都有一个自己对应的索引节点即inode,在这个inode里记录了文件在磁盘的块信息,以及链接数量等信息,一个文件在是否要被真正删除释放空间,取决于两个值,一个是 i_count ,代表引用计数;一个是 i_nlink ,代表硬链接数量,只有当两个都为0,文件才会真正释放。
struct inode{
atomic_t i_count;
unsignet int i_nlink;
......
};
当有进程使用该文件时候, i_count 就会加1,当进程不在引用或进程结束,就会减一。
硬链接也是如此,当为文件创建一个硬链接时, i_nlink 就会加一,删除就会减一,当减少为0时候,就会删除文件,释放空间。
在Linux中,硬链接指的是文件名与inode的链接,通常创建一个文件对应一个硬链接,我们可以手动通过ln命令或者程序触发link系统调用为一个文件创建一个硬链接,相当于两个文件名对应了同一个磁盘文件,两个都删除才会删除磁盘文件(没有进程引用的前提下)。而Linux的rm命令相当于执行了unlink系统调用,会使得i_nlink数量减1。
在上述示例中,由于文件并没有被真正删除,所以该文件是可以恢复的,只需要找到进程的pid,并进入 /proc/{pid}/fd 中,找到对应的文件描述符,执行 cp 命令复制即可找回文件。
推荐阅读