post-thumbnail

MongoDB Replica Set 환경 구축하기 (Feat. Docker)

MongoDB
Transacion
1077
4

들어가기 앞서

MongoDB 는 버전 4.0부터 트랜잭션을 지원합니다. 하지만 여타 다른 DB와 다르게 트랜잭션을 사용하기 위해선 Replica Set 환경을 구성해야합니다. 이번 포스트에선 Replica Set에 대해 알아보며 Docker를 통해 환경을 구성하는 방법을 알아봅니다.

🤔 Replica Set 이란?

Replica Set은 동일한 데이터 세트를 유지 관리하는 프로세스 그룹입니다. 여러 프로세스가 동일한 데이터 세트를 유지함을써 하나의 서버가 다운되더라도 제공하는 서비스에 문제가 생기지 않고 운영할 수 있다는 장점이 있습니다.
Replica Set을 구축하는 이유는 다음과 같습니다.
  • 데이터를 안전하게 보존하기 위해서
  • 24시간 접근 가능한 데이터의 상태를 유지하기 위해서
  • Transaction 처리를 위해 (테스트 환경이나 개발 환경에서는 Standalone mongod 인스턴스를 Replica Set으로 변환하여 사용할 수 있지만, 프로덕션 환경에서는 Replica Set 구축이 필수적입니다.)
  • 기타 등등..

구성 형태

post_image
Replica Set은 세가지 역할로 구성원을 나눌 수 있습니다.
  • Primary: 레플리카 셋에서 프라이머리 노드는 단 하나만 존재 할 수 있습니다. 클라이언트에서 DB로 읽기 및 쓰기 작업을 진행합니다.
  • Secondary: 프라이머리로부터 데이터를 동기화 합니다. 프라이머리의 장애 상황에서 어떤 세컨더리 노드를 프라이머리로 올릴것인지 투표권을 가지고 있습니다. 설정에 따라 프라이머리 노드의 읽기 작업을 분담할 수 있습니다.
  • Arbiter: 데이터를 동기화하지는 않으며 투표권만을 가져 프라이머리 장애 발생시 레플리카 셋의 복구를 돕는 역할을 합니다. (현재 예제에선 Arbiter을 다루지 않습니다.)
Replica Set의 구성원 사이에는 주기적으로 10초에 한번씩 Ping을 보내 서로의 노드를 확인하는 작업을 진행합니다. 이러한 작업을 Heartbeat 라고 부르며, 해당 작업을 통해 노드 및 DB의 장애를 파악합니다. 만약 장애가 발견된다면 다음과 같은 작업을 수행합니다.
post_image
다음 이미지는 프라이머리 노드에 장애가 발생해 다운되었을 때 입니다. 장애가 발견되면 세컨더리 노드 중 하나를 프라이머리 노드로 올리게 되는데 이때 투표권을 가진 노드들이 선거를 통해 프라이머리 노드로 승격시킬 노드를 결정합니다.

🐳 Docker로 MongoDB Replica Set 구축하기

해당 장에선 Docker를 통해 Replica Set 환경을 구성합니다. Docker를 통해 MongoDB 이미지를 가져와 컨테이너를 구동합니다. MongoDB 이미지에는 런타임 환경에 필요한 Dependency들이 포함되어 있기 때문에 로컬환경에서 MongoDB를 설치하지 않고 Container 단위로 관리할 수 있습니다.
  • 실제 프로덕션 환경에선 하나에 인스턴스에서 모든 노드들을 관리하는 것보다 분산해서 관리하는 것을 추천합니다.
  • 분산된 DB를 통해 프라이머리 노드가 중지되었을 때도 다른 노드들에 영향미치지 않기 때문입니다.
Docker를 통해 Replica Set 구축하는 단계는 다음과 같습니다.
  1. Docker Network 생성
  2. Docker를 통해 세 개의 MongoDB 컨테이너 생성
  3. Replica set 초기 설정
첫 단계부터 차근차근 정리해보도록 하겠습니다.

1️⃣ Docker Network 생성

첫 번째 단계는 Docker Network를 생성하는 것입니다. 실행중인 세 개의 MongoDB 컨테이너를 연결하기 위해 해당 단계를 진행합니다.
1docker network create mongoCluster
mongoCluster는 생성될 network 이름입니다. 사용자가 자유롭게 변경할 수 있습니다.

2️⃣ Docker를 통해 세 개의 MongoDB 컨테이너 생성

세 개의 컨테이너를 한번에 관리하기 위해서 docker-compose를 사용합니다. 먼저 MongoDB Image를 다운로드하는 것부터 진행하겠습니다.
1docker pull mongo
해당 Docker 명령어를 통해 MongoDB 최신 버전을 다운로드 합니다.
이후 docker-compose.yml 파일을 구성하기 전 Replica Set 내부 인증을 위한 키 파일을 먼저 생성해야 합니다.
1openssl rand -base64 756 > mongodb.key
2chmod 400 mongodb.key
해당 키 파일을 Replica Set 구성원들에 복사해 연결해주어야 합니다.
이제 docker-compose.yml 파일을 생성합니다. 중요한 부분만 잘라서 보겠습니다.
1# docker-compose.yml
2version: '3.8'
3services:
4  mongo1:
5    image: mongo:latest
6    hostname: mongo1
7    container_name: mongo1
8    restart: always
9    ports:
10      - 27017:27017
11    volumes:
12      - ./data:/data/db
13      - ./mongodb.key:/etc/mongodb.key
14    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'
15  mongo2:
16    ...
17  mongo3:
18    ...
해당 코드에서 봐야 할 부분은 hostnamevolumes, command입니다.

hostname
MongoDB는 Replica Set 구성하면서 연결된 IP 주소가 아닌 DNS host를 추천하고 있습니다. IP 주소 변경으로 인해 내부 구성원을 변경하는 상황을 방지하기 위해서 DNS Host를 연결하여 내부 설정을 유기적으로 관리하기 위함입니다.
해당 구성을 위해 /etc/hosts 파일에 다음과 같이 추가합니다.
mongo1 127.0.0.1
mongo2 127.0.0.1
mongo3 127.0.0.1

volumes
앞서 키 파일을 생성했습니다. 생성한 mongodb.key 파일을 컨테이너 복사합니다.
  • 파일 이름은 변경될 수 있습니다.

command
각 옵션단위로 분석해 보겠습니다.
  • --replSet: 연결할 Replica Set의 이름
  • --keyFile: 연결할 키 파일 이름
  • --bind_ip_all: 모든 ip 주소에 접근 가능하도록 설정합니다.

docker-compose.yml

전체 코드입니다.
1version: '3.8'
2services:
3  mongo1:
4    image: mongo:latest
5    hostname: mongo1
6    container_name: mongo1
7    restart: always
8    ports:
9      - 27017:27017
10    volumes:
11      - ./data:/data/db
12      - ./mongodb.key:/etc/mongodb.key
13    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'
14  mongo2:
15    image: mongo:latest
16    hostname: mongo2
17    container_name: mongo2
18    restart: always
19    depends_on:
20      - mongo1
21    ports:
22      - 27018:27017
23    volumes:
24      - ./data2:/data/db
25      - ./mongodb.key:/etc/mongodb.key
26    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'
27  mongo3:
28    image: mongo:latest
29    hostname: mongo3
30    container_name: mongo3
31    restart: always
32    depends_on:
33      - mongo2
34    ports:
35      - 27019:27017
36    volumes:
37      - ./data3:/data/db
38      - ./mongodb.key:/etc/mongodb.key
39    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'
40networks:
41  default:
42    name: mongoCluster
networks에 생성한 Docker Network를 연결해주고 dcoker-compose up -d 명령어를 실행합니다.

3️⃣ Replica set 초기 설정

성공적으로 컨테이너가 생성됐다면 Replica set 설정을 진행합니다.
1docker exec -it mongo1 mongosh --eval "rs.initiate({
2 _id: \"myReplicaSet\",
3 members: [
4   {_id: 0, host: \"mongo1\"},
5   {_id: 1, host: \"mongo2\"},
6   {_id: 2, host: \"mongo3\"}
7 ]
8})"
docker exec 명령어를 통해 컨테이너 내부에서 입력된 명령을 실행합니다. 해당 명령은 프라이머리 노드에서 한번 실행하면 됩니다.
rs.initiate는 입력된 정보를 통해 Replica set을 구성하는 명령어 입니다. 내부 정보는 다음과 같습니다.
1{
2 _id: "설정한 Replica set 이름",
3 members: [
4   {_id: 0, host: "설정한 hostname"},
5   ...
6 ]
7}
해당 명령어를 실행할 경우 { ok: 1 }이 반환된다면 성공입니다.

테스트 및 확인

모든 과정을 완료했다면 실행중인 3개의 컨테이터는 하나의 Replica set으로 구성되었습니다. rs.status() 명령어를 통해 구성원 목록을 포함하여 Replica set의 상태를 확인할 수 있습니다.
1docker exec -it mongo1 mongosh --eval "rs.status()"
정상적으로 실행중인 Replica set이라면 Primary 노드인 mongo1 이 종료되더라도 Secondary 노드인 mongo2mongo3Primary 노드로 변경되어 정상적으로 실행됩니다.
1docker stop mongo1
2docker exec -it mongo2 mongosh --eval "rs.status()"

마치며

오늘은 MongoDB Replica set을 구축하는 방법에 대해 정리했습니다. 이전 Transaction이란 무엇일까?글에서 작성했듯이 제 블로그에 Transaction을 적용하기 위해 겪은 과정을 정리하는 것이라 생각해주시면 됩니다.
앞으로 관련 글로 한 개의 글을 더 작성할 예정입니다. 다음 주제는 NestJS에 Transaction을 적용하면서 겪은 고민과 해결과정에 대해 정리할 예정입니다.
감사합니다.

참고