Шаблон рабочей нагрузки серверов CI/CD существенно отличается от других типовых шаблонов нагрузки. Причина лежит в характере рабочей нагрузки, которую создают приложения, выполняющиеся на этих серверах. В рамках процессов CI/CD существует два основных типа рабочей нагрузки:
- сборка, тестирование и развертывание артефактов;
- выполнение развернутых сред тестирования для веток Git, коммитов и конфигураций.
Обычно лучше разделять эти нагрузки так, чтобы они выполнялись на разных серверах. Основная предпосылка в том, что рабочая нагрузка, создаваемая процессами сборки, стремится утилизировать максимальное количество доступных вычислительных ресурсов (CPU, IO) для быстрого выполнения задачи. Когда на том же сервере размещаются и развернутые приложения, разработчики должны применять ограничивающие политики для защиты развернутых окружений от нехватки ресурсов, вызванной чрезмерным использованием серверных ресурсов во время выполняения процессов сборки и тестирования.
В современном инфраструктурном подходе, использующем выполнение приложений в контейнерах с использованием Docker, этого легко достичь, ограничив число ядер CPU для задачи сборки. В то же время, среды CI/CD позволяют ограничить параллелизм для выполнения задач сборки, организуя их в очередь. Однако, это ведет к неэффективному ограничению вычислительных ресурсов и может привести к более длительным сборкам. Поэтому, прежде всего постарайтесь разделить эти две нагрузки. Если вы используете такие системы, как Kubernetes, Docker Swarm или DC/OS, или выделяете серверы сборки динамически иным способом, то, при корректно спроектированных кластерах, вы скорее всего не столкнетесь с проблемой, когда задачи сборки приводят к нехватке ресурсов на серверах выполнения развернутых сред.
Серверы сборки
Предположим, вы решили выделить отдельные серверы для задач сборки. Рассмотрим, что необходимо сделать для обеспечения наилучшей производительности.
Локальный кэширующий реестр Docker. Если ваш процесс сборки основан на Docker, то, как правило, вы тратите некоторое время на скачивание слоев. Использование локального кэша помогает значительно сократить время сборки. Помните, что пока не скачаны слои, система сборки не сможет собрать артефакты, и конвейер CI/CD будет блокирован для выполнения других задач.
Локальный реестр Docker. Если для развертывания вы используете артефакты, загружаемые из репозиториев, то локальные репозитории артефактов помогают существенно ускорить как процесс загрузки так и процесс развертывания. Это особенно важно, если вы разворачиваете приложение как контейнерезированное приложение Docker, а для развертывания веток, коммитов, окружений используете теги. Помните, что пока артефакт не загружен в репозиторий, задача CI/CD продолжает выполняеться, а сервер сборки не может взять для исполнения следующую задачу.
Кэширующий прокси сервер HTTP. Современные приложения зависят от множества сторонних библиотек. Если вы когда-нибудь видели, как Maven собирает проект, вы представляете, что каждый проект Java скачивает большое количество файлов JAR, которые формируют дерево зависимостей. То же самое происходит в Node, Python, Ruby и других популярных средах разработки. Так как зависимости скачиваются с различных серверов, которые могут подвергаться атакам, испытывать большие нагрузки или на них могут проводиться технические работы, это может привести к тому, что сборка будет проходить гораздо медленнее или завершиться с ошибками (все могут вспомнить как РКН блокировал AWS, GCE и другие облачные сервисы в процессе охоты на Telegram). Локальный кэш HTTP находится в быстрой локальной сети и контролируется вашей организацией, что позволяет выполнять системе CI/CD сборку даже в том случае, когда серверы-источники, на которых размещаются зависимости, не доступны.
Локальный кэш HTTP можно формировать как в режиме online, например, с помощью прокси-сервера Squid, так и в режиме offline, с помощью организации контента на HTTP-сервере, например Nginx, и переопределения DNS-запросов с серверов CI/CD.
Быстрый и вместительный локальный кэш файлов. Выделите быстрое и объемное дисковое пространство для кэширования зависимостей локально. Можно использовать сервер NFS или сервис кэширования GitLab CI, который позволяет обойтись без загрузки зависимостей из сети. Подключите серверы сборки к кэшу, используя соединение со скоростью от 1 Гбит/с, чтобы гарантировать быстрый доступ к артефактам. Подумайте об использовании FS-Cache NFS для уменьшения количества операций чтения в NFS.
Быстрая локальная файловая система и ядра CPU. Процесс сборки в основном зависит от производительности CPU и файловой системы. Это означает, что если вы хотите, чтобы сборки проходили быстрее, следует подумать об использовании серверов со множеством ядер с высокой частотой ядра и хранилища со скоростными SSD-накопителями. Если нет возможности использовать хранилище со скоростными SSD-накопителями, подумайте об установке большего объема оперативной памяти, так как она используется в качестве буферного кэша, что поможет уменьшить число операций чтения файловой системы. Не используйте продвинутые файловые системы с CoW-архитектурой, такие, как ZFS, BTRFS, отдавайте предпочтение файловым системам EXT4 или XFS.
Эти шаги помогут ускорить сборки, значительно уменьшив время ожидания сети и файловой системы для конвейера CI/CD.
Серверы развертывания
Требования к серверам выполнения тестовых и промежуточных сред обычно отличаются от требований к серверам, на которых выполняются задачи сборки. Обычно на них развернуто много различных окружений, особенно когда используется современный процесс CI/CD, при котором происходит развертывание всех веток, коммитов, конфигураций, а доступ к ним осуществляется через специально спроектированный механизм маршрутизации, который может основываться на IP-адресах, доменных именах или маршрутизации L7 (NGINX, Traefik, Kong или HA Proxy).
Характерной особенностью серверов выполнения тестовых и промежуточных сред является наличие большого количества процессов, которые занимают оперативную память и дисковую память, но большую часть времени находятся в режиме ожидания. Например, ветка X развернута, но ждет, пока подразделение QA запустит регрессионные тесты, или предрелизная ветка Y развернута, но ожидает проведения демонстрации для заинтересованных лиц проекта. В то же время, для части из развернутых веток может выполняться пакет автоматизированных тестов.
Чтобы отвечать ожиданиям, серверы тестовой и промежуточной среды обычно требуют умеренного количества ресурсов CPU, но большого количества RAM и являются достаточно дорогими, поскольку облачные поставщики IaaS/PaaS стараются предоставлять сбалансированные вычислительные предложения, таким образом, вы будете нести дополнительные затраты за избыточные ресурсы CPU. Используя серверы в собственной инфраструктуре, вам также придется приобретать серверы в нестандартных конфигурациях, когда довольно большой объем памяти сочетается с умеренными ресурсами CPU.
Малоизвестно, но часто данные ограничения можно преодолеть, используя специальные механизмы сжатия RAM и современные высокоскоростные устройства NVMe. Рассмотрим эти инструменты более подробно. Все решения предназначены для использования в среде GNU/Linux. Для других операционных систем могут использоваться аналогичные подходы, если они в них реализованы.
Оптимизация компонентов инфраструктуры. Рассмотрим проблему развертывания компонентов инфраструктуры, таких как DBMS, брокеров очередей сообщений, поисковых систем, балансировщиков нагрузки и других системных компонентов. Как правило, нет смысла разворачивать отдельный экземпляр такой системы для каждой развернутой ветки, коммита, среды. Следует подумать о разработке сценария автоматического развертывания инфраструктурных компонентов, но такого, чтоб он не был встроен в конвейер CI/CD, а использовался разово для инициализации сервера. Для соотнесения объектов, создаваемых в рамках инфраструктурных компонентов, со средами используйте префиксы или пространства имен. Например, для имени базы данных можно использовать префикс <branch>_dbname
, чтобы использовать в каждой ветке отдельную базу данных. Это правильный подход, потому что он опирается на две идеи: нет необходимости тестировать надежные сторонние компоненты инфраструктуры, а единый экземпляр инфраструктурного компонента позволяет экономить RAM, дисковое пространство и ресурсы CPU.
Предлагаемый подход обычно не ограничивает разработчиков в части развертывания, выполняемого с помощью CI/CD. Для правильного применения необходимо создать отдельный скрипт для установки инфраструктурных компонентов с возможностью очистки и повторной установки инфраструктуры, чтобы быть увереным, что все аспекты развертывания под контролем. Для улучшения процесса и распространения знаний необходимо в каждой итерации назначать нового релиз-менеджера, которому предыдущий релиз-менеджер поможет развернуть инфраструктуру и приложения.
Оптимизация приложений. Среда Linux предоставляет несколько эффективных механизмов сжатия RAM, которые можно использовать для повышения плотности размещения приложений в памяти сервера. Дедупликация и сжатие производится четырьмя способами:
- дедупликация RAM для файлов;
- дедупликация разделяемых объектов;
- дедупликация общей системной памяти;
- обобщенный механизм сжатия RAM.
Дедупликация RAM для файлов. Дедупликация RAM работает в случаях, когда конкретные файловые объекты используются несколькими процессам в режиме “только чтение”. Известная оптимизация данного типа используется системой Docker, когда хранилище образов настроено для использования драйверов OverlayFS2, ZFS или Aufs, но она не работает с драйверами BTRFS и device-mapper.
Дедупликация разделяемых объектов. Ядро Linux дедуплицирует данные объектов кода, предназначенных только для чтения, если они собраны в форме динамически разделяемых библиотек (*.so). Такая дедупликация полностью автоматизирована, производится подсистемой управления процессами и не требует какой-либо настройки. Данная оптимизация также работает для процессов в контейнерах Docker, но она не будет работать для виртуальных машин под управлением KVM и других систем гипервизоров.
Дедупликация общей системной памяти. Linux поддерживает технологию KSM (Kernel same page merge), которая находит страницы с одним и тем же содержимым и дедуплицирует их, что ведет к высвобождению памяти. Чем больше объектов в RAM использует один и тот же исходный образ, тем больше страниц может быть объединено, и меньше памяти будет использовано. Так как серверы CI/CD обычно отвечают этим критериям, использование KSM может быть эффективно для оптимизации памяти, особенно если приложения запущены в изолированных гостевых VM.
Обобщенный механизм сжатия RAM. Новые версии ядра Linux предоставляют два механизма, которые можно использовать для сжатия памяти – zSwap и zRAM. Они похожи по своей сути, но могут быть использованы в разных случаях. zSwap реализует сжатие страниц, которые вытеснены в область подкачки, и занимает определенное количество памяти для динамического пула, который используется как первый и самый быстрый уровень swap-системы. zRAM используется для создания сжатого диска в оперативной памяти (ramdrive), который можно использовать как устройство для файловой системы или как высокоскоростное запоминающее устройство для swap. И zSwap, и zRAM используют высокоэффективные алгоритмы сжатия LZO, LZ4, LZ4HC или deflate для сжатия и декомпрессии данных. LZ4 дает лучшие результаты с точки зрения скорости сжатия и декомпрессии, LZO более эффективно сжимает данные. zSwap и zRAM помогают легко увеличить количество оперативной памяти в два раза и более для систем CI/CD, особенно когда используется малое количество из развернутых веток, а процессы в большинстве веток находятся в режиме ожидания.
Когда zSwap или zRAM используются для подкачки, основное запоминающее устройство, которое обычно реализуется с помощью SSD, должно работать очень быстро, чтобы при запросе вытесненных на него страниц они быстро восстанавливались в RAM. Подумайте об использовании современных устройств NVMe, которые используют традиционные SSD-технологии или память Intel Optane.
Мы проводили тесты с использованием накопителей Intel Optane 900p и получили великолепные результаты для виртуальной памяти, которая превышала физически доступную RAM в 4 раза.
Заключение
Полное и ясное понимание процессов CI/CD, которые используются в проекте, помогает реализовать эффективное и экономичное использование ресурсов сервера CI/CD, повысить плотность развертывания и существенно уменьшить затраты на инфраструктуру. Чтобы использовать вышеупомянутые практики, инженеры DevOps должны понимать экосистему Linux и уметь производить оценку, чтобы выявить полезность каждого используемого подхода.
Если статья вам понравилась и была для вас полезной, поделитесь ей с друзьями.