Book Image

Containerization with LXC

Book Image

Containerization with LXC

Overview of this book

In recent years, containers have gained wide adoption by businesses running a variety of application loads. This became possible largely due to the advent of kernel namespaces and better resource management with control groups (cgroups). Linux containers (LXC) are a direct implementation of those kernel features that provide operating system level virtualization without the overhead of a hypervisor layer. This book starts by introducing the foundational concepts behind the implementation of LXC, then moves into the practical aspects of installing and configuring LXC containers. Moving on, you will explore container networking, security, and backups. You will also learn how to deploy LXC with technologies like Open Stack and Vagrant. By the end of the book, you will have a solid grasp of how LXC is implemented and how to run production applications in a highly available and scalable way.
Table of Contents (17 chapters)
Containerization with LXC
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Dedication
Preface

Building containers with Docker


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