Gitlab CI/CD 实践三:Docker 安装 Gitlab Runner

2024-08-18 16:20:58 浏览数 (2)

问题现象

当前有个目录 a,需要对其创建一个软链接 b。

代码语言:javascript复制
   tree
   .
   `-- a
       |-- a1.txt
       `-- a2.txt

   1 directory, 2 files

第一次执行ln -s a b

成功执行,符合预期

代码语言:javascript复制
tree
.
|-- a
|   |-- a1.txt
|   `-- a2.txt
`-- b -> a

2 directories, 2 files

通过软链能正确访问到 a1.txt

代码语言:javascript复制
ls -l b/a1.txt
-rw-r--r-- 1 root root 0 Aug 17 02:08 b/a1.txt

第二次执行ln -s a b

成功执行,不符合预期

代码语言:javascript复制
tree
.
|-- a
|   |-- a -> a
|   |-- a1.txt
|   `-- a2.txt
`-- b -> a

2 directories, 3 files

此时软链接 b 已存在,我的预期是执行失败,或者覆盖软链接 b,但实际上在 a 下创建了一个软链接 a,这是第一个问题

第二个问题,为什么新创建的软链接文件名是 a,而不是 b

第三个问题,通过新创建的软链,无法访问到 a1.txt

代码语言:javascript复制
ls -l a/a/a1.txt
ls: cannot access 'a/a/a1.txt': Too many levels of symbolic links

第三次执行ln -s a b

执行失败,提示软链接 b/a 已存在。

代码语言:javascript复制
ln -s a b
ln: failed to create symbolic link 'b/a': File exists

第四个问题,为什么第二次执行都没报错,第三次却报错了?到底能不能重复执行?

软链接原理

先介绍下文件系统。

磁盘布局

在 VSFS(Very SimpleFile System,简单文件系统)中

superblock

超级块里存储了文件系统信息,inode 数量,数据块数量,inode 区域的开始位置等信息。

inode bitmap

用 bitmap 来储存 inode 区域的分配情况。当写入一个新文件时,需要创建一个 inode,保存在 inode 空闲区域。如果直接扫描 inode 区域,寻找空闲位置,是非常耗费 IO 性能的。而通过 inode bitmap,只需要一次 IO,就能知道整个 inode 区域的分配情况。再在内存中遍历下,得到一个空闲的 inode 号。通过 inode 号,就能找到 inode 区域中对应位置。

data bitmap

和 inode bitmap 功能类似。

inode

inode 是index node(索引节点)的缩写,因为inode号用于索引磁盘上的inode数组,以便查找该inode号对应的inode。

inode 用于保存文件的元数据,例如文件类型(例如,常规文件、目录等)、大小、分配给它的块数、保护信息(如谁拥有该文件以及谁可以访问它)、一些时间信息(包括文件创建、修改或上次访问的时间文件下),以及有关其数据块驻留在磁盘上的位置的信息(如某种类型的指针)。

注意,inode 里没有文件名,在文件系统中,文件的路径是由目录文件构成的。每个目录在数据块区域是一个目录文件,包含这个目录下的文件名、文件的 inode 号、这条记录的长度、文件名字符串的长度等信息。所以通过硬链接或软链接,可以使用不同的路径访问到同一个文件。 延伸想想同一个文件系统下,mv 命令是如何工作的。

使用 df -i 命令查看 inode 的使用量

代码语言:javascript复制
df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
overlay        3907584 119123 3788461    4% /
tmpfs          1003656     17 1003639    1% /dev
shm            1003656      1 1003655    1% /dev/shm
/dev/vda1      3907584 119123 3788461    4% /etc/hosts
tmpfs          1003656      1 1003655    1% /sys/firmware

小文件太多,可能造成磁盘没满,但是 inode 用完了,导致无法创建文件。可通过上诉命令检查。

拿上面的目录举例

代码语言:javascript复制
tree
.
`-- a
    |-- a1.txt
    `-- a2.txt

1 directory, 2 files

下图左边部分在磁盘的 inode 区域,右边部分在磁盘的数据块区域。

执行软链命令后

ln -s a b

代码语言:javascript复制
tree
.
|-- a
|   |-- a1.txt
|   `-- a2.txt
`-- b -> a

2 directories, 2 files

创建软链接执行了下列动作

  1. 修改当前目录对应的目录文件,增加软链接 b 的信息
  2. 创建软链接 b 的 inode 注意,没有磁盘指针,软链接是不会在数据块区域分配空间。 另外指向目标路径是个相对路径,和创建软链接的方式有关。

软链接相对路径问题

准备 case

代码语言:javascript复制
echo `date` > c.txt
mkdir d
ln -s c.txt d/c.txt

查看目录结果

代码语言:javascript复制
tree
.
|-- c.txt
`-- d
    `-- c.txt -> c.txt

查看软链报错

代码语言:javascript复制
cat d/c.txt
cat: d/c.txt: Too many levels of symbolic links

使用 stat 查看软链信息

代码语言:javascript复制
stat d/c.txt
  File: d/c.txt -> c.txt
  Size: 5           Blocks: 0          IO Block: 4096   symbolic link
Device: 3bh/59d Inode: 533547      Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2024-08-17 08:27:32.335451007  0000
Modify: 2024-08-17 08:27:12.832303012  0000
Change: 2024-08-17 08:27:12.832303012  0000
 Birth: -

d/c.txt -> c.txt 这是一个相对路径,相对于软链本身,那么这个软链指向的是自己,所以报错:“Too many levels of symbolic links”。

ln 命令 help 信息里也有说明相对路径的情况。

代码语言:javascript复制
ln --help
Usage: ln [OPTION]... [-T] TARGET LINK_NAME
  or:  ln [OPTION]... TARGET
  or:  ln [OPTION]... TARGET... DIRECTORY
  or:  ln [OPTION]... -t DIRECTORY TARGET...
In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic.
By default, each destination (name of new link) should not already exist.
When creating hard links, each TARGET must exist.  Symbolic links
can hold arbitrary text; if later resolved, a relative link is
interpreted in relation to its parent directory.

如何避免此问题?

  1. 使用绝对路径。
  2. 创建软链时,先进入到即将创建的软链的所在目录,然后被软链文件使用相对于当前目录的路径。

解释问题现象

第一个问题

为什么第二次执行ln -s a b时,没有覆盖软链接 b,而是在 a 下创建了一个软链接 a?

第一次创建软链后,生成了软链 b。在第二次创建软链时,会判断目标路径是否是存在的目录,此时目标路径是 b,它指向了 a,所以目标路径是存在的 a,那么就在 a 目录下创建软链。也就是上面ln --help信息里的第三种和第四种用法。

第二个问题

为什么新创建的软链接文件名是 a,而不是 b?

创建软链的目标路径是目录时,就在该目录下创建自身的同名软链,指向自己。

第三个问题

通过新创建的软链,无法访问到 a1.txt。

原因是上文提到的软链相对路径问题,检查 a 目录下的软链 a,看看它指向哪里。

代码语言:javascript复制
stat a/a
  File: a/a -> a
  Size: 1           Blocks: 0          IO Block: 4096   symbolic link
Device: 3bh/59d Inode: 533417      Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2024-08-17 03:18:11.949193005  0000
Modify: 2024-08-17 03:18:10.232193004  0000
Change: 2024-08-17 03:18:10.232193004  0000
 Birth: -

发现它指向的是自己,而不是预期的父目录,所以访问它时报错:“Too many levels of symbolic links”。

第四个问题

为什么第二次执行都没报错,第三次却报错了?到底能不能重复执行?

原因是软链目录是存在的目录时,就在该目录下创建自身的同名软链,指向自己。而第二次执行时已经创建了这个软链 a/a,再次创建时就会报错:“ File exists”。

解决

方案一

创建软链前先检查软链是否存在,存在的话检查是否是指向预期的源文件。

方案二

使用 ln 命令的选项 n。

-n, --no-dereference treat LINK_NAME as a normal file if it is a symbolic link to a directory

达到的效果是在第二次创建软链时,因为软链 b 已存在,报错。而不是在 b 软链指向的 a 目录下,再去创建一个软链指向自己。

代码语言:javascript复制
ln -sn a b
ln: failed to create symbolic link 'b': File exists

参考

  1. 《操作系统导论》
  2. https://linuxconfig.org/how-to-fix-too-many-levels-of-symbolic-links-error
  3. https://unix.stackexchange.com/questions/588021/why-does-ln-s-create-a-directory-if-soft-link-exists?rq=1

0 人点赞