- PVSM.RU - https://www.pvsm.ru -

Управление контейнерами с runC

runc

Продолжаем цикл статей о контейнеризации. Сегодня мы поговорим о runC [1] — инструменте для запуска контейнеров, разрабатываемом в рамках проекта Open Containers. Цель этого проекта заключается в разработке единого стандарта в области контейнерных технологий. Проект поддерживают такие компании, как Facebook, Google, Microsoft, Oracle, EMC, Docker. Летом 2015 года был опубликован черновой вариант спецификации под названием Open Container Initiative (OCI) [2].

RunC уже активно используется в современных инструментах контейнеризации. Так, последние версии Docker (начиная с 1.11, вышедшей весной этого года) созданы в соответствии со спецификациями OCI и работают на базе runC [3]. А библиотека libcontainer, которая по сути является частью runc, используется в Docker вместо LXC начиная ещё с версии 1.8.

В этой статье мы покажем, как можно создавать контейнеры и управлять ими с помощью runC.

Установка

Мы будем описывать установку runc для Ubuntu 16.04. В этой операционной системе последняя на текущий момент стабильная версия Go (1.6) уже включена в официальные репозитории и устанавливается стандартным способом:

$ sudo apt-get install golang-go

Runc тоже включён в репозитории Ubuntu 16.04, но далеко не в самой свежей версии. Последнюю на сегодняшний день версию (1.0.0) нужно собирать из исходного кода. Для этого сначала потребуется установить необходимые зависимости:

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

Вот и всё, можно приступать к сборке runC:

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

В результате выполнения этих команд runc будет установлен в директорию /usr/local/bin/runc.

Создаём первый контейнер

Итак, к созданию первого контейнера всё готово.

Первое, что нам нужно сделать — это создать отдельную директорию под новый контейнер, а внутри её — директорию rootfs:

$ mkdir /mycontainer
$ cd /mycontainer
$ mkdir rootfs

Начнём с cамого простого примера. Загрузим docker-образ memcached [4], преобразуем его в архив *.tar и распакуем в директорию rootfs:

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

В результате выполнения этой команды в директорию rootfs будут помещены системные файлы для будущего контейнера:

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

После этого мы можем запускать контейнеры и управлять ими, не прибегая к помощи Docker. Создадим конфигурационный файл, в котором будут прописаны настройки нового контейнера:

$ sudo runc spec

После этого в директории rootfs появится новый файл — config.json. К запуску нового контейнера всё готово. Выполним:

$ sudo runc run mycontainer

Внутри контейнера будет запущена командная оболочка.

Мы рассмотрели самый элементарный пример, в котором контейнер был запущен с автоматически сгенерированными настройками. Для более тонкой настройки контейнеров упомянутый выше файл config.json придётся редактировать вручную. Разберём его структуру более подробно.

Конфигурационный файл config.json

В первой части конфигурационного файла описываются общие характеристики контейнера: версия OCI, операционная система и её архитектура, параметры терминала:

 "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"
                ],

В следующем разделе приводятся настройки для директории, в которой работает контейнер:

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

Аббревиатура CWD означает current working directory. В нашем случае это директория «/». Далее указывается набор capabilities [5] (на русский язык этот термин не совсем удачно переводят как «возможности») — разрешений для исполняемых файлов на использование определённых подсистем без прав root. CAP_AUDIT_WRITE разрешает делать записи в журнал аудита, CAP_KILL — отправлять процессам сигналы, а CAP_NET_BIND_SERVICE — разрешает привязку сокетов к привилегированным портам (т.е. портам с номерами меньше 1024).

Следующий раздел — rlimits:

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

Здесь мы устанавливаем для контейнера лимит ресурсов, а именно — максимальное количество одновременно открытых файлов (RLIMIT_NOFILE), которое составляет 1024.

Далее идёт описание настроек корневой файловой системы:

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

В разделе mounts описываются cмонтированные в контейнер директории:

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

Мы рассмотрели лишь самые основные разделы файла config.json. О некоторых других его разделах мы поговорим ниже. С подробным описанием этого файла можно ознакомиться здесь [6].

Хуки

Ещё одна интересная возможность runc — настройка хуков: мы можем прописать в конфигурационном файле конкретные действия, которые будут выполнены перед запуском пользовательского процесса в контейнере (prestart), после запуска пользовательского процесса (poststart) и после его остановки (poststop).

Приведём несколько примеров, чтобы лучше понять, зачем нужны хуки и как прописываются соответствующие настройки в конфигурационном файле. Представим себе вполне такую ситуацию: прежде чем запускать некоторую программу в контейнере, нам нужно настроить сеть. Для этого добавим в конфигурационный файл такой хук (пример взят из официальной документации):

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

В разделе path прописывается путь к программе, которая и выполнит настройку сети. Готовые программные инструменты подобного рода можно найти на Github — см., например, здесь [7].

Рассмотрим теперь пример poststart-хука:

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

Когда этот хук сработает, будет запущен скрипт (в нашем примере она называется notify-start), который запишет в логи информацию о событиях, связанных с запуском контейнера.

Poststop-хуки инициируют действия, которые будут выполнены после завершения пользовательского процесса в контейнере. Они могут понадобиться, например, в случае, когда нам нужно удалить логи и сессионные файлы, оставленные контейнером в системе, а заодно и сам контейнер. Вот простой пример:

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

При срабатывании этого хука будет запущен скрипт cleanup.sh, который и выполнить все упомянутые выше действия.

Управление контейнерами: основные команды

Для управление контейнерами в runc используются простые и привычные команды. Вот их краткий список:

#просмотреть список контейнеров и информацию об их состоянии
runc list

#запустить процесс в контейнере
runc start mycontainerid

#остановить процесс в контейнерe
runc stop mycontainerid

#удалить контейнер
runc delete mycontainerid

Настройка сети

С базовыми операциями по управлению контейнерами мы разобрались. Попробуем настроить в контейнере сеть. Это не самая простая задача. Все операции нужно осуществлять вручную.

Для начала выполним следующую последовательность команд (взято из этой статьи [8]):

$ 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

Приведённые команды говорят сами за себя. Сначала мы создаём мост для связи между контейнером и интерфейсом на основном хосте. Затем «поднимаем» виртуальный интерфейс и добавляем его в мост. После этого мы создаём сетевое пространство имён (неймспейс) с именем runc и назначаем в нём IP-адрес интерфейсу eth1.

Чтобы настроить в контейнере сеть, мы должны ассоциировать с ним неймспейс runc. Для этого внесём небольшие изменения в файл config.json:

......

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

В разделе namespaces укажем путь к пространству имён runc:

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

Вот и всё: все необходимые настройки прописаны. Сохраняем внесённые изменения и перезапускаем контейнер. Выполним на основном хосте команду:

$  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

Её вывод свидетельствует о том, что контейнер принимает ping с основного хоста.

Заключение

Эта статья представляет собой лишь краткое введение в runC. Для желающих узнать больше приводим небольшую подборку полезных ссылок:

Если вы уже экспериментировали с runС — приглашаем поделиться опытом в комментариях.

Автор: Селектел

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/selectel/214973

Ссылки в тексте:

[1] runC: https://runc.io/

[2] Open Container Initiative (OCI): https://github.com/opencontainers/runtime-spec

[3] созданы в соответствии со спецификациями OCI и работают на базе runC: https://blog.docker.com/2016/04/docker-engine-1-11-runc/

[4] memcached: https://memcached.org/

[5] capabilities: http://man7.org/linux/man-pages/man7/capabilities.7.html

[6] здесь: https://github.com/opencontainers/runtime-spec/blob/master/config.md

[7] здесь: https://github.com/jessfraz/netns

[8] этой статьи: http://fstn.hateblo.jp/entry/2015/08/01/231302

[9] Вводная лекция о runС: https://www.youtube.com/watch?v=ZAhzoz2zJj

[10] Презентация-конспект лекции по ссылке выше: http://events.linuxfoundation.org/sites/events/files/slides/LCCC2016-%20runC-%20The%20Little%20(container)%20Engine%20That%20Could.pdf

[11] Статья о runС в блоге Docker: https://blog.docker.com/2015/06/runc/

[12] Краткая, но вполне неплохая статья о runC на русском языке: https://blog.amartynov.ru/docker-containerd-runc-runv/

[13] Источник: https://habrahabr.ru/post/316258/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best