问题发现

最近在测试遇到一个问题,容器日志过大导致系统磁盘爆满,造成的影响就是该服务器的一些服务挂掉了。日志过大,解决办法就是删啊,间接定位到容器日志地位发现高达5G,三下五除二就rm了,然而依然显示磁盘空间已满,然而du -sh命令也显示文件夹下为空。通过共事百度得悉,容器日志不能间接rm,要通过cat /dev/null > {log文件}形式将日志删除。因为该文件被过程所援用,间接删除并不能擦除磁盘上的文件block信息,解决形式就是进行过程。明天就来复现一下并探索一下底层原理。

复现形式

因为容器也属于一种过程,援用文件的形式并没有不同于其余一般过程,所以就应用一个简略的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 delfilelsof: 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-170 testfile]$ df -h .文件系统        容量  已用  可用 已用% 挂载点/dev/vda1        50G   49G  2.0G   97% /

重做一次下面的步骤,这次咱们应用正确的形式开释磁盘的空间,能够看到磁盘空间腾出来了。

[centos@guozhao-170 testfile]$ cat /dev/null > /tmp/testfile/delfiletest [centos@guozhao-170 testfile]$ [centos@guozhao-170 testfile]$ df -h .文件系统        容量  已用  可用 已用% 挂载点/dev/vda1        50G   49G  2.0G   97% /

起因探索

持续深挖一下造成这种后果的起因。

在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数量减一。

当然,因为文件并没有被真正删除,所以该文件是能够复原的,只须要找到过程的pid,并进入/proc/{pid}/fd中,找到对应的文件描述符,执行cp命令复制即可找回文件。