MeetBack 2026.08.21 7 min read

서버 수보다 경계를 먼저 정한 MeetBack 인프라 설계

MeetBack의 11개 서버 역할을 나눠 설계했다. Swarm, MySQL, NFS, 모니터링과 외부 진입의 선택 이유 및 각 구성에 남은 장애 지점을 정리했다.

프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: 11개 서버 역할과 계층별 인프라 설계 의사결정

MeetBack 인프라를 설계하면서 각 서버의 역할부터 나눴다. 장애가 났을 때 어떤 서비스를 복구해야 하고 어떤 데이터를 먼저 보호해야 하는지 정해 둘 필요가 있었다.

서비스 실행, 데이터베이스, 파일 저장소, 관측, 외부 진입과 관리 경로를 구분했다. 이 역할을 나누다 보니 전체 환경은 11개의 역할로 구성됐다.

Docker Swarm
  ├─ Manager 3
  └─ Worker 2

Database
  ├─ MySQL Primary 1
  └─ MySQL Read Only Replica 1

Storage
  ├─ NFS #1: application files
  └─ NFS #2: database backup and service logs

Operations
  ├─ Monitoring server 1
  └─ VyOS router 1

관리자는 일반 서비스 트래픽과 분리된 Tailscale 경로로 각 서버에 접근하도록 했다.

왜 Swarm Manager를 3대로 구성했는가

Swarm manager는 cluster 상태를 Raft 합의로 관리한다. 2대로 구성하면 한 대가 내려갔을 때 과반수를 유지하지 못한다. 3대라면 한 대가 실패해도 나머지 2대로 quorum을 유지할 수 있다.

반면 애플리케이션 실행은 worker에 집중하는 방향으로 잡았다. manager에도 task를 실행할 수 있지만, cluster 제어 역할과 application workload를 완전히 뒤섞고 싶지 않았다. 제한된 장비 환경이라 예외적인 배치는 가능하게 두더라도 placement의 기본 원칙은 분리하는 것으로 정했다.

manager 3대로 보호하는 범위는 Swarm 제어면이다. 애플리케이션 데이터, DB, NFS와 외부 진입은 각각 장애 지점이 남아 있었다. 그래서 계층마다 어떤 장애를 견디는지 따로 기록했다.

왜 MySQL을 Primary/Replica로 선택했는가

처음에는 자동 failover가 가능한 구성을 고민했다. 하지만 두 DB 서버만 있는 상황에서 자동 승격을 성급하게 넣으면 network partition과 source 장애를 구분하기 어렵고 split-brain 위험이 생긴다. 별도 quorum member와 router, 운영 절차까지 함께 설계해야 한다.

학원 프로젝트의 범위와 기간을 고려해 MySQL 8의 GTID 비동기 Primary/Replica 구조를 선택했다. Replica는 평소 읽기 전용으로 보호하고, Primary 장애 시 복제 상태와 transaction 위치를 확인한 뒤 수동으로 승격한다. 자동화 수준은 낮지만 왜 승격하는지 사람이 확인할 수 있고, 장애 시 데이터 정합성을 우선할 수 있다.

InnoDB Cluster도 검토했지만 프로젝트 기간에 운영해야 할 요소가 더 늘어난다고 판단했다. 이번에는 Primary/Replica의 복제 상태, backup, 수동 failover와 원복 절차를 직접 확인하는 범위로 잡았다.

왜 DB망을 따로 분리했는가

초기에는 모든 서버가 하나의 서비스망에 있었다. 이 구조는 구축하기 쉽지만 application server가 사용하는 DB traffic과 일반 server traffic, 관리 traffic의 경계가 흐려진다.

나는 DB 서버에 별도 network interface를 추가하고 private DB network로 옮겼다. 기존 서비스망 NIC는 최종적으로 연결을 제거했다. application worker와 monitoring server가 DB에 접근할 때는 VyOS router를 통과해야 한다.

MySQL 역시 DB private address 하나에만 listen하도록 했다. 두 주소에 동시에 listen하면 연결은 편해지지만 망분리의 의미가 약해진다. 운영체제 firewall은 전체 구성이 끝난 뒤 통신 matrix를 기준으로 적용하기로 했지만, 그 전에도 MySQL account의 Host 제한과 TLS 요구 조건으로 접근 범위를 줄였다.

왜 NFS를 두 역할로 나눴는가

사용자가 올린 이미지·첨부파일과 DB backup은 중요도와 access pattern이 다르다. 하나의 export에 모두 넣으면 application file 사용량이 급증했을 때 backup 공간까지 영향을 받을 수 있고, 권한도 지나치게 넓어진다.

그래서 NFS #1은 application data, NFS #2는 database backup과 service log로 역할을 나눴다. 두 서버 모두 hardware RAID5 logical disk 위에 LVM과 ext4를 구성했다.

MySQL data directory는 NFS로 옮기지 않았다. network filesystem 장애나 latency가 DB 실행 경로에 직접 들어오는 것을 피하기 위해 DB data는 local disk에 두고 dump 결과만 NFS에 보관했다.

왜 Monitoring을 별도 서버로 뒀는가

장애가 난 대상과 그 대상을 관측하는 도구가 같은 서버에 있으면 함께 사라질 수 있다. 나는 Prometheus, Grafana, Alertmanager, Loki, Alloy를 별도 monitoring server에 배치했다. node_exporter, mysqld_exporter, cAdvisor를 통해 node, DB, container를 수집한다.

monitoring server 자체는 단일 장애점으로 남았다. application node와 분리한 덕분에 worker 하나가 내려가도 monitoring server에서 상태를 확인할 수 있었다. 프로젝트에서는 이 범위로 장애 원인을 관찰했다.

외부 진입은 기본 경로와 예비 경로로 나눴다

기본 경로는 Cloudflare Tunnel로 정했다. 내부에서 outbound connector를 만들기 때문에 public inbound port를 항상 열 필요가 없고, Swarm global service로 여러 connector를 실행할 수 있다.

NGINX Proxy Manager는 public IP와 NAT가 가능할 때만 사용하는 예비 경로로 뒀다. DNS-01 wildcard certificate는 미리 준비했지만, 현재 public port forwarding을 사용할 수 없으므로 외부 NPM E2E를 완료했다고 표현하지 않는다.

기본: Client → Cloudflare → Tunnel → Swarm Service
예비: Client → Cloudflare → Public NAT → NPM → Swarm Service

NPM을 Tunnel의 필수 중간 계층으로 넣으면 한 component와 hop이 더 늘고, NPM 장애가 기본 경로까지 막을 수 있다. 그래서 두 경로의 책임을 분리했다.

설계 단계에서 발견한 가장 큰 오해

구성마다 이중화가 의미하는 범위가 달라 아래처럼 구분했다.

  • Manager 3대는 Swarm control plane quorum을 위한 구성이다.
  • Application replica는 process와 node 장애에 대응한다.
  • MySQL Replica는 data 복제본이지만 자동 failover를 의미하지 않는다.
  • RAID5는 disk 장애 허용이지 backup이 아니다.
  • NFS 서버 2대는 역할 분리이지 같은 data의 자동 mirror가 아니다.
  • Cloudflare connector 5개는 Tunnel 연결점 분산이지 origin application의 정상 상태를 보장하지 않는다.

설치한 것과 실제로 동작을 시험한 것을 구분하려고 각 문서에 “구성 완료”, “검증 완료”, “미검증”, “보류”를 나눠 기록했다.

남은 한계

이 글을 작성할 때 인프라 구성은 끝났고 실제 application stack의 개발 완료를 기다리고 있었다. NFS UID/GID, Docker Secret, replica 수, placement, rolling update와 rollback은 application 배포 후 최종 확인할 항목이다. Redis도 공통 module 구현까지 마쳤고 service 배포와 업무 기능 연동은 남아 있었다.

각 구성요소를 왜 선택했고 어떤 장애까지 대응할 수 있는지 확인하면서 설계했다. 이후 글에서는 이 구성을 실제 서버에 적용한 과정과 그때 발생한 문제를 나눠 기록한다.

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동