- PVSM.RU - https://www.pvsm.ru -

Всем привет! Меня зовут Александр, я обучаюсь в магистратуре СПбПУ. А заодно являюсь младшим разработчиком на C++ и стараюсь использовать и внедрять практики DevOps в мою ежедневную разработку. Недавно я получил зачет за то, что развернул собственный GitLab (именно GitLab, а не аналог) на серверах Selectel [1] с CI/CD и Container Registry. Собственно, об этом и расскажу и в статье.
Мы в Selectel готовим новый сервис. Если арендуете серверы в рабочих или личных проектах, нам очень поможет ваш опыт — записывайтесь [2] на короткое онлайн-интервью. За участие подарим плюшевого Тирекса и бонусы на услуги Selectel.
Используйте навигацию, если не хотите читать текст полностью:
→ Задача и инструменты [3]
→ Поднимаем GitLab [4]
→ Запуск GitLab Runner [5]
→ Тестируем Франкенштейна [6]
→ Выводы [7]
В процессе работы необходимо:
На выходе на руках должны оказаться список созданных учетных записей для потока студентов и полностью функционирующие GitLab и GitLab Runner. Все нужно описать кодом для дальнейшего переиспользования.
Для описания выделения ресурсов будем использовать Terraform. Он позволяет в формате кода разворачивать цифровые ресурсы. С точки зрения переиспользования конфигурации инфраструктуры это может быть очень полезным инструментом.
Для описания конфигурации серверов будем использовать Ansible. С его помощью можно настраивать созданные серверы путем описания конфигурационного файла с описанием необходимых компонентов.
Действия по развертыванию можно повторить самому, используя проект, опубликованный на GitHub [8].

Перед этим шагом необходимо выделить публичный IP и зарегистрировать на него домен нашего будущего GitLab.
После того как публичный IP зарегистрирован, заходим в панель управления Selectel [9] → Продукты → Облачные серверы.

Нажимаем Создать сервер.

Кликаем по полю Источник, переходим на вкладку Приложения и выбираем Cloud Gitlab 16.11.10 64-bit.

Использование готового образа поможет нам избежать ручной настройки БД и ручного конфигурирования GitLab. Кроме того, в образ входит конфигурация Container Registry. Она позволит хранить собранные для проекта Docker-образы.
Фактически, установка GitLab и GitLab Runner на серверах автоматизирована, необходимо лишь выделить нужное количество ресурсов под наши нужды. Это делается на той же странице чуть ниже.
Выбираем в конфигурации сервера 8 vCPU в соответствии с техническими требованиями GitLab [10].

Еще чуть ниже добавляем загрузочный диск (SSD Быстрый, 50 ГБ) и диск для данных (SSD Универсальный v2, 100 ГБ).

Листаем ниже до раздела Автоматизация. Здесь в поле для ввода текста вписываем скрипт cloud-init. Он позволит настроить root-пользователя и корректную временную зону.

#cloud-config
timezone: Europe/Moscow
# Start GitLab Instance
# Configure GitLab root user and DB
write_files:
- path: "/opt/gomplate/values/user-values.yaml"
permissions: "0644"
content: |
gitlabDomain: "<domain_name>"
gitlabRootEmail: "<email>"
gitlabRootPassword: "
"
gitlabPostgresDB: "gitlab"
gitlabPostgresUser: "gitlab"
gitlabPostgresPassword: "gitlab"
useExternalDB: false
Теперь нажимаем Создать сервер и ждем, когда он запустится. GitLab на это нужно около пяти минут, иногда меньше. Если статус сменился на ACTIVE, сервер готов и GitLab запущен.

Все действия выше можно описать с использованием Terraform.
# Создание ключевой пары для доступа к ВМ
module "keypair" {
source = "../modules/keypair"
keypair_name = "ssh_key_ed"
keypair_public_key = file("${var.ssh_key_file}.pub")
region = var.region
}
# Создание приватной сети для ВМ
module "nat" {
source = "../modules/nat"
}
# Создание GitLab-сервера.
module "gitlab_server" {
source = "../modules/server_gitlab"
server_name = "gitlab"
server_zone = var.server_zone
server_vcpus = var.gitlab_vcpus
server_ram_mb = var.gitlab_ram_mb
server_root_disk_gb = var.gitlab_root_disk_gb
server_boot_volume_type = var.gitlab_boot_volume_type
server_volume_type = var.server_volume_type
server_image_name = var.gitlab_image_name
server_ssh_key = module.keypair.keypair_name
region = var.region
network_id = module.nat.network_id
subnet_id = module.nat.subnet_id
attached_disk_gb = var.gitlab_attached_disk_gb
public_ip = var.gitlab_public_ip
user_data = file(var.gitlab_user_data_path)
server_preemptible_tag = var.server_no_preemptible_tag
}
# Создание inventory файла для ansible
resource "local_file" "ansible_inventory" {
content = templatefile("../resources/inventory.tmpl",
{
gitlab_public_ip = module.gitlab_server.floating_ip
ssh_key_file = var.ssh_key_file
}
)
filename = "../../ansible/resources/inventory.ini"
}
Как можно заметить, мы также добавили создание inventory-файла, который содержит публичный IP создаваемого сервера, путь до SSH-ключа и SSH-порт для доступа к серверу:
[gitlab]
${gitlab_public_ip} ansible_ssh_private_key_file=${ssh_key_file} ansible_port=22022
Inventory-файл пригодится нам на следующем этапе для настройки GitLab с помощью Ansible. Пока что будем держать в голове, что он есть.
Все файлы с конфигурацией Terraform и Ansible можно посмотреть в репозитории [8].
Запускаем создание инфраструктуры и переходим по указанному домену. Нас встретит страница входа.

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

У нас теперь есть свой GitLab!
При конфигурации необходимо создать группу пользователей, учетные записи пользователей и зарегистрировать раннер для группы. Для этого воспользуемся сервисом GitLab Rails, который позволяет управлять GitLab с помощью Ruby-скриптов.
Передача требуемых данных, скриптов, а также их запуск производятся посредствам Ansible.
Пользователей нужно создать внутри одной группы, чтобы в дальнейшем они могли использовать раннеры, привязанные к группе, а не к каждому пользователю.
def is_invalid_username(username);
is_invalid = false;
if User.find_by_username(username);
is_invalid = true;
end;
is_invalid;
End;
# Читаем список пользователей
users_list = File.read('/tmp/users.txt').split(/n/);
unique_users = users_list.group_by { |item| item.downcase };
users_creds = {'users' => Array.new};
# Создаем группу
group_name = 'Devops' + Date.today.cwyear.to_s;
unless Group.find_by_path_or_name(group_name);
puts 'Creating group';
group = Group.create;
group.name = group_name;
group.path = group.name.downcase;
group.lfs_enabled = false;
group.add_owner(User.first);
group.save!;
end;
group = Group.find_by_path_or_name(group_name);
# Создаем пользователей
puts 'Creating users';
unique_users.each do |key, names|;
names.each_index do |index|;
name = names[index];
t_username = name.gsub(/[[:space:]]/, '').downcase;
username = t_username;
password = SecureRandom.hex(12);
if is_invalid_username(username)
i = 1;
while not is_invalid_username(username);
username = t_username + i.to_s;
i += 1;
end;
end;
user = User.new(username: "#{username}", email: "#{username}@devops-spbstu.ru", name: "#{name}", password: "#{password}", password_confirmation: "#{password}", admin: false)
user.assign_personal_namespace(Organizations::Organization.default_organization)
user.skip_confirmation! # Пропускаем авторизацию пользователя
user.save!; # Сохраняем пользователя
users_creds['users'].append({"#{user.name}" => {'login'=>"#{user.username}", "email"=>"#{user.email}", "password"=>"#{user.password}"}}) # Сохраняем данные о пользователе
group.add_developer(user) # Добавляем пользователя в группу как разработчика
end;
end;
File.write("/tmp/users-creds.yaml", users_creds.to_yaml); # Сохраняем данные о пользователях в файл
В данном случае имена пользователей читаются из файла /tmp/users.txt. В результате выполнения скрипта данные будут выглядеть следующим образом:
---
users:
- Ivan Ivanov:
login: ivanivanov
email: ivanivanov@devops-spbstu.ru
password: <секретный пароль 1>
- John Cane:
login: johncane
email: johncane@devops-spbstu.ru
password: <секретный пароль 2>

Для работы с CI/CD в GitLab необходимо веб-приложение (агент) — GitLab Runner. Оно интерпретирует конфигурационный файл CI/CD и автоматически выполняет описанные задачи.
Прежде чем создавать инфраструктуру под веб-приложение через Terraform, необходимо зарегистрировать новую запись об агенте в GitLab и получить ключ доступа.
GitLab Runner регистрируется для всех пользователей и может быть использован для запуска через него CI/CD конвейеров. В данном случае создаем раннер типа Docker с тегом docker:
# Создаем раннер
runner = Ci::Runner.new(description: 'My Shared Runner', active: true, name: 'my-runner' + SecureRandom.hex(4), token: SecureRandom.hex(20), runner_type: Ci::Runner::runner_types["instance_type"]);
runner.docker_executor_type!;
runner.tag_list = ['docker'];
runner.save!; # Сохраняем раннера
runner_cred = {"#{runner.name}" => {"token" => "#{runner.token}"}};
reg_data = {"url"=>"Gitlab.config.gitlab.url", "token"=>"#{runner.token}"}
# Создаем cloud-init файл с токеном раннера
data = '#cloud-config
timezone: Europe/Moscow
write_files:
- path: "/opt/gomplate/values/user-values.yaml"
permissions: "0644"
content: |
'
data += " gitlabURL: "#{Gitlab.config.gitlab.url}"n"
data += " token: "#{runner.token}"n"
File.write("/tmp/runner-metadata.cfg", data);
File.write("/tmp/runner-creds.yaml", runner_cred.to_yaml);
И вуаля, раннер зарегистрирован. На выходе получаем файл конфигурации для cloud-init.
#cloud-config
timezone: Europe/Moscow
write_files:
- path: "/opt/gomplate/values/user-values.yaml"
permissions: "0644"
content: |
gitlabURL: "https://gitlab.devops-spbstu.ru"
token: <супер секретный ключ 1>
А также yaml-файл с данными о ранере: название и ключ доступа.
my-runnere8d7802a:
token: <супер секретный ключ 1>
Чтобы выполнить это не ручками, а автоматически, повторим все через Ansible.
- name: Configure Gitlab
hosts: gitlab
tasks:
# Меняем временную зону (исправляем 500 код)
- name: Changing systems timezone
community.docker.docker_container_exec:
container: gitlab
command: ln -s -f /usr/share/zoneinfo/Europe/Moscow /etc/localtime
- name: Changing gitlab timezone
community.docker.docker_container_exec:
container: gitlab
command: echo "gitlab_rails['time_zone'] = 'Europe/Moscow'" >> /etc/gitlab/gitlab.rb && gitlab-ctl reconfigure && gitlab-ctl restart
# Создаем пользователей
- name: Copy users-list to server
ansible.builtin.copy:
src: ./resources/users.txt
dest: /tmp/users.txt
- name: Copy users-list to gitlab
community.docker.docker_container_copy_into:
container: gitlab
path: /tmp/users.txt
container_path: /tmp/users.txt
#
- name: Copy users-create script to server
ansible.builtin.copy:
src: ./scripts/create_users.rb
dest: /tmp/users.rb
- name: Copy users-create script to gitlab
community.docker.docker_container_copy_into:
container: gitlab
path: /tmp/users.rb
container_path: /tmp/users.rb
- name: Create users
community.docker.docker_container_exec:
container: gitlab
command: gitlab-rails runner /tmp/users.rb
# Выгружаем данные о пользователях с сервера
- name: Load users creds to server
ansible.builtin.shell: docker cp gitlab:/tmp/users-creds.yaml /tmp/users-creds.yaml
- name: Load users creds locally
ansible.builtin.fetch:
src: /tmp/users-creds.yaml
dest: ./resources/user-creds.yaml
flat: true
# Создаем раннер для группы
- name: Copy runner-create script to server
ansible.builtin.copy:
src: ./scripts/create_runner.rb
dest: /tmp/runner.rb
- name: Copy runner-create script to gitlab
community.docker.docker_container_copy_into:
container: gitlab
path: /tmp/runner.rb
container_path: /tmp/runner.rb
- name: Create group-runner
community.docker.docker_container_exec:
container: gitlab
command: gitlab-rails runner /tmp/runner.rb
# Выгружаем данные о раннере с сервера
- name: Load runner creds from gitlab to server
ansible.builtin.shell: docker cp gitlab:/tmp/runner-creds.yaml /tmp/runner-creds.yaml
- name: Load runner creds locally
ansible.builtin.fetch:
src: /tmp/runner-creds.yaml
dest: ./resources/runner-creds.yaml
flat: true
# Выгружаем cloud-init конфиг раннера с сервера
- name: Load runner config from gitlab to server
ansible.builtin.shell: docker cp gitlab:/tmp/runner-metadata.cfg /tmp/runner-metadata.cfg
- name: Load runner config locally to terraform
ansible.builtin.fetch:
src: /tmp/runner-metadata.cfg
dest: ../terraform/resources/runner_metadata.cfg
flat: true
# Перезагружаем Gitlab
- name: Restarting gitlab
community.docker.docker_container_exec:
container: gitlab
command: gitlab-ctl restart
После запуска плейбука через Ansible произойдет магия. Учетные записи пользователей созданы, раннер зарегистрирован, осталось его запустить.

Проверяем, появился ли зарегистрированный раннер в панели администратора. Для этого возвращаемся к Terraform.
Ну что ж, мы на финишной прямой. После всех финтов ушами мы имеем файл конфигурации для раннера. Нам остается лишь установить GitLab Runner.
Рекомендации по установке GitLab Runner советуют ставить GitLab и Runner на отдельных серверах. Поэтому поднимем инфраструктуру для него через Terraform.
Возьмем за основу образ Cloud GitLab Runner 17.5.4 64-bit. Развернем его на базе сервера с 2 CPU, 4 Gb RAM и загрузочным диском (SSD).
Описание через Terraform имеет следующий вид:
# Создание ключевой пары для доступа к ВМ
module "keypair" {
source = "../modules/keypair"
keypair_name = "ssh_runner_key_ed"
keypair_public_key = file("${var.ssh_key_file}.pub")
region = var.region
}
# Создание приватной сети для ВМ
module "nat" {
source = "../modules/nat"
}
# Создание GitLab-runner сервера.
module "gitlab_runner_server" {
source = "../modules/server_gitlab_runner"
server_name = "runner"
server_zone = var.server_zone
server_vcpus = var.runner_vcpus
server_ram_mb = var.runner_ram_mb
server_root_disk_gb = var.runner_root_disk_gb
server_boot_volume_type = var.server_volume_type
server_image_name = var.runner_image_name
server_ssh_key = module.keypair.keypair_name
region = var.region
network_id = module.nat.network_id
subnet_id = module.nat.subnet_id
user_data = file(var.runner_user_data_path)
server_preemptible_tag = var.server_no_preemptible_tag
}
Запустим развертывание через Terraform. Результатом наших действий будет появившийся в панели облачной платформы сервер с GitLab Runner.

Кроме того, состояние нашего раннера должно измениться на активное в панели CI/CD в GItLab.

Раннер запустился и опознался готовым к работе в GitLab. Можно радоваться, жизнь удалась, GitLab и GitLab Runner работают!
Запустим простой пайплайн со сборкой Python-приложения в Docker-образ через kaniko и выгрузкой в Container Registry. Также заодно запустим тесты.
Описание тестового конвейера:
stages:
- build
- test
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
build app:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.23.2-debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "$IMAGE_TAG"
tags:
- docker
unit tests:
image: python:3.12-alpine
stage: test
before_script:
- python --version
- pip install pytest
- ls
- ls tests
script:
- python -m pytest tests/unit
needs: [ build app ]
tags:
- docker
Структура проекта содержит библиотеку (lib) и тесты на pytest (tests/unit).

Запускаем пайплайн и видим, что он успешно завершился.

Проверяем работу Container Registry и видим, что образ приложения появился в проекте.

А теперь небольшой «нытинг». Для меня задача по развертыванию GitLab оказалась новой и была неким вызовом, чтобы попробовать свои силы в области, отличной от моей основной деятельности.
При запуске образа были проблемы с рассинхронизацией времени сервера и контейнера с GitLab. При любой аутентификации вылетал код ошибки 500 — и живи с этим, как хочешь. Лишь после настройки через Ansible времени у GitLab получилось с этим справиться.
GitLab Rails открылся для меня очень удобным инструментом для конфигурации каждого из компонент GitLab. Однако каким бы он ни был удобным и крутым, я для него не смог найти подробной документации, описывающей все классы и модули. Справиться с выявлением нужных модулей, классов и методов помог «О, Великий гуглинг».
На этом этапе, можно сказать, что работа по развертыванию GitLab и GitLab Runner была выполнена. Задача была интересной и, надеюсь, ее результат пригодится кому-нибудь, кто будет жалеть свое время, развертывая инфраструктуру ручками.
Проект не идеален. В будущем хотелось бы улучшить создание и связь публичного IP-адреса с доменом, а также масштабирование количества раннеров.
Всем спасибо!
Автор: synex
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/oblako/413626
Ссылки в тексте:
[1] на серверах Selectel: https://selectel.ru/services/cloud/servers/?utm_source=habr.com&utm_medium=referral&utm_campaign=cloud_article_gitlab_120325_content
[2] записывайтесь: https://forms.selectel.ru/s/cm7efvbtv01xksm01fsatv38d?utm_source=habr.com&utm_medium=referral&utm_campaign=dedicated_article_gitlab_120325_banner_073_01_ord
[3] Задача и инструменты: #1
[4] Поднимаем GitLab: #2
[5] Запуск GitLab Runner: #3
[6] Тестируем Франкенштейна: #4
[7] Выводы: #5
[8] опубликованный на GitHub: https://github.com/AlexanderSynex/SelectelGitlab
[9] в панель управления Selectel: https://my.selectel.ru/?utm_source=habr.com&utm_medium=referral&utm_campaign=cloud_article_gitlab_120325_content
[10] с техническими требованиями GitLab: https://docs.gitlab.com/install/requirements
[11] Источник: https://habr.com/ru/companies/selectel/articles/890196/?utm_source=habrahabr&utm_medium=rss&utm_campaign=890196
Нажмите здесь для печати.