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

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

1. Tổng quan Kiến trúc và Lý thuyết về Hệ thống High Availability

Trong bối cảnh vận hành các hệ thống DevOps hiện đại, GitLab không chỉ đơn thuần là một kho lưu trữ mã nguồn (SCM) mà đã phát triển thành một nền tảng DevSecOps toàn diện. Đối với các doanh nghiệp có quy mô từ 3.000 người dùng trở lên, hoặc các tổ chức yêu cầu tính liên tục của dịch vụ (Business Continuity) khắt khe, việc triển khai GitLab trên một node đơn lẻ (Single Node) tiềm ẩn rủi ro rất lớn về điểm chết đơn lẻ (Single Point of Failure - SPOF).[1, 2] Do đó, kiến trúc High Availability (HA) là yêu cầu bắt buộc để đảm bảo tính sẵn sàng, khả năng chịu lỗi và hiệu suất cao.

Báo cáo này sẽ đi sâu vào chi tiết kỹ thuật của việc triển khai GitLab HA Production sử dụng Ansible, dựa trên các thực hành tốt nhất từ kiến trúc tham chiếu 3k user của GitLab [2] và các bài học thực tế từ cộng đồng DevOps Việt Nam.[3]

1.1. Triết lý thiết kế HA của GitLab

Kiến trúc HA của GitLab không chỉ đơn giản là nhân bản các máy chủ ứng dụng. Nó là sự phối hợp phức tạp giữa nhiều lớp dịch vụ (Service Layers), trong đó mỗi lớp có cơ chế chịu lỗi và đồng thuận riêng biệt. Khác với mô hình Active-Passive truyền thống, GitLab HA hướng tới mô hình Active-Active cho các thành phần phi trạng thái (Stateless) như Rails Application, và mô hình Cluster/Consensus cho các thành phần có trạng thái (Stateful) như Database và Storage.[3, 4]

Các thành phần cốt lõi trong kiến trúc này bao gồm:

  1. Lớp Cân bằng tải (Load Balancing Layer): Sử dụng HAProxy hoặc NGINX để phân phối lưu lượng truy cập HTTP/HTTPS và SSH đến các node ứng dụng. Đây là cửa ngõ đầu tiên và cũng là nơi quản lý SSL termination.[2]
  2. Lớp Ứng dụng (Application Layer): Bao gồm GitLab Rails, Puma, Workhorse và Sidekiq. Các thành phần này hoàn toàn phi trạng thái (stateless), cho phép mở rộng ngang (horizontal scaling) dễ dàng bằng cách thêm node mới và cập nhật cấu hình load balancer.[3]
  3. Lớp Cơ sở dữ liệu (Database Layer): Sử dụng PostgreSQL được quản lý bởi Patroni. Patroni cung cấp khả năng tự động failover (chuyển đổi dự phòng) dựa trên sự đồng thuận, đảm bảo rằng luôn có một node Primary chịu trách nhiệm ghi dữ liệu và các node Replica để đọc.[5]
  4. Lớp Kết nối (Connection Pooling Layer): PgBouncer được sử dụng để quản lý và tái sử dụng các kết nối đến PostgreSQL. Điều này cực kỳ quan trọng vì mô hình process-based của Ruby on Rails tiêu tốn rất nhiều kết nối database, có thể làm quá tải PostgreSQL nếu không có pooling.[2]
  5. Lớp Lưu trữ Metadata & Service Discovery: Consul đóng vai trò là "bộ não" của cluster. Nó lưu trữ trạng thái của các node database, hỗ trợ bầu chọn leader cho Patroni và cung cấp thông tin service discovery cho Prometheus giám sát.[3, 6]
  6. Lớp Caching & Job Queue: Redis kết hợp với Sentinel. Redis lưu trữ session người dùng, cache dữ liệu và hàng đợi background job cho Sidekiq. Sentinel giám sát sức khỏe của Redis Master và tự động thăng cấp Slave lên Master khi có sự cố.[4, 7]
  7. Lớp Lưu trữ Git (Git Storage Layer): Đây là phần phức tạp nhất. Thay vì dùng NFS (vốn có độ trễ cao và là SPOF), kiến trúc hiện đại sử dụng Gitaly Cluster với Praefect. Praefect đóng vai trò như một router và transaction manager, đảm bảo dữ liệu repository được sao chép (replicate) ra nhiều node Gitaly backend để đảm bảo an toàn dữ liệu (strong consistency).[4, 8]

1.2. Tại sao sử dụng Ansible?

Việc cấu hình thủ công (manual configuration) cho một hệ thống phân tán gồm hàng chục node với các file cấu hình gitlab.rb khác nhau là điều bất khả thi và dễ gây lỗi con người. Ansible, với đặc tính Idempotency (tính bất biến - chạy nhiều lần kết quả không đổi) và Agentless (không cần cài agent), là công cụ lý tưởng để quản lý cấu hình này.[6, 9]

Sử dụng Ansible cho phép:

  • Chuẩn hóa cấu hình: Đảm bảo tất cả các node trong cùng một nhóm (ví dụ: nhóm Database) có cấu hình giống hệt nhau.
  • Tự động hóa trình tự: Ansible kiểm soát chặt chẽ thứ tự khởi động dịch vụ (Consul -> Database -> App), điều mà các script bash rời rạc khó làm được.[6]
  • Khả năng mở rộng: Khi cần thêm node, chỉ cần cập nhật file Inventory và chạy lại playbook.[6]

2. Chuẩn bị Hạ tầng và Môi trường

Trước khi đi vào chi tiết code Ansible, chúng ta cần thiết lập một nền tảng hạ tầng vững chắc. Dựa trên tham chiếu từ devops.vn và tài liệu chính hãng, mô hình lab tối thiểu nhưng đầy đủ chức năng HA bao gồm 3 node vật lý hoặc ảo hóa (VM).[3]

2.1. Sizing phần cứng (Khuyến nghị cho 3k Users)

Mặc dù môi trường Lab có thể chạy với cấu hình thấp, nhưng để đáp ứng tải trọng sản xuất (Production) cho 3.000 users với 60 RPS (Requests Per Second), cấu hình phần cứng cần tuân thủ nghiêm ngặt.[2]

Thành phần Số lượng Node vCPU RAM Disk Ghi chú
Node 1, 2, 3 (Hybrid) 3 4 - 8 Core 16 GB+ 100GB (OS) + Data Disk Trong môi trường tiết kiệm (như Lab), ta có thể gộp các role (Consul, Postgres, Redis, App) lên cùng 3 server vật lý này. Tuy nhiên, Disk I/O phải rất tốt.
Load Balancer (HAProxy) 1-2 2 Core 4 GB 20 GB Cần 2 node cài Keepalived để có VIP (Virtual IP).[3]

Lưu ý quan trọng về bộ nhớ: GitLab khuyến nghị tối thiểu 4GB RAM cho mỗi node chỉ để chạy các dịch vụ cơ bản. Khi chạy Hybrid (gộp nhiều role), RAM là tài nguyên quan trọng nhất. Nếu thiếu RAM, Linux OOM Killer sẽ giết các tiến trình quan trọng như Patroni hoặc Sidekiq, gây ra failover không mong muốn.[2, 3]

2.2. Quy hoạch mạng và IP

Giả sử chúng ta sử dụng dải mạng 192.168.1.0/24 như ví dụ thực tế, việc quy hoạch IP tĩnh là bắt buộc để cấu hình Ansible không bị sai lệch.[3]

  • VIP (Virtual IP): 192.168.1.10 (IP này sẽ được trỏ bởi DNS gitlab.baotrongit.com).
  • GitLab Node 1: 192.168.1.11 (Master/Primary ban đầu).
  • GitLab Node 2: 192.168.1.12 (Replica/Secondary).
  • GitLab Node 3: 192.168.1.13 (Replica/Secondary).

2.3. Yêu cầu về Firewall và Ports

Đây là nguyên nhân thất bại phổ biến nhất khi triển khai HA. Các node phải giao tiếp được với nhau qua các cổng TCP cụ thể. Nếu sử dụng ufw hoặc iptables, bạn phải mở các port sau [2, 10]:

Port Giao thức Nguồn (Source) Đích (Dest) Mục đích
8500 TCP Tất cả Nodes Tất cả Nodes Consul HTTP API (Service Discovery).
8300-8302 TCP/UDP Tất cả Nodes Tất cả Nodes Consul Serf LAN/WAN (Gossip Protocol).
5432 TCP App Nodes, Pgbouncer Database Nodes Kết nối trực tiếp PostgreSQL (Replication).
6432 TCP App Nodes Pgbouncer Nodes Kết nối qua PgBouncer Pooling.
6379 TCP App Nodes Redis Nodes Kết nối Redis Cache/Queue.
26379 TCP App Nodes, Redis Nodes Redis Nodes Giao tiếp giữa các Redis Sentinel để bầu chọn Master.
8075 TCP Praefect Node Gitaly Nodes Praefect giao tiếp với Gitaly Storage.
2305 TCP App Nodes Praefect Node Ứng dụng kết nối tới Praefect (thay vì Gitaly trực tiếp).
9100 TCP Prometheus Node Tất cả Nodes Node Exporter (Metrics hệ thống).

2.4. Chuẩn bị Hệ điều hành (Ansible Role: common)

Trước khi cài đặt bất kỳ gói GitLab nào, hệ điều hành (Ubuntu 20.04/22.04 LTS được khuyến nghị) cần được tối ưu hóa. Role common hoặc base trong Ansible sẽ thực hiện việc này [3]:

  1. Đồng bộ thời gian (NTP/Chrony): Đây là yếu tố sống còn. Cơ chế bầu chọn Leader của Patroni và Consul dựa trên thời gian thực (Time-to-live - TTL). Nếu đồng hồ giữa các server lệch nhau quá vài giây, Cluster sẽ liên tục bầu lại Leader (flap), gây gián đoạn dịch vụ. Cần cài đặt và kích hoạt chrony.[3]
  2. Tắt Swap: Đối với các node chạy Database (PostgreSQL) và Redis, việc sử dụng Swap sẽ làm giảm hiệu năng nghiêm trọng và gây ra độ trễ (latency spikes). Ansible cần set vm.swappiness = 1 hoặc tắt hẳn swap.[3]
  3. Sysctl Tuning: Tăng giới hạn file descriptor (fs.file-max) và tối ưu hóa TCP stack cho kết nối mạng nội bộ tốc độ cao.

3. Kiến trúc Project Ansible

Một dự án Ansible chuyên nghiệp cho GitLab HA cần được tổ chức theo cấu trúc Modular (Role-based) để dễ bảo trì và mở rộng. Dưới đây là cấu trúc thư mục khuyến nghị, tích hợp từ thực tiễn triển khai [3, 6, 9]:

gitlab-ha-ansible/
├── inventory/
│   └── hosts.ini           # Định nghĩa các nhóm host (inventory)
├── group_vars/
│   ├── all.yml             # Biến toàn cục (version, mirror...)
│   └── gitlab_ha.yml       # Biến đặc thù cho cụm HA (passwords, VIP, IPs)
├── roles/
│   ├── common/             # Cấu hình OS cơ bản (NTP, Swap, SSH keys)
│   ├── consul/             # Cài đặt và cấu hình Consul Server/Client
│   ├── postgres_patroni/   # Cài đặt PostgreSQL và Patroni Cluster
│   ├── pgbouncer/          # Cài đặt PgBouncer Connection Pooler
│   ├── redis_sentinel/     # Cài đặt Redis và Sentinel
│   ├── gitaly/             # Cài đặt Gitaly (Storage)
│   ├── praefect/           # Cài đặt Praefect (Router/Manager)
│   ├── gitlab_rails/       # Cấu hình Application Server
│   └── haproxy/            # Cấu hình Load Balancer
├── site.yml                # Playbook chính điều phối toàn bộ quá trình
└── ansible.cfg             # Cấu hình Ansible runtime

3.1. Thiết kế Inventory (hosts.ini)

Inventory không chỉ liệt kê IP mà còn phải phân nhóm logic. Mặc dù trong bài lab tham khảo [3], 3 IP được dùng chung cho tất cả, nhưng ta nên tách alias để Ansible hiểu rõ role của từng node.

[gitlab_ha]
node1 ansible_host=192.168.1.11 hostname=gitlab-node1
node2 ansible_host=192.168.1.12 hostname=gitlab-node2
node3 ansible_host=192.168.1.13 hostname=gitlab-node3

# Nhóm chức năng (Trong mô hình 3 node hybrid, các nhóm này trùng member)
[consul:children]
gitlab_ha

[postgres:children]
gitlab_ha

[redis:children]
gitlab_ha

[gitaly:children]
gitlab_ha

[gitlab_rails:children]
gitlab_ha

3.2. Quản lý Biến (group_vars/gitlab_ha.yml)

Đây là nơi chứa "bí mật" của hệ thống. Trong môi trường Production thực tế, file này nên được mã hóa bằng ansible-vault.

# Thông tin chung
external_url: "[https://gitlab.baotrongit.com](https://gitlab.baotrongit.com)"
vip_address: "192.168.1.10"

# Passwords (Cần được bảo mật tuyệt đối)
gitlab_root_password: "ComplexRootPassword123!"
postgres_sql_password: "DBPasswordShouldBeLong"
postgres_user_password: "DBPasswordShouldBeLong"
redis_password: "RedisPasswordHere"
gitaly_token: "SecretTokenForGitalyCommunication"
praefect_external_token: "SecretTokenForPraefect"
consul_encryption_key: "Base64GeneratedKey=="

# Phiên bản GitLab
gitlab_version: "16.10.0-ee.0" # Nên ghim version cụ thể

Lưu ý: Biến consul_encryption_key phải được gen bằng lệnh consul keygen để đảm bảo độ dài và định dạng đúng.[3]


4. Chi tiết Triển khai: Lớp Consensus (Consul)

Consul là thành phần đầu tiên phải được khởi chạy. Nếu Consul không ổn định, Patroni sẽ không thể bầu leader, và toàn bộ database sẽ ngừng hoạt động.

4.1. Cơ chế hoạt động trong GitLab HA

GitLab sử dụng Consul để:

  1. Service Discovery: Các node Prometheus tự động tìm thấy các node cần giám sát thông qua Consul.[11]
  2. Database Leader Election: Patroni sử dụng Consul làm DCS (Distributed Configuration Store). Patroni cập nhật key trong Consul để duy trì quyền Leader (Leader Lease).

4.2. Cấu hình Ansible cho Consul

Trong role consul, chúng ta sử dụng gói cài đặt gitlab-ee nhưng tắt tất cả dịch vụ khác, chỉ bật Consul.

Nội dung file template gitlab.rb.j2 cho Consul:

# Chỉ bật Consul
roles(['consul_role'])

# Tắt tất cả dịch vụ khác để tiết kiệm tài nguyên
postgresql['enable'] = false
redis['enable'] = false
prometheus['enable'] = false
gitlab_rails['enable'] = false
sidekiq['enable'] = false
gitlab_workhorse['enable'] = false
nginx['enable'] = false

# Cấu hình Consul
consul['enable'] = true
consul['configuration'] = {
  server: true, # Node này đóng vai trò là Server
  retry_join: %w(192.168.1.11 192.168.1.12 192.168.1.13), # IP của các node khác để join cluster
  bootstrap_expect: 3, # Chỉ bầu leader khi đủ 3 node
  encrypt: "{{ consul_encryption_key }}", # Mã hóa giao tiếp Gossip
  datacenter: 'baotrongit-dc'
}

Phân tích sâu:

  • bootstrap_expect: 3: Đây là cấu hình quan trọng để tránh "Split Brain". Nếu set là 2 hoặc 1 trong cụm 3 node, khi network partition xảy ra, có thể xuất hiện 2 leader cùng lúc. Số 3 đảm bảo Quorum là (3/2)+1 = 2. Cần ít nhất 2 node để cluster hoạt động.[3]
  • retry_join: Giúp các node tự động tìm thấy nhau. Thay vì phải join thủ công từng node, Consul agent sẽ thử kết nối đến danh sách này khi khởi động.

5. Chi tiết Triển khai: Lớp Database (PostgreSQL + Patroni)

Đây là "trái tim" của hệ thống và là phần khó nhất. Patroni thay thế cơ chế quản lý PostgreSQL truyền thống của Omnibus.

5.1. Patroni và bài toán Failover

Patroni giám sát PostgreSQL. Khi node Primary chết (hoặc mất kết nối tới Consul), TTL (Time To Live) của key leader trong Consul sẽ hết hạn. Các node Replica còn lại sẽ tranh nhau quyền tạo key mới. Node nào tạo được key trước sẽ trở thành Leader mới. Patroni trên node đó sẽ promote PostgreSQL từ chế độ Read-Only sang Read-Write.[5, 12]

5.2. Cấu hình Ansible cho Patroni

Role postgres_patroni cần cấu hình gitlab.rb như sau:

roles(['postgres_role'])

postgresql['enable'] = true
postgresql['listen_address'] = '0.0.0.0'
postgresql['sql_user_password'] = '{{ postgres_sql_password_hash }}'

# Kích hoạt Patroni
patroni['enable'] = true
patroni['scope'] = 'gitlab-ha-cluster'
patroni['namespace'] = '/service'

# Cấu hình thông số Failover (Rất quan trọng)
patroni['ttl'] = 30
patroni['loop_wait'] = 10
patroni['retry_timeout'] = 10

# Cho phép PgBouncer và các node khác kết nối
postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 192.168.1.0/24)
postgresql['hot_standby'] = 'on'

Phân tích sâu:

  • postgresql['trust_auth_cidr_addresses']: Nếu thiếu dòng này, PgBouncer (chạy ở IP 192.168.1.x) sẽ nhận lỗi FATAL: no pg_hba.conf entry khi cố kết nối vào Database.[13]
  • ttl, loop_wait: ttl (30s) là thời gian key tồn tại. loop_wait (10s) là chu kỳ Patroni kiểm tra trạng thái. Nếu loop_wait quá gần ttl, có thể gây ra failover giả (false positive) khi mạng chập chờn nhẹ.

6. Chi tiết Triển khai: Lớp Connection Pooling (PgBouncer)

Vì Rails tạo ra mỗi process một kết nối DB, khi scale lên hàng chục worker, số connection có thể lên tới hàng nghìn, làm treo PostgreSQL. PgBouncer giải quyết vấn đề này bằng cách giữ một pool kết nối nhỏ tới DB và chia sẻ cho các App.[2]

6.1. Cấu hình Ansible cho PgBouncer

PgBouncer cần biết ai là Leader hiện tại. Nó làm điều này bằng cách query Consul (hoặc thông qua file json được cập nhật bởi consul-template, nhưng trong Omnibus GitLab, nó được tích hợp sẵn để watch Consul).

roles(['pgbouncer_role'])

pgbouncer['enable'] = true
pgbouncer['listen_addr'] = '0.0.0.0'
pgbouncer['listen_port'] = 6432

# Cấu hình danh sách Database
pgbouncer['databases'] = {
  gitlabhq_production: {
    host: '127.0.0.1', # PgBouncer nằm cùng node với Consul Agent local
    port: 5432,
    user: 'pgbouncer',
    password: '{{ pgbouncer_password_hash }}'
  },
  praefect_production: {... }
}

7. Chi tiết Triển khai: Lớp Caching (Redis Sentinel)

Redis Sentinel hoạt động theo cơ chế Master-Replica.

7.1. Cấu hình Ansible

Cần cấu hình redis.confsentinel.conf. Trong lần chạy đầu tiên, chúng ta phải chỉ định cứng ai là Master ban đầu để cluster hình thành, sau đó Ansible sẽ để Sentinel tự quản lý.[7]

roles(['redis_sentinel_role'])

redis['enable'] = true
redis['bind'] = '0.0.0.0'
redis['port'] = 6379
redis['password'] = '{{ redis_password }}'

# Sentinel configuration
sentinel['enable'] = true
sentinel['bind'] = '0.0.0.0'
sentinel['port'] = 26379
sentinel['quorum'] = 2 # Cần 2/3 sentinel đồng ý để failover

# Master ban đầu (Chỉ định Node 1)
redis['master'] = true # Set false cho Node 2, 3
redis['master_name'] = 'gitlab-redis'
redis['master_ip'] = '192.168.1.11'

8. Chi tiết Triển khai: Lớp Storage (Gitaly Cluster & Praefect)

Đây là phần cải tiến lớn nhất so với NFS.

8.1. Praefect là gì?

Praefect đứng giữa App và Gitaly. Khi App muốn ghi vào Repo A, nó gửi lệnh cho Praefect. Praefect xác định Repo A đang nằm trên Gitaly Node nào (Primary) và ghi vào đó, đồng thời replicate sang các Secondary.[8]

8.2. Cấu hình Ansible

Cần tạo Database riêng cho Praefect trên cụm PostgreSQL/Patroni trước.

Cấu hình cho Node Gitaly (Storage):

gitaly['enable'] = true
gitaly['auth_token'] = '{{ gitaly_token }}'
gitaly['listen_addr'] = '0.0.0.0:8075' # Phải listen TCP

Cấu hình cho Node Praefect (Router):

praefect['enable'] = true
praefect['configuration'] = {
  listen_addr: '0.0.0.0:2305',
  virtual_storage: [
    {
      name: 'default',
      node: [
        { storage: 'gitaly-1', address: 'tcp://192.168.1.11:8075', token: '{{ gitaly_token }}' },
        { storage: 'gitaly-2', address: 'tcp://192.168.1.12:8075', token: '{{ gitaly_token }}' },
        { storage: 'gitaly-3', address: 'tcp://192.168.1.13:8075', token: '{{ gitaly_token }}' }
      ]
    }
  ],
  database: {... } # Thông tin kết nối tới PostgreSQL Praefect DB
}

9. Chi tiết Triển khai: Lớp Application (Rails)

Sau khi các lớp dưới (Database, Redis, Gitaly) đã sẵn sàng, ta mới cấu hình lớp App.

9.1. Cấu hình Stateless

Node App không được lưu bất cứ dữ liệu gì quan trọng (ngoại trừ log tạm). Mọi trạng thái phải đẩy xuống DB hoặc Redis.

roles(['application_role'])

# Kết nối Database qua PgBouncer/Consul
gitlab_rails['db_host'] = '192.168.1.10' # VIP hoặc Load Balancer Internal
gitlab_rails['db_port'] = 6432

# Kết nối Redis qua Sentinel
gitlab_rails['redis_sentinels'] = [
  {'host' => '192.168.1.11', 'port' => 26379},
  {'host' => '192.168.1.12', 'port' => 26379},
  {'host' => '192.168.1.13', 'port' => 26379}
]

# Kết nối Gitaly qua Praefect
git_data_dirs({
  "default" => {
    "gitaly_address" => "tcp://192.168.1.10:2305" # Trỏ vào Praefect (thông qua VIP hoặc LB)
  }
})

# Tắt các dịch vụ local
postgresql['enable'] = false
redis['enable'] = false
gitaly['enable'] = false

Lưu ý: external_url phải trỏ về VIP của Load Balancer chính ([https://gitlab.baotrongit.com](https://gitlab.baotrongit.com)), không phải IP của từng node.[14]


10. Load Balancing và Monitoring

10.1. Load Balancer (HAProxy + Keepalived)

Cần cấu hình HAProxy để check health của các node App.

  • Health Check: GET /-/health. Nếu node App trả về 200 OK thì mới forward traffic vào.
  • Sticky Session: Không bắt buộc với GitLab hiện đại vì session lưu trong Redis, nhưng khuyến nghị bật để tối ưu cache.[2]

10.2. Monitoring (Prometheus)

Cấu hình consul['monitoring_service_discovery'] = true trong gitlab.rb. Prometheus sẽ tự động query Consul để lấy danh sách IP của tất cả các node đang chạy node_exporter, postgres_exporter, redis_exporter để scrape metrics.[11]


11. Vận hành Day-2: Backup, Restore và Upgrade

11.1. Chiến lược Backup

Trong mô hình HA, không thể dùng gitlab-backup create đơn thuần trên một node App vì dữ liệu nằm phân tán.

  • Database: Nên sử dụng WAL-G hoặc cơ chế backup của Patroni để stream WAL files lên S3/MinIO liên tục. Điều này cho phép Point-in-Time Recovery (PITR).[3]
  • Repositories: Gitaly Cluster tự replicate, nhưng vẫn cần backup lạnh (cold backup) snapshot của EBS/Disk hoặc dùng tính năng backup của Gitaly.
  • File cấu hình: Backup thư mục /etc/gitlab (đặc biệt là gitlab-secrets.json) là bắt buộc. Nếu mất file gitlab-secrets.json, toàn bộ dữ liệu mã hóa trong DB (CI variables, 2FA secrets) sẽ không thể giải mã.[11]

11.2. Chiến lược Upgrade (Zero Downtime)

Ansible phát huy tác dụng tối đa ở đây. Quy trình upgrade HA phải tuân thủ thứ tự [15]:

  1. Upgrade node Consul trước.
  2. Upgrade các node PostgreSQL (Replica) trước, sau đó switchover (đảo role) và upgrade node Primary cũ.
  3. Upgrade Gitaly/Praefect.
  4. Upgrade Application Node theo kiểu cuốn chiếu (Rolling Update):
    • Rút Node 1 khỏi Load Balancer.
    • Chạy Ansible upgrade Node 1.
    • Check health Node 1.
    • Đưa Node 1 lại vào Load Balancer.
    • Lặp lại với Node 2, 3.

12. Troubleshooting và Các vấn đề thường gặp

12.1. Lỗi kết nối Database ("pgbouncer cannot connect to server")

  • Triệu chứng: GitLab trả về lỗi 500, log production.log báo lỗi kết nối DB.
  • Nguyên nhân: Địa chỉ IP của PgBouncer hoặc App Node chưa được thêm vào trust_auth_cidr_addresses trong file cấu hình của Patroni.[13]
  • Khắc phục: Kiểm tra file gitlab.rb trên node Database, thêm dải mạng 192.168.1.0/24, và chạy gitlab-ctl reconfigure.

12.2. Patroni không bầu được Leader

  • Triệu chứng: Database ở trạng thái Read-Only, không thể ghi dữ liệu.
  • Khắc phục: Nếu Cluster bị "treo", thử restart Patroni trên từng node. Nếu tình huống Split-brain nghiêm trọng (do mất điện cả cụm), có thể cần xóa key trong Consul để reset bầu cử.[13, 16]
gitlab-ctl stop patroni
/opt/gitlab/embedded/bin/consul kv delete -recurse /service/gitlab-ha-cluster/
gitlab-ctl start patroni

13. So sánh: Manual Ansible vs GitLab Environment Toolkit (GET)

Đặc điểm Manual Ansible (Tự viết) GitLab Environment Toolkit (GET)
Độ linh hoạt Rất cao. Có thể tùy chỉnh từng dòng config. Trung bình. Phải tuân theo cấu trúc biến của GET.
Bảo trì Khó. Khi GitLab ra version mới phải tự sửa code. Dễ. GitLab maintain code này.[15]
Độ tin cậy Phụ thuộc vào kỹ năng người viết. Rất cao. Được test hàng ngày bởi GitLab.[17]

Kết luận

Triển khai GitLab High Availability là một quá trình chuyển đổi từ quản trị hệ thống đơn lẻ sang quản trị hệ thống phân tán. Sự thành công không chỉ nằm ở việc Ansible playbook chạy thành công ("xanh"), mà nằm ở khả năng hệ thống tự phục hồi (Self-healing) khi một node Database hoặc Redis bị tắt đột ngột.

Bằng cách tuân thủ kiến trúc tách biệt các lớp (Consensus, Data, App) và sử dụng Ansible để chuẩn hóa cấu hình, quản trị viên có thể xây dựng một hệ thống baotrongit.com đạt chuẩn Production, sẵn sàng cho quy mô hàng nghìn người dùng đồng thời.

Tài liệu tham khảo

  • [3] DevOps.vn - Triển khai GitLab HA Production bằng Ansible.
  • [2] GitLab Reference Architecture 3k Users.
  • [5] Patroni Replication & Failover.
  • [17] GitLab Environment Toolkit Documentation.
Xin chào! Mình là BaoTrongIT – một lập trình viên đam mê chia sẻ kiến thức lập trình, đặc biệt là về JavaScript, Node.js, NestJS, và các công nghệ backend/frontend hiện đại. Trên blog này, mình thường xuyên đăng tải các bài viết thủ thuật, kinh nghiệm thực chiến, ví dụ minh họa dễ hiểu, giúp bạn tiếp cận và hiểu sâu các khái niệm tưởng như phức tạp trong lập trình.