Ограничения ресурсов с помощью cgroups

Для того чтобы использовать cgroups вовсе необязательно использовать для этого systemd, docker или k8s. Для этого достаточно использовать набор команд, для ubuntu находящийся в пакете `cgroup-tools` и виртуальную файловую систему /sys.

Создадим cgroup с ограничением по памяти в 10Мб используя следующую команду (для cgroups v1):

cgcreate -g memory:cg1
echo 10M > /sys/fs/cgroup/memory/cg1/memory.limit_in_bytes

В случае cgroups v2:

cgcreate -g memory:cg1
echo 10M > /sys/fs/cgroup/cg1/memory.max

Для того чтобы протестировать ограничение по выделению памяти, написал небольшую программу на golang. Это оказалось не совсем тривиально, поскольку простой вызов make не даст сразу выделения памяти, поскольку сработает оптимизация, поэтому присваивается значение в ячейку памяти. Более того, чтобы оптимизация не сработала нужно, чтобы компилятор не знал заранее (на этапе компиляции) о том, в какую именно ячейку будет происходить присвоение. Поэтому индекс этой ячейки вычисляется как произвольное число.

package main

import (
  "math/rand"
  "time"
)

const k = 1024
const m = k * 1024

func main() {
  N := 20_000
  time.Sleep(3 * time.Second)
  a := make([]byte, N * m)
  a[rand.Intn(N * m)] = 1
}
go mod init a
go build

Такого рода игрушечные программы полезны при тестировании systemd файлов, чтобы проверить, что они корректно рестартуют исполняемый файл при достижении лимита по памяти. Так же, можно эмулировать поведение программы при старте, используя timer.Sleep(ms), что довольно удобно.

Теперь применим созданную cgroup к тестовой программке

sudo cgexec -g memory:cg1 ./a

В результате получите ошибку "out of memory" и завершение программы

В syslog вы увидите работу oom киллера

tail -n 100 /var/log/syslog

Так же, можно посмотреть, насколько снизится производительность программки при достижении лимита cpu.

Создадим еще одну небольшую программу для создания нагрузки (есть специальные бенчмарки для нагрузки CPU, но тут я решил размять руки):

Ссылка на gist

Данная программа при запуске создает некоторое количество блоков памяти, соединенных связным списком и заполняет их случайными байтами. Затем в 10 горутинах происходит вычисление simhash хеша (он довольно сильно нагружает процессор). В основном потоке происходит раз в 2 секунды выводится на экран информация о количестве операций в секунду.

Создадим еще один cgroup slice cg2 с параметрами cpu и переопределим cpu.max для использования 0.5 CPU

cgcreate -g cpu:cg2
echo '50000 100000' > /sys/fs/cgroup/cg2/cpu.max

Для запуска команды так же, как и в первом случае используем cgexec:

sudo cgexec -g cpu:cg2 ./readInf

На моем лаптопе (8 logical cpu cores) результаты выполнения программы следующие:

  • До применения ограничения
  • После применения

Можно поиграться с этим параметром, добиваясь оптимальных показателей производительности и потребления ресурсов (зависимость даже в этом простейшем случае нелинейная).