How to make a very, very, small docker image for dynamic programs.
I digging in the docker, and I found docker image containers too bigger than they need to be.
If you want a really small and functional docker image, look at "alpine", it uses actually 5.242MB, and has a bash and package manager. I really appreciate this image.
Anyway, I'm just too curious to stay on others job, I need to do it from myself, understand, and then I can accept that others has a better solution and just use it.
In this way, I did started a research to make my own basic functional image.
If you just want a basic structure to use docker with low space usage, use "docker run -ti alpine sh" and be happy. if you, as me, feel the need to learn more, keep reading.
The docker documentation show us how to create a image from scratch. http://docs.docker.com/articles/baseimages/#creating-a-simple-base-image-using-scratch
It's just
FROM scratch COPY true-asm /true CMD ["/true"]
If you have a statically linked program, it's all you need to know. Just copy your program and set it in CMD. A nice sample is here in this article/tutorial to create a "go" web server: http://blog.xebia.com/2014/07/04/create-the-smallest-possible-docker-container/
But what about a really clean docker image for dynamic linked programs? what about run bash in a smaller possible docker? Well, for it we need to learn/remember some concepts.
To avoid keep this post to big, you can learn this concepts here: http://www.ibm.com/developerworks/library/l-lpic1-v3-102-3/l-lpic1-v3-102-3-pdf.pdf
In theory, we just need to detect all the dependencies using "ld.so --list" and copy it to our scratch docker.
Let's do a docker image to run /bin/bash:
create a clean directory called docker_bash and enter it.
mkdir docker_bash; cd docker_bash;
Let's create a Dockerfile to build our image, and see how much space we use:
echo "FROM scratch" > Dockerfile docker build -t docker_bash .
Sending build context to Docker daemon 2.048 kB Sending build context to Docker daemon Step 0 : FROM scratch ---> Step 1 : CMD /bin/bash ---> Running in 61af7327eb2e ---> aeb4860259ea Removing intermediate container 61af7327eb2e Successfully built aeb4860259eaNow we can see that we have a empty image result zero bytes:
$ docker images |grep docker_bash docker_bash latest aeb4860259ea 8 seconds ago 0 B
My intent is to run a dynamic linked program (bash), so what my dependencies ? the ld.so can say it. To show what is your ld.so, just use:
$ ls /lib/ld-* -l -rwxr-xr-x 1 root root 163500 Abr 10 2013 /lib/ld-2.17.so lrwxrwxrwx 1 root root 10 Abr 8 2013 /lib/ld-linux-x86-64.so.2 -> ld-2.17.so
$ /lib/ld-2.17.so --list /bin/bash linux-vdso.so.1 (0x00007ffcb03ba000) libreadline.so.6 => /lib64/libreadline.so.6 (0x00007fbee994c000) libhistory.so.6 => /lib64/libhistory.so.6 (0x00007fbee9744000) libncursesw.so.5 => /lib64/libncursesw.so.5 (0x00007fbee94e1000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fbee92dd000) libc.so.6 => /lib64/libc.so.6 (0x00007fbee8f30000) /lib64/ld-linux-x86-64.so.2 => /lib/ld-2.17.so (0x00007fbee9ba8000)
Here you are, all your dependencies. You can do the same in each lib listed to ensure it has no others dependencies. I'm not sure "ld.so --list" show recursive dependencies, I don't think so. But for bash is just these.
The COPY command in Dockerfile just copy from current directory and below, so we need to copy those deps to current directory structure:
$ mkdir {lib{,64},bin} $ cp /lib64/ld-2.17.so lib64/ $ ln -sv /lib64/ld-2.17.so lib64/ld-linux-x86-64.so.2 “lib64/ld-linux-x86-64.so.2” -> “/lib64/ld-2.17.so” $ cp /lib64/libc.so.6 lib64/ $ cp /lib64/libdl.so.2 lib64/ $ cp /lib64/libncursesw.so.5 lib64/ $ cp /lib64/libhistory.so.6 lib64/ $ cp /lib64/libreadline.so.6 lib64/ $ cp /bin/bash bin/
Note the symlink was created to our root, but in our image it's correct.
We can use chroot to test our structure:
$ sudo chroot $PWD/ /lib64/ld-linux-x86-64.so.2 Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...] You have invoked `ld.so', the helper program for shared library executables. This program usually lives in the file `/lib/ld.so', and special directives in executable files using ELF shared libraries tell the system's program loader to load the helper program from this file. This helper program loads the shared libraries needed by the program executable, prepares the program to run, and runs it. You may invoke this helper program directly from the command line to load and run an ELF executable file; this is like executing that file itself, but always uses this helper program from the file you specified, instead of the helper program file specified in the executable file you run. This is mostly of use for maintainers to test new versions of this helper program; chances are you did not intend to run this program. --list list all dependencies and how they are resolved --verify verify that given object really is a dynamically linked object we can handle --inhibit-cache Do not use /etc/ld.so.cache --library-path PATH use given PATH instead of content of the environment variable LD_LIBRARY_PATH --inhibit-rpath LIST ignore RUNPATH and RPATH information in object names in LIST --audit LIST use objects named in LIST as auditors
ld.so is static and worked in chroot. What about bash ?
$ sudo chroot $PWD/ /bin/bash I have no name!:/$
You can play with bash bultin commands. When you ready to continue, just exit.
Time to build our image and see how it works.
$ cat >Dockerfile<<EOF FROM scratch COPY . / CMD /bin/bash EOF $ docker build -t "docker_bash" . Sending build context to Docker daemon 3.808 MB Sending build context to Docker daemon Step 0 : FROM scratch ---> Step 1 : COPY . / ---> 8539cef0990d Removing intermediate container 937d46fb9fa5 Step 2 : CMD /bin/bash ---> Running in a95d91c15f87 ---> 259060ab8e92 Removing intermediate container a95d91c15f87 Successfully built 259060ab8e92 $ docker images |grep docker_bash docker_bash latest 259060ab8e92 About a minute ago 3.799 MB
Our image has about 4MB. runnable ?
$ docker run -ti docker_bash exec: "/bin/sh": stat /bin/sh: no such file or directory FATA[0000] Error response from daemon: Cannot start container 8f3ced2cc7fd7c68b9afbf8f884f89ded8342ef58e576baccdc2be72782d5011: [8] System error: exec: "/bin/sh": stat /bin/sh: no such file or directory
the ENTRYPOINT is defalt to /bin/sh (which we don't have) so let's replace it:
$ docker run -ti --entrypoint /bin/bash docker_bash bash-4.2#
To be more complete, let's replace the ENTRYPOINT on Dockerfile to use ld.so, my final Dockerfile is:
$ cat Dockerfile FROM scratch COPY . / ENTRYPOINT [ "/lib64/ld-2.17.so" ] CMD [ "/bin/bash" ]this way you can build and run it:
$ docker build -t "docker_bash" . Sending build context to Docker daemon 3.808 MB Sending build context to Docker daemon Step 0 : FROM scratch ---> Step 1 : COPY . / ---> 7f4d4645dacc Removing intermediate container d44cea18c5e1 Step 2 : ENTRYPOINT /lib64/ld-2.17.so ---> Running in fd0b71a445e9 ---> d8655fc72a5b Removing intermediate container fd0b71a445e9 Step 3 : CMD /bin/bash ---> Running in 7bda8673eb93 ---> 057518a04f80 Removing intermediate container 7bda8673eb93 Successfully built 057518a04f80 $ docker run -ti docker_bash bash-4.2#
Note: The ENTRYPOINT and CMD commands in Dockerfile accepts both syntax "ENTRYPOINT /lib64/ld-2.17.so" and "ENTRYPOINT [ "/lib64/ld-2.17.so" ]" in Dockerfile but in the first the command are prepended with "/bin/sh -c". Ref: http://kimh.github.io/blog/en/docker/gotchas-in-writing-dockerfile-en/
But I just did it!I hope this post can be useful for anyone. (let me know).
Comentários