Всем привет! Я - Кирилл, 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.
Заключение
Как видите, решение не требует каких-то серьезных усилий. По сути уже есть все нужные компоненты, их требуется только собрать вместе.
На данный момент мы только начинаем использовать такой подход и не имеем эмпирических данных для того, чтобы оценить, насколько динамические раннеры улучшат скорость обработки очереди в пиковые периоды, в отличии от текущей реализации. В будущем мы обязательно поделимся своими результатами и попробуем оценить эффективность такого подхода.
Если у вас был опыт использования динамических раннеров в публичных облаках, то с радостью готовы обсудить это в комментариях.
