📜 ⬆️ ⬇️

Manage containers with runC

runc

We continue the cycle of articles on containerization. Today we will talk about runC , a container launch tool developed as part of the Open Containers project. The goal of this project is to develop a unified standard in the field of container technologies. The project is supported by companies such as Facebook, Google, Microsoft, Oracle, EMC, Docker. In the summer of 2015, a draft specification called the Open Container Initiative (OCI) was published.

RunC is already actively used in modern containerization tools. So, the latest versions of Docker (starting from 1.11, released this spring) are created in accordance with the OCI specifications and run on runC . And the libcontainer library, which is essentially a part of runc, is used in Docker instead of LXC starting from version 1.8.

In this article, we will show how you can create and manage containers using runC.
')

Installation


We will describe the runc installation for Ubuntu 16.04. In this operating system, the latest stable version of Go (1.6) is already included in the official repositories and is installed in the standard way:

$ sudo apt-get install golang-go 

Runc is also included in the repositories of Ubuntu 16.04, but not in the most recent version. The latest version to date (1.0.0) should be collected from the source code. To do this, you first need to install the necessary dependencies:

 sudo apt-get install build-essential make libseccomp-dev 

That's it, you can start building runC:

 $ git clone https://github.com/opencontainers/runc $ cd runc $ make $ sudo make install 

As a result of these commands, runc will be installed in the / usr / local / bin / runc directory.

Create the first container


So, to create the first container everything is ready.

The first thing we need to do is create a separate directory for the new container, and inside it is the rootfs directory:

 $ mkdir /mycontainer $ cd /mycontainer $ mkdir rootfs 

Let's start with the simplest example. Download the memcached docker image, convert it to the * .tar archive and unpack it into the rootfs directory:

 $ docker export $(docker create memcached) | tar -C rootfs -xvf - 

As a result of this command, the system files for the future container will be placed in the rootfs directory:

 $ ls rootfs bin dev etc lib media opt root sbin sys usr boot entrypoint.sh home lib64 mnt proc run srv tmp var 

After that we can launch containers and manage them without the help of Docker. Create a configuration file in which the settings for the new container will be written:

 $ sudo runc spec 

After that, a new file will appear in the rootfs directory - config.json. To launch a new container, everything is ready. Perform:

 $ sudo runc run mycontainer 

A shell will be launched inside the container.

We considered the most elementary example in which the container was launched with automatically generated settings. To fine tune containers, the config.json file mentioned above will have to be edited manually. Let us analyze its structure in more detail.

Config.json configuration file


The first part of the configuration file describes the general characteristics of the container: the OCI version, the operating system and its architecture, and the terminal parameters:

  "ociVersion": "1.0.0-rc1", "platform": { "os": "linux", "arch": "amd64" }, "process": { "terminal": true, "user": { "uid": 0, "gid": 0 }, "args": [ "sh" ], "env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm" ], 

The following section lists the settings for the directory in which the container works:

 "cwd": "/", "capabilities": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], 

CWD stands for current working directory. In our case, this is the “/” directory. Further, a set of capabilities is indicated (this term is not quite successfully translated into Russian as “capabilities”) - permissions for executable files to use certain subsystems without root rights. CAP_AUDIT_WRITE allows writing to the audit log, CAP_KILL sending signals to processes, and CAP_NET_BIND_SERVICE allowing binding sockets to privileged ports (that is, ports with numbers less than 1024).

The next section is rlimits:

 "rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024 } ], "noNewPrivileges": true }, 

Here we set the limit of resources for the container, namely, the maximum number of simultaneously open files (RLIMIT_NOFILE), which is 1024.

The following is a description of the root file system settings:

 "root": { "path": "rootfs", "readonly": true } 

The mounts section describes the directories mounted in the container:

 "mounts": [ { "destination": "/tmp", "type": "tmpfs", "source": "tmpfs", "options": ["nosuid","strictatime","mode=755","size=65536k"] }, { "destination": "/data", "type": "bind", "source": "/volumes/testing", "options": ["rbind","rw"] } ] 

We considered only the most basic sections of the config.json file. We will discuss some of its other sections below. A detailed description of this file can be found here .

Hooks



Another interesting runc feature is setting up hooks: we can write specific actions in the configuration file that will be performed before the user process starts in the container (prestart), after the user process starts (poststart) and after it stops (poststop).

Here are some examples to better understand why hooks are needed and how the corresponding settings are written in the configuration file. Imagine a situation like this: before we run some program in a container, we need to configure the network. To do this, add such a hook to the configuration file (an example is taken from the official documentation):

 "hooks": { "prestart": [ { "path": "/path/to/script" } 

In the path section, the path to the program is written, which will configure the network. Ready-made software tools of this kind can be found on Github - see, for example, here .

Consider now an example of poststart-hook:

 "poststart": [ { "path": "/usr/bin/notify-start", "timeout": 5 } 

When this hook works, a script will be launched (in our example it is called notify-start), which will log information about events related to the launch of the container.

Poststop-hooks initiate actions that will be executed after the completion of the user process in the container. They may be needed, for example, when we need to delete the logs and session files left by the container in the system, and at the same time the container itself. Here is a simple example:

 "poststop": [ { "path": "/usr/sbin/cleanup.sh", "args": ["cleanup.sh", "-f"] } 

When this hook is triggered, the cleanup.sh script will be launched, which will perform all the actions mentioned above.

Container Management: Basic Commands


To manage containers in runc, simple and familiar commands are used. Here is a short list of them:

 #        runc list #    runc start mycontainerid #   e runc stop mycontainerid #  runc delete mycontainerid 

Network configuration


We dealt with basic container management operations. Let's try to set up a network in the container. This is not an easy task. All operations must be carried out manually.

First, let's execute the following sequence of commands (taken from this article ):

 $ sudo brctl addbr runc0 $ sudo ip link set runc0 up $ sudo ip addr add 192.168.10.1/24 dev runc0 $ sudo ip link add name veth-host type veth peer name veth-guest $ sudo ip link set veth-host up $ sudo brctl addif runc0 veth-host $ sudo ip netns add runc $ sudo ip link set veth-guest netns runc $ sudo ip netns exec runc ip link set veth-guest name eth1 $ sudo ip netns exec runc ip addr add 192.168.10.101/24 dev eth1 $ sudo ip netns exec runc ip link set eth1 up $ sudo ip netns exec runc ip route add default via 192.168.10.1 

The commands listed speak for themselves. First, we create a bridge for communication between the container and the interface on the main host. Then we “raise” the virtual interface and add it to the bridge. After that, we create a network namespace (namespace) named runc and assign an IP address to the eth1 interface in it.

To set up a network in a container, we need to associate runc namespace with it. To do this, make small changes to the config.json file:

 ...... "root": { "path": "rootfs", "readonly": false } 

In the namespaces section, specify the path to the runc namespace:

  { "type": "network", "path": "/var/run/netns/runc" }, 

That's all: all the necessary settings are registered. Save the changes and restart the container. Run the following command on the main host:

 $ ping 192.168.10.101 PING 192.168.10.101 (192.168.10.101) 56(84) bytes of data. 64 bytes from 192.168.10.101: icmp_seq=2 ttl=64 time=0.070 ms 64 bytes from 192.168.10.101: icmp_seq=3 ttl=64 time=0.090 ms 64 bytes from 192.168.10.101: icmp_seq=4 ttl=64 time=0.106 ms 64 bytes from 192.168.10.101: icmp_seq=5 ttl=64 time=0.091 ms 64 bytes from 192.168.10.101: icmp_seq=6 ttl=64 time=0.097 ms 

Its output indicates that the container accepts ping from the primary host.

Conclusion


This article is just a brief introduction to runC. For those who want to learn more, here is a small selection of useful links:


If you have already experimented with runC, we invite you to share your experience in the comments.

Source: https://habr.com/ru/post/316258/


All Articles