---
title: "Galera Cluster로 구축하는 OpenStack 환경의 고가용성 MySQL 데이터베이스 완벽 가이드"
description: "이 포스트는 OpenStack 환경에서 고가용성을 위해 Galera Cluster와 HAProxy·Keepalived를 활용하여 MySQL을 동기식 복제 클러스터로 구성하고, 설치·설정·부팅·상태 확인·장애 복구·운영 시 주의사항을 단계별로 안내한다."
date: "2025-06-16"
last_modified: "2026-08-26T07:38:00.000Z"
type: "Post"
tags:
  - "openstack"
  - "mysql"
  - "haproxy"
categories:
  - "📗 Docs"
series:
  - "오픈스택 운영"
canonical_url: "https://blog.pieroot.xyz/galera-openstack-db"
markdown_url: "https://blog.pieroot.xyz/galera-openstack-db.md"
---

# Galera Cluster로 구축하는 OpenStack 환경의 고가용성 MySQL 데이터베이스 완벽 가이드

이 포스트는 OpenStack 환경에서 고가용성을 위해 Galera Cluster와 HAProxy·Keepalived를 활용하여 MySQL을 동기식 복제 클러스터로 구성하고, 설치·설정·부팅·상태 확인·장애 복구·운영 시 주의사항을 단계별로 안내한다.

### 서론: 왜 Galera Cluster를 사용하는가?

OpenStack 환경을 운영하다 보면 데이터베이스의 고가용성이 필수적입니다. 단일 MySQL 서버로 운영할 경우, 해당 서버에 장애가 발생하면 전체 OpenStack 서비스가 중단되는 심각한 문제가 발생합니다.

기존의 Master-Slave 구조를 살펴보겠습니다:

Master-Slave 구조에서는 Master 노드에서만 쓰기가 가능하므로, Master 노드 장애 시 다른 노드를 Master로 승격해야 합니다. 자동 승격 기능이 구현되어 있더라도 일정 시간의 서비스 중단이 불가피하죠.

**Galera Cluster**는 이러한 문제의 대안입니다. 모든 노드가 Master 역할을 수행하며, 실시간으로 데이터를 동기화하고 복제합니다.

> **Galera Cluster의 장점**
> 
> - 모든 노드에서 읽기와 쓰기가 가능합니다
> 
> - 노드 1개 장애 시 나머지 2개가 쿼럼을 유지하면 자동으로 계속 운영됩니다
> 
> - 데이터 일관성이 항상 보장됩니다

> **전체 전원 손실은 자동 복구가 아니다.** 3노드가 모두 내려가면 어느 노드도 새 Primary Component를 만들지 않는다. 이는 split-brain을 막기 위한 정상 동작이다. 이때 관례상 1번 노드를 bootstrap하거나 `safe_to_bootstrap`을 확인 없이 1로 바꾸면 안 된다.

![Galera Cluster 3노드 아키텍처와 쿼럼 개념](https://blog.pieroot.xyz/api/image-proxy?id=918db1b6-611a-40a3-9ac7-4e37994f84b0&kind=s3&pageId=214067c0-15d0-80cd-a372-d981c5aa8df8&source=block&blockId=9dd465e1-2351-4ffc-9a0a-876247489efd)

*OpenStack 서비스가 3개 Galera 노드로 DB 트래픽을 보내고, 노드 간 동기 복제와 쿼럼을 통해 고가용성을 유지하는 구조입니다.*

다만 모든 쿼리를 동기화해야 하기 때문에 트래픽이 많은 환경에서는 부하가 발생할 수 있습니다. 따라서 이 포스트에서는 **HAProxy와 VIP를 활용한 하이브리드 구성**도 함께 다루겠습니다.

---

### 시스템 요구사항

Galera Cluster를 구성하기 전에 다음 요구사항을 확인하세요:

> **왜 홀수 개의 노드가 필요한가요?**
> 
> 클러스터가 정상 작동하려면 **쿼럼(Quorum)** 형성이 필수입니다. 예를 들어 2개 노드만 있을 때 1개가 장애가 나면, 남은 1개 노드가 과반수인지 판단할 수 없어 클러스터 전체가 중단됩니다. 3개 노드라면 1개가 장애가 나도 2개가 과반수를 유지하여 정상 운영됩니다.

---

### MySQL 설치

모든 노드에서 MySQL(또는 MariaDB)을 설치합니다.

```bash
# 패키지 업데이트
sudo apt update

# MySQL 서버 설치 (MariaDB도 가능)
sudo apt install mysql-server -y
```

Galera 플러그인은 대부분 자동으로 설치됩니다. 자동 설치되지 않았다면 직접 설치하세요:

```bash
# Galera 플러그인 수동 설치 (필요시)
sudo apt install galera-4 -y
```

---

### 1번 노드 설정 (Bootstrap 노드)

첫 번째 노드는 클러스터를 초기화하는 **Bootstrap 노드**입니다.

#### 설정 파일 편집

```bash
sudo vi /etc/mysql/mariadb.conf.d/60-galera.cnf
```

> **파일명의 숫자 60은 무엇인가요?**
> 
> MariaDB는 `/etc/mysql/mariadb.conf.d/` 디렉토리의 설정 파일들을 숫자 순서대로 로드합니다. `60-galera.cnf`는 다른 기본 설정(50-server.cnf 등) 이후에 로드되어 Galera 관련 설정을 오버라이드합니다.

기존 주석 처리된 내용을 아래와 같이 수정합니다:

```toml
#
# * Galera-related settings
#
[galera]
# 필수 설정
wsrep_on                 = ON
wsrep_cluster_name       = "openstack-galera-cluster"
wsrep_cluster_address    = "gcomm://"                    # 첫 노드는 비워둡니다
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2

# 네트워크 설정
bind-address = 0.0.0.0

# 노드 식별 설정
wsrep_sst_method         = rsync
wsrep_node_address       = "<1번 노드 IP>"
wsrep_node_name          = "<1번 노드 hostname>"
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
```

#### 주요 설정 옵션 설명

#### 클러스터 초기화

```bash
sudo galera_new_cluster
```

> **초기 구성과 장애 복구를 혼동하지 말 것**
> 
> `galera_new_cluster`는 **초기 구성 때** 1번 노드에서 1회만 실행한다. **전원 복구 때는 1번 노드가 아니다.** recovered seqno가 가장 높고 `safe_to_bootstrap: 1`인 노드에서만 실행한다.

---

### 2번 노드 설정

2번 노드부터는 기존 클러스터에 조인하는 방식으로 설정합니다.

#### 설정 파일 편집

```bash
sudo vi /etc/mysql/mariadb.conf.d/60-galera.cnf
```

```toml
#
# * Galera-related settings
#
[galera]
# 필수 설정
wsrep_on                 = ON
wsrep_cluster_name       = "openstack-galera-cluster"
wsrep_cluster_address    = "gcomm://<1번 노드 IP>,<2번 노드 IP>"
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2

# 네트워크 설정
bind-address = 0.0.0.0

# 노드 식별 설정
wsrep_sst_method         = rsync
wsrep_node_address       = "<2번 노드 IP>"
wsrep_node_name          = "<2번 노드 hostname>"
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
```

> **wsrep\_cluster\_address에 모든 노드 IP를 넣어야 하나요?**
> 
> 이론적으로 자신의 IP는 생략해도 되지만, 클러스터 간 주소 정보가 항상 제대로 동기화되는 것은 아닙니다. **모든 노드의 IP를 명시적으로 입력하는 것을 권장합니다.** 이렇게 하면 재시작 시에도 안정적으로 클러스터에 조인할 수 있습니다.

#### MySQL 재시작

```bash
sudo systemctl restart mysql
```

2번째 이후 노드들은 위와 같이 설정하고 MySQL을 재시작하면 자동으로 클러스터에 조인됩니다.

---

### 3번 노드 설정

3번 노드도 동일한 방식으로 설정합니다:

```toml
wsrep_cluster_address    = "gcomm://<1번 노드 IP>,<2번 노드 IP>,<3번 노드 IP>"
wsrep_node_address       = "<3번 노드 IP>"
wsrep_node_name          = "<3번 노드 hostname>"
```

```bash
sudo systemctl restart mysql
```

---

### 클러스터 상태 확인

MySQL 콘솔에 접속하여 클러스터 상태를 확인합니다:

```bash
mysql -u root -p
```

#### 동기화 상태 확인

```sql
SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
```

```javascript
+---------------------------+--------+
| Variable_name             | Value  |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+
```

#### 클러스터 크기 확인

```sql
SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size';
```

```javascript
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+
```

#### 클러스터 상태 및 연결된 노드 확인

```sql
-- Primary 상태 확인 (마스터가 될 자격이 있는지)
SHOW GLOBAL STATUS LIKE 'wsrep_cluster_status';

-- 연결 상태 확인
SHOW GLOBAL STATUS LIKE 'wsrep_connected';

-- 연결된 노드 IP 확인
SHOW GLOBAL STATUS LIKE 'wsrep_incoming_addresses';
```

> **정상 상태 체크리스트**
> 
> - `wsrep_local_state_comment` = **Synced**
> 
> - `wsrep_cluster_size` = **노드 수와 일치**
> 
> - `wsrep_cluster_status` = **Primary**
> 
> - `wsrep_connected` = **ON**

---

### 1번 노드 wsrep\_cluster\_address 재설정

> **중요: 이 단계를 반드시 수행하세요!**
> 
> 1번 노드의 `wsrep_cluster_address`가 비어 있으면 시스템 재시작 시 클러스터가 정상적으로 연결되지 않습니다.

1번 노드의 설정 파일을 다시 열어 모든 클러스터 IP를 입력합니다:

```bash
sudo vi /etc/mysql/mariadb.conf.d/60-galera.cnf
```

```toml
# 변경 전
wsrep_cluster_address    = "gcomm://"

# 변경 후
wsrep_cluster_address    = "gcomm://<1번 노드 IP>,<2번 노드 IP>,<3번 노드 IP>"
```

설정 파일을 수정한 후에는 MySQL을 재시작하지 않아도 됩니다. 다음 재시작 시 적용됩니다.

---

### 트러블슈팅: 전체 재시작 또는 전원 손실 후

노드 1개가 죽으면 나머지 2개가 Primary를 유지한다. **세 노드가 모두 내려가면 어느 노드도 새 Primary를 자동으로 만들지 않는다.** 이때 빠른 점검보다 정확한 기준 노드 선택이 중요하다.

```mermaid
flowchart TD
  A["모든 MariaDB inactive?"] -->|아니오| Z["중단: 이미 Primary가 있을 수 있음"]
  A -->|예| B["cluster UUID 동일?"]
  B -->|아니오| Z2["중단: 다른 클러스터"]
  B -->|예| C["recovered seqno 최고 노드 = safe_to_bootstrap 1?"]
  C -->|예| D["그 노드만 galera_new_cluster"]
  C -->|아니오| Z3["중단: 수동 변경 금지, 일관성 검토"]
  D --> E["나머지 노드를 Synced/Ready 확인 후 순차 합류"]
```

#### 하지 말 것

- 관례상 1번 노드라는 이유만으로 `galera_new_cluster`

- `wsrep_cluster_address`를 비우고 아무 노드나 bootstrap

- seqno 비교 없이 `grastate.dat`의 `safe_to_bootstrap`을 1로 수정

- 세 노드에서 동시에 `galera_new_cluster`

- Galera보다 앞에 OpenStack 컨테이너를 일괄 재시작

#### 해야 할 것

1. 모든 MariaDB가 중지 또는 실패 상태인지 확인한다.

1. 각 노드의 cluster UUID가 같은지 확인한다.

1. `grastate.dat`와 journal의 `Recovered position`을 비교한다. `seqno: -1`이어도 실패로 단정하지 말 것.

1. recovered seqno가 가장 높은 노드가 `safe_to_bootstrap: 1`인지 확인한다.

1. 두 조건이 일치할 때만 그 노드에서 `galera_new_cluster`를 실행한다.

1. 나머지 노드는 `systemctl start mariadb`로 하나씩 합류한다. `Synced / Ready ON`을 확인한 뒤에 다음 노드를 시작한다.

1. UUID가 다르거나 최고 seqno 노드와 safe 노드가 다르면 **중단**한다.

```bash
# 각 노드에서 수집
systemctl is-active mariadb || true
sudo sed -n '1,20p' /var/lib/mysql/grastate.dat
sudo journalctl -u mariadb -b --no-pager | grep "Recovered position" | tail -1

# 기준 노드만, MariaDB가 안 돌고 있을 때
sudo galera_new_cluster

# 나머지 노드는 일반 시작
sudo systemctl start mariadb
```

> **safe\_to\_bootstrap 수동 변경은 기본 금지**
> 
> 조건이 충돌할 때 값을 1로 바꾸고 시작하면, 낮은 seqno 노드로 최신 트랜잭션을 잃거나 split-brain을 만들 수 있다. 마지막 수단이며, 사람이 모든 노드의 recovered position과 InnoDB 상태를 검토한 뒤에만 최고 seqno 노드에 적용한다.

#### 전원 복구 시 OpenStack·Ceph

Galera만 살렸다고 끝난 것이 아니다. 스토리지가 같이 내려갔다면 [Ceph 클러스터 안전한 종료 및 재부팅 가이드: 전원 꺼짐과 문제 해결 방법](https://app.notion.com/p/21a067c015d080ec9fbee6495e6ee782)를 먼저 확인하고, 이어서 OpenStack API를 검증한다.

```bash
openstack token issue
openstack compute service list
openstack volume service list
```

---

### HAProxy + Keepalived로 고가용성 구성

Galera Cluster는 모든 노드에서 쓰기가 가능하지만, 동시 쓰기로 인한 충돌을 방지하기 위해 **단일 진입점**을 구성하는 것이 좋습니다.

![HAProxy와 Keepalived를 이용한 Galera Cluster 고가용성 아키텍처](https://blog.pieroot.xyz/api/image-proxy?id=2d896cb5-fddb-4c2f-ae62-fb3157a0bb65&kind=s3&pageId=214067c0-15d0-80cd-a372-d981c5aa8df8&source=block&blockId=c3e498c8-f9ff-4890-8de3-5c8755bb667d)

*클라이언트와 OpenStack 서비스는 VIP로 접근하고, HAProxy/Keepalived 계층이 활성 DB 노드 장애 시 백업 노드로 트래픽을 넘겨 단일 진입점을 유지합니다.*

#### HAProxy 설정

```bash
sudo vi /etc/haproxy/haproxy.cfg
```

```
listen galera_cluster
    bind <VIP>:3306
    mode tcp
    option tcpka
    balance source
    
    # 옵션: MySQL 헬스체크
    # option mysql-check user haproxy
    
    server galera-node1 <1번 노드 IP>:3306 check weight 1
    server galera-node2 <2번 노드 IP>:3306 backup check weight 1
    server galera-node3 <3번 노드 IP>:3306 backup check weight 1
```

> **설정 설명**
> 
> - `bind <VIP>:3306`: 가상 IP로 들어오는 연결을 수신합니다
> 
> - `balance source`: 동일 클라이언트는 동일 서버로 연결됩니다
> 
> - `backup`: 기본 서버 장애 시에만 활성화됩니다

이 설정으로 VIP를 통해 들어오는 모든 요청은 1번 노드로 전달되며, 1번 노드 장애 시 자동으로 2번 노드가 요청을 받습니다.

---

### 핵심 정리

이 포스트에서 다룬 MySQL Galera Cluster 구성을 정리하면 다음과 같습니다:

✅ **Galera Cluster**: 모든 노드가 Master 역할을 수행하는 동기식 복제 클러스터

✅ **노드 구성**: 최소 3개 이상의 홀수 노드로 구성하여 쿼럼 유지

✅ **Bootstrap**: 첫 번째 노드는 `galera_new_cluster` 명령으로 초기화

✅ **클러스터 조인**: 2번째 이후 노드는 `wsrep_cluster_address`에 기존 노드 IP 입력 후 재시작

✅ **상태 확인**: `wsrep_cluster_size`, `wsrep_local_state_comment` 등으로 모니터링

✅ **장애 복구**: 전체 전원 손실 후에는 최고 recovered seqno + `safe_to_bootstrap: 1` 노드만 bootstrap. 수동 플래그 변경은 금지

✅ **고가용성**: HAProxy + Keepalived로 단일 진입점 및 자동 장애 조치 구현

---

### 주의사항

> **운영 시 주의할 점**
> 
> - **정기 백업**: Galera Cluster도 완벽하지 않습니다. 정기적인 백업을 권장합니다
> 
> - **네트워크 지연**: 노드 간 네트워크 지연이 크면 성능 저하가 발생합니다
> 
> - **동시 쓰기**: 같은 행을 여러 노드에서 동시에 수정하면 충돌이 발생할 수 있습니다
> 
> - **클러스터 확장**: 새 노드 추가 시 SST(State Snapshot Transfer)로 인해 일시적 성능 저하 가능
> 
> - **전체 종료 전 기록**: 마지막 Primary, 각 노드 seqno, `safe_to_bootstrap`, 마지막으로 내린 노드를 남긴다
> 
> - **복구 기준**: 관례상 1번 노드가 아니라 가장 높은 recovered seqno + safe 플래그 노드

---

### 참고 자료

- [MariaDB Galera Cluster 공식 문서](https://mariadb.com/kb/en/galera-cluster/)

- [Galera Cluster for MySQL 공식 문서](https://galeracluster.com/library/documentation/)

- [HAProxy 공식 문서](https://www.haproxy.org/#docs)

- [Recovering from a Non-Primary Cluster](https://galeracluster.com/library/documentation/crash-recovery.html)

- [MariaDB — Recovering a Galera Cluster](https://mariadb.com/kb/en/recovering-a-mariadb-galera-cluster/)

- [Ceph 클러스터 안전한 종료 및 재부팅 가이드: 전원 꺼짐과 문제 해결 방법](https://app.notion.com/p/21a067c015d080ec9fbee6495e6ee782)
