Сбор метрик для мониторинга работы контейнеров в среде Docker

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

Данная статья является переводом англоязычной статьи из официальной документации Docker. В процессе перевода мы постарались упростить статью.

Отслеживание статистики с помощью docker stats

Для отслеживания метрик среды выполнения контейнера в режиме реального времени можно использовать команду docker stats. Команда отображает статистику использования ресурсов контейнерами и поддерживает такие метрики, как утилизация CPU, памяти, ограничения на использование памяти, а так же метрики для сети и блочного IO.

Ниже приведен пример вывода команды docker stats:

$ docker stats redis1 redis2

CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O
redis1              0.07%               796 KB / 64 MB        1.21%               788 B / 648 B       3.568 MB / 512 KB
redis2              0.07%               2.746 MB / 64 MB      4.29%               1.266 KB / 648 B    12.4 MB / 0 B

Более подробно о команде вы сможете прочитать на странице docker stats

Контрольные группы

Контейнеры в Linux реализованы с использованием контрольных групп, которые не только позволяют задать ограничения для групп процессов, но также предоставляют метрики использования CPU, памяти, блочного ввода-вывода. Помимо этих метрик, можно также получить статистику использования сети. Все это относится как к контейнерам LXC, так и к контейнерам Docker.

Контрольные группы реализуются через специальную псевдофайловую систему. В последних версиях дистрибутивов вы можете найти данную файловую систему в каталоге /sys/fs/cgroup, для некоторых дистрибутивов может использоваться другой путь. В этой файловой системе вы увидите множество подкаталогов, которые называются devices, freezer, blkio и другие; каждый подкаталог соответствует отдельной иерархии контрольных групп.

Чтобы найти точку монтирования, к которой у вас подключены контрольные группы, воспользуйтесь командой:

$ grep cgroup /proc/mounts

Перечисление cgroups

Чтобы определить известные системе подсистемы контрольных групп и статистику групп в них, просмотрите файл /proc/cgroups.

$ cat /proc/cgroup

#subsys_name	hierarchy	num_cgroups	enabled
cpuset	         2	            1	       1
cpu	             3	          122	       1
cpuacct	         3	          122	       1
blkio	        10	          122	       1
memory	        11	          226	       1
devices	         8	          123	       1
freezer	         6	            2          1
net_cls	        12	            1	       1
perf_event	     7	            1	       1
net_prio        12	            1	       1
hugetlb	         9	            1	       1
pids	         4	          134	       1
rdma	         5	            1	       1

Вы можете просмотреть файл /proc/<pid>/cgroup, чтобы определить, к каким контрольным группам принадлежит процесс. Контрольная группа отображается как путь от директории монтирования иерархии. / означает, что процесс не был назначен конкретной группе, а /lxc/pumpkin указывает на то, что процесс принадлежит контейнеру pumpkin.

Поиск контрольной группы конкретного контейнера

Для каждого контейнера в каждой иерархии создается одна cgroup. В ранних системах с более ранними версиями пользовательских утилит LXC, имя контейнера является именем контрольной группы. В более поздних версиях инструментов LXC, имя группы соответствует lxc/<container_name>.

Для контейнеров Docker, именем контейнера является полный ID контейнера. Если контейнер отображается в docker ps как ae836c95b4c3, его полный ID может быть, например, таким: ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79. Его можно узнать с помощью docker inspect docker ps --no-trunc или docker ps --no-trunc.

Если обобщить все сказанное выше, то, к примеру, метрики памяти контейнера Docker находятся в каталоге /sys/fs/cgroup/memory/docker/<longid>/.

Метрики из cgroup: память, CPU, блочный I/O

Для каждой подсистемы (память, CPU и блок ввода-вывода) есть один или несколько псевдофайлов со статистикой.

Метрики памяти

Метрики памяти находятся в контрольной группе “memory”. Эта группа создает дополнительную небольшую нагрузку на систему, потому что производит подробный учет использования памяти на вашем хосте. Таким образом, во многих дистрибутивах ее по умолчанию отключают. Как правило, чтобы ее включить, достаточно добавить параметры командной строки ядра: cgroup_enable=memory swapaccount=1.

Метрики сохраняются в псевдофайл memory.stat и выглядят следующим образом:

cache 11492564992
rss 1930993664
mapped_file 306728960
pgpgin 406632648
pgpgout 403355412
swap 0
pgfault 728281223
pgmajfault 1724
inactive_anon 46608384
active_anon 1884520448
inactive_file 7003344896
active_file 4489052160
unevictable 32768
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 11492564992
total_rss 1930993664
total_mapped_file 306728960
total_pgpgin 406632648
total_pgpgout 403355412
total_swap 0
total_pgfault 728281223
total_pgmajfault 1724
total_inactive_anon 46608384
total_active_anon 1884520448
total_inactive_file 7003344896
total_active_file 4489052160
total_unevictable 32768

Первая половина показателей, без префикса total_, содержит статистику процессов, относящихся к контрольной группе, не включая подгруппы. Вторая половина метрик, с префиксом total_, включает в себя итоговую статистику, включая подгруппы.

Некоторые показатели отражают текущие значения, то есть могут увеличиваться или уменьшаться. Например, swap – размер файла подкачки, используемого участниками контрольной группы. Другие показатели являются “счетчиками”, то есть значениями, которые могут только увеличиваться и сбрасываться, потому что они подсчитывают некоторые события. Например, pgfault указывает количество промахов страниц виртуальной памяти, которые произошли с момента создания группы.

Метрики CPU

Метрики использования ресурсов процессора для каждого контейнера содержится в псевдофайле cpuacct.stat. Они аккумулируют использование процессора всеми процессами контейнера и разбиты по времени по нахождению процессов в пространстве пользователя и пространстве ядра:

  • user – период времени, в течение которого процесс использует процессор для выполнения непривилегированного кода процесса;
  • system – период времени, в течение которого ядро выполняет системные вызовы от имени процесса.

Время подсчитывается в единицах, равных 1/100 секунды, также известных как jiffies или тики. Количество тиков в секунду определяется переменной USER_HZ, и в системах x86 USER_HZ равен 100. Раньше это количество соответствовало количеству тиков планировщика в секунду, но с повышением частоты и появлением бестиковых ядер показатель стал полностью синтетическим.

Метрики блочного I/O

Метрики блочного ввода/вывода учитываются в подсистеме blkio. В разных файлах сохраняются разные метрики. Подробнее о них можно узнать из документации ядра. Ниже представлен краткий список наиболее часто используемых метрик:

Метрика Описание
blkio.sectors Содержит количество 512-байтовых секторов, считанных и записанных процессами контрольной группы для каждого устройства. Операции чтения и записи учитываются в одном показателе.
blkio.io_service_bytes Определяет количество байт, считанных и записанных контрольной группой. Имеет 4 счетчика для каждого устройства, поскольку для каждого устройства отдельно учитываются синхронные и асинхронные операции ввода/вывода, а также операции чтения и записи.
blkio.io_serviced Число произведенных операций ввода/вывода, вне зависимости от их размера. Также имеет 4 счетчика для каждого устройства.
blkio.io_queued Определяет количество запросов ввода/вывода, инициированных контрольной группой, которые на данный момент поставлены в очередь.

Метрики сети

Метрики сети напрямую не учитываются контрольными группами. Этому есть хорошее объяснение: сетевые интерфейсы существуют в контексте сетевых пространств имен. Ядро могло бы собирать метрики о количестве пакетов и байтов, отправленных и полученных группой процессов, но эти показатели были бы бесполезны. Пользователю обычно нужны метрики по каждому интерфейсу. Но так как процессы в отдельной контрольной группе могут относиться к множеству сетевых пространств имен, эти показатели будет сложно интерпретировать – множество сетевых пространств имен означает множество интерфейсов lo, множество интерфейсов eth0 и так далее; поэтому собрать показатели сети с контрольных групп нелегко.

Вместо этого можно собирать метрики сети из других источников.

IPtables

Достаточно подробный учет могут делать IPtables (или фреймворк netfilter, для которого iptables является просто интерфейсом).

Например, можно задать правило учета исходящего HTTP-трафика на веб-сервере:

$ iptables -I OUTPUT -p tcp --sport 80

Здесь нет флага -j или -g, поэтому правило считает только соответствующие пакеты и переходит к следующему правилу.

Затем, можно проверить значения счетчиков с помощью:

$ iptables -nxvL OUTPUT

Счетчики учитывают пакеты и байты. Если необходимо задать такие метрики для трафика контейнера, можно выполнить цикл for, чтобы добавить две цепочки iptables для каждого IP-адреса контейнера в цепочке FORWARD.

Затем нужно периодически проверять эти счетчики. Если вы используете collectd, есть хороший плагин для автоматизации сбора учетных данных с IPtables.

Счетчики на уровне интерфейса

Так как каждый контейнер имеет виртуальный интерфейс Ethernet, возможно, вам захочется проверить напрямую счетчики TX и RX этого интерфейса. Каждый контейнер в вашем хосте связан с виртуальным интерфейсом Ethernet с именем, например, vethKk8Zqi. К сожалению, сложно определить, какой интерфейс к какому контейнеру относится.

На сегодня лучше всего проверять метрики из контейнеров. Для этого вы можете запустить исполнительный файл из среды хоста в сетевом пространстве имен контейнера, используя ip-netns.

Команда ip-netns exec позволяет выполнить программу в любом сетевом пространстве имен, доступном текущему процессу. Это значит, что ваш хост может войти в сетевое пространство имен контейнера.

Формат команды следующий:

$ ip netns exec <nsname> <command...>

Например:

$ ip netns exec mycontainer netstat -i

ip-netns находит контейнер mycontainer с помощью псевдофайлов пространств имен. Каждый процесс относится к одному сетевому пространству имен, одному пространству имен PID, одному пространству имен mnt и так далее. Эти пространства имен описываются в /proc/<pid>/ns/*. Например, сетевое пространство имен PID 42 отображается в псевдофайл /proc/42/ns/net.

При запуске ip netns exec mycontainer ... ожидается, что /var/run/netns/mycontainer будет одним из таких псевдофайлов. (Допускаются ссылки).

Иными словами, для выполнения команды внутри сетевого пространства имен контейнера необходимо:

  • Определить PID любого процесса в контейнере, данные которого мы хотим получить;
  • Создать ссылку из /var/run/netns/<somename> к /proc/<thepid>/ns/net;
  • Выполнить ip netns exec <somename> .....

Заново просмотрите раздел «Перечисление групп» для того чтобы понять, как найти контрольную группу, для которой вы хотите хотите измерить статистику сети. Затем вы можете просмотреть псевдофайл с именем «tasks» в контрольной группе, который содержит все PID в группе (и, следовательно, в контейнере). Выберите любой из PID.

В общем, если “короткий ID” контейнера содержится в переменной окружения $CID, то можно сделать следующее:

$ TASKS=/sys/fs/cgroup/devices/docker/$CID*/tasks
$ PID=$(head -n 1 $TASKS)
$ mkdir -p /var/run/netns
$ ln -sf /proc/$PID/ns/net /var/run/netns/$CID
$ ip netns exec $CID netstat -i

Советы для более эффективного сбора статистики

Запускать новый процесс каждый раз, когда нужно обновить показатели, может быть довольно затратно. Если вы хотите собирать показатели с большой частотой и/или с большого количества контейнеров (например, 1000 контейнеров на одном хосте), не следует каждый раз создавать новый процесс.

Вы можете собирать метрики с помощью одного процесса, написав обработчик для сбора метрик на С или любом другом языке, который дает возможность выполнять низкоуровневые системные вызовы. Необходимо использовать специальный системный вызов, setns(), который позволит текущему процессу попасть в пространство имен. Для данного системного вызова необходимо наличие открытого файлового дескриптора для псевдофайла пространства имен (речь идет о псевдофайле в /proc/<pid>/ns/net).

Не оставляйте дескриптор открытым после сбора метрик, поскольку пока существует последний процесс контрольной группы, будут существовать и пространство имен, а его сетевые ресурсы (например, виртуальный интерфейс контейнера) не будут освобождены.

Правильнее каждый раз при сборе статистики для некоторого контейнера заново открывать псевдофайл пространства имен при необходимости.

Сбор метрик после завершения контейнера

Иногда нет необходимости отслеживать метрики в режиме реального времени, но когда контейнер уже остановлен, требуется понять сколько CPU, памяти и других ресурсов он использовал.

Для Docker это довольно сложно реализовать, что связано с использованием lxc-start, которая тщательно удаляет все, относящееся к контейнеру после вызова. Обычно проще собирать метрики через равные промежутки времени. Именно так работает LXC-плагин collectd. Но если все же необходимо собрать статистику после остановки контейнера, то это можно сделать так:

Для каждого контейнера запустите процесс сбора статистики, и переместите его в те контрольные группы, которые вы хотите отслеживать, записав PID процесса в файл tasks контрольной группы. Процесс сбора метрик должен периодически перечитывать файл tasks, чтобы проверить, является ли данный процесс последним в контрольной группе. Если нужно также собрать статистику сети, как описано в предыдущем разделе, то необходимо перенести процесс в соответствующее сетевое пространство имен.

Когда контейнер будет остановлен, lxc-start попытается удалить контрольные группы. У нее это не получится это сделать, так как контрольные группы еще используются нашим процессом сбора метрик. Теперь ваш процесс должен определить, что он является единственным процессом, оставшимся в группе, и собрать метрики для контейнера.

Наконец, процесс должен вернуться в корневую контрольную группу и удалить контрольную группу контейнера. Чтобы ее удалить, используйте rmdir. После очистки можно безопасно завершить процесс сбора статистики.

Если статья вам понравилась и была для вас полезной, поделитесь ей с друзьями.