Wednesday, September 13, 2017

DOCKER ENTRYPIONT VS CMD




This is a good article http://www.johnzaccone.io/entrypoint-vs-cmd-back-to-basics/ explaining the difference between ENTRYPOINT and CMD. I am going to design some tests to illustrate the differences further more. The things to remember are:

1)      In the “docker run” command, things after the image name replaces CMD
2)      ENTRYPOINT has two forms:
ENTRYPOINT [“executable”, “param1”,”param2”] (exec form, preferred)
ENTRYPOINT command param1 param2 (shell form)

With shell form, the process with PID 1 is the shell. When you send a killing signal to a container, PID 1 process receives the signal. If PID 1 is shell, you need to take care of handling killing signals.

My docker version is:
ubuntu@exp1:~$ docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   3a232c8
 Built:        Tue Feb 28 08:01:32 2017
 OS/Arch:      linux/amd64

Dockerfile_1

FROM openjdk:8
ENTRYPOINT ["java"]
CMD ["-version"]

Build and run Dockerfile_1
sudo docker build -t test1 -f Dockerfile_1 .

sudo docker run -it --name test1 --rm test1
This runs “java version”

sudo docker run -it --name test1  --rm test1 aaa
(--rm deletes the container after it has finished running)
This runs “java aaa”. Things behind the image name replaces CMD in the Dockerfile. Since there is no class aaa, this will throw out an error: Error: Could not find or load main class aaa

sudo docker run -it --name test1  --rm --entrypoint "echo" test1 aaa
This runs “echo aaa”.  --entrypoint runtime argument replace ENTRYPOINT in the Dockerfile, note the arguments to --entrypoint is placed at the end.

Dockerfile_2

FROM openjdk:8
ENTRYPOINT ["java aaa"]
CMD ["-version"]

Build and run Dockerfile_2
sudo docker build -t test2 -f Dockerfile_2 .

sudo docker run -it --name test2  --rm test2
This runs into an error: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "exec: \"java aaa\": executable file not found in $PATH". This is because “java aaa” is treated as one executable.

Dockerfile_3

FROM openjdk:8
ENTRYPOINT ["/bin/sh", "-c", "echo apple && java"]
CMD ["-version"]

Build and run Dockerfile_3
sudo docker run -it --name test3  --rm test3
This runs "echo apple && java" not "echo apple && java -version", it is impossible to append additional arguments to “/bin/sh -c”.

Dockerfile_4

FROM openjdk:8
CMD ["java", "-version"]

Build and run Dockerfile_4
sudo docker build -t test4 -f Dockerfile_4 .

sudo docker run -it --name test4 --rm test4
This run "java -version".

sudo docker run -it --name test4 --rm test4  aaa
This run “aaa”, and throws an error: docker: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "exec: \"aaa\": executable file not found in $PATH".
This is because things after the image name replaces CMD, so 'java -version' is not run, "aaa" is run instead.

sudo docker run -it --name test4  --rm test4 /bin/sh
"/bin/sh" replaces CMD, which enables you to get inside the container.

 

Dockerfile_5

FROM openjdk:8
ENTRYPOINT ["java"]

Build and run Dockerfile_5
sudo docker build -t test5 -f Dockerfile_5 .

sudo docker run --rm --name test5 test5 aaa
This runs “java aaa”. Since there is no class aaa, this will throw out an error: Error: Could not find or load main class aaa.
This behavior is exactly the same as Dockerfile_1, even though there is no CMD defined in the Dockefile.

Dockerfile_6

FROM openjdk:8
RUN touch /tmp.txt
ENTRYPOINT tail -f /tmp.txt

Build and run Dockerfile_6
sudo docker build -t test6 -f Dockerfile_6 .

sudo docker run  -d --name test6 test6
(-d runs container in the background)

$ sudo docker exec test6 ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.8  0.0   4288   724 ?        Ss   09:34   0:00 /bin/sh -c tail -f /tmp.txt
root         6  0.0  0.0   5980   756 ?        S    09:34   0:00 tail -f /tmp.txt

docker exec needs the container to be alive, “tail -f” makes sure the container is alive.
Note the PID 1 process is shell.
$ sudo docker inspect test6
"Path": "/bin/sh",
"Args": [
           "-c",
           "tail -f /tmp.txt"
 ]

Dockerfile_7

FROM openjdk:8
RUN touch /tmp.txt
ENTRYPOINT ["tail", "-f", "/tmp.txt"]

Note, you can’t write ENTRYPOINT ["tail", "-f /tmp.txt"], upon running the image, you will receive an error: tail: invalid option -- ' '

Build and run Dockerfile_7
sudo docker build -t test7 -f Dockerfile_7 .

sudo docker run  -d --name test7 test7
$ sudo docker exec test7 ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  1.2  0.0   5980   708 ?        Ss   09:43   0:00 tail -f /tmp.txt

Note the PID 1 process is tail.



In my tests, running both test6 and test7 in the foreground (without -d), ctrl+c to kill them doesn’t work. I had expected to see it work on test7, because in test7, PID 1 is tail, but it didn’t. This may have something to do with https://www.weave.works/blog/my-container-wont-stop-on-ctrl-c-and-other-minor-tragedies/.

Saturday, August 12, 2017

Practices for building a docker image

The first thing docker build does is to send build context to Docker daemon, if the build context is big, it can take a long time – so long that you doubt if it hangs. This blog shares some practices I am doing to speed up building a docker image.

Speed up Vagrant

if you are hosting vagrant VMs on Windows, accessing sync folder will be slow. There is a plugin https://github.com/winnfsd/vagrant-winnfsd that adds NSF to Windows, unfortunately, it doesn’t work for me: my McAfee blocks it.

There are some configurations that can speed up Vagrant:

config.vm.provider "virtualbox" do |v|
               v.memory =2048
               v.cpus = "2"
                
               # change the network card hardware for better performance
               v.customize ["modifyvm", :id, "--nictype1", "virtio" ]
               v.customize ["modifyvm", :id, "--nictype2", "virtio" ]

               # suggested fix for slow network performance
               # see https://github.com/mitchellh/vagrant/issues/1807
               v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
               v.customize ["modifyvm", :id, "--natdnsproxy1", "on"] 
end 
   

.dockerignore

Add .dockerignore in the build context, files defined in the file will be ignored by docker when sending build context to the docker daemon.

echo ./server/log > .dockerignore
echo ./server/tmp >> .dockerignore

Change files in a running container

After you spend a lot of time building the docker image, you may find that it doesn’t work out as you expected, perhaps some configuration files are not setup correctly, you change those files, and then you need to rebuild the image again, which is unbearable waste of time.

My approach is:
1)      Add a volume to the image, which will be used to hold files to be copied to the container
VOLUME ["/942-docker-volume"]
Build the image

2)      Run a container, in the run command, mount the volume, and switch the entrypoint command. In the replaced entrypoint command, copy files from the mounted volume to replace files in the container. 

sudo docker run -it --name pm942 -p 8080:8080  -v /942-docker-volume/home:/942-docker-volume/home --entrypoint /bin/sh pm942 -c "cp /942-docker-volume/home/*.conf /pm942/ && cp /942-docker-volume/home/bin/*.sh /pm942/bin/ && cd ./bin && ./Start.sh"

Notice the --entrypoint parameter, whose value is “/bin/sh”. Also notice the argument to “/bin/sh” is placed at the end, after the image name pm942. This --entrypoint command copies files from the mounted volume and replace files accordingly inside the container.

Or you can ignore the argument, and thus log into the container to manipulate it:

sudo docker run -it --name pm942 -p 8080:8080  -v /942-docker-volume/home:/942-docker-volume/home --entrypoint /bin/sh pm942