Всем привет! Я - Кирилл, 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. 

Заключение

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

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

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

Комментарии (1)


  1. AlexGluck
    17.11.2022 02:01
    -1

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