无法使用Rust可执行文件运行Docker镜像

18

我试图使用我用Rust编写的二进制文件创建一个图像,但是我遇到了不同的错误。这是我的Dockerfile

FROM scratch
COPY binary /
COPY .env /
COPY cert.pem /etc/ssl/
ENV RUST_BACKTRACE 1
CMD /binary

建筑完成得很好,但当我尝试运行它时,我得到了这个:

$ docker run binary
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown.
ERRO[0000] error waiting for container: context canceled 

还有这个:

$ docker run binary /binary
standard_init_linux.go:195: exec user process caused "no such file or directory"

我不知道该怎么办。这个错误信息对我来说看起来非常奇怪。根据官方Docker文档,这应该是行得通的。

系统信息:最新的Arch Linux和Docker:

Docker version 18.02.0-ce, build fc4de447b5

我用C++程序进行了测试,使用clang和gcc都可以正常工作。

但是,它无法在scratchalpinebusybox或基于bash的镜像上工作,但使用postgresqlubuntudebian镜像可以正常运行。问题与Rust和轻量级Docker镜像有关-否则一切正常。


CP: https://github.com/docker-library/hello-world/issues/44 - Victor Polevoy
可能与 Rust 无关,请查看以下问题 https://github.com/moby/moby/issues/17896 - Oleg Sklyar
2个回答

18
如@Oleg Sklyar所指出的,问题在于Rust二进制文件是动态链接的。
这可能会有些令人困惑,因为许多听说过Rust的人也听说过Rust二进制文件是静态链接的,但这是指创建中的Rust代码:crate是静态链接的,因为它们在编译时都已知。这并不涉及程序可能链接到的现有C动态库,例如libc和其他必备库。通常情况下,这些库也可以构建为静态链接的工件(请参见本文末尾)。要检查您的程序或库是否动态链接,可以使用ldd实用程序:
$ ldd target/release/t
    linux-vdso.so.1 (0x00007ffe43797000)
    libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fa78482d000)
    librt.so.1 => /usr/lib/librt.so.1 (0x00007fa784625000)
    libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fa784407000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fa7841f0000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007fa783e39000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fa784ca2000)

你需要在Docker镜像中使用这些库。你还需要解释器;你可以使用objdump工具来获取其路径:
$ LANG=en objdump -s -j .interp target/release/t

target/release/t:     file format elf64-x86-64

Contents of section .interp:
 0270 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 0280 7838362d 36342e73 6f2e3200           x86-64.so.2.  

将文件复制到预期目录中,一切都能正常工作。
还有第二个选项是使用 rust-musl-builder Docker 镜像。虽然在使用 postgresqldiesel 时会出现一些问题,但对于大多数项目来说,这是一个很好的选择。它通过生成静态链接可执行文件来工作,您可以直接复制和使用。如果您想提供一个更小的 Docker 镜像并且不需要所有那些无用的额外数据(如解释器、未使用的库等),则此选项比使用解释器和动态库要好得多。

3
我不会只是从编译系统复制库(特别是解释器)到Docker镜像中。相反,应使用发行版的软件包管理器在Docker镜像中安装库,然后在容器内构建Rust程序,并自动链接到适当的库。您还可以选择使用MUSL构建静态二进制文件。 - Shepmaster
问题中的示例只是一个简单的hello-world应用程序,我的真实二进制文件有很多没有静态版本的动态库。解释器也是一个好问题 - 没有它我们无法运行任何东西。我想使用scratch基础镜像构建一个docker镜像。您建议我使用rust基础镜像,在那里编译任何内容,然后将二进制文件和其他所有内容从其中复制到scratch基础镜像中(双FROM Dockerfile部分)吗? - Victor Polevoy
2
像这样吗?https://docs.docker.com/develop/develop-images/multistage-build/#before-multi-stage-builds如果是这样,有什么区别呢?解释器有什么特别之处? - Victor Polevoy
2
是的,多阶段构建是解决这个问题的正确工具;您不希望在最终镜像中运行Rust编译器。我甚至从未听说过有人在机器之间复制解释器,所以这感觉非常可疑。Rust playground是一个静态二进制文件,因此您可以通过静态链接走得更远,但是您是正确的,在某些时候它会变得复杂。有像rust-musl-builder这样的镜像可以使它变得更容易。 - Shepmaster

0
在我的情况下,问题是我传递了一个无效的可执行文件名:
CMD ["liquidator"]

液化器是Docker镜像的名称,但我需要这个:

CMD ["hifi-liquidator"]

基本上,CMD 必须与 Cargo.toml 文件中的 "name" 字段相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接