Runtime

External Link:

В 2007 году Google сделали проект Let Me Contain That For You (LMCTFY), в 2008 году появился Linux Containers (LXC). Для управления LXC в 2013 году появился инструмент Docker. Далее в 2015, команда Docker разработали проект libcontainer на языке Go. Также, в 2015 вышел Kubernetes 1.0. В 2015 собрали Open Container Initiative (OCI), которые стали разрабатывать стандарты на метаданные (манифесты-спецификации), образы контейнеров, методы управления ими. В том числе, в рамках OCI создали инструмент запуска и работы с контейнерами runc.

runc

sudo apt install runc
runc spec
cat config.json

В спецификации от runc можно увидеть всё необходимое для создания и запуска контейнера: environment variables, user + group IDs, mount points, Linux namespaces. Не хватает только файловой системы (rootfs), базового образа контейнера:

sudo apt install skopeo, umoci # Ubuntu 2404+
skopeo copy docker://opensuse/tumbleweed:latest oci:tumbleweed:latest
sudo umoci unpack --image tumbleweed:latest bundle

В распакованном образе можно найти готовую Runtime Specification:

sudo chown -R $(id -u) bundle
cat bundle/config.json

В ней можно увидеть обычные поля из runc, а доп заполненные annotations:

  "annotations": {
    "org.opencontainers.image.title": "openSUSE Tumbleweed Base Container",
    "org.opencontainers.image.url": "https://www.opensuse.org/",
    "org.opencontainers.image.vendor": "openSUSE Project",
    "org.opencontainers.image.version": "20190517.6.190",

Чтобы создать контейнер с runc, нужно его зацепить на терминал ввода команд TTY:

sudo runc create -b bundle container

ERRO[0000] runc create failed: cannot allocate tty if runc will detach without setting console socket

На существующий TTY зацепить контейнер нельзя (потому что окно удалённого xTerm не поддерживает такое), нужно создать новый виртуальный TTY и указать его сокет. Для этого надо установить Golang, скачать приложение rectty, создать с его помощью виртуальный терминал, после чего В ДРУГОМ ОКНЕ терминала создать контейнер и зацепить его на создвнный TTY:

sudo apt install wget
wget https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.23.3.linux-amd64.tar.gz

/usr/local/go/bin/go install github.com/opencontainers/runc/contrib/cmd/recvtty@latest

rectty tty.sock

В ДРУГОМ ОКНЕ терминала создать контейнер и зацепить его на создвнный TTY:

sudo runc create -b bundle --console-socket $(pwd)/tty.sock container
sudo runc list # контейнер в статуса created, не запущен
sudo runc ps container # посмотрим что внутри него
UID        PID  PPID  C STIME TTY          TIME CMD
root     29772     1  0 10:35 ?        00:00:00 runc init

runc init создаёт новую среду со всеми namespaces. /bin/bash ещё не запущен в контейнере, но уже можно запускать в нём свои процессы, полезно чтоб настроить сеть:

sudo runc exec -t container echo "Hello, world!"
Hello, world!

Для запуска контейнера выполним:

sudo runc start container
sudo runc list
sudo runc ps container
UID        PID  PPID  C STIME TTY          TIME CMD
root      6521  6511  0 14:25 pts/0    00:00:00 /bin/bash

Исходный runc init пропал, теперь только /bin/bash существует в контейнере. На ПЕРВОМ ОКНЕ терминала появилась консоль контейнера:

$ ps aux
ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   5156  4504 pts/0    Ss   10:28   0:00 /bin/bash
root        29  0.0  0.0   6528  3372 pts/0    R+   10:32   0:00 ps aux

Можно проверить управление: заморозим контейнер. Во ВТОРОМ ОКНЕ терминала выполним:

sudo runc pause container
# в первом окне ввод команд прервётся
sudo runc resume container

# Рассмотрим события контейнера:
sudo runc events container
#{...}

Для остановки контейнера достаточно выйти из rectty-сессии, после чего удалить контейнер. Остановленный контейнер нельзя перезапустить, можно лишь пересоздать в новом состоянии:

> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED                         OWNER
container   0           stopped     /bundle     2019-05-21T10:28:32.765888075Z  root
> sudo runc delete container
> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED     OWNER

Можно модифицировать спецификацию в контейнере (bundle/config.json):

> sudo apt install moreutils, jq # инструмент jq для работы с JSON
> cd bundle
> jq '.process.args = ["echo", "Hello, world!"]' config.json | sponge config.json
> sudo runc run container
> Hello, world!

Можно удалить разделение PID namespace процессов в контейнере с хостом:

> jq '.process.args = ["ps", "a"] | del(.linux.namespaces[0])' config.json | sponge config.json
> sudo runc run container
16583 ?        S+     0:00 sudo runc run container
16584 ?        Sl+    0:00 runc run container
16594 pts/0    Rs+    0:00 ps a
[output truncated]
  • runc очень низкоуровневый и позволяет серьёзно нарушить работу и безопасность контейнеров.
  • Поэтому сделаны надстройки обеспечения ИБ уровня ОС: seccomp, SELinux и AppArmor
  • Однако, их намного удобнее использовать на уровне управления выше
  • Для защиты также можно запускать контейнеры в режиме rootless из runc
  • С помощью runc нужно руками настраивать сетевые интерфейсы, очень трудоемко

CRI-O

Инструмент CRI-O разработан в 2016 при участии OCI в рамках проекта Kubernetes. Философия UNIX, максимально лёгкий аналог Docker/containerd. Он НЕ предназначен как инструмент для приёма команд от разработчиков. Задача - принимать команды от K8s. Внутри себя CRI-O использует runc как backend, и принимает команды по gRPC API как frontend.

Попробуем CRI-O с помощью спец-контейнера с crictl:

sudo apt install podman
sudo vim /etc/containers/registries.conf
# нужно задать репозиторий для скачивания:
# unqualified-search-registries=["docker.io"] 
sudo podman run --privileged -h crio-playground -it saschagrunert/crio-playground

Внутри лежит файл sandbox.yml:

---
metadata:
  name: sandbox
  namespace: default
dns_config:
  servers:
    - 8.8.8.8

Из него можно создать Pod:

$ crictl runp sandbox.yml
5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336