The Docker project was released in 2013 and gained popularity quickly, surpassing that of OpenVZ and LXC. Large production deployments now run Docker, with various orchestration frameworks, such as Apache Mesos and Kubernetes, offering Docker integration.
Unlike LXC and OpenVZ, Docker is better suited for running single applications in a minimal container setup. It uses Docker Engine daemon, which controls the containerd
process for managing the life cycle of the containers, thus making it harder to integrate with other init systems such as systemd
.
Docker exposes a convenient API that various tools use, and makes it easy to provision containers from prebuilt images, hosted either on remote public or private repositories/registries.
We can run LXC and Docker containers on the same host without any problems, as they have clear separation. In the next section, we are going to explore most of Docker's features, by examining the life cycle of a Docker container and seeing how it compares to LXC.
Let's start by updating our server and installing the repo and key:
root@docker:~# apt-get update && apt-get upgrade && reboot ... root@docker:~# apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D Executing: /tmp/tmp.Au9fc0rNu3/gpg.1.sh --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D gpg: requesting key 2C52609D from hkp server ha.pool.sks-keyservers.net gpg: key 2C52609D: public key "Docker Release Tool (releasedocker) <[email protected]>" imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) root@docker:~# echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" | sudo tee /etc/apt/sources.list.d/docker.list deb https://apt.dockerproject.org/repo ubuntu-xenial main root@docker:~# apt-get update
List the currently available package versions and install the latest candidate:
root@docker:~# apt-cache policy docker-engine docker-engine: Installed: (none) Candidate: 1.12.4-0~ubuntu-xenial Version table: 1.12.4-0~ubuntu-xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.12.3-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.12.2-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.12.1-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.12.0-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.11.2-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.11.1-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages 1.11.0-0~xenial 500 500 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 Packages root@docker:~# apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual root@docker:~# apt-get install docker-engine
Start the Docker services and ensure they are running:
root@docker:~# service docker start root@docker:~# pgrep -lfa docker 24585 /usr/bin/dockerd -H fd:// 24594 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc root@docker:~#
With the Docker daemon running, let's list all available containers, of which we currently have none:
root@docker:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root@docker:~#
Let's find an Ubuntu image from the upstream public registry, by executing the following command:
root@docker:~# docker search ubuntu NAME DESCRIPTION STARS OFFICIAL AUTOMATED ubuntu Ubuntu is a Debian-based Linux operating s... 5200 [OK] ubuntu-upstart Upstart is an event-based replacement for ... 69 [OK] ... root@docker:~#
We pick the official Ubuntu image for our container; to create it, run the following command:
root@docker:~# docker create --tty --interactive ubuntu bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu af49a5ceb2a5: Pull complete 8f9757b472e7: Pull complete e931b117db38: Pull complete 47b5e16c0811: Pull complete 9332eaf1a55b: Pull complete Digest: sha256:3b64c309deae7ab0f7dbdd42b6b326261ccd6261da5d88396439353162703fb5 Status: Downloaded newer image for ubuntu:latest ec66fcfb5960c0779d07243f2c1e4d4ac10b855e940d416514057a9b28d78d09 root@docker:~#
We should now have a cached Ubuntu image on our system:
root@docker:~# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 4ca3a192ff2a 2 weeks ago 128.2 MB root@docker:~#
Let's list the available container on the host again:
root@docker:~# docker ps --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec66fcfb5960 ubuntu "bash" 29 seconds ago Created docker_container_1 root@docker:~#
Starting the Ubuntu Docker container is just as easy:
root@docker:~# docker start docker_container_1 docker_container_1 root@docker:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec66fcfb5960 ubuntu "bash" About a minute ago Up 17 seconds docker_container_1 root@docker:~#
By examining the process list, notice how we now have a single bash process running as a child of the dockerd
and docker-containerd
processes:
root@docker:~# ps axfww ... 24585 ? Ssl 0:07 /usr/bin/dockerd -H fd:// 24594 ? Ssl 0:00 \_ docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc 26942 ? Sl 0:00 \_ docker-containerd-shim ec66fcfb5960c0779d07243f2c1e4d4ac10b855e940d416514057a9b28d78d09 /var/run/docker/libcontainerd/ec66fcfb5960c0779d07243f2c1e4d4ac10b855e940d416514057a9b28d78d09 docker-runc 26979 pts/1 Ss+ 0:00 \_ bash root@docker:~#
By attaching to the container, we can see that it is running a single bash process, unlike the full init system that LXC or OpenVZ use:
root@docker:~# docker attach docker_container_1 root@ec66fcfb5960:/# ps axfw PID TTY STAT TIME COMMAND 1 ? Ss 0:00 bash 10 ? R+ 0:00 ps axfw root@ec66fcfb5960:/# exit exit root@docker:~#
Notice that after exiting the container, it is terminated:
root@docker:~# docker attach docker_container_1 You cannot attach to a stopped container, start it first root@docker:~# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec66fcfb5960 ubuntu "bash" 3 minutes ago Exited (0) 26 seconds ago docker_container_1 root@docker:~#
Let's start it back up again; we can either specify its name or ID, in the same manner as with OpenVZ, or libvirt LXC:
root@docker:~# docker start ec66fcfb5960 ec66fcfb5960 root@docker:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec66fcfb5960 ubuntu "bash" 3 minutes ago Up 2 seconds docker_container_1 root@docker:~#
To update the container's memory, execute the following command:
root@docker:~# docker update --memory 1G docker_container_1 docker_container_1 root@docker:~#
Inspect the memory settings of the container to make sure the memory was successfully updated:
root@docker:~# docker inspect docker_container_1 | grep -i memory "Memory": 1073741824, "KernelMemory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, root@docker:~#
Just like LXC and OpenVZ, the corresponding cgroup hierarchy was updated. We should be able to see the same memory amount in the cgroup file for the container ID:
root@docker:~# cat /sys/fs/cgroup/memory/docker/ec66fcfb5960c0779d07243f2c1e4d4ac10b855e940d416514057a9b28d78d09/memory.limit_in_bytes 1073741824 root@docker:~#
Let's update the CPU shares:
root@docker:~# docker update --cpu-shares 512 docker_container_1 docker_container_1 root@docker:~#
Then, examine the setting in the cgroup file, replacing the container ID with the one running on your host:
root@docker:~# cat /sys/fs/cgroup/cpu/docker/ec66fcfb5960c0779d07243f2c1e4d4ac10b855e940d416514057a9b28d78d09/cpu.shares 512 root@docker:~#
Docker provides few ways of monitoring the container status and resource utilization, very much like LXC:
root@docker:~# docker top docker_container_1 UID PID PPID C STIME TTY TIME CMD root 27812 27774 0 17:41 pts/1 00:00:00 bash root@docker:~# docker stats docker_container_1 CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS docker_container_1 0.00% 1.809 MiB / 1 GiB 0.18% 648 B / 648 B 0 B / 0 B 1 ^C root@docker:~#
We can also run a command inside the containers' namespace without attaching to it:
root@docker:~# docker exec docker_container_1 ps ax PID TTY STAT TIME COMMAND 1 ? Ss+ 0:00 bash 11 ? Rs 0:00 ps ax root@docker:~#
Copying a file from the host filesystem to that of the container is done with the following command; we saw similar examples with LXC and OpenVZ:
root@docker:~# touch /tmp/test_file root@docker:~# docker cp /tmp/test_file docker_container_1:/tmp/ root@docker:~# docker exec docker_container_1 ls -la /tmp total 8 drwxrwxrwt 2 root root 4096 Dec 14 19:39 . drwxr-xr-x 36 root root 4096 Dec 14 19:39 .. -rw-r--r-- 1 root root 0 Dec 14 19:38 test_file root@docker:~#
Moving the container between hosts is even easier with Docker; there's no need to manually archive the root filesystem:
root@docker:~# docker export docker_container_1 > docker_container_1.tar root@docker:~# docker import docker_container_1.tar sha256:c86a93369be687f9ead4758c908fe61b344d5c84b1b70403ede6384603532aa9 root@docker:~# docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> c86a93369be6 6 seconds ago 110.7 MB ubuntu latest 4ca3a192ff2a 2 weeks ago 128.2 MB root@docker:~#
To delete local images, run the following command:
root@docker:~# docker rmi c86a93369be6 Deleted:sha256:c86a93369be687f9ead4758c908fe61b344d5c84b1b70403ede6384603532aa9 Deleted:sha256:280a817cfb372d2d2dd78b7715336c89d2ac28fd63f7e9a0af22289660214d32 root@docker:~#
The Docker API exposes a way to define the software networks, similar to what we saw with libvirt LXC. Let's install the Linux bridge and see what is present on the Docker host:
root@docker:~# apt-get install bridge-utils root@docker:~# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242d6dd444c no vethf5b871d root@docker:~#
Notice the docker0
bridge, created by the Docker service. Let's list the networks that were automatically created:
root@docker:~# docker network ls NETWORK ID NAME DRIVER SCOPE a243008cd6cd bridge bridge local 24d4b310a2e1 host host local 1e8e35222e39 none null local root@docker:~#
To inspect the bridge
network, run the following command:
root@docker:~# docker network inspect bridge [ { "Name": "bridge", "Id":"a243008cd6cd01375ef389de58bc11e1e57c1f3 e4965a53ea48866c0dcbd3665", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16" } ] }, "Internal": false, "Containers": { "ec66fcfb5960c0779d07243f2c1e4d4ac10b8 55e940d416514057a9b28d78d09": { "Name": "docker_container_1", "EndpointID":"27c07e8f24562ea333cdeb5c 11015d13941c746b02b1fc18a766990b772b5935", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } }, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} } ] root@docker:~#
Finally, to stop and delete the Docker container, execute the following command:
root@docker:~# docker stop docker_container_1 docker_container_1 root@docker:~# docker rm docker_container_1