Когда в пятый раз у тебя появляется на работе падаван, которому надо все рассказать по нескольку раз, в какой-то момент приходит в голову светлая мысль все свои речи законспектировать, попутно хоть немного структурировав все это дело. Так что сия заметка о сontainerd для того, чтобы не повторяться в сотый раз. Возможно, кому-то еще это будет интересно, хотя тут все без рокет-сайнс.
После скачивания архива из релиза containerd мы получаем набор бинарей:
containerd
containerd-shim
containerd-shim-runc-v1
containerd-shim-runc-v2
crictl
ctr
Демон containerd по умолчанию использует файловую систему overlayfs для сборки конечного образа из "снапшотов". В терминологии containerd так называют "слои" докер/cri образов. Поэтому стоит проследить чтобы модуль overlay был включен в ядре (modprobe overlay) Дефолтный systemd-unit можно найти в репозитории.
Пример конфига containerd, а также здесь есть более подробное описание всей структуры конфига. В частности, описано как настроить insecure registry
Пример
[plugins] [plugins.cri.containerd] snapshotter = "overlayfs" [plugins.cri.registry.mirrors."local.insecure-registry.io"] endpoint = [" http://registry.com:5000"]
Kubelet взаимодействует с containerd через сокет, расположение которого указывается через аргумент:
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock
Сontainerd, получив спеки от кубелета, запускает контейнеры через прослойку - containerd-shim, который уже в свою очередь выполняет бинарь рантайма с нужными параметрами. Эталонной реализацией считается runc.
В данный момент есть две версии api, которое использует containerd-shim. На данный момент актуальной является v2. (Прошу понять и простить за то, что примеры будут с v1). Подробнее описано здесь.
Сontainerd-shim позволяет не привязывать процессы, запущенные в контейнере к демону containerd, что есть весьма хорошо, на случай если вы вдруг решили, например, добавить внезапно "insecure registry" или другой параметр в конфиге и, вследствие этого, понадобилось перезапустить демон containerd. Если посмотреть на список процессов, то можно увидеть что изолированные процессы являются дочерними по отношению к containerd-shim, который в свою очередь выглядит примерно следующим образом:
containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
-namespace в данному случае - это не тот, немспейс, который в кубе. Это изолированный раздел в рамках самого containerd. Для kubelet'а по умолчанию создается немспейс k8s, но вы можете создать другой, если вдруг нашли оркестратор получше или запускаете что-то руками.
-workdir определяет рабочую директорию для процесса, как ни странно.
-address и -containerd-binary указывают на сокет и бинарь containerd (а точнее, containerd-shim стучится в аргумент "containerd publish"), для того, чтобы уведомлять о состоянии контейнера в основной демон. Именно из-за этого, в случае рестарта containerd, шимы оперативно сообщат о своем состоянии и вы сможете наблюдать актуальную картину запущенных контейнеров без запуска всего с нуля для приведения к тому состоянию, которое от него требует kubelet.
Собственно, запуск контейнеров осуществляется не самим containerd, а через исполняемый файл рантайма, коих в наше время больше, чем кажется. Эталонным в наше время, как уже было отмечено, является runc, который и занимается, собственно, изоляцией или "контейнеризацией". Запускать контейнеры можно и напрямую через него (runc --help), однако при использовании containerd, runc list нам ничего не покажет. Это потому, что директория с информацией о запущенных контейнерах хранится в другом месте, в частности контейнеры бьются на каталоги соответствующие немспейсам containerd, например для куба это:
runc --root /run/containerd/runc/k8s.io/ list
Можете посмотреть состояние какого-нибудь контейнера, например:
root@kube03a:~# runc --root /run/containerd/runc/k8s.io/ state 2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644
Результат
{ "ociVersion": "1.0.2-dev", "id": "2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644", "pid": 28627, "status": "running", "bundle": "/run/containerd/io.containerd.runtime.v1.linux/k8s.io/2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644", "rootfs": "/run/containerd/io.containerd.runtime.v1.linux/k8s.io/2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644/rootfs", "created": "2021-07-05T02:22:28.018197224Z", "annotations": { "io.kubernetes.cri.container-name": "nginx", "io.kubernetes.cri.container-type": "container", "io.kubernetes.cri.image-name": "docker.io/library/nginx:latest", "io.kubernetes.cri.sandbox-id": "aae43202632ad129b71f6672c3ca089e76a399d6234d89cc08751b85645f31c6", "io.kubernetes.cri.sandbox-name": "nginx-7848d4b86f-xztfp", "io.kubernetes.cri.sandbox-namespace": "default" }, "owner": "" }
Но runc, за счет своей низкоуровневости, не самое лучшее место для просмотра состояния контейнеров. Containerd распологает двумя утилитами для взаимодействия пользователя с ним: crictl и ctr.
crictl является основной утилитой для взаимодействия с containerd. Помимо аналога действий, присущих docker cli (наподобие create, exec, images и тд), есть и более интересные. К примеру, containerd знает о существовании таких сущностей, как кубовые поды (runp, rmp, pods, stopp, inspectp). Попробую вкратце упомянуть некоторые интересные вещи. Если вдруг containerd демон запущен, а crictl ругается что не может найти его, укажите сокет напрямую, например:
crictl --runtime-endpoint /var/run/containerd/containerd.sock
Начнем с info:
root@kube03a:~# crictl info 2d538b1bdc00a | jq -r '.status'
Результат
{ "conditions": [ { "type": "RuntimeReady", "status": true, "reason": "", "message": "" }, { "type": "NetworkReady", "status": true, "reason": "", "message": "" } ] }
crictl inspect и inspectp выведет крайне много интересной информации. Описывать все это бессмыслено, да и все вполне очевидно. Например перечисляются маунты:
crictl inspect 2d538b1bdc00a | jq -r '.info.runtimeSpec.mounts[]'
...где сможем видеть сгенерированный resolv.conf
... { "destination": "/etc/resolv.conf", "type": "bind", "source": "/var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/aae43202632ad129b71f6672c3ca089e76a399d6234d89cc08751b85645f31c6/resolv.conf", "options": [ "rbind", "rprivate", "rw" ] } ...
Или сетевые устройства в поде:
crictl inspectp aae43202632ad | jq -r '.info.cniResult.Interfaces'
Результат
{ "cnio0": { "IPConfigs": null, "Mac": "3e:44:1a:e2:03:f0", "Sandbox": "" }, "eth0": { "IPConfigs": [ { "IP": "10.150.21.7", "Gateway": "10.150.21.1" } ], "Mac": "5a:fe:ec:a0:c2:59", "Sandbox": "/var/run/netns/cni-6070de8e-4e69-99c0-e619-63535af42ce5" }, "lo": { "IPConfigs": [ { "IP": "127.0.0.1", "Gateway": "" }, { "IP": "::1", "Gateway": "" } ], "Mac": "00:00:00:00:00:00", "Sandbox": "/var/run/netns/cni-6070de8e-4e69-99c0-e619-63535af42ce5" }, "veth335fa7aa": { "IPConfigs": null, "Mac": "de:30:14:fa:26:57", "Sandbox": "" } }
Из этого вывода или с помощью команды:
crictl inspectp aae43202632ad | jq -r '.info.runtimeSpec.linux.namespaces'
вы сможете обнаружить имя изолированного сетевого немспейса (это уже совсем-совсем другой немспейс)
... { "type": "network", "path": "/var/run/netns/cni-6070de8e-4e69-99c0-e619-63535af42ce5" } ...
Вбиваем
ip netns exec cni-6070de8e-4e69-99c0-e619-63535af42ce5 ip a show type veth
и получаем параметры сети в испектируемом поде.
Возвращаемся к crictl.
crictl stats -a вернет нам табличку с потребляемыми ресурсами (cpu, disk, mem, inodes), а флаг -o json вернет нам все еще и в json виде, на случай если вы вдруг что-то мониторите.
К слову, crictl imagefsinfo вернет вам что-то вроде:
{ "status": { "timestamp": "1626124762748935841", "fsId": { "mountpoint": "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs" }, "usedBytes": { "value": "1969491968" }, "inodesUsed": { "value": "77458" } } }
Еще мы можем, например, посмотреть на процесс внутри контейнера:
root@kube03a:~# cat "/proc/$(crictl inspect 2d538b1bdc00a | jq -r '.info.pid')/cmdline"
...вернет:
nginx: master process nginx -g daemon off;
А в корневой каталог попасть через /proc/$PID/root/
root@kube03a:~# cat "/proc/$(crictl inspect 2d538b1bdc00a | jq -r '.info.pid')/root/etc/hostname"
nginx-7848d4b86f-xztfp
Мы видим что все, что было в inpect в списке для монтирования, на этом этапе уже на своем месте.
Можем еще получить, например, id контейнера:
сrictl inspect 2d538b1bdc00a | jq -r '.status.id'
2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644
Через этот id мы можем найти каталог с его конфигами:
root@kube03a:~# ls /var/run/containerd/io.containerd.runtime.v1.linux/k8s.io/$(crictl inspect 2d538b1bdc00a | jq -r '.status.id')
address config.json init.pid log.json rootfs shim.pid
init.pid содержит уже известный нам pid процесса в контейнере, а shim.pid - pid родительского containerd-shim. В каталоге rootfs содержится собранный из снапшотов (слоев) overlayfs запущенного контейнера, но без дополнительных монтирований.
Если посмотреть на cgroups, то тут есть два варианта, в зависимости от выбранного драйвера cgroups. Если выбран драйвер systemd, то путь в cgroups_v1 будет примерно следующий:
root@kube03a:~# cat /sys/fs/cgroup/pids/system.slice/containerd.service/kubepods-besteffort-pod$(crictl inspectp aae43202632ad | jq -r '.status.metadata.uid' | sed 's/-/_/g').slice:cri-containerd:$(crictl inspect 2d538b1bdc00a | jq -r '.status.id')/cgroup.procs
28627 28664 28665
по человечески:
cat /sys/fs/cgroup/pids/system.slice/containerd.service/kubepods-besteffort-podc7205bb2_8c97_4f79_b4c9_915e402cc7d3.slice:cri-containerd:2d538b1bdc00a5f6251c9f47babca6163794a065133bcd2a0a0264a37a533644/cgroup.procs
crictl inspectp aae43202632ad | jq -r '.status.metadata.uid' - вернет нам uid пода, а crictl inspect 2d538b1bdc00a | jq -r '.status.id' - уже известный нам id контейнера. По аналогии можно обратиться к другим cgroups директориям.
Если же у вас драйвером выбран cgroups:
cat /sys/fs/cgroup/pids/kubepods/pod7d5e31f8-8797-457d-aaf2-f55464d338c6/eb2c3ad61742de7ed7a8758cc563a1470b969632f857429787668e1f354e357a/cgroup.procs
В реалтайме вы можете посмотреть cgroups через systemd-cgtop -m. Тут ведь все любят systemd, так?
Дошли наконец-таки до ctr
Для начала можно посмотреть доступные встроенные плагины.
ctr plugins ls - здесь можно посмотреть доступные снапшоттеры, которые составляют из слоев докер-образа конечный образ. Есть поддержа ZFS и BTRFS.
Далее смотрим доступные немспейсы containerd:
ctr namespaces ls
По умолчанию доступен немспейс "k8s.io". В дальнейших командах необходимо его явно указать: ctr --namespace=k8s.io containers ls
Так же можно посмотреть images (образы), events (отлов событий), content (бинарные данные из образов), snapshots (слои из образов), leases (аренда каких-либо ресурсов, подробней ), tasks (запущенные в контейнерах процессы).
Из необычного, вы можете взаимодействовать напрямую с shim или установить бинари и библиотеки из образа через crt install (это, видимо, для особо прогрессивных).
Напоследок ссылка на описание запуска контейнера через crictl для дебага.
