背景

最近在折腾把 LNMP 统统部署到 Docker 上,为此写了一系列 Dockerfile 和 docker-compose.yml 文件。不过主要还是在写 Nginx 的 Dockerfile 上面下了更多功夫,因为不太中意官方安装模块的方式,更喜欢传统的 static module,这样就不用每添加一个模块时都要修改 Nginx 配置文件了。而编译模块会生成一些依赖库,这就要从上一个构建阶段赋值到最后一个阶段。

之前我是采用 Dockerfile 里一行一行地把依赖全都用 COPY 指令复制到最后一个构建阶段,就像下面这样:

COPY --from=builder /usr/lib/x86_64-linux-gnu/libjemalloc* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libbrotli* /usr/lib/x86_64-linux-gnu/

上面的是两个模块的依赖,分别是 jemalloc 和 brotli,但这样做在后续添加新模块的时候难免变得繁琐,于是想到了几条指令。

ldd 命令

ldd 命令用于打印程序或者库文件所依赖的共享库列表,说人话就是把某个二进制可执行文件依赖打印出来,比如 Nginx 的依赖如下:

root@d927bde6eeab:/# ldd /usr/local/nginx/sbin/nginx
        linux-vdso.so.1 (0x00007ffe40d88000)
        libjemalloc.so.2 => /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 (0x00007f7170129000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7170123000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7170101000)
        libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f71700c6000)
        libbrotlienc.so.1 => /usr/lib/x86_64-linux-gnu/libbrotlienc.so.1 (0x00007f7170035000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f716ffc2000)
        libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007f716ff2d000)
        libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007f716fc39000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f716fc1c000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f716fa48000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f716f904000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f716f737000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f716f71b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f717054e000)
        libbrotlicommon.so.1 => /usr/lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007f716f6f8000)

得到了依赖列表,但这没法让 cp 命令识别,我们需要用到其他命令对这一个列表进行简化。

cut 命令

在上一步,我们已经获取了 Nginx 二进制可执行文件的依赖,下边使用 cut 命令进行修剪:

ldd /usr/local/nginx/sbin/nginx | \
grep '=>' | \
cut -d '>' -f2 | \
cut -d '(' -f1 | \
cut -d ' ' -f2

一行行来看:

第一行使用 ldd 命令来打印依赖列表

第二行 grep '=>' 是选择所有包含 => 的行

第三第四行使用 cut 命令保留 > 和 ( 两个字符之间的内容
    例如“ /usr/lib/x86_64-linux-gnu/libjemalloc.so.2”
    注意开头有一个空格

第五行使用 cut 命令删除所有行前面的空格

到了这一步,一开始的 Nginx 依赖列表将会变成这样:

root@d927bde6eeab:/# ldd /usr/local/nginx/sbin/nginx | grep '=>' | cut -d '>' -f2 | cut -d '(' -f1 | cut -d ' ' -f2
/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
/lib/x86_64-linux-gnu/libdl.so.2
/lib/x86_64-linux-gnu/libpthread.so.0
/lib/x86_64-linux-gnu/libcrypt.so.1
/usr/lib/x86_64-linux-gnu/libbrotlienc.so.1
/lib/x86_64-linux-gnu/libpcre.so.3
/usr/lib/x86_64-linux-gnu/libssl.so.1.1
/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
/lib/x86_64-linux-gnu/libz.so.1
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/libm.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6
/lib/x86_64-linux-gnu/libgcc_s.so.1
/usr/lib/x86_64-linux-gnu/libbrotlicommon.so.1

一般来说 /lib/x86_64-linux-gnu/ 中的依赖都是系统所需的核心依赖,由用户安装的程序依赖通常会存放在 /usr/lib/x86_64-linux-gnu/ 目录,所以需要排除掉不需要的依赖。

sed 命令

sed 是一个根据特定规则对文本进行修改的强大命令,下面继续处理刚刚的输出结果:

ldd /usr/local/nginx/sbin/nginx | \
grep '=>' | \
cut -d '>' -f2 | \
cut -d '(' -f1 | \
cut -d ' ' -f2 | \
sed '/^\/lib\/x86_64-linux-gnu\//d'

此处需要注意需要使用 ^ 在每行开头进行匹配,否则会把 /usr/lib/x86_64-linux-gnu/ 目录的依赖也排除掉,那么经过这些操作之后,得到的列表如下:

root@d927bde6eeab:/# ldd /usr/local/nginx/sbin/nginx | grep '=>' | cut -d '>' -f2 | cut -d '(' -f1 | cut -d ' ' -f2 | sed '/^\/lib\/x86_64-linux-gnu\//d'
/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
/usr/lib/x86_64-linux-gnu/libbrotlienc.so.1
/usr/lib/x86_64-linux-gnu/libssl.so.1.1
/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
/usr/lib/x86_64-linux-gnu/libstdc++.so.6
/usr/lib/x86_64-linux-gnu/libbrotlicommon.so.1

复制依赖

上面我们已经提取出来整个所需依赖,我们把它存储到一个 lib.list 文件里,然后再通过 cp 命令复制到另一个文件夹,这样就能方便 Dockerfile 里 COPY 命令复制到最后一个阶段的构建中。

ldd /usr/local/nginx/sbin/nginx | grep '=>' | cut -d '>' -f2 | cut -d '(' -f1 | cut -d ' ' -f2 | sed '/^\/lib\/x86_64-linux-gnu\//d' \
> /lib.list

然后再配合 xargs 指令进行复制:

xargs -a /lib.list cp -t /lib-to-copy/

至此所有依赖已经复制到了 /lib-to-copy 文件夹内,大功告成!现在我们只需要一行 COPY 指令就能复制所有依赖了:

COPY --from=builder /lib-to-copy/* /usr/lib/x86_64-linux-gnu/

参考资料