Как стать автором
Поиск
Написать публикацию
Обновить
518.82
Сбер
Технологии, меняющие мир

Оптимизируем системные ресурсы при развёртывании за счёт перехода на динамику

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров1.8K

Всем привет! Если в компании растёт количество продуктов, а для их развёртывания используются виртуальные машины, то рано или поздно возникает задача оптимизации ресурсов. Скажем, вы используете для оркестрации Jenkins. Количество агентов на ВМ при этом статично, а количество развёртываний в разное время разное. В этом случае при массовых установках агенты периодически упираются в установленный лимит исполнителей (executor), а в свободные часы ВМ простаивают, занимая ресурсы.

Мы, команда Run4Change в СберТехе, сопровождаем тестовые среды. В наши задачи входит в том числе развёртывание продуктов облачной платформы Platform V на стендах для последующего тестирования. Расскажем, как мы решили проблему использования системных ресурсов и отказались от виртуальных машин в пользу cloud‑native‑решения. Статья может быть полезна тем, кто планирует начать использование динамических агентов Jenkins, и может использоваться как первоначальное руководство.

Проблема баланса «затраты‑производительность»

Обычно для развёртывания мы используем набор конвейеров, которые оркеструются с помощью Jenkins. До 2022 года используемый нами КТС для развёртывания выглядел так:

  1. Контроллер Jenkins на отдельном хосте (виртуальная машина) под управлением Red Hat Enterprise Linux.

  2. Набор агентов, каждый на отдельной виртуальной машине с ОС RHEL.

Сами агенты были практически идентичны как по ресурсам, так и по набору установленного на них ПО.

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

Увеличивать количество виртуальных машин для агентов было нецелесообразно, а после 2022 года RHEL к тому же перестала поддерживаться в России. Всё это стало толчком для решительных действий: мы поняли, что нужно не только оптимизировать ресурсы, но и менять RHEL на другую ОС.

Решение рядом: переезжаем с ВМ на динамические агенты

Альтернатива ОС от RedHat была очевидна. В 2023 году в СберТехе появилась собственная операционная система Platform V OS SberLinux. Jenkins — как сам контроллер, так и агент — это Java‑приложение, поэтому переход с одной ОС на другую выглядел тривиальной задачей. А с учётом того, что Platform V SberLinux в своей основе совместим с RHEL, нам не нужно было даже менять набор пакетов для установки.

Виртуальные машины решили заменить динамическими агентами, работающими в кластере Platform V DropApp, совместимом с Kubernetes. Это должно было решить описанные недостатки при использовании виртуальных машин. Агент создаётся по запросу мастера Jenkins и уничтожается сразу после завершения конвейера, освобождая используемые ресурсы. Количество запускаемых агентов ограничивается только ресурсами самого кластера Platform V DropApp.

При этом дополнительно можно гарантировать, что на одном агенте одновременно работает только один конвейер. Это обеспечивает изолированность разных развёртываний друг от друга, что является дополнительным преимуществом и минимизирует возникновение потенциальных «коллизий».

Собираем образ, разбираемся с параметрами

Работа динамических агентов в Jenkins реализуется с помощью плагина Kubernetes. Динамический агент инициируется запросом от контроллера Jenkins через плагин в сторону API‑сервера кластера Platform V DropApp. По сути, в кластер передаётся полностью готовый манифест ресурса PodTemplate со всеми необходимыми для работы параметрами, включая имя агента, URL контроллера, секрет для подключения и другие параметры.

По этому шаблону в кластере создаётся под, который после запуска инициирует подключение к контроллеру Jenkins. Как только агент подключился к контроллеру, начинается обычное взаимодействие по JNLP‑протоколу, как и с обычным статическим агентом. По окончании задания контроллер инициирует удаление пода, отправляя команду в API‑сервер кластера.

Итак, у нас есть работающий контроллер Jenkins и чистый кластер Platform V DropApp. В первую очередь нам необходим образ агента. Создадим Dockerfile для сборки образа.

# В качестве базового образа возьмем образ SberLinux
FROM localregistry/sblnxos/container-8-ubi-sbt:8.8.2-189
#В базовый образ требуется установить необходимое ПО:
# - Java Development Kit для запуска агента Jenkins
# - Python для выполнения кода деплоя
# - Git для работы с Source Code Management
# - Вспомогательные утилиты (jq, zip, unzip, gcc, rsync, gettext и т.д.)
# Полный набор необходимого ПО:
RUN yum -y install glibc-langpack-ru glibc-langpack-en java-17-openjdk openssh git sudo openssl sshpass time jq wget zip unzip python36 python36-devel gcc rsync gettext
#Если используется стороннее зеркало с модулями Python, как у нас, то следует не забыть #принести информацию о нем, например, через определение зеркала в /etc/pip.conf (https://pip.pypa.io/en/stable/topics/configuration/). Копируем свой pip.conf в образ.
COPY add/pip.conf /etc 
#Дальше устанавливаем модули для Python. 
RUN pip3 install --no-deps ansible==2.9.24 asn1crypto==0.24.0 certifi==2021.10.8 cffi==1.12.3 charset-normalizer==2.0.10 cryptography==2.8 cssselect==0.9.1 Genshi==0.7 html5lib==0.999999999 hvac==0.11.2 idna==3.3 Jinja2==2.11.0 jmespath==0.10.0 lxml==4.4.2 MarkupSafe==2.0.1 pycparser==2.19 pycrypto==2.6.1 pyOpenSSL==18.0.0 python-ntlm==1.1.0 PyYAML==6.0 requests==2.27.1 six==1.12.0 urllib3==1.26.8 webencodings==0.5.1 dnspython==1.16.0
#Добавим пользователя, от которого будет запускаться агент, и его рабочий каталог
RUN useradd -u 1000 jenkins && mkdir -p /u01/jenkins && chown -R jenkins:jenkins /u01/jenkins
#Cкопируем файл агента в образ. 
COPY add/agent.jar /usr/share/java
#Следующие шаги будут выполняться в окружении пользователя jenkins
USER jenkins
# Определим переменные окружения:
ENV HOME=/home/jenkins
ENV JAVA_HOME=/usr/lib/jvm/jre/
ENV LANGUAGE=en_US:en
ENV LANG=en_US.UTF-8
ENV AGENT_WORKDIR=/u01/jenkins
ENV TZ=Europe/Moscow
ENV ANSIBLE_HOST_KEY_CHECKING=False
#И наконец команда для запуска процесса агента:
ENTRYPOINT [‘java -cp / usr/share/java/slave.jar -headless $TUNNEL $URL $WORKDIR $OPT_JENKINS_SECRET $OPT_JENKINS_AGENT_NAME "$@"’]

Мы специально используем опцию ‑no‑deps, чтобы запретить пакетам приносить зависимости других версий. Правда, в этом случае требуемый набор зависимостей нужно установить здесь же самим.

Файл клиента agent.jar «прибиваем гвоздями» в образе. Вообще, версия агента должна соответствовать версии контроллера Jenkins. Актуальный для этой версии контроллера агент всегда можно получить по адресу ${JENKINS_URL}/jnlpJars/agent.jar. Но так как нам важна стабильность, мы не обновляем Jenkins при каждом его релизе, и необходимость актуализации агента в образе возникает не чаще раза в квартал.

Пересборка образа с новым агентом занимает от силы минут 5. Поэтому вариант выкачивания актуального образа напрямую с контроллера при каждом запуске пода мы отмели для экономии времени и ресурсов.

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

docker build. ‑t dockerregistry/jenkins/sbel‑agent:p3

Далее нам нужно «подружить» контроллер с кластером Platform V DropApp. На стороне кластера потребуется завести учётку (Service Account) и роль (Role), и связать их (RoleBinding). Описание манифестов можно взять из примера.

Применим файл с манифестами:

kubectl ‑f service‑account.yml

Токен для Service Account можно сгенерировать следующим YAML‑манифестом:

apiVersion: v1
kind: Secret
metadata:
name: jenkins-secret
annotations:
kubernetes.io/service-account.name: jenkins
type: kubernetes.io/service-account-token

И также применить его:

kubectl ‑f jenkins‑secret.yaml

Сам токен можно получить, выполнив команду:

kubectl describe secret jenkins‑secret

Для скачивания образа кластером Platform V DropApp из Docker‑репозитория требуется создать секрет типа kubernetes.io/dockerconfigjson — это обычный JSON‑конфиг для Docker, который можно создать и сразу же применить такой конструкцией:

kubectl create secret docker-registry dockerregistry-secret --docker-server=dockerregistry --docker-username=$DOCKER_USER --docker-password=$DOCKER_PASSWORD --docker-email=$DOCKER_EMAIL -o yaml | kubectl apply -f -

На стороне кластера DropApp работы завершены. Переходим к настройке контроллера Jenkins. Переходим по пути «Настроить Jenkins — Nodes — Clouds — New cloud». Указываем любое имя, выбираем тип «Kubernetes» и жмём «Создать»:

На следующем экране раскрываем детали и указываем URL API‑сервера кластера Platform V DropApp, пространство имён кластера, при использовании HTTPS указываем ключ сертификата (Kubernetes server certificate key) или же вообще запрещаем проверку сертификатов (Disable https certificate check).

В «Credentials» нужно добавить токен, сгенерированный на шаге подготовки кластера Platform V DropApp. Жмём «+Add» и в глобальном домене для учётных данных добавляем запись с типом «Secret text»: сам токен в поле Secret, его идентификатор (ID) и описание, если надо.

Остальные параметры можно не заполнять или оставить со стандартными значениями. После сохранения параметров можно зайти в созданное облако и проверить соединение с кластером через кнопку «Test connection».

Далее переходим в раздел «Pod templates» и создаём шаблон пода динамического агента.

Добавляем контейнер в шаблон пода через «Add Container»:

Name — имя, обязательно.

Namespace — пространство имён в Platform V DropApp. Необязательное, будет использовано пространство, указанное в общих настройках облака.

ImagePullSecrets — имя секрета в кластере, который содержит учётные данные для извлечения Docker‑образа из репозитория (значение dockerregistry-secret в примере выше).

Label — метка агента, использующаяся для связи задачу Jenkins и агента.

Name — имя контейнера, исторически и для обратной совместимости это «jnlp».

Docker image — образ контейнера, в нашем примере это ранее нами созданный dockerregistry/jenkins/sbel‑agent:p3.

Always pull image — рекомендую всегда использовать эту опцию. Она соответствует строке манифеста шаблона пода imagePullPolicy: Always.

При отсутствии опции политика пуллинга будет IfNotPresent: скачивание образа при его отсутствии на воркер‑ноде кластера. И в этом случае можно столкнуться с тем, что на стороне кластера будет использоваться ранее кешированный на воркере образ агента, который может не соответствовать актуальной версии собранного образа.

При установленной опции даже в случае большого образа длительность из‑за скачивания не увеличится. Реальное скачивание произойдёт только в том случае, если дайджест кешированного на воркере образа не будет соответствовать дайджесту текущего актуального образа в docker‑registry.

Working directory — рабочий каталог на агенте с полным доступом для пользователя в образе, от которого запущен процесс агента.

Command to run — можно не указывать, потому что в образе мы использовали инструкцию ENTRYPOINT, которая и будет запускать процесс на агенте.

Arguments to pass to the command — а эта опция важна, потому что через неё передаются «агентозависимые» параметры, использующиеся для подключения агента к контроллеру, такие как:

  • ${computer.name} — имя агента;

  • ${computer.jnlpmac} — секрет для подключения агента к контроллеру (вычисляется контроллером по алгоритму на основе имени агента). Эта строка заменит собой специальный параметр $@ при вызове ENTRYPOINT образа.

Есть ещё множество параметров, которые можно заполнить в шаблоне пода или контейнера в Jenkins. Все они будут транслированы плагином в соответствующие ключи манифеста шаблона пода или контейнера. При незаполненном значении параметры будут отсутствовать в шаблоне, а значит заполнятся уже на стороне Platform V DropApp стандартными значениями.

Обратите внимание на параметр Raw YAML for the Pod и сопутствующий ему параметр Yaml merge strategy. Они позволяют принести в шаблон пода любой, даже неопределённый в плагине параметр. Достаточно дописать YAML‑фрагмент, который необходимо слить с манифестом шаблона, а также выбрать стратегию слияния: Override (переопределить) или Merge (объединить).

При заполнении важно соблюсти необходимое количество пробелов, чтобы результирующий YAML‑файл в конечном итоге был корректен. В качестве примера приведём добавление в контейнер реквестов и лимитов и монтирование ConfigMap через параметр Raw YAML for the Pod:

spec:
  containers:
    - resources:
        limits:
          cpu: '4'
          memory: 8Gi
        requests:
          cpu: '4'
          memory: 8Gi
      name: jnlp
      volumeMounts:
        - name: config
          mountPath: /u01/config
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: cm-jenkins

Проверяем результат

Ну и, наконец, проверка работы. Создадим в Jenkins тестовый джоб (New Item) типа Pipeline со следующим скриптом:

pipeline {
    agent {
        label('sbel-agent-p3')
    }
    stages {
        stage('Testing of dynamic agent') {
            steps {
                sh ('echo "Hello from dynamic agent $HOSTNAME"')
            }
        }
    }
}

Запустим задачу и посмотрим результат в выводе консоли Jenkins:

Из журнала видно, что на основе шаблона агента с именем sbel‑agent‑p3 в кластере PlatformVDropApp создался под с именем sbel‑agent‑p3-k7rsr. Jenkins‑агент, запущенный в поде, соединился с контролером Jenkins, получил от него задание выполнить указанную в конвейере команду. По завершении задачи под удалился, освободив системные ресурсы.

Вместо заключения

Сейчас мы полностью отказались от агентов на виртуальных машинах и используем исключительно реализацию на динамике. Каких преимущества это даёт:

  • Динамические агенты позволяют легко масштабировать систему в зависимости от нагрузки.

  • Динамические агенты обеспечивают эффективно управлять ресурсами, позволяя останавливать и запускать контейнеры по мере необходимости.

  • Контейнеры имеют свои собственные настройки и ограничения и изолированы друг от друга.

В дальнейшем рассматриваем возможность переноса контроллера Jenkins с виртуальной машины в кластер Platform V DropApp. Например, в случае запуска большого количества параллельных развёртываний может снижаться производительность и самого контролера. В таком случае в этот период можно запускать дополнительный экземпляр контроллера для распределения нагрузки между ними.

Надеемся, что статья была вам полезна! Если появились вопросы о технических деталях, которые мы могли упустить в статье, добро пожаловать в комментарии.

Теги:
Хабы:
Всего голосов 7: ↑7 и ↓0+12
Комментарии1

Информация

Сайт
www.sber.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия