Об опыте перехода с on-premises на облачные Gitlab runners

от автора

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

Изначальный расчет был на то, что данное решение позволит существенно сократить обслуживание «железной» инфраструктуры. А вместе с ним 一 и расходы на IT-отдел в принципе. Дополнительно планировались привести в порядок текущий парк, который в тот момент насчитывал большое количество неуправляемых раннеров. Часть из них была просто забыта и заброшена, другая часть 一 в один прекрасный момент зависла и так и не пришла в себя. И, разумеется, никому не хотелось с этим разбираться, ведь гораздо проще было просто создать новый раннер. 

Все это выливалось в то, что длительность сборок проектов составляла по несколько часов 一 долго, нудно, тяжело и нерационально. 

Итого, наша мотивация для перехода в облако включала в себя:

  • Масштабирование в зависимости от нагрузки;

  • Значительное сокращение времени сборки;

  • Возможность оперативной адаптации под новый проект или задачу. 

Сравним текущее решение on-premises с облаком

On-premises не увеличивает ресурсы по щелчку пальцев: приходится поднимать виртуалочку, ставить туда раннер и регистрировать его в Gitlab. И только после этого танца с бубнами им можно наконец-то пользоваться. Другое дело облако, в котором все происходит автоматически, по запросу от Gitlab-менеджера.

Наш выбор и ход действий

В итоге пал на одного из крупных российских облачных провайдеров с его autoscale-группами. Немного поговорим и об его инфраструктуре и подготовке. 

Нами была создана сегментированная сеть с ограничением исходящего трафика, чтобы у раннеров не было прямого доступа в Сеть. Также был подготовлен образ Gitlab-менеджера(по совместительству и linux раннера) с заранее установленным плагином для беспроблемной интеграции с Облаком, а также образ Windows-раннера, настроенный средствами автоматизации конфигурации.

И вот у нас на руках, казалось бы, готовое решение. Но нет. Все оказалось не так просто.

После первого запуска и быстрого теста вылезла проблема 一 плагин переставал скейлить раннеры как вверх, так и вниз. В результате до них не доходили задачи с Gitlab, не создавались новые и не удалялись старые виртуалки. 

Анализ этой ситуации показал, что так происходит из-за того, что в конфигурации Gitlab-runner было описано сразу 2 группы раннеров: Windows и Linux. По какой-то причине автоскеллер сначала инициировал масштабирование Linux-виртуальных машин, после чего пытался их удалить, но терялся и начинал всю эту бессмысленную процедуру по новой. Итог был один: постоянная попытка заскейлиться в ту или иную сторону без какой-либо практической пользы. 

Мы решили это разделением 2-х групп раннеров используя 2 сервиса: отдельно зарегистрировали сервис для Windows и сервис для Linux.

concurrent = 10 check_interval = 0 connection_max_age = "15m0s" shutdown_timeout = 0   ######### # Linux runner group ######### [[runners]]   name = "gitlab-runner-linux-autoscaler"   url = "${url}"   id = ${id_linux}   token = "${token_linux}"   executor = "docker-autoscaler"   output_limit = 20480 # 20MB     [runners.feature_flags]     FF_USE_FLEETING_ACQUIRE_HEARTBEATS = true     [runners.cache]     Type = "s3"     Path = "/"     Shared = true     [runners.cache.s3]       ServerAddress = "storage.yandexcloud.net"       AccessKey = "${s3_bucket_ak}"       SecretKey = "${s3_bucket_sk}"       BucketName = "${s3_bucket_runner_cache}"       BucketLocation = "ru-central1"       Insecure = false     [runners.docker]     pull_policy = "if-not-present"     tls_verify = false     image = "alpine:latest"     privileged = true     disable_entrypoint_overwrite = false     oom_kill_disable = false     disable_cache = true # use s3 cache for runners     volumes = ["/cache","/var/run/docker.sock:/var/run/docker.sock"]     shm_size = 0     network_mtu = 0   # https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersautoscaler-section   [runners.autoscaler]     plugin = "fleeting-plugin-yc"     capacity_per_instance = 2     max_use_count = 100     max_instances = 5     instance_ready_command = "sudo cloud-init status --wait"     [runners.autoscaler.state_storage]     enabled = true     [runners.autoscaler.plugin_config]     name        = "gitlab-linux-runners"     folder_id   = "${folder_id}"     config_file = "/etc/gitlab-runner/template_linux.yml"     [runners.autoscaler.connector_config]     username = "ubuntu"   # https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersautoscalerpolicy-sections   [[runners.autoscaler.policy]]     idle_count = 1     idle_time  = "15m0s"     # periods    = ["* * * * sat-sun"]    concurrent = 10 check_interval = 0 connection_max_age = "15m0s" shutdown_timeout = 0   ######### # Windows runner group ######### [[runners]]   name = "gitlab-runner-windows-autoscaler"   url = "${url}"   id = ${id_windows}   token = "${token_windows}"   executor = "docker-autoscaler"   output_limit = 20480 # 20MB   builds_dir = "C:/builds"     [runners.feature_flags]     FF_USE_FLEETING_ACQUIRE_HEARTBEATS = true     FF_USE_POWERSHELL_PATH_RESOLVER = true     [runners.cache]     Type = "s3"     Path = "/"     Shared = true     [runners.cache.s3]       ServerAddress = "storage.yandexcloud.net"       AccessKey = "${s3_bucket_ak}"       SecretKey = "${s3_bucket_sk}"       BucketName = "${s3_bucket_runner_cache}"       BucketLocation = "ru-central1"       Insecure = false     [runners.docker]     pull_policy = "if-not-present"     tls_verify = false     image = "windows/servercore:ltsc2019"     # privileged = true     disable_entrypoint_overwrite = false     oom_kill_disable = false     disable_cache = false     volumes = ["//./pipe/docker_engine://./pipe/docker_engine"]     shm_size = 0     network_mtu = 0   # https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersautoscaler-section   [runners.autoscaler]     plugin = "fleeting-plugin-yc"     capacity_per_instance = 2     max_use_count = 100     max_instances = 5     [runners.autoscaler.state_storage]     enabled = true     [runners.autoscaler.plugin_config]     name        = "gitlab-windows-runners"     folder_id   = "${folder_id}"     config_file = "/etc/gitlab-runner/template_windows.yml"     [runners.autoscaler.connector_config]     os          = "windows"     protocol    = "ssh"     username    = "Administrator"   # https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersautoscalerpolicy-sections   [[runners.autoscaler.policy]]     idle_count = 1     idle_time  = "15m0s"     # periods    = ["* * * * sat-sun"]

И это сработало! Бонусом это внесло удобство и для администрирования: стало возможно спокойно зайти, перезагрузить нужную группу раннеров, не трогая вторую. 

Историю можно было бы на этом закончить, если бы проблема была одна. Другая заключалась в длительности сборок. Тут все было завязано на том, что пакеты и артефакты локально лежали на Gitlab-мастере, а скорости между ним и раннерами оставляли желать лучшего. 

Решено было переносить артефакты в объектное хранилище совместимое с S3 API, чтобы сократить скорость скачивания и скорость загрузки. В совокупности бы это позволило прокачать скорость сборки.

gitlab_rails['object_store']['enabled'] = true gitlab_rails['object_store']['proxy_download'] = false gitlab_rails['object_store']['connection'] = {   'provider' => 'AWS',   'endpoint' => '{{ gitlab_s3_endpoint }}',   'path_style' => true,   'region' => '{{ gitlab_s3_region }}',   'aws_access_key_id' => '{{ gitlab_s3_access_key }}',   'aws_secret_access_key' => '{{ gitlab_s3_secret_key }}' } gitlab_rails['object_store']['objects']['lfs']['enabled'] = false gitlab_rails['object_store']['objects']['artifacts']['bucket'] = '{{ gitlab_artifacts_s3_bucket }}' gitlab_rails['object_store']['objects']['packages']['bucket'] = '{{ gitlab_packages_s3_bucket }}'

Но и на этом не все. При сборке на Windows-раннерах были специфические окружения, например, тот же SDK. Все это приходилось пересобирать на Docker-контейнерах. 

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

Про интеграцию в пайплайны

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

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

Промежуточные результаты

Чем мы можем похвастаться на данный момент?

  • Часть команд перешла на использование облаков и отзываются о наших раннерах более, чем положительно: удобно, понятно и просто.

  • Часть же команды пока пребывает на старых раннерах. Тут все завязано на специфике проектов. За один подход это не решишь.

  • Нет полной картинки и возможности ее получить из-за регулярных изменений конфигурации автоскеллера, которые то увеличивают, то уменьшают инстанс — мы еще в поисках оптимальных значений для себя

Но уже сейчас можно сказать, что и нагрузка на IT-отдел, и затраты на обслуживание «железа» поползли вниз, что не могло не понравится руководству компании. 


ссылка на оригинал статьи https://habr.com/ru/articles/915696/