Что нужно знать перед тем, как работать с контейнерами

Рассказываем, почему все пользуются технологией контейнеризации и зачем знать про namespaces и системные вызовы при работе с контейнерами.

2К открытий9К показов

Все умеют работать с контейнерами, но мало кто понимает, как работают они сами. Это ограничивает возможности диагностики и управления программным обеспечением и средствами контейнеризации. DevOps-практики оказываются неэффективными, а безопасность просаживается. Разбираемся, на каких уровнях могут возникнуть ошибки, где можно зайти в тупик и что нужно знать, чтобы этого не произошло.

Почему мы пользуемся контейнерами

Разработчики пользуются библиотеками. Они позволяют не изобретать велосипед и пользоваться готовыми функциями.

К сожалению, если появляется новый дистрибутив, подтягиваются и новые библиотеки. Все они поставляются с определенными версиями ОС, и их нужно тестировать в различных средах. А также закрывать уязвимости, делать бэкпорт фич с учетом старых версий библиотек на более ранних дистрибутивах. И учитывать, что в разных дистрибутивах могут различаться стандарты размещения файлов — FHS.

Чаще всего ресурсов на поддержку такого «зоопарка» нет. Чтобы избежать постоянных тестов совместимости, слой библиотек передается вместе с docker image, из которого разворачивается тот самый контейнер. В нем есть всё необходимое в переносимом виде.

Чем технология контейнеризации отличается от виртуализации

У операционной системы есть абстракция-посредник между программами и оборудованием — ядро, которое обеспечивает API-вызовы. Происходит это через системные вызовы. Чаще всего они относительно низкоуровневые, надежные и примитивные. Например, «считай 10 байт, открой файл, считай 10 байт». Большинству программ этого достаточно.

Обратите внимание на эти системные вызовы: с помощью fopen можно смотреть, по какому пути открывается файл, а с clone(fork) и exec — видеть, как идут запуски тредов и внешних программ в контейнере.

Если, например, приложению приходится работать с аппаратными токенами, то нужны полноценные драйверы. Здесь может понадобиться виртуализация, где гипервизор host-OS позволяет запустить любое ядро с нужными драйверами.

Получается матрешка. Есть железо, на нем работает программа — ядро. Она создала процесс и запустила в нем гипервизор. Тот создал абстракцию железа, в которой запустилась программа-ядро. Оно создало процесс, в котором запустило программу. Не про это ли думал Нолан, когда снимал «Начало»?

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

Что почитать: «Linux. Системное администрирование», Р. Лав. В книге описаны системные вызовы, происходящие в Linux. Помогает понять, как работает ОС «с пользовательской точки зрения».

Что такое namespace

Ядро умеет изолировать программы друг от друга через процессы. Между ними изоляция осуществляется благодаря namespaces, которые позволяют отвечать на системные вызовы каждого приложения по-разному. Это как с билетами на концерт: по одному можно пройти на танцпол, по другому — в ложу, по VIP — пообщаться с артистом за кулисами.

С помощью Linux namespace мы показываем процессу, что ядро должно отдавать ресурсы из особой области, где распакованы docker image или лежат socket-файлы сетевых подключений.

Нужно понимать, за что отвечают такие namespaces:

  • Файловая система (Mount) — область файловой системы, которая предоставляется приложению как корневая.
  • UTS — собственный hostname.
  • PID — позволяет в различных пространствах давать одинаковые ID процессам.
  • Сети (Network) — позволяет изолировать ресурсы, связанные с сетью. При этом процессы из разных пространств видят свои сетевые устройства, таблицы маршрутизации, правила firewall и так далее.
  • Межпроцессное взаимодействие (IPC) — позволяет организовать межпроцессное взаимодействие.
  • Пользовательские ID (User) — позволяет иметь копию пользовательских и групповых идентификаторов.
  • Время (Time) — позволяет разным процессам видеть разное системное время или часовой пояс.

Процесс в среде разработки ведет себя немного по-другому, нежели чем в контейнере после сборки. Обработка тех же системных вызовов и набор ресурсов, которые в итоге система вернет процессу при запросе, могут отличаться. Например, процесс запрашивает список пользователей, а он может быть вынесен в другой namespace. Или система может выдать другое дерево файловой системы, потому что базовый образ файловой системы для этого docker image от другого дистрибутива.

Также на удаленке многие разработчики начали работать в других временных зонах. Часто при дебаге тестировщики не могут найти ошибки в соседних системах из-за разницы временных меток в логах разработчика и системы.

Что почитать: «Внутреннее устройство Linux», Д. Кетов. В книге рассматриваются основные подсистемы ядра и их сущности: файлы и файловые системы, виртуальная память и отображаемые файлы, процессы, нити и средства межпроцессного взаимодействия, каналы, сокеты и разделяемая память.

Посмотрим на конкретных примерах, какие проблемы можно отследить.

Кейс №1: выросло System time

Процесс может потреблять большое количество процессорного времени. Можно диагностировать это стандартными средствами. Посмотреть на цифры в выводе команды TOP:

  • US( user time);
  • SY (system time);
  • WA (wait);
  • IO wait и так далее.

Если разработчик не понимает, за что эти цифры отвечают, он решит, что накосячил где-то в коде. Хотя на самом деле процесс может потреблять много system time.

Пример из практики: информационная безопасность экспериментировала с журналами аудита и добавила дополнительные правила. По графикам системы мониторингов Grafana нагрузка на сервере подскочила процентов на 30. В этот же день был релиз фронтенд-приложения. Естественно, все решили, что фронт начал атаковать бэкенд, и именно он потребляет больше ресурсов даже не на пике.

Первая идея — искать, какие REST-вызовы дали нагрузку. На сервере мы видим, что на самом деле выросло SY. В процессе разработчики поняли, что были добавлены новые правила аудита, и решили их убрать. Тогда нагрузка вернулась к обычным стандартным значениям.

Что почитать: «Сетевые операционные системы», В. Олифер, Н. Олифер. В книге рассматриваются концепции и принципы построения большинства современных ОС.

Кейс №2: ошибка в документации

Команда писала приложение, в котором использовались криптографические подписи. После сборки и упаковки в Image, все перестало работать, это типичная ситуация. Ошибка говорит, что недоступен файл, который точно должен быть доступен. Базовый образ минималистичен, утилит диагностики в себе не содержит. Для разработчика это тупиком.

Проблема решилась обычным strace на компьютере разработчика, в котором довольно быстро удалось выудить ошибку системного вызова fopen. Оказалось, в документации криптобиблиотеки ошибка, и монтировать файлы с закрытым ключом нужно по другому пути. Разработчик же пользовался графическим интерфейсом криптобиблиотеки, и о путях знал только из документации.

Что в итоге

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

Следите за новыми постами
Следите за новыми постами по любимым темам
2К открытий9К показов