Как стать автором
Обновить

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

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

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

Изначальный расчет был на то, что данное решение позволит существенно сократить обслуживание «железной» инфраструктуры. А вместе с ним 一 и расходы на 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-отдел, и затраты на обслуживание «железа» поползли вниз, что не могло не понравится руководству компании. 

Теги:
Хабы:
+5
Комментарии3

Публикации

Работа

Ближайшие события