I have a Rust binary I want to put in a docker image. To minimize the image size,
I used http://github.com/larsks/dockerize/ which adds libc and a few nss library files. However it's failing to resolve DNS and I want to know what's the hidden system file dependency of reqwest
or hyper
.
Here is an example:
Cargo.toml
[package]
name = "example"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
reqwest = { version = "0.11.4", default-features = false, features = ["json", "rustls-tls"] }
tokio = { version = "1.8.1", features = [ "full" ] }
src/main.rs
#[tokio::main]
async fn main() {
println!("{:?}", reqwest::get("https://bing.com").await);
}
Building the image with certs and strace
:
❯ dockerize --tag test --add-file /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt --add-file /usr/bin/strace /usr/bin/strace --add-file $(pwd)/target/debug/example /app/example
Successfully tagged test:latest
The resulting docker image:
│ Current Layer Contents ├───────────────────────────────────────────────────────────────────────────────────────────
Permission UID:GID Size Filetree
-rw-r--r-- 0:0 25 B ├── Dockerfile
drwxr-xr-x 0:0 60 MB ├── app
-rwxr-xr-x 0:0 60 MB │ └── example
drwxr-xr-x 0:0 203 kB ├── etc
-rw-r--r-- 0:0 126 B │ ├── group
-rw-r--r-- 0:0 76 B │ ├── nsswitch.conf
-rw-r--r-- 0:0 513 B │ ├── passwd
drwxr-xr-x 0:0 202 kB │ └── ssl
drwxr-xr-x 0:0 202 kB │ └── certs
-r--r--r-- 0:0 202 kB │ └── ca-certificates.crt
drwxr-xr-x 0:0 343 kB ├── lib64
-rwxr-xr-x 0:0 222 kB │ ├── ld-linux-x86-64.so.2
-rwxr-xr-x 0:0 40 kB │ ├── libnss_compat.so.2
-rwxr-xr-x 0:0 31 kB │ ├── libnss_dns.so.2
-rwxr-xr-x 0:0 51 kB │ └── libnss_files.so.2
drwxr-xr-x 0:0 4.2 MB └── usr
drwxr-xr-x 0:0 4.2 MB └── lib
-rwxr-xr-x 0:0 2.2 MB ├── libc.so.6
-rwxr-xr-x 0:0 23 kB ├── libdl.so.2
-rw-r--r-- 0:0 476 kB ├── libgcc_s.so.1
-rwxr-xr-x 0:0 1.3 MB ├── libm.so.6
-rwxr-xr-x 0:0 40 kB ├── libnss_compat.so.2
-rwxr-xr-x 0:0 31 kB ├── libnss_dns.so.2
-rwxr-xr-x 0:0 51 kB ├── libnss_files.so.2
-rwxr-xr-x 0:0 154 kB └── libpthread.so.0
Running the binary fails with "Device or resource busy" msg:
❯ docker run test /app/example
Err(reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("bing.com")), port: None, path: "/", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("dns error", Os { code: 16, kind: ResourceBusy, message: "Device or resource busy" })) })
What's confusing is that ping
binary in the same environment just works. This means reqwest
or hyper
needs additional setting or file. I ran strace
to find out what they require but couldn't find it. I also tried the trust-dns
feature of reqwest
and it works for only some domains.
Here is strace of the binary:
❯ docker run test strace /app/example 2>&1 | rg open
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v3/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v2/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/self/cgroup", O_RDONLY|O_CLOEXEC) = 3
What should I add to the image so the reqwest
+hyper
with tls works?
An alternative solution is to configure Hyper not to use getaddrinfo
and keep the address resolution entirely in rust.
You can do this in Reqwest by setting the trust_dns
feature on the crate, which makes it use trust_dns_resolver instead of getaddrinfo
.