이번에는 db1, db2로 향하는 Load Balancer(Internal) 구성을 해보자.
근데 여기서 궁금한 점이 있을 수도 있다.
- DB가 Active-Standby(db1은 사용, db2는 대기) 구조라면 트래픽을 5:5로 나눌 필요가 없는데, 왜 굳이 중간에 로드밸런서(LB)를 두는가?
사실 이에 대한 답은 트래픽 분산이 목적이 아닌 자동 장애 조치 (Failover)다.
우리는 서비스가 중단되는 것을 최대한 방지해야 한다. LB가 있는, 없는 상황을 나눠서 생각해보자.
- LB가 없을 때: db1이 죽으면 개발자가 웹 서버의 소스 코드에서 DB 접속 IP를 db2로 일일이 수정하고 재시작해야 한다.
- LB가 있을 때: 웹 서버는 항상 LB(192.168.56.30)만 바라본다.
- db1에 문제가 생기면 LB가 알아서 감지하고 트래픽을 즉시 db2로 돌린다.
- 웹 서버는 뒤에서 무슨 일이 일어났는지 모른 채 서비스가 유지된다.
또한 내부 LB를 두면 웹 서버는 오직 하나의 대표 IP만 알면 되므로 인프라 관리가 단순해진다.
마지막으로 지금은 Active-Standby지만, 나중에 데이터가 많아져서 "읽기 전용 트래픽은 db2로 보내자(Read-only Scaling)"라고 전략을 바꿀 때, 웹 서버 코드를 수정할 필요 없이 LB 설정만 바꾸면 즉시 부하 분산 구조로 전환할 수 있다.
즉, 관리에 있어서 + 서비스의 고가용성을 위해 Load Balancer를 내부에서 설정해주는 것이다.
개요
웹 서버와 DB 사이에 위치하는 내부 로드밸런서를 생성해본다. Nginx의 stream 모듈을 사용해 TCP L4 프록시를 구성하고, db1(Primary) 장애 시 Db2(Backup)로 자동 FailOver 되도록 설정한다.
- stream 모듈?
- Nginx는 원래 웹 서버다. 기본적으로 HTTP 트래픽을 처리하는 http 모듈이 핵심이다.
- 하지만, 우리는 MySQL 트래픽을 처리해야한다. 즉, MySQL이 사용하는 TCP 프로토콜을 사용할 수 있도록 해야하는데
- Nginx에 TCP/UDP 트래픽을 처리하는 모듈인 'stream' 모듈을 사용한다.
- L4 프록시?
- OSI 7계층 중에서 4계층(전송 계층) 프록시를 의미한다.
- L4 프록시는 IP와 포트만 보고 트래픽을 전달한다. 패킷 안에 뭐가 들어있는지는 들여다보지 않는다.
- TCP 기반 프로토콜인 MySQL은 클라이언트가 192.168.56.30:3306으로 접속하면 그냥 db1이나 db2로 넘겨주기만 하면 된다. L7 까지 갈 이유가 없으므로 L4 프록시를 구성하는 것이다.
| 항목 | 내용 |
| LB IP | 192.168.56.30 |
| 프로토콜 | TCP (L4) |
| 리슨 포트 | 3306 |
| Primary | db1 192.168.56.21:3306 |
| Backup | db2 192.168.56.22:3306 |
이제 lb_internal VM을 부팅하면서 시작해보자.
Nginx 설치
Rocky Linux 9에서 stream 모듈은 기본 패키지에 포함되지 않으므로 nginx-mod-stream을 별도로 설치해야 한다.
sudo dnf install -y nginx
sudo dnf install -y nginx-mod-stream
Nginx L4 설정
1] Nginx 설정 구조 이해
Nginx는 기능별로 블록을 분리해서 설정한다.
| 블록 | 역할 | 주요 사용처 |
| http { } | HTTP/HTTPS 트래픽 처리 | 웹 서버, 리버스 프록시 |
| stream { } | TCP/UDP 트래픽 처리 | MySQL, Redis 등 L4 프록시 |
Nginx의 설계 원칙과 관련된 내용으로 조금 어려워 쉽게 비유를 해보자.
Nginx라는 큰 아파트에는 성격이 전혀 다른 2개의 방이 있다고 생각할 수 있다.
- http { } 방(L7): 위에서 언급했듯 내용을 보고 전달하는 방으로, 편지의 내용을 읽어서(HTTP 헤더) 누구에게 줄지 결정한다.
- stream { } 방(L4): 내용을 안 보고 통로만 연결하는 방으로, 편지 내용은 관심 없고, 단순히 들어온 파이프라인(TCP 포트)을 다른 곳으로 연결만 해준다.
이 두 방은 나란히 있는 방으로, http 방 안에 stream 방을 만들 수 없고, 반대도 안 된다.
보통 Nginx를 설치하면 기본 설정 파일 nginx.conf의 위치, 내용은 아래와 같다.
# nginx 설정 파일
/etc/nginx/
├── nginx.conf ← http {} 블록 + stream {} 블록 추가
└── conf.d/ ← http {} 블록 안에 include (웹 설정)
# nginx.conf 내부
http {
...
include /etc/nginx/conf.d/*.conf;
}
문제는 여기서 발생한다. conf.d/ 폴더에 있는 모든 파일은 자동으로 http {...} 블록 내부로 들어간다.
그런데 우리가 하고자 하는 L4 설정은 stream {...} 블록에 작성해야 한다.
만약 conf.d/mysql.conf 파일을 만들고 그 안에 stream {...} 을 적게 되면, 실제로는 http { stream {...} } 구조가 된다.
Nginx는 웹 설정 방안에 stream을 가져오게 되면 에러를 출력한다.
그래서 우리는 http 방의 간섭을 받지 않는 새로운 서랍장(stream.d/)을 밖으로 빼서 만들어야 한다.
/etc/nginx/
├── nginx.conf ← http {} 블록 + stream {} 블록 추가
├── conf.d/ ← http {} 블록 안에 include (웹 설정)
└── stream.d/ ← stream {} 블록 안에 include (L4 설정)
최종적으로 아래와 같은 nginx.conf 구조가 만들어지게 된다.
user nginx;
worker_processes auto;
http {
# 웹 관련 설정들은 여기서 알아서 include 됨
include /etc/nginx/conf.d/*.conf;
}
# http 방 밖(형제 라인)에 stream 방을 새로 정의하고,
# 여기서 stream.d 폴더를 불러오도록 설정합니다.
stream {
include /etc/nginx/stream.d/*.conf;
}
2] stream.d 디렉토리 생성 & 설정
위에서 봤듯이 stream.d 디렉토리를 /etc/nginx에 생성한다.
sudo mkdir -p /etc/nginx/stream.d
stream.d 내부에 mysql_lb.conf 파일을 만들고, 내용을 작성한다.
sudo tee /etc/nginx/stream.d/mysql_lb.conf << 'EOF'
upstream mysql_backend {
server 192.168.56.21:3306;
server 192.168.56.22:3306 backup;
}
server {
listen 3306;
proxy_pass mysql_backend;
proxy_connect_timeout 3s;
proxy_timeout 10s;
}
EOF
코드의 의미는 아래와 같다.
- 아키텍쳐의 목적을 알게 된다면, 각 지시어가 무엇을 의미하는지 쉽게 알 수 있다.
| 지시어 | 값 | 설명 |
| upstream mysql_backend | — | 트래픽을 분산할 서버 그룹을 정의하는 블록 |
| server 192.168.56.21:3306 | — | Primary 서버. 기본적으로 이 서버로 트래픽을 전달 |
| server 192.168.56.22:3306 backup | — | Backup 서버. Primary가 모두 다운됐을 때만 사용 |
| listen 3306 | — | 클라이언트 요청을 받을 포트 |
| proxy_pass mysql_backend | — | 수신한 트래픽을 위에서 정의한 upstream으로 전달 |
| proxy_connect_timeout | 3s | upstream 서버에 연결을 맺는 시간 제한. 3초 안에 연결이 안 되면 Failover 판단 |
| proxy_timeout | 10s | 연결이 맺어진 후 데이터 송수신 사이의 최대 유휴 시간. 초과 시 연결 종료 |
proxy_connect_timeout을 통해 Failover 동작을 수행한다.
- Nginx는 proxy_connect_timeout 안에 Primary(db1)에 연결이 안 되면 해당 서버를 down으로 판단하고 backup으로 지정된 db2로 트래픽을 전환한다.
- db1이 복구되면 자동으로 다시 Primary로 돌아온다.
SELinux 설정
SELinux는 SELinux란? 글을 참고하자.
Rocky Linux 9은 SELinux가 기본으로 활성화되어 있는데, Nginx는 http 계열 프로세스로 분류되어 허용된 포트, 네트워크 동작이 제한된다. MySQL 프록시 구성에서는 두 가지를 추가로 허용해야 한다.
1] 3306 포트 바인딩 허용
sudo semanage port -a -t http_port_t -p tcp 3306
SELinux는 http_port_t 타입에 속하는 포트만 Nginx가 바인딩할 수 있도록 허용한다.
- 기본적으로 80, 443 등 HTTP 관련 포트만 포함되어 있으므로, 3306 포트를 http_port_t에 추가해야 한다.
- 미설정 시 nginx -t는 통과하지만 systemctl start nginx에서 오류가 난다.
- bind() to 0.0.0.0:3306 failed (13: Permission denied)
2] upstream 외부 연결 허용
sudo setsebool -P httpd_can_network_connect 1
SELinux는 기본적으로 http 계열 프로세스가 외부 서버에 직접 연결하는 것을 차단한다.
- httpd_can_network_connect boolean을 활성화하면 Nginx가 upstream 서버(db1, db2)로 연결할 수 있다.
- -P 옵션은 재부팅 후에도 설정이 유지되도록 영구 적용한다.
- 미설정 시 nginx는 동작하지만 upstream 연결 시 에러가 발생한다.
- connect() to 192.168.56.21:3306 failed (13: Permission denied)
방화벽 설정
클라이언트(웹 서버)에서 lb_internal의 3306 포트로 접근할 수 있도록 허용한다.
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload
Nginx 시작
# 설정 파일 문법 검증
sudo nginx -t
# 서비스 활성화 및 시작
sudo systemctl enable --now nginx
# 3306 포트 리스닝 확인
ss -tlnp | grep 3306
정상적으로 작동 시 3306 포트가 리스닝 중인 것을 확인할 수 있다.
연결 테스트
lb_internal에 mysql 클라이언트를 설치한다.
sudo dnf install -y mysql
1] 직접 연결 테스트(LB 없이)
mysql -h 192.168.56.21 -P 3306 -u saauser -p'패스워드' saa_db
2] LB를 통한 연결 테스트
mysql -h 192.168.56.30 -P 3306 -u saauser -p'패스워드' saa_db
Failover 테스트
1] db1 MySQL 중단
# db1에서
sudo systemctl stop mysqld
2] lb_internal에서 접속 시도
mysql -h 192.168.56.30 -P 3306 -u saauser -p'패스워드' saa_db
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 8.0.XX MySQL Community Server - GPL
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> SHOW VARIABLES LIKE 'server_id';
db2로 Failover 되어 정상 접속되면 성공이다. (server-id가 2면 성공)
3] db1 MySQL 복구
# db1에서
sudo systemctl start mysqld
마찬가지로 위에서 나온 것처럼 lb_internal에서 접속을 시도해 mysql에 진입하고, 쿼리문을 통해 server-id를 확인해보자.
최종 상태 요약
| 항목 | 상태 | 비고 |
| nginx-mod-stream 설치 | 완료 | - |
| stream 블록 설정 | 완료 | stream.d/mysql_lb.conf |
| SELinux 3306 포트 허용 | 완료 | semanage port |
| SELinux 외부 연결 허용 | 완료 | httpd_can_network_connect |
| 방화벽 3306 포트 허용 | 완료 | lb_internal |
| db2 방화벽 MySQL 허용 | 완료 | 2단계 누락분 보완 |
| LB 연결 테스트 | 성공 | db1으로 라우팅 확인 |
| Failover 테스트 | 성공 | db1 중단 시 db2로 전환 확인 |
다음 단계에서는 web1(192.168.56.41), web2(192.168.56.42) VM에 Apache httpd를 구축하고, NFS 마운트 및 내부 LB(192.168.56.30:3306) 연동을 설정한다.
'Infra > Project' 카테고리의 다른 글
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - LoadBalancer(External) 구성 (0) | 2026.02.25 |
|---|---|
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - Web 서버 (2) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - DB 서버 (0) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - Storage 서버 (0) | 2026.02.25 |
| [세미 프로젝트] AWS SAA 기출문제 풀이 플랫폼 - VM 세팅 (0) | 2026.02.25 |