От переводчика

Это перевод статьи ссылка

Автор оригинальной  статьи: Ian Lewis.

Ссылка на первую часть

Ссылка на тетью часть

Это вторая часть из четырех статей о средах запуска контейнеров. В первой части я сделал общий обзор и рассказал о различиях между низкоуровневыми и высокоуровневыми средами. В этой статье мы погрузимся в детали низкоуровневых сред.

Низкоуровневые среды имеют ограниченный набор функций и обычно выполняют базовые задачи по запуску контейнеров. С такими средами разработчики не сталкиваются в повседневной работе. Low-level runtime представляют собой простой набор утилит и библиотек. Хоть большинство разработчиков не использует данные инструменты, но их полено знать и то как они работают, чтобы уметь траблшутить неполадки.

В первой части я рассказывал, на основе чего создаются контейнеры: это Linux namespaces и cgroup. Неймспейсы позволяют виртуализировать системные ресурсы такие как файловая система или сеть для каждого контейнера. С другой стороны есть cgroup, который контролирует и ограничивает системные ресурсы – CPU и память, разделяя их между контейнерами. По своей сути, низкоуровневые среды отвечают за настройку неймспейсов, разделения ресурсов между контейнерами, и выполнения команд внутри неймспейсов. У большинства сред существуют и другие особенности, но выше перечисленные являются основными функциями. Обязательно ознакомьтесь с крутым материалом Building a container from scratch in Go Liz Rice. Она очень классно объясняет, как реализованы Low-level среды. Лиз пошагово рассказывает про самые базовые вещи, которые должен выполнять container runtime, к ним относятся:

  • Создание cgroup;

  • Запуск команд в cgroup;

  • Разделить пространство имен;

  • Очистить cgroup после выполнения команд (namespaces удаляются автоматически, когда на него не ссылается никакой активный процесс).

Серьезная низкоуровневая среда запуска умеет еще много
чего, например, настройку лимитов ресурсов в cgroup, настройка
корневой файловой системы, и привязку к ней процесса

Создание простейшего Runtime

Давайте пробежимся через простые ad hoc команды для создания контейнера. Мы можем воспроизвести шаги, использующиеся стандартными линукс командами: cgcreatecgsetcgexecchroot и unshare. Вам необходимо запускать команды с привилегиями рута.

Сперва давайте настроем корневую файловую систему для нашего контейнера. Мы будем использовать busybox Docker контейнер как основу. Тут мы создадим временную директорию и распакуем busybox. Большинству команд нужны привилегии рута.

$ CID=$(docker create busybox)
$ ROOTFS=$(mktemp -d)
$ docker export $CID | tar -xf - -C $ROOTFS

Теперь давайте создадим наши cgroup и настроим ограничения памяти и CPU. Лимит памяти настраивается в bytes. Тут мы задаем лимит нашему контейнеру в 100 МВ.

$ UUID=$(uuidgen)
$ cgcreate -g cpu,memory:$UUID
$ cgset -r memory.limit_in_bytes=100000000 $UUID
$ cgset -r cpu.shares=512 $UUID

Настройку CPU можно сделать двумя способами. Мы можем задать лимит используя “shares”. Шары — это количество процессорного времени по отношению к другим запущенным процессам. Контейнеры работают сами по себе и могут использовать весь CPU, но если запущены другие контейнеры, то им выделяется процессорное время пропорционально заданным их CPU shares.

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

К примеру, мы хотим установить лимит для нашего контейнера в два ядра, то мы должны установить период равный одной секунде, и квоту в две секунды (одна секунда равна 1000000 мили секунд) эти настройки позволят нашему контейнеру использовать два ядра на протяжении одной секунды. В этой статье подробно объясняется механизм работы.

$ cgset -r cpu.cfs_period_us=1000000 $UUID
$ cgset -r cpu.cfs_quota_us=2000000 $UUID

Следующим шагом мы можем выполнить команду внутри контейнера. Запустим команду внутри, созданного нами cgroup, изолируем пространство имен, задами hostname, и сменим нашу файловую систему.

$ cgexec -g cpu,memory:$UUID \
>     unshare -uinpUrf --mount-proc \
>     sh -c "/bin/hostname $UUID && chroot $ROOTFS /bin/sh"
/ # echo "Hello from in a container"
Hello from in a container
/ # exit

После выполнения команды, мы можем очистить все просто удалив cgroup и временную директорию.

$ cgdelete -r -g cpu,memory:$UUID
$ rm -r $ROOTFS

В будущем посмотрим, как это работает, я написал простой runtime на баше и назвал его execc. Он поддерживает монтирование, создание пользователей, pid, ipc, uts и сеть в неймспейсе; настройку памяти, лимиты CPU по количеству ядер, монтирование файловой системы proc и запуск контейнера в собственной файловой системе.

Обзор низкоуровневых сред запуска

Лучший способ узнать как это все работает это посмотреть несколько примеров существующих систем. Эти среды запуска имеют свои особенности и подчёркивают разные аспекты контейнеризации.

IMCTFY

Это проект Гугла, хотя и широкого распространения он не получил. IMCTFY основан на другой среде запуска – Borg. Одна из самых интересных фич это поддержка иерархии контейнеров. К примеру, корневой контейнер имеет имя “busybox”, который создал саб контейнер с именем “ busybox/sub1” или “ busybox/sub2” где имена создают своего рода пути. В результате каждый дочерний контейнер имеет собственный cgroup, который лимитирован cgroup родителя. Эта особенность Borg дает возможность контейнерам IMCTFY создавать дочерние контейнеры в соответствии с предварительно выделенными ресурсами на сервере, и таким образом мы получаем более жесткий SLO, чем мог быть предоставлен самим рантаймом.

IMCTFY поддерживает еще несколько интересных особенностей и идей, но другие среды были более юзабельные, поэтому Гугл решил, что сообществу стоит сосредоточится на работу с Docker libcontainer чем над IMCTFY.

RUNC

Runc в настоящее время является самой популярной средой. Первоначально она была разработана как часть Docker, но позже отделился от проекта.

Runc запускает контейнеры также как я описал это выше, но он делает это по стандарту OCI. Это означает, что контейнеры запускаются по спецификацйии “OCI bundle format”. Этот формат содержит файл config.json для конфигурации и файловую системы для контейнера. Вы можете подробнее ознакомится со стандартом тут. И тут как установить runc.

Сначала создадим файловую систему, используем все тот же busybox:

$ mkdir rootfs
$ docker export $(docker create busybox) | tar -xf - -C rootfs

Следующим шагом создадим файл с настройками.

 $ runc spec

Эта команда создаст шаблон файла, он выглядит так:

$ cat config.json
{
        "ociVersion": "1.0.0",
        "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": {
...

По умолчанию, это запускает sh команду в контейнере с корневой файловой системе ./rootfs. Теперь мы можем просто запустить контейнер.

$ sudo runc run mycontainerid
/ # echo "Hello from in a container"
Hello from in a container

RKT

Rkt это популярная альтернатива Docker/runc созданная разработчиками CoreOS. Rkt сложно отнести к low-level runtime, т.к. кроме основных функций низкоуровневых сред у нее есть и функции high-level сред запуска. Я расскажу про , базовые функции rkt а высокоуровневые оставим для следующей статьи.

Rkt использует appc стандарт, который был создан как альтернатива Docker формату. Appc так и не получил широкого распространения и в данный момент активно не развивается, но он достиг своих целей – обеспечить открытом стандартом все сообщество. Вместо appc rkt в будущем будет использовать формат OCI.

Application Container Image (ACI) – это стандарт образа стандарта appc. Образ в архиве tar.gz содержит manifest и rootfs директории в корневой файловой системе. Прочитать подробнее можно тут.

Вы можете собрать образ контейнера при помощи инструмента acbuild. Аcbuild очень похож на Dockerfile, можно использовать в терминале.

acbuild begin
acbuild set-name example.com/hello
acbuild dep add quay.io/coreos/alpine-sh
acbuild copy hello /bin/hello
acbuild set-exec /bin/hello
acbuild port add www tcp 5000
acbuild label add version 0.0.1
acbuild label add arch amd64
acbuild label add os linux
acbuild annotation add authors "Carly Container <carly@example.com>"
acbuild write hello-0.0.1-linux-amd64.aci
acbuild end

Adiós!

Я надеюсь, что эта статья поможет вам понять, что такие низкоуровневые среды. Большинство людей использует высокоуровневые среды, и это круто знать и понимать, как работает ваш инструмент внутри. Такие знания помогут искать и исправлять проблемы.

В следующем посте мы поговорим о высокоуровневых средах выполнения. Я расскажу, что это такое, и почему они лучше подходят для разработчиков приложений. Мы также поговорим о популярных средах таких как, Docker и rkt фичах high-level. Обязательно добавь мой RSS канал или подпишетесь в твиторе, чтобы получать уведомление о новых постах.

  • Отправляя и отвечая на вопросы на Stack Overflow

  • Подписавшись на Twitter @Kubernetesio

  • Присоединившись к Kubernetes Slack и общаясь с нами. (Я ianlewis так что скажи «Привет»!)

  • Сделав вклад в Kubernetes на GitHub

Комментарии (0)