- PVSM.RU - https://www.pvsm.ru -
Продолжаем цикл статей о контейнеризации. Сегодня мы поговорим о 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 придётся редактировать вручную. Разберём его структуру более подробно.
В первой части конфигурационного файла описываются общие характеристики контейнера: версия 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
Нажмите здесь для печати.