只有两个月了,今年还有 6 篇博客没有完成!

水一个曾经困扰了我很久的问题——某天,我在构建基于 ubuntu 18.04 的 docker 镜像时,发现 apt install 变得特别特别特别特别特别慢,可能需要花几十分钟才能构建完成。

解决这个问题花费了我不少时间,因为相关搜索结果基本上被「配置 docker 代理」的文章占据了,可惜我很清楚这不是代理的问题。一通搜索后,我最终在一个 GitHub issue 上找到了罪魁祸首[^1]——nofile。

nofile 限制了一个进程能打开的文件描述符最大数量,这个值通常是 1023,数据库之类的重 IO 应用会建议你使用 ulimit -n 65535 甚至 ulimit -n unlimited 来设置一个超大值(测试结果为 $2^{19}$)。而在 systemd 下,你可以使用 LimitNOFILE=infinity 来获得一个更大值(测试结果为 $2^{30}$)。但是,事实证明,对某些程序来说,$2^{30}$ 这个值有点太极端了……

fakeroot 是一个构建软件包时的常用工具,它可以在普通用户下模拟 root 用户。然而低版本的 fakeroot 有这么一个行为,会把所有的文件描述符都遍历一遍 [^2]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  if(!foreground){
    /* literally copied from the linux klogd code, go
to background */
    if ((pid=fork()) == 0){
      int fl;
      int num_fds = getdtablesize();  
  
      fflush(stdout);  

      /* This is the child closing its file descriptors. */
      for (fl= 0; fl <= num_fds; ++fl)
#ifdef FAKEROOT_FAKENET
        if (fl != sd)
#endif /* FAKEROOT_FAKENET */
          close(fl);

显然,当 nofile 为 $2^{30}$ 时,遍历所有文件描述符会非常耗时,这也是为什么 apt install 会卡在安装步骤上……

解决方案

覆盖 containerd 和 docker 服务的 LimitNOFILE 配置,设置为一个够大,但是不至于太大的值。

1
2
systemctl edit containerd.service --drop-in=ulimit.conf
systemctl edit docker.service --drop-in=ulimit.conf
1
2
[Service]
LimitNOFILE=1048576

参考