Network & Server Factory

개인 공부 기록

Project/홈랩

[홈랩] PostgreSQL 스트리밍 복제와 VIP로 LMS DB 이중화하기 (8편)

1nfra 2026. 10. 29. 08:27
LMS의 DB는 Kubernetes로 옮기지 않고 VM으로 남겼습니다. 대신 다른 호스트에 복제본 VM을 하나 더 만들어 PostgreSQL 스트리밍 복제로 실시간 사본을 두고, 앱은 primary에만 붙는 VIP로 접속하게 했습니다. 기존 DB는 다시 시작하지 않고 설정 다시 읽기만으로 복제를 시작했고, 복제본과 primary의 데이터 전체 덤프가 일치하는 것까지 확인했습니다. 이 글에서는 이중화 방식을 고른 기준, 복제 설정과 검증, VIP와 백업, 모니터링 설정을 정리합니다. 홈랩 재구성 시리즈 8편이며, 이전 글은 Docker 컨테이너와 LMS를 Kubernetes로 옮기기입니다.

이전을 마친 구성입니다. 요청은 Gateway를 거쳐 Pod로 가고, 앱은 primary에만 붙는 DB VIP로 접속합니다

1. 이중화 방식 고르기

LMS DB는 호스트 1에 있는 PostgreSQL 15 VM 1대였습니다. 이 호스트가 멈추면 DB도 멈추고, 데이터의 실시간 사본도 없습니다. 두 가지 방법을 비교했습니다.

 

항목 VM 2대 스트리밍 복제 / Kubernetes 안의 DB(CloudNativePG)
배치 호스트마다 VM 1대씩 / 인스턴스 2개를 호스트별로 나눔
장애 조치 수동 승격 후 VIP가 따라감 / 자동, 복제본 재생성도 자동
한계 사람이 승격해야 함 / 마스터 3대 중 2대가 있는 호스트 1이 멈추면 Kubernetes API가 멈춰 자동 장애 조치도 멈춤

 

Kubernetes 안에서 DB를 돌리면 자동 장애 조치가 되지만, 2편에서 정리한 대로 지금 클러스터는 호스트 1이 멈추면 과반을 잃습니다. DB가 있는 호스트가 멈추는 바로 그 상황에서 자동 장애 조치가 동작하지 않는 것입니다. 또 1편에서 정한 대로 노드 로컬 디스크나 NAS 위에 DB를 두는 것은 아직 위험하다고 봤습니다. 그래서 VM 2대 스트리밍 복제를 골랐습니다. 호스트가 2대뿐이면 어느 방식이든 자동 장애 조치에는 세 번째 장소가 필요하고, 데이터를 다른 호스트에 실시간으로 남기는 것은 두 방식 모두 할 수 있습니다.

2. 복제본 VM 만들기

복제본(DB-02)은 호스트 2에 기존 DB와 같은 크기(vCPU 2개, 메모리 4GB)로 만들었습니다. OS는 기존 DB와 같은 Rocky Linux 9이고, kickstart로 무인 설치했습니다. 라벨이 OEMDRV인 볼륨에 kickstart 파일을 넣어 붙이면 설치 프로그램이 자동으로 읽어 갑니다. Ubuntu 노드를 설치할 때 쓴 cidata 볼륨과 같은 역할입니다. 설치는 7분 만에 끝났습니다.

3. primary 준비

기존 DB(DB-01)는 운영 중이라 다시 시작하지 않고, 설정을 다시 읽는 것(pg_reload_conf)만으로 적용할 수 있는 변경만 했습니다. 다행히 복제에 필요한 기본 설정(wal_level=replica, max_wal_senders, hot_standby)은 이미 켜져 있었습니다.

 

설정 내용
복제 계정 복제 전용 계정을 만들고 비밀번호는 별도 보관
pg_hba 복제본 IP에서 오는 복제 접속만 scram-sha-256으로 허용
복제 슬롯 복제본 전용 물리 슬롯을 만들어, 복제본이 잠시 멈춰도 필요한 WAL을 남겨 둠
max_slot_wal_keep_size 2GB, 복제본이 오래 멈춰도 슬롯이 WAL을 무한정 붙잡아 primary 디스크가 차지 않게 함

 

복제 슬롯은 양날의 칼입니다. 복제본이 따라잡을 때까지 WAL을 지우지 않으므로 복제가 끊기지 않지만, 복제본이 오래 멈추면 WAL이 계속 쌓여 primary의 디스크가 가득 찰 수 있습니다. max_slot_wal_keep_size로 상한을 두면, 상한을 넘을 경우 그 복제본만 다시 만들면 되고 primary는 지킬 수 있습니다.

4. 복제본 설정

복제본에서는 pg_basebackup으로 primary의 데이터를 통째로 받아 왔습니다. -R 옵션은 복제에 필요한 접속 정보와 standby 설정을 자동으로 만들어 줍니다. -S로 앞에서 만든 슬롯을 지정하고, -X stream으로 백업 중에 생긴 WAL까지 함께 받습니다. 복제 계정의 비밀번호는 명령줄이나 설정 파일에 직접 쓰지 않고 postgres 계정의 .pgpass 파일에만 두었습니다.

primary의 WAL이 복제본으로 실시간 전달되고, 앱은 primary에만 붙는 VIP로 접속합니다

 

복제본은 설치하면서 OS를 최신으로 올렸더니 기존 DB보다 OS 마이너 버전이 하나 높아졌습니다. PostgreSQL 버전은 둘 다 15.15로 같으므로 복제에는 문제가 없습니다. 스트리밍 복제는 PostgreSQL 메이저 버전이 같아야 하고, OS 마이너 버전 차이는 영향이 없습니다.

5. 복제 확인

 

확인 결과
primary의 복제 상태(pg_stat_replication) 복제본이 streaming 상태, 지연 0바이트, 적용 지연 1ms
복제 슬롯 사용 중(active)
복제본의 복구 모드(pg_is_in_recovery) true, 읽기 전용 복제본으로 동작
주요 테이블 행 수 양쪽이 같음
데이터 전체 덤프 해시 양쪽이 같음

 

분석할 때 확인한 영상 수가 1,446개였는데, 복제를 확인할 때는 1,441개였습니다. 그사이 운영 중에 영상을 삭제한 내역이 복제본에도 그대로 반영된 것입니다.

 

복제 지연이 0으로 나와서 정말 0인지도 확인했습니다. primary의 현재 WAL 위치와 복제본이 받은 위치, 적용한 위치를 바이트 단위로 비교하자 세 값이 같았고, 적용 지연은 1ms 미만이라 반올림되어 0으로 보였습니다. 다만 이 복제는 비동기 방식이라, primary가 갑자기 멈추면 마지막 몇 ms 사이에 들어온 쓰기는 복제본에 없을 수 있습니다.

6. 앱이 접속할 VIP

앱이 DB IP를 직접 쓰면, 복제본을 primary로 올린 뒤 앱 설정을 모두 바꿔야 합니다. 그래서 두 DB 서버에 keepalived를 두고 VIP를 하나 정했습니다.

 

설정 내용
VIP 조건 상태 확인 스크립트가 "복구 모드가 아님(pg_is_in_recovery = false)"을 확인한 서버, 즉 primary에만 VIP
복제본 상태 확인이 실패하므로 VIP 없이 대기
승격 후 복제본을 primary로 올리면 상태 확인이 성공해 VIP가 넘어옴
통신 VRRP를 unicast로 서로 지정

 

일반적인 keepalived 설정은 우선순위가 높은 서버가 VIP를 가집니다. 하지만 DB에서는 VIP가 반드시 primary를 따라가야 합니다. 그래서 우선순위 대신 PostgreSQL에 직접 물어보는 상태 확인 스크립트를 조건으로 썼습니다. 복제본이 VIP를 가져가서 앱이 읽기 전용 DB에 쓰려고 하는 상황을 막기 위해서입니다.

 

Rocky Linux는 SELinux가 켜져 있어서, keepalived가 상태 확인 스크립트를 실행할 수 있도록 스크립트에 맞는 SELinux 라벨을 붙이고 방화벽에 VRRP를 허용했습니다. 적용한 뒤 SELinux 거부 기록은 0건이었습니다. Kubernetes의 Pod가 DB에 접속할 때는 출발지 IP가 노드 IP로 바뀌므로, pg_hba에는 워커 노드 IP 대역을 추가했습니다. Pod에서 일부러 틀린 비밀번호로 접속했습니다. 비밀번호 인증 실패 메시지가 나오는 것을 보고 접속 규칙을 통과했음을 확인했습니다.

7. 백업과 모니터링

백업은 두 서버 모두에서 매일 새벽 pg_dump로 받고 14일 동안 보관합니다. 복제본에서도 pg_dump를 쓸 수 있으므로, 서로 다른 호스트에 백업 사본이 하나씩 생깁니다.

 

모니터링을 위해 두 서버에 Zabbix agent2와 PostgreSQL 플러그인을 설치했습니다. 감시 계정은 primary에서 만들면 복제본에도 그대로 복제됩니다. 기본 템플릿에는 복제 상태를 보는 트리거가 없어서, 서버의 현재 역할에 따라 동작하는 트리거를 직접 추가했습니다. 이렇게 하면 장애 조치로 역할이 바뀌어도 트리거가 바뀐 역할에 맞게 동작합니다.

 

트리거 심각도
primary에 연결된 복제본이 0개 High
복제본이 WAL을 받지 못함 High
복제 지연 64MB 초과 Warning
primary가 2대 Disaster

 

마지막 트리거는 장애 조치 뒤 기존 primary가 다시 켜져서 primary가 둘이 되는 상황을 잡기 위한 것입니다. 두 서버가 각자 쓰기를 받으면 데이터가 갈라지므로 가장 높은 심각도로 두었습니다.

8. 남은 일과 시리즈를 마치며

장애 조치는 아직 사람이 승격하는 방식입니다. 승격 절차를 문서로 정리하고, 세 번째 장비가 생기면 자동 장애 조치를 다시 검토할 계획입니다.

 

이것으로 홈랩 재구성 시리즈의 이관 작업을 마칩니다. 웹 서비스와 Docker 서비스, LMS는 Kubernetes로 옮겼고, DB와 관리용 서버는 VM으로 남겼습니다. 지원이 끝난 OS를 쓰던 서비스 VM은 대부분 사라졌고, 앱은 Jenkins와 Argo CD로 빌드하고 배포하며, 전체 상태는 Zabbix와 Grafana로 한곳에서 봅니다. VM은 한때 약 68대였지만, 지금은 Kubernetes 노드 7대를 포함해 20대입니다. 남은 과제는 세 번째 장비를 마련해 마스터와 DB의 장애 조치를 완성하는 것입니다.

9. 정리

  • 호스트가 2대인 지금은 Kubernetes 안의 DB도 호스트 1 장애 때 자동 장애 조치가 멈추므로, VM 2대 스트리밍 복제를 골랐습니다.
  • primary는 다시 시작하지 않고 설정 다시 읽기만으로 복제 계정, pg_hba, 복제 슬롯을 준비했습니다.
  • 복제 슬롯에는 max_slot_wal_keep_size로 상한을 두어 primary 디스크를 지킵니다.
  • 복제 상태, 행 수, 데이터 전체 덤프 해시로 복제본의 데이터가 primary와 같은지 확인했습니다.
  • 비동기 복제라 primary가 갑자기 멈추면 마지막 몇 ms의 쓰기는 잃을 수 있습니다.
  • keepalived VIP는 우선순위가 아니라 PostgreSQL에 직접 확인한 primary에만 붙게 했습니다.
  • 복제 트리거는 서버 역할에 따라 동작하므로, 장애 조치 뒤에도 바뀐 역할에 맞게 동작합니다.

참고

728x90
서울
--:--:--
-전체 글
-카테고리
누적 조회수