Решил поднять свой личный GitLab - в тот момент актуальной версией была 17. Для этого заказал VPS на 2 ядра и 2 гига оперативки под управлением OL8. Помимо базовой настройки я расскажу еще и о Pages с пространством имен в URL.

Подготовка

Oracle Linux 8 выбран несколько случайно. Вроде как существует специализированный пакет, но оказалось, что он для 9-ки. А шаблона OL9 у хостера не было… Ничего страшного, оно 8-й вроде как ресурсов поменьше жрет.

Поехали (здесь и далее работаем под root, поэтому sudo или там решетки к командам не добавляю). Меняем собственный пароль (root'а в данном случае), обновляем систему и для удобства устанавливаем Midnight Commander:

passwd
dnf update
dnf install mc

Kdump включен, отключил:

systemctl disable kdump
grubby --remove-args="crashkernel" --update=ALL

Создаем файл подкачки – я что-то разозлился и создал на 4 гига, а надо было наверное на 3. Но не суть:

fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

В /etc/fstab добавляем:

/swapfile none swap defaults 0 0

В /etc/sysctl.conf добавляем:

vm.swappiness=10

В firewalld открываем порты 80 и 443:

firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https

Желательно защитить SSH fail2ban'ом и/или настроить доступ по ключам. Позволю себе не заострять на этом внимание в данной статье.

Перезагружаемся. Если система загрузилась, значит мы почти готовы к установке – осталось настроить DNS-записи. Далее будут фигурировать git.example.com и pages.example.com, так что минимально это должны быть A-записи (и AAAA, если поддерживается IPv6), указывающие на сервер.

Установка GitLab

Я устанавливал прямо на хост из пакетов. Добавляем репозитории волшебным скриптом:

curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | bash

Устанавливаем GitLab с помощью dnf. Предлагаю сразу задать пароль root (в смысле для GitLab) и внешний URL:

GITLAB_ROOT_PASSWORD="generate_secure_pwd" EXTERNAL_URL="https://git.example.com" dnf install -y gitlab-ce

Если пароль не указать (например, чтобы он не остался в истории команд), то временный сохранится в файле /etc/gitlab/initial_root_password. В случае URL с https установщик настроит Let's Encrypt для получения сертификатов SSL. Должен сказать, PostgreSQL и прочие Redis'ы входят в сам пакет (я думал это будут внешние зависимости), а процесс установки достаточно длительный. В частности он надолго застревает на:

execute[clear the gitlab-rails cache] action run

Надеюсь, в конечном итоге установщик справится. Заходим на свежеустановленный git.example.com:

gitlab.png

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

Есть официальная инструкция по оптимизации потребления памяти. Честно говоря, я выполнил ее не всю – побоялся копировать довольно внушительный кусок настроек для Gitaly (сервиса хранилища) и некую MALLOC_CONF в gitlab_rails['env']. Тем не менее, внес несколько изменений в /etc/gitlab/gitlab.rb.

Отключил кластеризованный режим Пумы (веб-сервера Ruby):

puma['worker_processes'] = 0

Понизил конкуренцию ключевого второстепенного персонажа – демона фоновых процессов Sidekiq:

sidekiq['concurrency'] = 8

И отключил Прометей (мониторинг):

prometheus_monitoring['enable'] = false

Переконфигурировал GitLab (эта команда впоследствии будет часто требоваться):

gitlab-ctl reconfigure

Кстати мониторинг надо отключать не только через файл конфигурации, но и через админ-панель: Admin Area (это такая едва заметная кнопка внизу у root'а, в более поздних версиях просто Admin) – Settings – Metrics and profiling, развернуть Metrics – Prometheus и отключить. Я бы заодно и сбор статистики отключил (Usage statistics).

Настройка после установки

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

Admin area – Settings

General

  • Visibility and access controls:
    • Enabled Git access protocols – Only HTTP(s)
  • Account and limit:
    • Отключил Gravatar
  • Import and export settings:
    • Отметил источники импорта проектов (GitHub, Bitbucket)
  • Sign-up restrictions:
    • Sign-up enabled – по идее отключили сразу
    • Email confirmation settings – Hard
    • Minimum password length (number of characters) – подсыпать
  • Sign-in restrictions:
    • Отключил Allow password authentication for Git over HTTP(S) – буду пользоваться токенами
  • Diagrams.net – не пользуюсь, отключил

CI/CD

  • Continuous Integration and Deployment:
    • Поскольку пока не нужен, отключил Default to Auto DevOps pipeline for all projects и Enable instance runners for new projects.
    • На всякий случай прописал example.com (мой домен) в Auto DevOps domain.

Отключил Container register совсем через gitlab.rb (и затем переконфигурировал):

registry['enable'] = false

Reporting

Настроил Spam and Anti-bot Protection. Правда поддерживается только reCaptcha v2, но пусть будет.

Network

Включить лимиты? По факту ничего не стал менять.

Appearance

Не настраиваем smile

Preferences

  • What's New – отключил
  • Localization – настроил

Admin area – Overview – Users

На странице пользователей создал себя, в том числе выпустил ключ для доступа к репозиториям (права read_repository, write_repository).

Отправка почты

https://docs.gitlab.com/omnibus/settings/smtp/

Настраиваем через gitlab.rb:

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.mail.ru"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
gitlab_rails['gitlab_email_from'] = 'noreply@example.com'
gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com'

Я пользуюсь сервисом от mail.ru, поэтому указан его smtp_address. Соответственно никакие sendmail с postfix'ом не требуются. Как видите, здесь не прописаны логин и пароль – зашифруем их. Для этого сначала создадим smtp.yaml в открытом виде:

user_name: 'noreply@example.com'
password: 'secure_smtp_password'

А далее выполним команды:

cat smtp.yaml | gitlab-rake gitlab:smtp:secret:write
rm smtp.yaml
gitlab-ctl reconfigure

Проверить отправку почты можно через консоль. Для ее запуска:

gitlab-rails console

В ней:

Notify.test_email('your@email.com', 'GitLab test', 'Hello! This is message body.').deliver_now

Импорт репозиториев из BitBucket

https://docs.gitlab.com/integration/bitbucket/

Заходим в BitBucket, настройки рабочего пространства. Там по идее и будут OAuth consumers. Добавляем оного с правами доступа:

  • Account: Email, Read
  • Projects: Read
  • Repositories: Read
  • Pull Requests: Read
  • Issues: Read
  • Wiki: Read and Write

Нас интересуют ключ и секрет созданного «потребителя». С этими данными добавляем провайдер в gitlab.rb:

gitlab_rails['omniauth_providers'] = [
  {
    name: "bitbucket",
    app_id: "<bitbucket_app_key>",
    app_secret: "<bitbucket_app_secret>",
    url: "https://bitbucket.org/"
  }
]

После традиционного переконфигурирования дальше вроде все просто – Новый проект, импорт, Bitbucket Cloud, выбрать репозиторий.

Импорт репозиториев из GitHub

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

Let's Encrypt

Желательно указать свой e-mail в gitlab.rb:

letsencrypt['contact_emails'] = ['admin@example.com']

Ну и вообще убедиться, что он включен – letsencrypt['enable'] = true.

GitLab Pages

Эту страсть я изначально решил настроить в контексте хранения «локальной» копии документации к GitLab, однако в итоге эта самая документация у меня просто крутится в контейнере Docker. Тем не менее, в рамках настройки Pages мы в немалой степени продвигаемся по линии DevOps, а там может и какой-нибудь статический сайт можно будет развернуть.

Настройка

Для начала добавляем альтернативный домен (в gitlab.rb), чтобы Let's Encrypt выпустил сертификат:

letsencrypt['alt_names'] = ['pages.example.com']

Важно сразу же переконфигурировать, чтобы отработала проверка (.well-known и т.д.)

Далее:

pages_external_url 'https://pages.example.com'
gitlab_pages['enable'] = true
gitlab_pages['namespace_in_path'] = true
pages_nginx['redirect_http_to_https'] = true

Внезапно установка URL действительно без =. Я решил включить пространства имен в URL вместо доменов, получается, 4-го уровня (не хватало еще с ними возиться).

Принудительно указываем путь к «общему» сертификату:

pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/git.example.com.crt"
pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/git.example.com.key"

Очередная реконфигурация.

Это, видимо, больше относится к варианту пространства имен в доменах, но все же: под root, Admin Area, Settings – Preferences – Pages. Обнулить лимит, указать e-mail и согласиться с условиями использования Let's Encrypt.

Бегун

Для всякого рода CI/CD, к коим относится и развертывание страниц, требуется gitlab-runner. Опять-таки устанавливаем его через репозитории:

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | bash
dnf install gitlab-runner

Ну и куда же теперь без портового рабочего

yum-config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

Да, внезапно именно yum-config-manager, а не к примеру dnf-config-manager.

dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Включаем:

systemctl enable --now docker

По желанию – проверяем:

docker run hello-world

Возвращаемся в GitLab. Под root, в Admin Area, CI/CD – Runners создаем его:

  • Tags: pages
  • Description – по желанию («Публикация GitLab Pages»)

Раз мы указали метку, то теперь нужно будет вручную добавлять ее в файлы .gitlab-ci.yml. С этой точки зрения можно было бы оставить бегуна и без меток. Так или иначе, мы должны получить токен для регистрации, которая выполняется в командной строке:

gitlab-runner register --url https://git.example.com --token glrt-xxxx-yyyyyyyyyyyyyyy

Будет задано несколько вопросов, в частности:

  • URL – передали в параметре
  • Имя – по умолчанию
  • Исполнитель (executor): docker
  • Образ по умолчанию: alpine:latest (или docker:latest, или любой другой не слишком большой)

Исполнитель, казалось бы, shell, ан нет – образы (image) таковой не поддерживает. Именно для этого и накатил докер. Собственно образ по умолчанию по идее не критичен, поскольку конкретный будет определен в пайплайне.

Во избежание возможных ошибок предлагаю увеличить максимальный размер артефактов: Admin Area, Settings – CI/CD – Continuous Integration and Deployment, Maximum artifacts size (MB).

Проверка

В GitLab есть несколько шаблонов проектов Pages, в принципе для наших целей достаточен Pages/Plain HTML. Хотя в него уже входит .gitlab-ci.yml, GitLab тупит и приходится настраивать развертывание заново (Deploy – Pages).

На первом шаге указываем образ (busybox) и директорию для публикации (public, по умолчанию). Второй шаг (Installation steps) пропускаем. Build steps берем из шаблона: echo "The site will be deployed to $CI_PAGES_URL". На четвертом шаге непосредственно в черновик (Draft: .gitlab-ci.yml) добавляем метку:

  tags:
    - pages

Коммит. Смотрим Build – Jobs, если все в порядке, то через некоторое время система опубликует сайт. Правда под каким-то очень странным «уникальным» URL, мне кажется лучше снять галку Use unique domain в Deploy – Pages, вкладке Domains & settings.

Pages как «кастомный» сайт

Выполненная ранее настройка проработает ровно до того момента, когда наступит время обновлять сертификат Let's Encrypt. Дело в том, что обновляется он с использованием проверки по http (http-01 challenge), а для этого должна быть доступна /.well-known/acme-challenge/. В конфигурации Nginx для Pages эта локация не настроена.

Таким образом, делаем «финт ушами». Копируем /var/opt/gitlab/nginx/conf/gitlab-pages.conf в (например) /etc/nginx/reverse-proxy/pages.conf, немного чистим комментарии и добавляем блоки location /.well-known/acme-challenge/. Я еще заодно логи отключил для http и добавил прослушивание по IPv6. Должно получиться примерно следующее:

###################################
##         configuration         ##
###################################

## Redirects all HTTP traffic to the HTTPS host
server {
  listen *:80;
  listen [::]:80;
  ## Experimental - Handle requests having namespace in path
  server_name  ~^pages\.example\.com$;
  server_tokens off; ## Don't show the nginx version number, a security best practice

  location /.well-known/acme-challenge/ {
    root /var/opt/gitlab/nginx/www/;
  }

  location / {
    return 301 https://$http_host:$request_uri;
  }

  access_log  off;
  error_log   off;
}

server {
  listen *:443 ssl http2;
  listen [::]:443 ssl http2;
  ## Experimental - Handle requests having namespace in path
  server_name  ~^pages\.example\.com$;

  server_tokens off; ## Don't show the nginx version number, a security best practice

  ## Disable symlink traversal
  disable_symlinks on;

  ## Strong SSL Security
  ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
  ssl_certificate /etc/gitlab/ssl/git.example.com.crt;
  ssl_certificate_key /etc/gitlab/ssl/git.example.com.key;

  # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
  ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
  ssl_protocols  TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers off;
  ssl_session_cache  shared:SSL:10m;
  ssl_session_tickets off;
  ssl_session_timeout  1d;

  ## Real IP Module Config
  ## http://nginx.org/en/docs/http/ngx_http_realip_module.html

  ## HSTS Config
  ## https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
  add_header Strict-Transport-Security "max-age=63072000  ";

  ## Individual nginx logs for this GitLab vhost
  access_log  /var/log/gitlab/nginx/gitlab_pages_access.log gitlab_access;
  error_log   /var/log/gitlab/nginx/gitlab_pages_error.log error;

  # Define custom error pages
  error_page 403 /403.html;
  error_page 404 /404.html;

  # Pass everything to pages daemon when namespace in host
  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Ssl on;

    # Prevent NGINX from caching pages in response to the pages `Cache-Control`
    # header.
    #
    # Browsers already respect this directive and Pages can handle the request
    # volume without help from NGINX.
    #
    # If this changes in the future, ensure `proxy_cache_key` is set to a value
    # like `$scheme$host$request_uri`, as the default value does not take the
    # Pages hostname into account, leading to incorrect responses being served.
    #
    # See https://gitlab.com/gitlab-org/gitlab-pages/issues/73
    proxy_cache off;

    proxy_http_version 1.1;
    proxy_pass          http://localhost:8090;
  }

  location /.well-known/acme-challenge/ {
    root /var/opt/gitlab/nginx/www/;
  }
}

Отключаем Nginx для страниц в gitlab.rb:

pages_nginx['enable'] = false

В нем же добавляем подключение дополнительных файлов конфигураций Nginx:

nginx['custom_nginx_config'] = "include /etc/nginx/reverse-proxy/*.conf;"

Фактически благодаря этому трюку мы можем размещать какие-то свои ресурсы помимо GitLab. Переконфигурирование, возможно, с первого раза не сработает из-за «сертификации». В самом крайнем случае можно перезапустить GitLab или даже сервер целиком.

Копия документации

Поскольку у нас теперь есть Docker, копию документации к GitLab можно развернуть в виде контейнера. Набросаем /root/gldocs/compose.yaml (напоминаю, что я бессовестным образом работаю под root, но Вы так не делайте):

services:
  gitlab_docs:
    image: registry.gitlab.com/gitlab-org/gitlab-docs/archives:17.4
    restart: unless-stopped
    ports:
      - "127.0.0.1:4000:4000"

Примечание. Начиная с версии 17.8 путь к образам другой: registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/archives.

Запускаем:

docker compose up -d

В gitlab.rb добавляем домен для сертификации:

letsencrypt['alt_names'] = ['pages.example.com', 'gldocs.example.com']

И добавляем примерно такую конфигурацию обратного прокси /etc/nginx/reverse-proxy/gldocs.conf:

server {
  listen *:80;
  listen [::]:80;
  server_name gldocs.example.com;
  server_tokens off;

  location /.well-known/acme-challenge/ {
    root /var/opt/gitlab/nginx/www/;
  }

  location / {
    return 301 https://$http_host:$request_uri;
  }

  access_log off;
  error_log /var/log/gitlab/nginx/gldocs_error.log;
}

server {
  listen *:443 ssl http2;
  listen [::]:443 ssl http2;
  server_name gldocs.example.com;
  server_tokens off; ## Don't show the nginx version number, a security best practice

  ssl_certificate /etc/gitlab/ssl/git.example.com.crt;
  ssl_certificate_key /etc/gitlab/ssl/git.example.com.key;

  # Let's Encrypt SSL options
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 1440m;
  ssl_session_tickets off;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers off;

  ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

  access_log off;
  error_log /var/log/gitlab/nginx/gldocs_error.log;

  location /.well-known/acme-challenge/ {
    root /var/opt/gitlab/nginx/www/;
  }

  location / {
    proxy_cache off;
    proxy_pass http://localhost:4000;
  }
}

Реконфигурация. Возможно, не будет работать редирект на актуальную версию, тогда придется открывать вручную адрес типа gldocs.example.com/17.4/. А еще может задалбывать согласие на использование печенек. Тем не менее для страховки пусть живет.

Резервное копирование

Это тоже весьма обширная тема, но если совсем коротко, то для резервного копирования предусмотрен скрипт:

gitlab-backup create

В архив не попадает содержимое объектного хранилища (S3), если вдруг оно настроено, ну и конфигурацию надо отдельно сохранять. В любом случае это:

  • /etc/gitlab/gitlab-secrets.json
  • /etc/gitlab/gitlab.rb

И, в моем конкретном случае:

  • /var/opt/gitlab/gitlab-rails/shared/encrypted_settings/smtp.yaml.enc
  • /etc/gitlab-runner
  • /etc/nginx/reverse-proxy

В gitlab.rb раскомментировал строку (и конечно же переконфигурировал GitLab):

gitlab_rails['backup_keep_time'] = 604800

Тем самым бэкапы будут храниться 7 дней.

В crontab -e root'а добавил задание:

0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1

Добавил некий специальный параметр, указывающий, что резервирование происходит в рамках регулярной процедуры. Кстати tar внезапно в Oracle Linux может быть не установлен, если это так, то:

dnf install tar

В свою очередь, чтобы сохранить полученные архивы в облако, я пользуюсь rclone. Он есть в EPEL:

dnf install epel-release
dnf install rclone
rclone config

Создаю подключение к mega.nz (в программе называется просто Mega) под названием meganz. Хотя конечно вопрос с шифрованием доступов остается открытым: для SMTP зашифровали, а для rclone – нет. Зная путь к архивам (/var/opt/gitlab/backups), добавляем примерно такое задание на 4 часа утра (уж за два-то часа наверное личный GitLab успеет сохраниться):

0 4 * * * /usr/bin/rclone sync /var/opt/gitlab/backups meganz:/gitlab/backups

Возникает вопрос, как в случае чего все восстанавливать. Все не так просто – версии GitLab должны строго совпадать (что, кстати, может быть затруднительным в случае установки из репозиториев), а целевая инстанция должна быть сконфигурированной (т.е.: установить, восстановить как минимум gitlab.rb и gitlab-secrets.json, переконфигурировать) и запущенной.

Перед восстановлением предлагается остановить процессы, работающие с БД:

gitlab-ctl stop puma
gitlab-ctl stop sidekiq

Разместить архив в новой /var/opt/gitlab/backups и наконец восстановить уже (в параметре не указывается _gitlab_backup.tar):

gitlab-backup restore BACKUP=1728082883_2024_10_04_17.4.0

Если я правильно понял, после восстановления нужно еще раз выполнить реконфигурацию (наверное в любом случае не повредит):

gitlab-ctl reconfigure

Запускаем все обратно и проверяем:

gitlab-ctl start
gitlab-rake gitlab:check SANITIZE=true
gitlab-rake gitlab:doctor:secrets

Наконец, рекомендуется обновить статистические данные БД:

gitlab-psql -c 'SET statement_timeout = 0; ANALYZE VERBOSE;'

Искренне желаю вам удачи, чтобы ничего восстанавливать не пришлось.

Заключение

Если честно, промучился я со всем этим примерно год (пока сервер был оплачен), а потом взял и перенес все на более мощный VPS 4/4, да еще и полностью в Docker настроил и Traefik'ом заполировал. Возможно я зря не выполнил инструкцию по экономии памяти полностью, но на 2 гигах оперативки было тяжело. Еще я пока не настраивал реестр контейнеров, как настрою – постараюсь написать отдельную статью.

Но в целом собственный GitLab определенно стоит своих денег за VPS и времени на настройку.


Категория: Программирование, веб | Опубликовано 04.12.2024 | Редакция от 04.07.2025

Похожие материалы


Комментарии, обсуждение