Когда в пятый раз у тебя появляется на работе падаван, которому надо все рассказать по нескольку раз, в какой-то момент приходит в голову светлая мысль все свои речи законспектировать, попутно хоть немного структурировав все это дело. Так что сия заметка о с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 для дебага.