Эта структура хранится в /sys/fs/cgroup/, а для Kubernetes интерес представляет часть под названием kubepods.slice.
/sys/fs/cgroup/
└── kubepods.slice/
├── kubepods-besteffort.slice/
│ └── ...
├── kubepods-guaranteed.slice/
│ └── ...
└── kubepods-burstable.slice/
└── kubepods-burstable-pod<SOME_ID>.slice/
├── crio-<CONTAINER_ONE_ID>.scope/
│ ├── cpu.weight
│ ├── cpu.max
│ ├── memory.min
│ └── memory.max
└── crio-<CONTAINER_TWO_ID>.scope/
├── cpu.weight
├── cpu.max
├── memory.min
└── memory.max
Все Kubernetes cgroups находятся в подкаталоге kubepods.slice/, который делится на kubepods-besteffort.slice/, kubepods-burstable.slice/ и kubepods-guaranteed.slice/ для каждого типа качества обслуживания (QoS). В этих подкаталогах находятся папки для каждого Pod, а внутри них — папки для каждого контейнера.
На каждом уровне расположены файлы, такие как cpu.weight или cpu.max, указывающие, сколько ресурса (например, CPU) может использовать группа. Эти значения соответствуют запросам и лимитам ресурсов, указанным в манифестах Pod-ов. Однако на практике значения в этих файлах могут отличаться от прямых запросов и лимитов.
Наконец, на «листах» дерева находятся файлы, которые описывают, сколько памяти (memory.min и memory.max), CPU (cpu.weight и cpu.max) или других ресурсов выделено каждому контейнеру. Эти файлы представляют собой прямую интерпретацию запросов и ограничений на ресурсы, указанных в манифестах Pod. Однако, если заглянуть в эти файлы, значения могут показаться не связанными с запросами и ограничениями. Тогда что означают эти значения и откуда они берутся?
Давайте рассмотрим каждый этап, чтобы понять, как запросы и лимиты Pod-а передаются и отображаются в файлах директории /sys/fs/….
Мы начнем с простого определения Pod, в котором указаны запросы и лимиты на память и CPU.
# kubectl apply -f pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: webserver
name: webserver
spec:
containers:
- image: nginx
name: webserver
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Когда мы создаем или применяем манифест Pod, Pod назначается на Node. Kubelet на этом Node берет спецификацию Pod (PodSpec) и передает её интерфейсу Container Runtime (CRI), например, containerd или CRI-O. CRI переводит эту спецификацию на низкоуровневый формат OCI JSON, который описывает создаваемый контейнер.
{
"status": {
"id": "d94159cf8228addd7a29beaa3f59794799e0f3f65e856af2cb6389704772ffee",
"metadata": {
"name": "webserver"
},
"image": {
"image": "docker.io/library/nginx:latest"
},
"imageRef": "docker.io/library/nginx@sha256:ab589a3...c332a5f0a8b5e59db"
},
"info": {
"runtimeSpec": {
"hostname": "webserver",
"linux": {
"resources": {
"memory": {
"limit": 134217728,
"swap": 134217728
},
"cpu": {
"shares": 256,
"quota": 50000,
"period": 100000
},
"pids": { "limit": 0 },
"hugepageLimits": [{ "pageSize": "2MB", "limit": 0 }],
"unified":{
"memory.high": "107374182",
"memory.min": "67108864"
}
},
"cgroupsPath":
"kubepods-burstable-pod6910effd_ea14_4f76_a7de_53c333338acb.slice:crio:d94159cf8228addd7a29b...389704772ffee"
}}}}
Как видно из вышеописанного, спецификация включает cgroupsPath, где будут находиться файлы cgroup. В разделе info.runtimeSpec.linux.resources уже содержатся переведенные запросы и лимиты ресурсов. Затем спецификация передается на более низкий уровень — в OCI-контейнерный рантайм, например, runc, который взаимодействует с драйвером systemd, создавая для контейнера systemd unit и устанавливая значения в файлах на cgroupfs.
Для первоначального просмотра systemd-unit контейнера:
# Find the container ID:
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
029d006435420 ... 6 minutes ago Running webserver 0 72d13807f0ab1 webserver
# Find the slice and scope
systemd-cgls --unit kubepods.slice --no-pager
Unit kubepods.slice (/kubepods.slice):
├─kubepods-burstable.slice
│ ├─kubepods-burstable-pod6910effd_ea14_4f76_a7de_53c333338acb.slice
│ │ └─crio-029d0064354201e077d8155f2147907dfe8f18ef2ccead607273d487971df7e0.scope ...
│ │ ├─6166 nginx: master process nginx -g daemon off;
│ │ ├─6212 nginx: worker process
│ │ └─6213 nginx: worker process
│ ├─kubepods-burstable-pod3fee6bda_0ed8_40fa_95c8_deb824f6de93.slice
│ └─ ...
│ └─...
│ └─...
└─...
└─...
└─...
└─...
systemctl show --no-pager crio-029d0064354201e077d8155f2147907dfe8f18ef2ccead607273d487971df7e0.scope
MemoryMin=0
MemoryMin=67108864
MemoryHigh=107374182
MemoryMax=134217728
MemorySwapMax=infinity
MemoryLimit=infinity
CPUWeight=10
CPUQuotaPerSecUSec=500ms
CPUQuotaPeriodUSec=100ms
...
# More info https://github.com/opencontainers/runc/blob/main/docs/systemd.md
Для начала находим ID контейнера с помощью crictl ps, аналогично docker ps для CRI. В выводе команды видим наш Pod webserver и ID контейнера. Далее используем systemd-cgls, чтобы рекурсивно отобразить содержимое контрольных групп. В его выводе находим группу с ID нашего контейнера, например, crio-029d006435420.... Наконец, с помощью systemctl show --no-pager crio-029d006435420... получаем свойства systemd, использованные для установки значений в файлах cgroup.
Для дальнейшего исследования самой файловой системы cgroups:
cd /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/
ls -l kubepods-burstable-pod6910effd_ea14_4f76_a7de_53c333338acb.slice
...
-rw-r--r-- 1 root root 0 Dec 3 11:46 cpu.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 cpu.weight
-r--r--r-- 1 root root 0 Dec 3 11:46 hugetlb.2MB.current
-rw-r--r-- 1 root root 0 Dec 3 11:46 hugetlb.2MB.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 io.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 io.weight
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.high
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.low
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.min
...
cat .../cpu.weight # 10
# $MAX $PERIOD
cat .../cpu.max # 50000 100000
cat .../memory.min # 67108864
cat .../memory.max # 134217728
Мы переходим в директорию kubepods-burstable-pod6910effd_ea14_4f76_a7de_53c333338acb.slice, указанную в выводе systemd-cgls, где находим файлы cgroup для всего Pod webserver. Ключевые файлы и значения:
Дополнительные файлы в cgroup не настраиваются через Pod манифесты.
Если вы хотите разобраться самостоятельно, альтернативным/более быстрым способом найти эти значения может быть получение пути к cgroupfs спецификации среды выполнения контейнера, упомянутой ранее:
POD_ID="$(crictl pods --name webserver -q)"
crictl inspectp -o=json $POD_ID | jq .info.runtimeSpec.linux.cgroupsPath -r
# Output (a path to Pod's pause container):
# kubepods-burstable-pod6910effd_..._53c333338acb.slice:crio:72d13807f0ab1a3860478d6053...745a50e5e296ddd7570e1fa9
# Translates to
ls -l /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod6910effd_..._53c333338acb.slice/
-rw-r--r-- 1 root root 0 Dec 3 11:46 cpu.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 cpu.weight
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.high
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.low
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.max
-rw-r--r-- 1 root root 0 Dec 3 11:46 memory.min
...
Мониторинг использования ресурсов также осуществляется через cgroups с помощью компонента cAdvisor, встроенного в kubelet. cAdvisor собирает метрики потребления ресурсов, которые можно просматривать как значения из файлов cgroups.
Для получения метрик:
# Directly on the node:
curl -sk -X GET "https://localhost:10250/metrics/cadvisor" \
--key /etc/kubernetes/pki/apiserver-kubelet-client.key \
--cacert /etc/kubernetes/pki/ca.crt \
--cert /etc/kubernetes/pki/apiserver-kubelet-client.crt
# Remotely using "kubectl proxy"
kubectl proxy &
# [1] 2933
kubectl get nodes
NAME STATUS ROLES AGE VERSION
some-node Ready control-plane 7d v1.25.4
curl http://localhost:8001/api/v1/nodes/some-node/proxy/metrics/cadvisor
Независимо от того, какой вариант используете, вы получите огромный список показателей, который будет выглядеть примерно так.
Вот некоторые из наиболее интересных показателей, которые вы там найдете:
# Memory limit defined for that container (memory.max)
container_spec_memory_limit_bytes{
container="webserver",
id="/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod691...38acb.slice/crio-d94159cf...04772ffee.scope",
image="docker.io/library/nginx:latest",
name="k8s_webserver_webserver_default_6910effd-ea14-4f76-a7de-53c333338acb_1",
namespace="default",
pod="webserver"} 1.34217728e+08
# If the CPU limit is "500m" (500 millicores) for a container and
# the "container_spec_cpu_period" is set to 100,000, this value will be 50,000.
# cpu.max 1st value
container_spec_cpu_quota{...} 50000
# The number of microseconds that the scheduler uses as a window when limiting container processes
# cpu.max 2nd value
container_spec_cpu_period{...} 100000
# CPU share of the container
# cpu.weight
container_spec_cpu_shares{...} 237
В завершение, все настройки и переводы из Pod манифестов можно суммировать в таблице, связывающей параметры манифеста с файлами cgroupfs.
Почему стоит разбираться в работе cgroups в Kubernetes? Несмотря на то, что Linux и Kubernetes делают большую часть работы за нас, глубокое понимание помогает при отладке и дает доступ к продвинутым функциям. Вот несколько примеров:
Эти примеры лишь отражают часть возможностей, которые открывает использование cgroups. В будущем, вероятно, появится еще больше функций для управления ресурсами в Kubernetes, например, регулировка дисковых операций, сетевого I/O и управления нагрузкой на ресурсы.
Хотя работа Kubernetes может показаться "магией", за многими процессами стоит грамотное использование возможностей Linux, как мы видим на примере управления ресурсами. Знание работы cgroups полезно не только для операторов и пользователей, но и помогает эффективно решать сложные задачи и применять продвинутые функции для стабильной работы кластера.