Часть 1. Введение
Зачем нужен GitLab Cluster?
В процессе роста нашей инфраструктуры мы столкнулись с тем, что Single Node (all-in-one) инсталляции GitLab стало недостаточно. Производительность начала снижаться, а любое обновление или сбой сервиса приводило к простою всей разработки. Поэтому мы приняли решение перейти на отказоустойчивый GitLab Cluster с возможностью бесшовных обновлений (zero downtime upgrade).
Для автоматизированного развёртывания и управления кластером мы выбрали Ansible, так как:
-
Он позволяет быстро и повторяемо разворачивать инфраструктуру.
-
Хорошо интегрируется с существующими CI/CD процессами.
-
Позволяет централизованно управлять конфигурацией и обновлениями.
-
Поддерживает идемпотентность, что минимизирует риски ошибок при повторных запусках.
В этой статье я подробно расскажу, как мы развернули GitLab Cluster с использованием Ansible и организовали бесшовные обновления.
Версию используем Community Edition.
Роли для раскатки PostgreSQL, HAProxy и Redis ищите отдельно, тут их нет.
Ниже представлена схема нашей исходной архитектуры GitLab, когда он был развёрнут на одном сервере:
Скрытый текст

Часть 2. Архитектура GitLab Cluster
Основные компоненты кластера
После перехода на кластерное развертывание наша архитектура значительно изменилась. Теперь GitLab состоит из нескольких сервисов, разнесённых по различным узлам:
-
2 сервера Rails (4 vCPU, 8 RAM, 30 SSD) – основной веб-интерфейс и API.
-
3 сервера Gitaly (4 vCPU, 4 RAM, 50 SSD) – отвечает за доступ к репозиториям Git.
-
3 сервера Praefect (2 vCPU, 2 RAM, 30 SSD) – прокси-слой для управления реплицированными экземплярами Gitaly.
-
2 сервера Sidekiq (4 vCPU, 8 RAM, 30 SSD) – обработка фоновых задач.
-
1 сервер PostgreSQL (4 vCPU, 8 RAM, 50 SSD)– база данных, и для Praefect, и для Rails.
-
1 сервер Redis (2 vCPU, 2 RAM, 20 SSD) – используется для кеширования и фоновых заданий.
-
1 сервер HAProxy – балансировщик нагрузки.
В этой статье не рассматриваются:
-
Настройка PostgreSQL.
-
Настройка Redis.
-
Настройка HAProxy.
Схема новой архитектуры
Скрытый текст
картинку вставлю чуть позже
Как обеспечивается отказоустойчивость?
-
Rails развернут в двух экземплярах за HAProxy, что обеспечивает балансировку нагрузки и отказоустойчивость.
-
Sidekiq распределён между двумя серверами, что позволяет выполнять фоновые задачи без простоев.
-
Praefect управляет репликацией Gitaly, предотвращая потери данных.
-
Gitaly распределён между несколькими узлами для балансировки нагрузки.
-
Балансировщик нагрузки (HAProxy) направляет трафик на доступные инстансы, включая Rails и Praefect.
Часть 3. Развёртывание кластера с помощью Ansible
Подготовка окружения
Перед началом развертывания кластера необходимо подготовить серверы и настроить Ansible-инвентарь. Важно убедиться, что у вас есть доступ по SSH ко всем узлам и что на них установлены необходимые пакеты.
Для автоматизации развертывания была использована следующая Ansible-роль: gitlab-omnibus.
Для управления развертыванием мы используем следующий инвентарь, который описывает все компоненты кластера и содержит групповые переменные:
environments/dev/ybd.yml:
Скрытый текст
--- haproxy: children: haproxy_dev: hosts: ybd-bln-hpr01: ansible_host: 10.0.220.189 ansible_port: 222 gitlab_cluster: children: gitlab_rails: hosts: ybd-git-rai01: ansible_host: 10.0.220.180 ybd-git-rai02: ansible_host: 10.0.220.40 gitlab_sidekiq: hosts: ybd-git-sid01: ansible_host: 10.0.220.181 ybd-git-sid02: ansible_host: 10.0.220.86 gitlab_praefect: hosts: ybd-git-prf01: ansible_host: 10.0.220.182 gitlab_omnibus_praefect_override: auto_migrate: true ybd-git-prf02: ansible_host: 10.0.220.183 ybd-git-prf03: ansible_host: 10.0.220.184 gitlab_gitaly: hosts: ybd-git-gtl01: ansible_host: 10.0.220.185 ybd-git-gtl02: ansible_host: 10.0.220.186 ybd-git-gtl03: ansible_host: 10.0.220.187 gitlab_postgresql: hosts: ybd-git-psg01: ansible_host: 10.0.220.188 gitlab_redis: hosts: ybd-git-red01: ansible_host: 10.0.220.190 master: true
environments/dev/group_vars/haproxy_dev/variables.yml:
Скрытый текст
--- haproxy_packet: "{{ haproxy_packet_name }}" haproxy_packet_name: "haproxy" haproxy_defaults_log: 127.0.0.1:5140 len 65535 local0 haproxy_global_nbthread: 2 haproxy_global_raw_options: - stats socket /var/lib/haproxy/stats mode 660 level admin expose-fd listeners - stats timeout 30s haproxy_logformat_tcp: &haproxy_logformat_tcp logformat: >- '{"appname":"haproxy","@timestamp":"%Ts","pid":%pid,"haproxy_frontend_type":"tcp","haproxy_process_concurrent_connections":%ac, "haproxy_frontend_concurrent_connections":%fc,"haproxy_backend_concurrent_connections":%bc, "haproxy_server_concurrent_connections":%sc,"haproxy_backend_queue":%bq,"haproxy_server_queue":%sq, "haproxy_queue_wait_time":%Tw,"haproxy_server_wait_time":%Tc,"response_time":%Td,"session_duration":%Tt, "request_termination_state":"%tsc","haproxy_server_connection_retries":%rc,"remote_addr":"%ci","remote_port":%cp, "frontend_addr":"%fi","frontend_port":%fp,"frontend_ssl_version":"%sslv","frontend_ssl_ciphers":"%sslc", "haproxy_frontend_name":"%f","haproxy_backend_name":"%b","haproxy_server_name":"%s","response_size":%B, "request_size":%U}' haproxy_logformat_http: &haproxy_logformat_http logformat: >- '{"appname":"haproxy","@timestamp":"%Ts","pid":%pid,"haproxy_frontend_type":"http","haproxy_process_concurrent_connections":%ac, "haproxy_frontend_concurrent_connections":%fc,"haproxy_backend_concurrent_connections":%bc, "haproxy_server_concurrent_connections":%sc,"haproxy_backend_queue":%bq,"haproxy_server_queue":%sq,"haproxy_client_request_send_time":%Tq, "haproxy_queue_wait_time":%Tw,"haproxy_server_wait_time":%Tc,"haproxy_server_response_send_time":%Tr, "response_time":%Td,"session_duration":%Tt,"request_termination_state":"%tsc","haproxy_server_connection_retries":%rc, "remote_addr":"%ci","remote_port":%cp,"frontend_addr":"%fi","frontend_port":%fp,"frontend_ssl_version":"%sslv", "frontend_ssl_ciphers":"%sslc","request_method":"%HM","request_uri":"%[capture.req.uri,json(utf8s)]", "request_http_version":"%HV","host":"%[capture.req.hdr(0)]","referer":"%[capture.req.hdr(1),json(utf8s)]", "haproxy_frontend_name":"%f","haproxy_backend_name":"%b","haproxy_server_name":"%s","status":%ST,"response_size":%B, "request_size":%U}' haproxy_defaults_option: - forwardfor - dontlognull haproxy_default_raw_options: - backlog 10000 - retries 3 haproxy_defaults_timeout: - type: connect timeout: 5000 - type: client timeout: 50000 - type: server timeout: 101000 - type: http-request timeout: 10000 - type: queue timeout: 30000 haproxy_frontend: - name: http description: Front-end for all HTTP traffic bind: - listen: "0.0.0.0:80" - listen: "0.0.0.0:443 ssl crt /opt/testlab.com.pem crt /opt/testlab.org.pem crt /opt/pages.testlab.org.pem alpn h2,http/1.1" mode: http <<: *haproxy_logformat_http raw_options: - redirect scheme https unless { ssl_fc } - http-request redirect scheme https code 301 unless { ssl_fc } acl: - string: host_gitlab-backup hdr(host) -i git-backup.testlab.org - string: host_pages-backup hdr_end(host) -i pages-backup.testlab.org http_request: - action: capture param: req.hdr(Host) len 1000 - action: capture param: req.hdr(Referer) len 1000 use_backend: - gitlab-backup if host_gitlab-backup - pages-backup if host_pages-backup - name: exporter bind: - listen: 0.0.0.0:8404 <<: *haproxy_logformat_http option: - http-use-htx http_request: - action: use-service param: prometheus-exporter cond: "if { path /metrics }" raw_options: - stats enable - stats uri /stats - stats refresh 10s - name: gitlab-backup-ssh description: gitlab-backup-ssh bind: - listen: "0.0.0.0:22" mode: tcp option: - tcplog - clitcpka <<: *haproxy_logformat_tcp default_backend: gitlab-backup-ssh - name: praefect-back description: praefect-back bind: - listen: "0.0.0.0:2305" option: - tcplog - clitcpka mode: tcp <<: *haproxy_logformat_tcp default_backend: git-prafect-back haproxy_backend: - name: git-prafect-back description: git-prafect-back mode: tcp balance: roundrobin options: - srvtcpka - tcp-check server: - name: ybd-git-prf01 listen: ybd-git-prf01:2305 check - name: ybd-git-prf02 listen: ybd-git-prf02:2305 check - name: ybd-git-prf03 listen: ybd-git-prf03:2305 check - name: gitlab-backup description: gitlab-backup http mode: http balance: roundrobin http_check: expect string '{"status":"ok"}' option: - httpchk GET /-/liveness - tcp-check - srvtcpka server: - name: ybd-git-rai01 listen: ybd-git-rai01:80 check - name: ybd-git-rai02 listen: ybd-git-rai02:80 check - name: pages-backup description: pages-backup http mode: http balance: roundrobin http_check: expect string '{"status":"ok"}' option: - httpchk GET /-/liveness - tcp-check - srvtcpka server: - name: ybd-git-rai01 listen: ybd-git-rai01:8081 check port 80 - name: ybd-git-rai02 listen: ybd-git-rai02:8081 check port 80 - name: gitlab-backup-ssh description: gitlab-backup-ssh mode: tcp balance: roundrobin http_check: expect string '{"status":"ok"}' option: - httpchk GET /-/liveness - tcp-check - srvtcpka server: - name: ybd-git-rai01 listen: ybd-git-rai01:22 check port 80 - name: ybd-git-rai02 listen: ybd-git-rai02:22 check port 80 vector_version: "0.31.0" vector_configs: - name: haproxy config: sources: local_haproxy: address: 127.0.0.1:5140 max_length: 102400 mode: udp type: syslog sinks: sink_kafka: type: kafka inputs: - local_haproxy bootstrap_servers: "{{ vector_kafka_url }}" topic: "infra-haproxy" compression: none encoding: codec: text tls: enabled: true
environments/dev/group_vars/gitlab_cluster/variables.yml:
Скрытый текст
--- gitlab_omnibus_packet_version: 17.7.3-ce.0 gitlab_omnibus_restic_password: "{{ vault_gitlab_omnibus_restic_password }}" gitlab_omnibus_haproxy_hosts: - ybd-bln-hpr01 gitlab_omnibus_gitaly: data_hosts: - ybd-git-gtl01:8075 - ybd-git-gtl02:8075 - ybd-git-gtl03:8075 gitlab_omnibus_shell: secret_token: "{{ vault_gitlab_omnibus_shell_secret_token }}" gitlab_omnibus_pages_external_url: https://pages-backup.testlab.org gitlab_omnibus_pages: gitlab_server: "https://git-backup.testlab.org" gitlab_secret: "{{ vault_gitlab_omnibus_pages_gitlab_secret }}" api_secret_key: "{{ vault_gitlab_omnibus_pages_api_secret_key }}" gitlab_omnibus_praefect: external_address: tcp://git-backup.testlab.org:2305 praefect_external_token: "{{ vault_gitlab_omnibus_praefect_praefect_external_token }}" praefect_internal_token: "{{ vault_gitlab_omnibus_praefect_praefect_internal_token }}" database_host: ybd-git-psg01 database_user: gitlab_praefect_backup database_password: "{{ vault_gitlab_omnibus_praefect_database_password }}" database_dbname: gitlab_praefect_backup gitlab_omnibus_rails: gitlab_email_from: noreply@testlab.com gitlab_email_reply_to: noreply@testlab.com smtp_authentication: login smtp_domain: testlab.com smtp_enable: false gitlab_email_enabled: false gitlab_shell_ssh_port: 22 smtp_address: lalala.testlab.com smtp_port: 587 smtp_password: "{{ vault_gitlab_omnibus_rails_smtp_password }}" smtp_openssl_verify_mode: peer smtp_enable_starttls_auto: true smtp_user_name: noreply@testlab.com ldap_servers_main_password: "{{ vault_gitlab_omnibus_rails_ldap_servers_main_password }}" ldap_servers_main_label: LDAP ldap_servers_main_host: 'testlab.com' ldap_servers_main_port: 636 ldap_servers_main_uid: 'sAMAccountName' ldap_servers_main_encryption: 'simple_tls' ldap_servers_main_bind_dn: 'CN=gitlab ldap,OU=Service Accounts,OU=Organization,DC=testlab,DC=com' ldap_servers_main_base: 'OU=Organization,DC=testlab,DC=com' initial_root_password: "{{ vault_gitlab_omnibus_rails_initial_root_password }}" db_host: ybd-git-psg01 db_port: 5432 db_database: gitlab_rails_backup db_username: gitlab_rails_backup db_password: "{{ vault_gitlab_omnibus_rails_db_password }}" external_url: https://git-backup.testlab.org internal_api_url: https://git-backup.testlab.org secret_key_base: "{{ vault_gitlab_omnibus_rails_secret_key_base }}" otp_key_base: "{{ vault_gitlab_omnibus_rails_otp_key_base }}" db_key_base: "{{ vault_gitlab_omnibus_rails_db_key_base }}" encrypted_settings_key_base: "{{ vault_gitlab_omnibus_rails_encrypted_settings_key_base }}" openid_connect_signing_key: "{{ vault_gitlab_omnibus_rails_openid_connect_signing_key }}" ci_jwt_signing_key: "{{ vault_gitlab_omnibus_rails_ci_jwt_signing_key }}" packages_enable: false object_store: enabled: true proxy_download: true connection: aws_access_key_id: "{{ vault_gitlab_omnibus_rails_object_store_connection_aws_access_key_id }}" aws_secret_access_key: "{{ vault_gitlab_omnibus_rails_object_store_connection_aws_secret_access_key }}" endpoint: https://storage.yandexcloud.net host: storage.yandexcloud.net objects: artifacts_bucket: gitlab-prod-artifacts external_diffs_bucket: gitlab-prod-external-diffs lfs_bucket: gitlab-prod-lfs uploads_bucket: gitlab-prod-uploads packages_bucket: crutch dependency_proxy_bucket: crutch2 terraform_state_bucket: gitlab-prod-terraform-state pages_bucket: gitlab-prod-pages ci_secure_files_bucket: gitlab-prod-ci-secure-files backup_bucket: backup-gitlab redis: sentinels: - host: ybd-git-red01 port: 26379 password: "{{ vault_gitlab_omnibus_rails_redis_password }}" master_name: gitlab_redis postgresql_databases: - name: "{{ gitlab_omnibus_rails.db_database }}" owner: "{{ gitlab_omnibus_rails.db_username }}" - name: "{{ gitlab_omnibus_praefect.database_dbname }}" owner: "{{ gitlab_omnibus_praefect.database_user }}" postgresql_users: - name: "{{ gitlab_omnibus_rails.db_username }}" role_attr_flags: superuser password: "{{ gitlab_omnibus_rails.db_password }}" db: "{{ gitlab_omnibus_rails.db_database }}" - name: "{{ gitlab_omnibus_praefect.database_user }}" role_attr_flags: createdb password: "{{ gitlab_omnibus_praefect.database_password }}" db: "{{ gitlab_omnibus_praefect.database_dbname }}" postgresql_hba_entries: - type: local database: all user: postgres auth_method: peer - type: local database: all user: all auth_method: peer - type: host database: all user: all address: '127.0.0.1/32' auth_method: "{{ postgresql_auth_method }}" - type: host database: all user: all address: '10.0.0.0/8' auth_method: "{{ postgresql_auth_method }}" - type: host database: all user: all address: '172.16.0.0/12' auth_method: "{{ postgresql_auth_method }}" - type: host database: all user: all address: '::1/128' auth_method: "{{ postgresql_auth_method }}" postgresql_global_config_options: - option: listen_addresses value: '*' - option: max_connections value: 500 - option: shared_buffers value: 1GB - option: effective_cache_size value: 3GB - option: maintenance_work_mem value: 256MB - option: checkpoint_completion_target value: 0.9 - option: wal_buffers value: 16MB - option: default_statistics_target value: 100 - option: random_page_cost value: 1.1 - option: effective_io_concurrency value: 200 - option: work_mem value: 2621kB - option: huge_pages value: 'off' - option: min_wal_size value: 1GB - option: max_wal_size value: 4GB gitlab_omnibius_ssh_keys: - path: /etc/ssh/ssh_host_dsa_key content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_dsa_key }}" mode: '0600' - path: /etc/ssh/ssh_host_dsa_key.pub mode: '0644' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_dsa_key_pub }}" - path: /etc/ssh/ssh_host_ecdsa_key mode: '0600' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_ecdsa_key }}" - path: /etc/ssh/ssh_host_ecdsa_key.pub mode: '0644' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_ecdsa_key_pub }}" - path: /etc/ssh/ssh_host_ed25519_key mode: '0600' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_ed25519_key }}" - path: /etc/ssh/ssh_host_ed25519_key.pub mode: '0644' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_ed25519_key_pub }}" - path: /etc/ssh/ssh_host_rsa_key mode: '0600' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_rsa_key }}" - path: /etc/ssh/ssh_host_rsa_key.pub mode: '0644' content: "{{ vault_gitlab_omnibus_gitlab_ssh_keys_ssh_host_rsa_key_pub }}" redis_server_password: "{{ vault_gitlab_omnibus_rails_redis_password }}" redis_sentinel_password: "{{ vault_gitlab_omnibus_rails_redis_password }}" redis_node_roles: - master - sentinel redis_mastername: gitlab_redis redis_masteruser: gitlab_redis redis_masterauth: "{{ vault_gitlab_omnibus_rails_redis_password }}" redis_sentinel_monitors: - name: gitlab_redis host: ybd-git-red01 port: 6379 quorum: 2 auth_pass: "{{ vault_gitlab_omnibus_rails_redis_password }}" down_after_milliseconds: 1000 parallel_syncs: 1 failover_timeout: 1000 notification_script: false rename_commands: [] redis_sentinel_extra_config: sentinel: resolve_hostnames: "yes" announce_hostnames: "yes"
Развёртывание кластера
Развёртывание производится с помощью единого Ansible-плейбука, который автоматически настраивает все компоненты кластера. Плейбук включает:
-
Настройку и создание баз данных PostgreSQL и Redis.
-
Установку и настройку Rails, Gitaly, Praefect, Sidekiq.
-
Настройку конфигурации для каждого сервиса.
-
Запуск служб и проверку их работоспособности.
Пример плейбука:
playbooks/gitlab_cluster/gitlab_cluster.yml:
Скрытый текст
--- - name: pull gitlab secrets from vault hosts: gitlab_cluster tasks: - name: Read the vault community.hashi_vault.vault_kv2_get: url: 'https://vault.testlab.org' path: '{{ environments }}/gitlab_cluster' auth_method: 'approle' role_id: "{{ vault_role_id }}" secret_id: "{{ vault_secret_id }}" engine_mount_point: 'kv-devops' register: secrets - name: set env ansible.builtin.set_fact: "{{ item.key }}": "{{ item.value }}" loop: "{{ secrets.secret | dict2items }}" no_log: true tags: - gitlab - postgresql - redis-cluster - hosts: gitlab_postgresql become: true roles: - postgresql-local tags: - postgresql - hosts: gitlab_redis become: true roles: - redis-cluster tags: - redis-cluster - hosts: - gitlab_cluster:!gitlab_postgresql:!gitlab_redis become: true roles: - gitlab-omnibus tags: - gitlab
для запуска можно использовать команду
ansible-playbook -i environments/dev playbooks/gitlab_cluster/gitlab_cluster.yml
После успешного выполнения всех шагов кластер GitLab готов к работе.
Часть 4. Zero-Downtime Upgrade (Бесшовное обновление)
Зачем нужны бесшовные обновления?
Одной из ключевых задач при эксплуатации кластера GitLab является обновление без простоев. Нам необходимо было обеспечить бесперебойную работу всех сервисов, чтобы пользователи не испытывали проблем в процессе обновления.
Рекомендую ознакомиться с статьей Zero-downtime upgrades.
В статье указан рекомендуемый порядок обновления и действия, которые необходимо соблюсти.
Порядок обновления следующий:
-
Gitaly
-
Обновление производится поочерёдно для каждой ноды Gitaly.
-
Перед обновлением ноду временно исключают из пула, проводят обновление, затем возвращают в рабочую группу, что позволяет поддерживать доступность Git-операций.
-
-
Praefect
-
После успешного обновления Gitaly переходим к обновлению Praefect, который выступает в роли маршрутизатора запросов к Gitaly.
-
Также используется поочерёдное обновление, чтобы обеспечить корректное распределение запросов во время переходного периода.
-
В процессе обновления конкретная нода выводится из roundrobin на HAProxy, чтобы в неё не поступали новые запросы. Это позволяет избежать потенциальных ошибок или некорректного распределения нагрузки.
-
После успешного обновления и проверки работоспособности ноды, она возвращается в пул, и HAProxy вновь начинает распределять запросы равномерно между всеми доступными нодами.
-
-
Rails
-
После обновления инфраструктурных компонентов (Gitaly и Praefect) начинается обновление Rails-серверов.
-
Обновление первой ноды:
-
Исключение из HAProxy: Первую ноду исключают из roundrobin HAProxy, чтобы на неё не поступали новые запросы.
-
После успешного обновления ноды выполняют миграции базы данных. Важно, что миграции запускаются только на первой ноде – это позволяет внести необходимые изменения в схему базы данных, обеспечивая корректную работу приложения и совместимость новой версии с уже работающими компонентами.
-
Возврат в пул: После проверки работоспособности ноды её возвращают в пул HAProxy, и трафик снова распределяется между всеми серверами.
-
-
Обновление остальных нод:
-
Каждую последующую ноду предварительно исключают из HAProxy, затем проводят обновление (без повторного запуска миграций) и после проверки возвращают обратно в пул.
-
-
-
Sidekiq
-
После обновления Rails происходит обновление процессов Sidekiq, отвечающих за выполнение фоновых задач.
-
Перезапуск и контроль очередей:
-
Обновление Sidekiq выполняется так, чтобы новые воркеры корректно обрабатывали как оставшиеся задачи предыдущей версии, так и новые поступающие задачи.
-
Обычно процессы Sidekiq перезапускаются после обновления Rails, чтобы обеспечить совместимость.
-
-
Для обновления можно использовать следующий плейбук:
playbooks/gitlab_cluster/gitlab_cluster_upgrade.yml:
Скрытый текст
--- - name: pull gitlab secrets from vault hosts: gitlab_cluster tasks: - name: Read the vault community.hashi_vault.vault_kv2_get: url: 'https://vault.testlab.org' path: '{{ environments }}/gitlab_cluster' auth_method: 'approle' role_id: "{{ vault_role_id }}" secret_id: "{{ vault_secret_id }}" engine_mount_point: 'kv-devops' register: secrets - name: set env ansible.builtin.set_fact: "{{ item.key }}": "{{ item.value }}" loop: "{{ secrets.secret | dict2items }}" no_log: true - name: upgrade_gitaly hosts: gitlab_gitaly become: true roles: - gitlab-omnibus vars: gitlab_omnibus_upgrade_gitaly: true serial: 1 - name: upgrade_praefect hosts: gitlab_praefect become: true roles: - gitlab-omnibus vars: gitlab_omnibus_upgrade_praefect: true serial: 1 - name: upgrade_rails[0] hosts: gitlab_rails[0] become: true roles: - gitlab-omnibus vars: gitlab_omnibus_upgrade_rails: true serial: 1 - name: db_migrate over gitlab_rails[0] hosts: gitlab_rails[0] become: true roles: - gitlab-omnibus vars: gitlab_omnibus_db_migrate: true serial: 1 run_once: true - name: upgrade_rails[1] hosts: gitlab_rails[1] become: true roles: - gitlab-omnibus vars: gitlab_omnibus_upgrade_rails: true serial: 1 - name: upgrade_sidekiq hosts: gitlab_sidekiq become: true roles: - gitlab-omnibus vars: gitlab_omnibus_upgrade_sidekiq: true serial: 1
После успешного выполнения всех шагов кластер GitLab обновлен и готов к работе.
Часть 5. Выводы
Итоги проделанной работы
В ходе внедрения отказоустойчивого GitLab Cluster мы смогли:
-
Перейти от Single Node к распределённой архитектуре, улучшив надёжность и отказоустойчивость системы.
-
Настроить автоматизированное развёртывание инфраструктуры с помощью Ansible, что позволило упростить управление и сократить время на развёртывание новых узлов.
-
Реализовать бесшовное обновление (Zero-Downtime Upgrade), обеспечив непрерывную работу сервиса даже в моменты обновления критических компонентов.
Основные рекомендации
-
Планирование критично: перед каждым обновлением или изменением в инфраструктуре необходимо тщательно тестировать изменения в staging-среде.
-
Идемпотентность Ansible: важно поддерживать плейбуки в идемпотентном состоянии, чтобы минимизировать риски сбоев при повторных запусках.
-
Мониторинг и логирование: важно настроить мониторинг критических сервисов (например, через Prometheus и Grafana), чтобы оперативно выявлять возможные проблемы.
-
Постепенное обновление: критически важно обновлять компоненты поочерёдно, чтобы избежать массовых сбоев.
-
Обновляйтесь только на один минорный релиз за раз. То есть, с 17.1 до 17.2, а не до 17.3. Если пропустить релизы, изменения в базе данных могут выполняться в неправильной последовательности, что приведёт к нарушению схемы базы данных.
-
Перед обновлением убедитесь что завершены все Background migrations (admin/background_migrations) после предыдущего обновления.
Этот опыт помог нам повысить стабильность инфраструктуры, упростить её поддержку и обеспечить разработчикам бесперебойный доступ к инструментам CI/CD.
Такой подход может быть полезен для команд, которые стремятся внедрить отказоустойчивую версию GitLab и минимизировать время простоя при обновлениях.
И немного метрик
Было:
Скрытый текст

Стало:
Скрытый текст

ссылка на оригинал статьи https://habr.com/ru/articles/879602/
Добавить комментарий