Triển khai GitLab High availability Production bằng Ansible

Đây là tài liệu mà tôi làm để triển khai cho công ty nên rất chi tiết. Vì thấy tài liệu Gitlab High availability bằng tiếng việt còn ít quá và cũng thiếu sự thực tế nên chia sẻ tài liệu này với mọi người để các bạn chưa có kinh nghiệm, chưa biết làm thì thêm nguồn tham khảo.

Lưu ý: có thể thời điểm tôi làm mọi người chạy ngon nhưng có thể lúc bạn làm có thứ bị outdate, cứ comment tôi sẽ update cho mọi người nhé.

adeb3bb2-61d2-4f23-8c2a-3bc8a383e629

Kiến trúc tổng thể

0. Hạ tầng

Servers Ubuntu 22.04 (nếu bạn muốn làm lab thì RAM nên tối thiểu 4GB RAM nhé):

  • 192.168.1.11
  • 192.168.1.12
  • 192.168.1.13

1. Cluster Coordination & State Management

  • Consul (DCS) Distributed Configuration Store: Lưu trữ cấu hình, quản lý leader election cho Patroni, và cung cấp service discovery cho các dịch vụ khác.

2. Database & Connection Layer

  • Patroni + PostgreSQL (DB HA) Cụm PostgreSQL có automatic failoversynchronous replication.
  • PgBouncer Connection pooler, tạo endpoint ổn định cho ứng dụng và Praefect.

3. Caching & Queueing Layer

  • Redis + Sentinel (Cache/Queues HA) Cung cấp cache và hàng đợi HA cho GitLab RailsSidekiq.

4. Git Storage Layer

  • Gitaly Cluster (3 backends) + Praefect (Orchestrator) Quản lý nhân bản repo, điều phối đọc/ghi, loại bỏ SPOF trong lớp Gitaly.

5. Application Layer

  • GitLab Rails / Workhorse / Puma / Sidekiq chạy trên cả 3 node xử lý web UI, API, và background jobs.

6. Access & Load Balancing Layer

  • HAProxy + Keepalived (VIP) Load balancer cho HTTP(S), SSH, và (tuỳ chọn) gRPC Praefect. Cung cấp virtual IP, health check, và failover tự động.

7. Object Storage Layer

  • Object Storage (S3 / GCS được khuyến nghị) Lưu trữ artifacts, uploads, LFS, packages, registry. Giúp tách biệt dữ liệu khỏi ổ đĩa local, tăng khả năng mở rộng.

8. Observability & Backup Strategy

  • Prometheus + Grafana + Exporters Theo dõi hiệu năng, tài nguyên, và cảnh báo.
  • Backup chiến lược: WAL, Database, Repo, và Object Storage lifecycle.

Step 0. Cấu trúc thư mục

gitlab-ha-3nodes/
├─ inventory/
│  └─ hosts.ini
├─ group_vars/
│  └─ gitlab_ha.yml
├─ site.yml
├─ roles/
│  ├─ base/
│  │  └─ tasks/main.yml
│  ├─ consul/
│  │  ├─ tasks/main.yml
│  │  └─ templates/consul.hcl.j2
│  ├─ patroni_pg/
│  │  ├─ tasks/main.yml
│  │  └─ templates/patroni.yml.j2
│  ├─ pgbouncer/
│  │  ├─ tasks/main.yml
│  │  └─ templates/pgbouncer.ini.j2
│  ├─ redis_sentinel/
│  │  ├─ tasks/main.yml
│  │  ├─ templates/redis.conf.j2
│  │  └─ templates/sentinel.conf.j2
│  ├─ gitaly/
│  │  ├─ tasks/main.yml
│  │  └─ templates/gitaly.rb.j2
│  ├─ praefect/
│  │  ├─ tasks/main.yml
│  │  └─ templates/praefect.toml.j2
│  ├─ gitlab_app/
│  │  ├─ tasks/main.yml
│  │  └─ templates/gitlab.rb.j2
│  ├─ haproxy_keepalived/
│  │  ├─ tasks/main.yml
│  │  ├─ templates/haproxy.cfg.j2
│  │  └─ templates/keepalived.conf.j2
│  └─ monitoring/
│     └─ tasks/main.yml
└─ README.md

Step 1. Inventory & Group variables

inventory/hosts.ini

[gitlab_ha]
192.168.1.11 node_id=1 hostname=gitlab-01.example.local
192.168.1.12 node_id=2 hostname=gitlab-02.example.local
192.168.1.13 node_id=3 hostname=gitlab-03.example.local

group_vars/gitlab_ha.yml

# Thông tin chung
ansible_user: ubuntu
ansible_become: true

# Tên miền/VIP
external_url: "https://gitlab.example.com"
gitlab_ssh_host: "gitlab.example.com"
git_ssh_port: 22
vip_address: 192.168.1.10
vip_interface: "ens160"       # đổi theo interface của bạn
vip_priority_primary: 150      # node_id=1 làm master VIP mặc định

# Token
redis_password: "CHANGE_ME_STRONG"
postgres_password: "CHANGE_ME_STRONG"
pgbouncer_password: "CHANGE_ME_STRONG"
gitaly_token: "CHANGE_ME_STRONG"
praefect_db_password: "CHANGE_ME_STRONG"
consul_encrypt: "CHANGE_ME_32BYTESKEY=="    # consul key base64 hoặc key 16/32 bytes
consul_datacenter: "dc1"

# S3 Object Storage (khuyến nghị dùng dịch vụ managed)
s3_enabled: true
s3_endpoint: "https://s3.amazonaws.com"     # hoặc endpoint S3 compatible
s3_region: "ap-southeast-1"
s3_access_key: "AKIA..."
s3_secret_key: "..."
s3_bucket_artifacts: "gitlab-artifacts"
s3_bucket_uploads: "gitlab-uploads"
s3_bucket_lfs: "gitlab-lfs"
s3_bucket_packages: "gitlab-packages"
s3_bucket_registry: "gitlab-registry"

# Praefect virtual storage đặt tên 'default'
praefect_virtual_storage: "default"

# Đường dẫn dữ liệu
pg_data_dir: "/var/opt/gitlab/postgresql"
pg_wal_dir: "/var/opt/gitlab/postgresql-wal"
redis_data_dir: "/var/opt/gitlab/redis"
gitaly_data_dir: "/var/opt/gitlab/git-data"

# Phiên bản/gói
omnibus_package_url: "https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh" # hoặc CE
use_ee: false  # true nếu EE

# Ports chuẩn
postgres_port: 5432
pgbouncer_port: 6432
redis_port: 6379
redis_sentinel_port: 26379
gitaly_port: 8075
praefect_port: 2305
http_port: 80
https_port: 443

# Health checks đơn giản
haproxy_backend_timeout: 90s

Step 2. site.yml (chạy tuần tự, idempotent)

- hosts: gitlab_ha
  any_errors_fatal: true
  vars:
    need_reboot: false
  roles:
    - role: base
    - role: consul
    - role: patroni_pg
    - role: pgbouncer
    - role: redis_sentinel
    - role: gitaly
    - role: praefect
    - role: gitlab_app
    - role: haproxy_keepalived
    - role: monitoring

Step 2. Role: base

roles/base/tasks/main.yml

- name: Cập nhật APT & cài gói cơ bản
  apt:
    update_cache: yes
    name:
      - curl
      - gnupg
      - lsb-release
      - jq
      - python3-psycopg2
      - net-tools
      - htop
      - chrony
      - acl
    state: present

- name: Đồng bộ thời gian
  service:
    name: chrony
    state: started
    enabled: yes

- name: Tắt swap (khuyến nghị cho DB)
  command: swapoff -a
  when: need_reboot

- name: Đảm bảo fqdn hostname
  hostname:
    name: "{{ hostvars[inventory_hostname].hostname | default(inventory_hostname) }}"

Step 4. Role: consul (DCS cho Patroni, service discovery)

roles/consul/tasks/main.yml

- name: Cài đặt Consul
  apt:
    deb: https://releases.hashicorp.com/consul/1.19.1/consul_1.19.1_linux_amd64.deb

- name: Tạo thư mục cấu hình
  file:
    path: /etc/consul.d
    state: directory
    mode: '0755'

- name: Render cấu hình Consul
  template:
    src: consul.hcl.j2
    dest: /etc/consul.d/consul.hcl
    mode: '0644'

- name: Tạo service systemd
  copy:
    dest: /etc/systemd/system/consul.service
    mode: '0644'
    content: |
      [Unit]
      Description=Consul Agent
      After=network-online.target
      Wants=network-online.target

      [Service]
      ExecStart=/usr/bin/consul agent -config-file=/etc/consul.d/consul.hcl
      Restart=on-failure
      LimitNOFILE=65536

      [Install]
      WantedBy=multi-user.target

- name: Khởi động Consul
  systemd:
    name: consul
    state: started
    enabled: yes

roles/consul/templates/consul.hcl.j2

server = true
datacenter = "{{ consul_datacenter }}"
data_dir = "/var/lib/consul"
bootstrap_expect = 3
encrypt = "{{ consul_encrypt }}"
retry_join = [
  "192.168.1.11",
  "192.168.1.12",
  "192.168.1.13"
]
bind_addr = "{{ ansible_default_ipv4.address }}"
client_addr = "0.0.0.0"
ui_config { enabled = true }

Step 5. Role: patroni_pg (PostgreSQL HA + Patroni)

roles/patroni_pg/tasks/main.yml

- name: Cài đặt gpg & repo GitLab Omnibus
  shell: |
    curl -s {{ omnibus_package_url }} | bash
  args:
    creates: /etc/apt/sources.list.d/gitlab_gitlab-*.list

- name: Cài đặt PostgreSQL dependencies
  apt:
    name:
      - gitlab-common
      - procps
    state: present

- name: Tạo thư mục dữ liệu
  file:
    path: "{{ item }}"
    state: directory
    owner: gitlab-psql
    group: gitlab-psql
    mode: '0750'
  loop:
    - "{{ pg_data_dir }}"
    - "{{ pg_wal_dir }}"

- name: Cài đặt Patroni
  apt:
    name:
      - patroni
      - python3-psycopg2
    state: present

- name: Render cấu hình Patroni
  template:
    src: patroni.yml.j2
    dest: /etc/patroni.yml
    mode: '0644'

- name: Tạo service Patroni
  copy:
    dest: /etc/systemd/system/patroni.service
    mode: '0644'
    content: |
      [Unit]
      Description=Patroni PostgreSQL
      After=network.target consul.service

      [Service]
      ExecStart=/usr/bin/patroni /etc/patroni.yml
      Restart=on-failure
      User=gitlab-psql

      [Install]
      WantedBy=multi-user.target

- name: Khởi động Patroni
  systemd:
    name: patroni
    state: started
    enabled: yes

roles/patroni_pg/templates/patroni.yml.j2

scope: gitlab-pg
namespace: /service/
name: "pg-{{ hostvars[inventory_hostname].node_id }}"

dcs:
  consul:
    host: 127.0.0.1:8500
  ttl: 30
  loop_wait: 10
  retry_timeout: 10
  maximum_lag_on_failover: 1048576

restapi:
  listen: 0.0.0.0:8008
  connect_address: {{ ansible_default_ipv4.address }}:8008

postgresql:
  listen: 0.0.0.0:{{ postgres_port }}
  connect_address: {{ ansible_default_ipv4.address }}:{{ postgres_port }}
  data_dir: {{ pg_data_dir }}
  bin_dir: /usr/lib/postgresql/14/bin
  parameters:
    wal_level: replica
    hot_standby: "on"
    max_wal_senders: 10
    max_replication_slots: 10
    shared_buffers: "2GB"
    work_mem: "64MB"
    maintenance_work_mem: "256MB"
    effective_cache_size: "6GB"
    wal_keep_size: "1024"
  authentication:
    replication:
      username: replicator
      password: "{{ postgres_password }}"
    superuser:
      username: postgres
      password: "{{ postgres_password }}"
    rewind:
      username: rewind
      password: "{{ postgres_password }}"

bootstrap:
  dcs:
    synchronous_mode: true
    synchronous_node_count: 1
  method: initdb
  initdb:
    - encoding: UTF8
    - data-checksums
  users:
    gitlab:
      password: "{{ postgres_password }}"
      options:
        - CREATEDB
        - CREATEROLE

watchdog:
  mode: off

Step 6. Role: pgbouncer (endpoint ổn định cho App/Praefect)

roles/pgbouncer/tasks/main.yml

- name: Cài PgBouncer
  apt:
    name: pgbouncer
    state: present

- name: Render pgbouncer.ini
  template:
    src: pgbouncer.ini.j2
    dest: /etc/pgbouncer/pgbouncer.ini

- name: Tạo users.txt
  copy:
    dest: /etc/pgbouncer/users.txt
    mode: '0600'
    content: |
      "gitlab" "{{ pgbouncer_password }}"
      "praefect" "{{ pgbouncer_password }}"

- name: Khởi động PgBouncer
  systemd:
    name: pgbouncer
    state: started
    enabled: yes

roles/pgbouncer/templates/pgbouncer.ini.j2

[databases]
gitlabhq_production = host=127.0.0.1 port={{ postgres_port }} user=gitlab dbname=gitlabhq_production
praefect_production = host=127.0.0.1 port={{ postgres_port }} user=praefect dbname=praefect_production

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = {{ pgbouncer_port }}
pool_mode = session
max_client_conn = 2000
server_tls_sslmode = disable
auth_type = plain
auth_file = /etc/pgbouncer/users.txt
admin_users = postgres,gitlab

Step 7. Role: redis_sentinel (Redis HA)

roles/redis_sentinel/tasks/main.yml

- name: Cài Redis
  apt:
    name: redis-server
    state: present

- name: Render redis.conf
  template:
    src: redis.conf.j2
    dest: /etc/redis/redis.conf

- name: Khởi động Redis
  systemd:
    name: redis-server
    state: started
    enabled: yes

- name: Render sentinel.conf
  template:
    src: sentinel.conf.j2
    dest: /etc/redis/sentinel.conf

- name: Service Sentinel
  copy:
    dest: /etc/systemd/system/redis-sentinel.service
    mode: '0644'
    content: |
      [Unit]
      Description=Redis Sentinel
      After=network.target

      [Service]
      ExecStart=/usr/bin/redis-server /etc/redis/sentinel.conf --sentinel
      Restart=always

      [Install]
      WantedBy=multi-user.target

- name: Khởi động Sentinel
  systemd:
    name: redis-sentinel
    state: started
    enabled: yes

roles/redis_sentinel/templates/redis.conf.j2

bind 0.0.0.0
port {{ redis_port }}
requirepass {{ redis_password }}
appendonly yes
dir {{ redis_data_dir }}

roles/redis_sentinel/templates/sentinel.conf.j2

port {{ redis_sentinel_port }}
sentinel monitor gitlab-redis 192.168.1.11 {{ redis_port }} 2
# gợi ý: ban đầu chọn 1 master (11), sau khi cụm ổn bạn có thể chuyển đổi
sentinel auth-pass gitlab-redis {{ redis_password }}
sentinel down-after-milliseconds gitlab-redis 5000
sentinel parallel-syncs gitlab-redis 1
sentinel failover-timeout gitlab-redis 60000

Nếu muốn đầy đủ hơn, bạn có thể generate monitor lines theo node_id và set replicaof ở redis.conf các node phụ. Khi failover, Sentinel cập nhật master mới; ứng dụng nên trỏ vào Sentinel list thay vì 1 host cụ thể.

Step 8. Role: gitaly (backend repo)

roles/gitaly/tasks/main.yml

- name: Cài omnibus gitlab (để lấy gitaly, gitlab-shell, ruby env)
  apt:
    name: gitlab-ce
    state: present

- name: Tạo thư mục dữ liệu Gitaly
  file:
    path: "{{ gitaly_data_dir }}"
    state: directory
    owner: git
    group: git
    mode: '0750'

- name: Render gitaly.rb
  template:
    src: gitaly.rb.j2
    dest: /etc/gitlab/gitaly.rb

- name: Reconfigure chỉ Gitaly
  shell: gitlab-ctl reconfigure

roles/gitaly/templates/gitaly.rb.j2

gitaly['enable'] = true
gitaly['listen_addr'] = "0.0.0.0:{{ gitaly_port }}"
# khuyến nghị TLS trong production
# gitaly['auth_token'] = "{{ gitaly_token }}"

# đường dẫn repo
git_data_dirs({
  "default" => { "path" => "{{ gitaly_data_dir }}" }
})

Step 9. Role: praefect (orchestrator Gitaly)

roles/praefect/tasks/main.yml

- name: Render praefect.toml
  template:
    src: praefect.toml.j2
    dest: /etc/gitlab/praefect.toml

- name: Tạo DB praefect (qua PgBouncer)
  shell: |
    psql "host=127.0.0.1 port={{ pgbouncer_port }} user=gitlab password={{ pgbouncer_password }} dbname=postgres" \
      -c "CREATE USER praefect WITH PASSWORD '{{ pgbouncer_password }}';" || true
    psql "host=127.0.0.1 port={{ pgbouncer_port }} user=gitlab password={{ pgbouncer_password }} dbname=postgres" \
      -c "CREATE DATABASE praefect_production OWNER praefect;" || true

- name: Reconfigure praefect
  shell: gitlab-ctl reconfigure

roles/praefect/templates/praefect.toml.j2

[postgresql]
host = "127.0.0.1"
port = {{ pgbouncer_port }}
dbname = "praefect_production"
user = "praefect"
password = "{{ pgbouncer_password }}"

[virtual_storage."{{ praefect_virtual_storage }}"]
# 3 nodes gitaly
nodes = [
  { address = "tcp://192.168.1.11:{{ gitaly_port }}", token = "{{ gitaly_token }}" },
  { address = "tcp://192.168.1.12:{{ gitaly_port }}", token = "{{ gitaly_token }}" },
  { address = "tcp://192.168.1.13:{{ gitaly_port }}", token = "{{ gitaly_token }}" }
]

[auth]
token = "{{ gitaly_token }}"

[failover]
# có thể tinh chỉnh: election strategy, quorum, backoff

Step 10. Role: gitlab_app (Rails/Workhorse/Puma/Sidekiq, Redis Sentinel, DB qua PgBouncer, S3)

roles/gitlab_app/tasks/main.yml

- name: Cài omnibus gitlab app
  apt:
    name: "{{ 'gitlab-ee' if use_ee else 'gitlab-ce' }}"
    state: present

- name: Render gitlab.rb
  template:
    src: gitlab.rb.j2
    dest: /etc/gitlab/gitlab.rb

- name: Reconfigure GitLab app
  shell: gitlab-ctl reconfigure

roles/gitlab_app/templates/gitlab.rb.j2

external_url "{{ external_url }}"

# SSH
gitlab_rails['gitlab_ssh_host'] = "{{ gitlab_ssh_host }}"
gitlab_rails['gitlab_shell_ssh_port'] = {{ git_ssh_port }}

# DB qua PgBouncer local
postgresql['enable'] = false
pgbouncer['enable'] = true
pgbouncer['listen_addr'] = "0.0.0.0"
pgbouncer['listen_port'] = {{ pgbouncer_port }}

# App kết nối PgBouncer (localhost)
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_host'] = '127.0.0.1'
gitlab_rails['db_port'] = {{ pgbouncer_port }}
gitlab_rails['db_username'] = 'gitlab'
gitlab_rails['db_password'] = '{{ pgbouncer_password }}'

# Redis Sentinel list
redis['enable'] = false
sentinel_hosts = [
  '192.168.1.11:{{ redis_sentinel_port }}',
  '192.168.1.12:{{ redis_sentinel_port }}',
  '192.168.1.13:{{ redis_sentinel_port }}'
]

# GitLab sidekiq/rails config tới Sentinel
gitlab_rails['redis_sentinels'] = sentinel_hosts
gitlab_rails['redis_master_name'] = 'gitlab-redis'
gitlab_rails['redis_password'] = '{{ redis_password }}'
# nếu dùng TLS cho Redis: gitlab_rails['redis_ssl'] = true

# Gitaly qua Praefect
gitlab_rails['gitaly_token'] = '{{ gitaly_token }}'
git_data_dirs({
  "{{ praefect_virtual_storage }}" => {"gitaly_address" => "tcp://127.0.0.1:{{ praefect_port }}"}
})

# Object Storage
{{ '# S3 conf' if s3_enabled else '# S3 disabled' }}
{% if s3_enabled %}
gitlab_rails['object_store'] = {
  'enabled' => true,
  'connection' => {
    'provider' => 'AWS',
    'aws_access_key_id' => '{{ s3_access_key }}',
    'aws_secret_access_key' => '{{ s3_secret_key }}',
    'region' => '{{ s3_region }}',
    'endpoint' => '{{ s3_endpoint }}'
  }
}

gitlab_rails['artifacts_object_store_enabled'] = true
gitlab_rails['artifacts_object_store_remote_directory'] = '{{ s3_bucket_artifacts }}'

gitlab_rails['uploads_object_store_enabled'] = true
gitlab_rails['uploads_object_store_remote_directory'] = '{{ s3_bucket_uploads }}'

gitlab_rails['lfs_object_store_enabled'] = true
gitlab_rails['lfs_object_store_remote_directory'] = '{{ s3_bucket_lfs }}'

gitlab_rails['packages_object_store_enabled'] = true
gitlab_rails['packages_object_store_remote_directory'] = '{{ s3_bucket_packages }}'

registry_external_url "https://registry.gitlab.example.com"
registry['storage'] = {
  's3' => {
    'bucket' => '{{ s3_bucket_registry }}',
    'region' => '{{ s3_region }}',
    'accesskey' => '{{ s3_access_key }}',
    'secretkey' => '{{ s3_secret_key }}',
    'endpoint' => '{{ s3_endpoint }}'
  }
}
{% endif %}

# Praefect proxy lắng nghe local (qua HAProxy hoặc trực tiếp)
praefect['enable'] = true
praefect['listen_addr'] = "0.0.0.0:{{ praefect_port }}"
# praefect['auth_token'] = "{{ gitaly_token }}"

Gợi ý: với Omnibus, bạn có thể để Praefect chạy như một thành phần nằm cùng máy app, hoặc tách role ra (đã có role praefect). Ở mô hình 3 máy, chạy cùng vẫn ổn.

Step 11. Role: haproxy_keepalived (LB + VIP)

roles/haproxy_keepalived/tasks/main.yml

- name: Cài HAProxy & Keepalived
  apt:
    name:
      - haproxy
      - keepalived
    state: present

- name: Render haproxy.cfg
  template:
    src: haproxy.cfg.j2
    dest: /etc/haproxy/haproxy.cfg

- name: Render keepalived.conf (ưu tiên node_id=1)
  template:
    src: keepalived.conf.j2
    dest: /etc/keepalived/keepalived.conf

- name: Enable & start services
  systemd:
    name: "{{ item }}"
    state: started
    enabled: yes
  loop:
    - haproxy
    - keepalived

roles/haproxy_keepalived/templates/haproxy.cfg.j2

global
  log /dev/log local0
  maxconn 20000

defaults
  mode http
  timeout connect 10s
  timeout client {{ haproxy_backend_timeout }}
  timeout server {{ haproxy_backend_timeout }}

# HTTPS passthrough/termination tuỳ bạn. Ở đây demo HTTP -> backend HTTP (đơn giản)
frontend fe_http
  bind *:{{ http_port }}
  default_backend be_gitlab

backend be_gitlab
  option httpchk GET /-/health
  server app1 192.168.1.11:{{ http_port }} check
  server app2 192.168.1.12:{{ http_port }} check
  server app3 192.168.1.13:{{ http_port }} check

# TCP cho SSH
frontend fe_ssh
  bind *:{{ git_ssh_port }}
  mode tcp
  default_backend be_ssh

backend be_ssh
  mode tcp
  server ssh1 192.168.1.11:{{ git_ssh_port }} check
  server ssh2 192.168.1.12:{{ git_ssh_port }} check
  server ssh3 192.168.1.13:{{ git_ssh_port }} check

# gRPC Praefect (tuỳ bạn tách riêng)
frontend fe_praefect
  bind *:{{ praefect_port }}
  mode tcp
  default_backend be_praefect

backend be_praefect
  mode tcp
  server pr1 192.168.1.11:{{ praefect_port }} check
  server pr2 192.168.1.12:{{ praefect_port }} check
  server pr3 192.168.1.13:{{ praefect_port }} check

roles/haproxy_keepalived/templates/keepalived.conf.j2

vrrp_instance VI_1 {
    state {{ 'MASTER' if (hostvars[inventory_hostname].node_id | int) == 1 else 'BACKUP' }}
    interface {{ vip_interface }}
    virtual_router_id 51
    priority {{ vip_priority_primary if (hostvars[inventory_hostname].node_id | int) == 1 else 100 + (3 - (hostvars[inventory_hostname].node_id | int)) }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 42secret
    }
    virtual_ipaddress {
        {{ vip_address }}/24
    }
}

Nếu cần HTTPS termination tại HAProxy, bổ sung bind *:443 ssl crt /etc/haproxy/certs/ và chỉnh backend health check/headers.

Step 12. Role: monitoring (gợi ý)

roles/monitoring/tasks/main.yml

- name: Cài node_exporter (tham khảo)
  get_url:
    url: https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
    dest: /tmp/node_exporter.tar.gz
  register: dl

- name: Giải nén
  unarchive:
    src: /tmp/node_exporter.tar.gz
    dest: /usr/local/bin/
    remote_src: yes
  when: dl is changed

- name: Tạo service node_exporter
  copy:
    dest: /etc/systemd/system/node_exporter.service
    content: |
      [Unit]
      Description=Node Exporter
      After=network.target

      [Service]
      ExecStart=/usr/local/bin/node_exporter-1.7.0.linux-amd64/node_exporter

      [Install]
      WantedBy=multi-user.target

- name: Start node_exporter
  systemd:
    name: node_exporter
    state: started
    enabled: yes

Step 13. Cách chạy

cd gitlab-ha-3nodes
python3 -m venv .venv && source .venv/bin/activate
pip install ansible
ansible-galaxy collection install community.general

# Kiểm tra kết nối
ansible -i inventory/hosts.ini gitlab_ha -m ping

# Triển khai
ansible-playbook -i inventory/hosts.ini site.yml

Sau khi xong:

  • Truy cập http://gitlab.example.com (hoặc HTTPS nếu đã cấu hình) qua VIP 192.168.1.10.
  • Kiểm tra health: /-/health, /-/readiness, /-/liveness.
  • SSH: ssh git@gitlab.example.com (port 22 hoặc port bạn đổi).

Step 14. Kiểm thử HA từng lớp

  1. Consul/Patroni/DB: dừng Patroni ở node leader, xem failover sang node khác; kiểm tra kết nối PgBouncer vẫn ổn.
  2. Redis/Sentinel: dừng master Redis, kiểm tra Sentinel bầu master mới; ứng dụng vẫn push pipeline/sidekiq bình thường.
  3. Gitaly/Praefect: dừng gitaly ở 1 node, clone/push vẫn OK; kiểm tra replication lag.
  4. HAProxy/Keepalived: shutdown máy giữ VIP, VIP chuyển qua node khác, HTTP/SSH không gián đoạn đáng kể.

Step 15. Runbook sự cố (tóm tắt, thao tác nhanh)

15.1 DB (Patroni)

  • Phát hiện: API Patroni :8008 trả leader/followers. Log tại /var/log/syslog (hoặc journald) và patronictl list.
  • Leader down:

    • Xác nhận quorum Consul OK (consul members).
    • Patroni sẽ tự failover (synchronous_mode=true). Đợi tối đa 30–60s.
    • Nếu không failover: patronictl failover từ 1 follower đủ điều kiện.
  • Split-brain phòng tránh: giữ Consul quorum 3/3, tránh thao tác thủ công khi chưa rõ tình trạng mạng.
  • Khôi phục leader cũ: khởi động lại dịch vụ Patroni, nó sẽ join thành follower và tự theo kịp WAL.

15.2 Redis/Sentinel

  • Phát hiện: redis-cli -p 26379 SENTINEL get-master-addr-by-name gitlab-redis.
  • Master down: Sentinel chọn master mới; không đổi cấu hình app.
  • Sự cố auth: kiểm tra requirepassauth-pass đồng nhất; nếu dùng TLS bật cùng tham số ở app.

15.3 Gitaly/Praefect

  • Repo lỗi: kiểm tra gitlab-rake gitlab:check GITLAB_LOG_LEVEL=debug.
  • Node Gitaly down: Praefect phục vụ từ node còn lại; kiểm tra đồng bộ, sau đó đưa node về bằng cách khởi động service Gitaly.
  • Metadata Praefect: DB praefect_production phải hoạt động qua PgBouncer; kiểm tra kết nối.

15.4 HAProxy/Keepalived

  • VIP mất: ip a | grep {{ vip_address }} trên 3 node, xem node nào đang giữ VIP. Kiểm tra keepalived trạng thái.
  • LB lỗi health check: xem /var/log/haproxy.log, và curl -I http://127.0.0.1:{{ http_port }}/-/health tại backend.

15.5 Object Storage

  • Upload/pipeline fail: kiểm tra thông tin S3: bucket, quyền IAM, endpoint, region; log production_json.log của Rails.
  • Tắc nghẽn: bật lifecycle, versioning; tránh để local disk đầy.

15.6 Backup/Restore nhanh

  • DB: dùng pg_basebackup/wal-g về S3, restore theo timeline gần nhất.
  • Repos: snapshot filesystem + kiểm tra tính nhất quán Praefect.
  • Artifacts/LFS/Uploads/Registry: đã trên S3, kiểm tra versioning để rollback.

Step 16. Nâng cấp không/ít downtime

  • Ứng dụng (Omnibus): rolling từng node, theo thứ tự: app => praefect/gitaly => haproxy (nếu cần). Theo dõi gitlab-ctl tail.
  • DB: dùng Patroni để điều phối switchover, đảm bảo synchronous_modemaximum_lag_on_failover hợp lý.
  • Redis: nâng cấp replica trước, chuyển role, cuối cùng nâng cấp node còn lại.

Step 17. Bảo mật/System Hardening

  • Bật HTTPS end‑to‑end (HAProxy TLS termination + mTLS Gitaly nếu có thể).
  • Hạn chế SSH bằng Fail2ban/SecurityGroup.
  • Bật iptables/nftables chỉ cho phép port cần thiết giữa các node.
  • Thay toàn bộ mật khẩu/token mặc định trong group_vars.
  • Theo dõi CVE GitLab/Redis/PostgreSQL định kỳ.

Step 18. Ghi chú thực tế

  • 3 node đủ quorum cho Consul/Redis/Praefect, nhưng không thay thế được DR/Geo. Nếu cần DR, dựng Geo secondary site + logical backup xuyên site.
  • Hiệu năng phụ thuộc IOPS của Gitaly. Ưu tiên SSD/NVMe, noatime, và theo dõi latency.
  • Runners nên tách ra cụm riêng; cache/logs của Runner dùng S3 để giảm tải I/O.

Step 19. README.md

# GitLab HA 3 Nodes (Ubuntu 22.04)

## Chuẩn bị
- DNS: gitlab.example.com => VIP 192.168.1.10
- TLS certs nếu terminate ở HAProxy
- S3 buckets và quyền IAM

## Triển khai
ansible -i inventory/hosts.ini gitlab_ha -m ping
ansible-playbook -i inventory/hosts.ini site.yml

## Kiểm thử
- /-/health, /-/readiness
- patronictl list, consul members
- redis sentinel get-master-addr-by-name
- clone/push qua VIP

## Runbook
Xem phần 15.

Thông tin nổi bật

Báo cáo quan trọng

Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận