Настраиваем динамические Gitlab Runners в Yandex Cloud

Всем привет! Я - Кирилл, DevOps компании sports.ru. Не так давно мы начали процесс переезда в Yandex Cloud, хочу рассказать, как это было.

Параллельно мы стали искать, где еще мы можем применить сильные стороны публичного облака. Сразу вспомнилась давняя проблема с периодическими всплесками активности разработчиков, которая приводила к исчерпанию gitlab runners и, следовательно, к долгому ожиданию в очереди. Раньше для решения этой задачи нужно было добавить новый сервер для раннеров. Но, поскольку, это было эпизодически, горизонтальное масштабирование было нецелесообразным. А вот Яндекс Облако дает нам возможность быстро получить ресурсы на короткий период. В Gitlab подобный функционал реализуется через Docker Machine Executor.

Стоит отметить, что Docker Machine сейчас находится в deprecated статусе, но команда gitlab продолжает поддерживать свой форк, по этому можно считать это решение вполне надежным. Однако, в будущем autoscale будет реализован на собственной технологии gitlab, но сроков перехода на данный момент нет. 

Подготовительный этап

Перед установкой runner будет несколько подготовительных шагов. 

В первую очередь, нам нужна утилита Docker Machine. Тут достаточно скачать последнюю версию бинарного файла с репозитория Gitlab и разметить его по пути /usr/bin

Для работы с Yandex Cloud нам так же потребуется официальный драйвер который тоже нужно разметить в /usr/bin. Тут стоит отметить, что название файла драйвера крайне важно, как и права на него, он должен быть исполняемым. 

Последним подготовительным этапом будет генерация "Авторизованного ключа" в Яндекс Облаке. Для начала создаем служебный аккаунт с правами "compute.admin", если для вашего раннера вдруг потребуется еще и внешний IP адрес, добавьте аккаунту права "vpc.admin". Далее генерируем ключ, сделать это можно через UI или через утилиту yc. Последний вариант более удобен, сама команда выглядит следующим образом yc iam key create --service-account-name <service-account-name> --output key.json --folder-id <folder-id>. На выходе получаем json, который стоит держать в зашифрованном виде, например в ansible vault. 

Установка gitlab runner

Итак, как же нам получить авто-масштабируемый раннер? Для начала потребуется поднять виртуальную машину на которой будет установлен GitlabRunner. Для этих целей мы используем готовую ansible-роль debops.gitlab_runner, которая служила верой и правдой долгие годы, но в этом случаи подвела. 

Мейнтейнеры забросили развитие роли (последний коммит был в 2018 году) и, ожидаемо, появились неподдерживаемые параметры, в частности, устарел "Off Peak time mode" и появился отдельный раздел в настройке ранера: "runners.machine.autoscaling". Именно по этому пришлось форкнуть роль и попутно разметить ее у нас на github.

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

gitlab_runner__machine_autoscaling:
  - period: "* * 7-19 * * mon-fri *"
    idel_count: 0
    idel_time: 600
    timezone: "UTC"

Особое же внимание стоит обратить на настройку драйвера для Яндекс Облака.

gitlab_runner__machine_idle_count: 0
gitlab_runner__machine_idle_time: 900
gitlab_runner__machine_name: "auto-scale-%s"
gitlab_runner__machine_driver: "yandex"
gitlab_runner__machine_options: ["yandex-sa-key-file=/etc/gitlab-runner/key.json", "yandex-folder-id={{ yc_qa__folder_id }}", "yandex-cloud-id={{ yc_cloud_id }}", "yandex-subnet-id={{ yc_qa__subnet_id }}", "yandex-use-internal-ip=true", "yandex-image-family=ubuntu-2004-lts", "yandex-cores=4", "yandex-disk-type=network-ssd", "yandex-memory=8", "yandex-preemptible=true"]

gitlab_runner__machine_idle_count - Количество раннеров которые должны быть всегда доступны; 0 означает, что раннер будет запускаться только по требованию.

gitlab_runner__machine_idle_time - Время (в секундах) до того, как раннер будет удален; отсчитывается от последней выполненной джобы. 

gitlab_runner__machine_name - имя автоматически заданной VM.

gitlab_runner__machine_driver - имя драйвера для Docker Machine.

gitlab_runner__machine_options - список передаваемых параметров.

У драйвера достаточно много параметров, их список и значения можно посмотреть в официальном репозитории. Остановимся только на нескольких из них. Ключ, который мы сгенерировали на предварительном этапе, нужно указать в параметре yandex-sa-key-file. Если инстанс gitlab находится в одной сети с раннером, то можно использовать только внутренний IP адрес, для этого указываем yandex-use-internal-ip=true. В противном случае нужно указать yandex-nat=true, тогда VM получит белый IP адрес. И еще один параметр, который стоит указать, yandex-preemptible=true, это позволит создавать "прерываемые машины", которые значительно дешевле. 

После настройки и прокатки роли мы получим раннер. А его конфигурационный файл будет выглядеть примерно так:

concurrent = 50

[[runners]]
  name = "gitlab-runner-test-autoscale"
  url = "https://gitlab-test.test.ru/"
  token = "TOKEN"
  environment = [ "DOCKER_BUILDKIT=1", "DOCKER_DRIVER=overlay2" ]
  limit = 10
  executor = "docker+machine"
  [runners.docker]
    image = "docker:dind"
    privileged = true
    disable_cache = false
    cache_dir = "/home/gitlab-runner/cache"
    cap_drop = [ "NET_ADMIN", "SYS_ADMIN", "DAC_OVERRIDE" ]
    volumes = [ "/var/run/docker.sock:/var/run/docker.sock", "/home/gitlab-runner/cache" ]
  [runners.machine]
    IdleCount = 0
    IdleTime = 600
    MaxBuilds = 100
    MachineName = "auto-scale-%s"
    MachineDriver = "yandex"
    MachineOptions = [
      "yandex-sa-key-file=/etc/gitlab-runner/key.json",
      "yandex-folder-id=<ID>",
      "yandex-cloud-id=<ID>",
      "yandex-subnet-id=<ID>",
      "yandex-use-internal-ip=true",
      "yandex-image-family=ubuntu-2004-lts",
      "yandex-cores=4",
      "yandex-disk-type=network-ssd",
      "yandex-memory=8",
      "yandex-preemptible=true"
    ]
            [[runners.machine.autoscaling]]
          Periods = ['* * 7-19 * * mon-fri *']
          IdleCount = 2
          IdleTime  = 1800
          Timezone  = "UTC"
            [[runners.machine.autoscaling]]
          Periods = ['* * * * * sat,sun *']
          IdleCount = 0
          IdleTime  = 600
          Timezone  = "UTC"

Исходя из настроек, в буднии дни с 7 до 19 Gitlab будет держать на "горячем старте" две VM. А вот ночью и в выходные ради экономии все виртуалки будут удаляться. Кроме того время их ожидания сократится до 600 секунд. В случае большого наплыва задач раннер сможет создать до 10 виртуальных машин (определяется параметром limit = 10), которые будут удалены, если на эти раннеры больше не придет заданий в течении 30 минут. Следует отметить, что на создание одной машины уходит от 2 до 3 минут, они добавятся к общему времени выполнения pipeline. 

Заключение

Как видите, решение не требует каких-то серьезных усилий. По сути уже есть все нужные компоненты, их требуется только собрать вместе. 

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

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

@GoooodBoy
17.11.2022 02:44 UTC
Первоисточник

Комментарии

@AlexGluck
16.11.2022 23:01 UTC
-3

КДПВ напоминает аббревиатуру СВО, немного напрягает мои нежные инженерные чувства.

@kolossradosskiy
07.05.2023 18:23 UTC
+1

>В будущем мы обязательно поделимся своими результатами и попробуем оценить эффективность такого подхода.

Не поделитесь ли информацией, насколько динамические раннеры улучшили работу и уменьшили расходы на облачного провайдера?

@GoooodBoy
07.05.2023 18:36 UTC
+1

Да, пока еще не успели подвести итоги и вывести какие-то конкретные цифры.

Если кратко, то:

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

  2. По деньгам тут сложно сказать. Но динамические раннеры точно дешевле, поскольку работают только когда нужно. Так например, ночью и в выходные они соверешенно не тратят деньги. Плюс они коротко живущие, у нас каждый раннер живет не более 30 минут, если на него не прилетает нового задания.

08.05.2023 07:13 UTC
0

Я не виду у вас настройки распределённого кеша (да и сама идея распределённого кеша смущает). Учитывая, что раннеры теперь живут по 30 минут — точно ли стало быстрее?


Для долгоживущих-то раннеров можно node_modules между сборками сохранять...

08.05.2023 07:43 UTC
0

Распределенный кэш уже позже добавлял, после написания статьи. Реализовано через "файловое хранилище" (аля NFS от Яндекс Облако). Правда пришлось дописывать docker-machine провайдер от яндекса(пока не хватает времени завершить МР), чтобы динамические раннеры умели при создании подхватывать и монтировать ФХ. В результате как раз у раннеров, где собирается nodejs есть общая папка, которая используется для хранения разнообразного кэша или общих файлов.