[Kubernetes, Программирование] Как устроен процесс создания docker-контейнера (от docker run до runc) (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
17-Июл-2020 15:30

Перевод статьи подготовлен в преддверии старта курса «Инфраструктурная платформа на основе Kubernetes».
За последние несколько месяцев я потратил немало личного времени на изучение работы Linux-контейнеров. В частности, что конкретно делает docker run. В этой статье я собираюсь резюмировать то, что я выяснил, и попытаюсь показать как отдельные элементы формируют цельную картину. Начнем мы наше путешествие с создания контейнера alpine с помощью docker run:
$ docker run -i -t --name alpine alpine ash

Этот контейнер будет использоваться в выводе ниже. Когда вызывается команда docker run, она анализирует параметры, переданные ей в командной строке, и создает JSON объект для представления объекта, который нужно создать docker. Затем этот объект отправляется демону docker через сокет домена UNIX /var/run/docker.sock. Для наблюдения за вызовами API мы можем использовать утилиту strace:
$ strace -s 8192 -e trace=read,write -f docker run -d alpine

[pid 13446] write(3, "GET /_ping HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.1 (linux)\r\n\r\n", 79) = 79
[pid 13442] read(3, "HTTP/1.1 200 OK\r\nApi-Version: 1.26\r\nDocker-Experimental: false\r\nServer: Docker/1.13.1 (linux)\r\nDate: Mon, 19 Feb 2018 16:12:32 GMT\r\nContent-Length: 2\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nOK", 4096) = 196
[pid 13442] write(3, "POST /v1.26/containers/create HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.1 (linux)\r\nContent-Length: 1404\r\nContent-Type: application/json\r\n\r\n{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":[],"Cmd":null,"Image":"alpine","Volumes":{},"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{},"HostConfig":{"Binds":null,"ContainerIDFile":"","LogConfig":{"Type":"","Config":{}},"NetworkMode":"default","PortBindings":{},"RestartPolicy":{"Name":"no","MaximumRetryCount":0},"AutoRemove":false,"VolumeDriver":"","VolumesFrom":null,"CapAdd":null,"CapDrop":null,"Dns":[],"DnsOptions":[],"DnsSearch":[],"ExtraHosts":null,"GroupAdd":null,"IpcMode":"","Cgroup":"","Links":null,"OomScoreAdj":0,"PidMode":"","Privileged":false,"PublishAllPorts":false,"ReadonlyRootfs":false,"SecurityOpt":null,"UTSMode":"","UsernsMode":"","ShmSize":0,"ConsoleSize":[0,0],"Isolation":"","CpuShares":0,"Memory":0,"NanoCpus":0,"CgroupParent":"","BlkioWeight":0,"BlkioWeightDevice":null,"BlkioDeviceReadBps":null,"BlkioDeviceWriteBps":null,"BlkioDeviceReadIOps":null,"BlkioDeviceWriteIOps":null,"CpuPeriod":0,"CpuQuota":0,"CpuRealtimePeriod":0,"CpuRealtimeRuntime":0,"CpusetCpus":"","CpusetMems":"","Devices":[],"DiskQuota":0,"KernelMemory":0,"MemoryReservation":0,"MemorySwap":0,"MemorySwappiness":-1,"OomKillDisable":false,"PidsLimit":0,"Ulimits":null,"CpuCount":0,"CpuPercent":0,"IOMaximumIOps":0,"IOMaximumBandwidth":0},"NetworkingConfig":{"EndpointsConfig":{}}}\n", 1556) = 1556
[pid 13442] read(3, "HTTP/1.1 201 Created\r\nApi-Version: 1.26\r\nContent-Type: application/json\r\nDocker-Experimental: false\r\nServer: Docker/1.13.1 (linux)\r\nDate: Mon, 19 Feb 2018 16:12:32 GMT\r\nContent-Length: 90\r\n\r\n{"Id":"b70b57c5ae3e25585edba898ac860e388582391907be4070f91eb49f4db5c433","Warnings":null}\n", 4096) = 281

Вот тут уже начинается настоящее веселье. Как только демон docker получит запрос, он проанализирует вывод и свяжется с containerd через gRPC API для настройки рантайма (или среды выполнения) контейнера с использованием параметров, переданных в командной строке. Для наблюдения за этим взаимодействием мы можем использовать утилиту ctr:
$ ctr --address "unix:///run/containerd.sock" events

TIME                           TYPE                           ID                             PID                            STATUS
time="2018-02-19T12:10:07.658081859-05:00" level=debug msg="Calling POST /v1.26/containers/create"
time="2018-02-19T12:10:07.676706130-05:00" level=debug msg="container mounted via layerStore: /var/lib/docker/overlay2/2beda8ac904f4a2531d72e1e3910babf145c6e68dfd02008c58786adb254f9dc/merged"
time="2018-02-19T12:10:07.682430843-05:00" level=debug msg="Calling POST /v1.26/containers/d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f/attach?stderr=1&stdin=1&stdout=1&stream=1"
time="2018-02-19T12:10:07.683638676-05:00" level=debug msg="Calling GET /v1.26/events?filters=%7B%22container%22%3A%7B%22d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f%22%3Atrue%7D%2C%22type%22%3A%7B%22container%22%3Atrue%7D%7D"
time="2018-02-19T12:10:07.684447919-05:00" level=debug msg="Calling POST /v1.26/containers/d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f/start"
time="2018-02-19T12:10:07.687230717-05:00" level=debug msg="container mounted via layerStore: /var/lib/docker/overlay2/2beda8ac904f4a2531d72e1e3910babf145c6e68dfd02008c58786adb254f9dc/merged"
time="2018-02-19T12:10:07.885362059-05:00" level=debug msg="sandbox set key processing took 11.824662ms for container d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f"
time="2018-02-19T12:10:07.927897701-05:00" level=debug msg="libcontainerd: received containerd event: &types.Event{Type:"start-container", Id:"d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f", Status:0x0, Pid:"", Timestamp:(*timestamp.Timestamp)(0xc420bacdd0)}"
2018-02-19T17:10:07.927795344Z start-container                d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f                                0
time="2018-02-19T12:10:07.930283397-05:00" level=debug msg="libcontainerd: event unhandled: type:"start-container" id:"d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f" timestamp:<seconds:1519060207 nanos:927795344 > "
time="2018-02-19T12:10:07.930874606-05:00" level=debug msg="Calling POST /v1.26/containers/d1a6d87886e2d515bfff37d826eeb671502fa7c6f47e422ec3b3549ecacbc15f/resize?h=35&w=115"

Настройка рантайма контейнера — довольно существенная задача. Должны быть настроены пространства имен, должен быть смонтирован образ, должны быть включены элементы управления безопасностью (профили защиты приложений, профили seccomp, возможности) и т. д., и т. д., и т. д… Вы можете получить довольно неплохое представление обо всем, что требуется для настройки рантайма, просмотрев вывод docker inspect containerid и файл спецификации среды выполнения config.json (подробнее об этом чуть позже).
Строго говоря, containerd не создает рантайм контейнера. Он настраивает среду, а затем вызывает containerd-shim для запуска рантайма контейнера через настроенный рантайм OCI (управляемый параметром containerd «–runtime»). У большинства современных систем среда выполнения контейнера работает на основе runc. Мы можем наблюдать это с помощью утилиты pstree:
$ pstree -l -p -s -T
systemd,1 --switched-root --system --deserialize 24
  ├─docker-containe,19606 --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m --debug
  │   ├─docker-containe,19834 93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /var/run/docker/libcontainerd/93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /usr/libexec/docker/docker-runc-current

Так как pstree отсекает имя процесса, мы можем проверить PID с помощью ps:
$ ps auxwww | grep [1]9606

root     19606  0.0  0.2 685636 10632 ?        Ssl  13:01   0:00 /usr/libexec/docker/docker-containerd-current --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m --debug

$ ps auxwww | grep [1]9834

root     19834  0.0  0.0 527748  3020 ?        Sl   13:01   0:00 /usr/libexec/docker/docker-containerd-shim-current 93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /var/run/docker/libcontainerd/93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /usr/libexec/docker/docker-runc-current

Когда я впервые начал исследовать взаимодействие между dockerd, containerd и shim, я не до конца понимал, для какой цели служил shim. К счастью Google привел к отличному изложению Майкла Кросби. Shim служит для нескольких целей:
  • Позволяет запускать контейнеры без демонов.
  • STDIO и остальные FD остаются открытыми в случае смерти containerd и docker.
  • Сообщает в containerd о состоянии выхода контейнеров.

Первый и второй ключевые моменты очень важны. Эти функции позволяют отделить контейнер от демона docker, позволяя обновлять или перезапускать dockerd без влияния на работающие контейнеры. Весьма эффективно! Я упомянул, что shim отвечает за запуск runc для фактического запуска контейнера. Для выполнения своей работы runc необходимы две вещи: файл спецификации и путь к образу корневой файловой системы (их комбинация называется бандлом). Чтобы увидеть, как это работает, мы можем создать rootfs, экспортировав образ alpine docker:
$ mkdir -p alpine/rootfs

$ cd alpine

$ docker export d1a6d87886e2 | tar -C rootfs -xvf -

time="2018-02-19T12:54:13.082321231-05:00" level=debug msg="Calling GET /v1.26/containers/d1a6d87886e2/export"
.dockerenv
bin/
bin/ash
bin/base64
bin/bbconfig
.....

Опция экспорта принимает контейнер, который вы можете найти в выводе docker ps -a. Для создания файла спецификации вы можете использовать команду спецификации runc:
$ runc spec

Это создаст файл спецификации с именем config.json в вашем текущем каталоге. Этот файл может быть настроен в соответствии с вашими потребностями и требованиями. После того, как вы удовлетворены файлом, вы можете запустить runc с каталогом rootfs в качестве единственного аргумента (конфигурация контейнера будет считана из файла config.json):
$ runc run rootfs

Этот простой пример создаст оболочку alpine ash:
$ runc run rootfs

/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.7.0
PRETTY_NAME="Alpine Linux v3.7"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"

Возможность создавать контейнеры и играть с runc спецификацией среды выполнения невероятно мощна. Вы можете оценить различные профили приложений, протестировать возможности Linux и поэкспериментировать с каждым аспектом среды выполнения контейнера без необходимости устанавливать докер. Я только слегка коснулся поверхности и очень рекомендую почитать документацию runc и containerd. Очень крутые инструменты!
Узнать подробнее о курсе.
===========
Источник:
habr.com
===========

===========
Автор оригинала: https://prefetch.net/
===========
Похожие новости: Теги для поиска: #_kubernetes, #_programmirovanie (Программирование), #_docker, #_kubernetes, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
)
, #_kubernetes, #_programmirovanie (
Программирование
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 23-Ноя 00:30
Часовой пояс: UTC + 5