📌 읽기 전에
- 해당 글은 저희가 운영중인 MongoDB 서버를 Atlas로 마이그레이션하기 전, 동일한 환경을 구성하여 프로덕션 환경에서의 마이그레이션을 시뮬레이션(?)하기 위한 과정에서 Replica Set을 구축한 과정을 기록한 글입니다. - Replica Set을 간단히 구축해보고 싶거나, 구축 과정에 대한 이해를 하기에 적합한 수준으로 작성하였습니다. - Replica Set의 개념에 대해 알고싶으시다면 앞부분을, 직접 구축해보고 싶으신 분들은 뒷부분을 집중해주세요.
📌 Replica Set?
- Kubernetes에도 동일한 개념이 존재하지만 여기서는 MongoDB 관점에서의 개념을 설명하고자 합니다.
- Replica(Replication)부터 설명하자면, DB의 데이터들을 여러 서버에 동기화(synchronization)하는 것을 의미합니다.. 여러 서버가 모두 동일한 데이터를 가짐에 따라 하나의 서버가 다운되더라도 제공하는 서비스에 문제가 생기지 않고 운영을 할 수 있다는 장점이 있습니다.. 각 서버에 데이터 복구/리포팅/백업 역할(용도)을 설정할 수도 있습니다.
Replica Set을 구축하는 이유?
서비스 운영에 MongoDB를 사용할 경우, Replica Set을 구축하지 않을 이유는 없어보입니다. 물론 구축과 실제 운영 알아야할 것들에 차이가 있지만, 후회하지 않는 선택이라고 생각합니다.
- 데이터를 안전하게 보존하기 위해
- 24시간 접근 가능한 데이터의 상태를 유지하기 위해
- 서비스 운영시 다운타임(인덱스 적용, 백업 작업 등에 의해)을 없애기 위해
- 기타 등등…
용어
-
Primary node
,Secondary node
모든 write 작업을 수행합니다. primary node에 해당 작업이 수행되면 oplog라는 것에 모든 작업 로그를 저장하는데, secondary node에서는 이 oplog를 보고 동일한 작업을 수행합니다. 쉽게 말해서 primary node에서 변화된 데이터를 복사하는 것이죠.
출처 : MongoDB 공식 문서
-
Election
우리 말로는 ‘선거’, primary node가 이용 불가능한 상태에 빠졌을 때, secondary node들 중에서 primary node를 하나 정하는 과정을 의미합니다.
-
Arbiter node
primary나 secondary node처럼 데이터를 가지진 않고 secondary node들 중에서 primary node를 선정하는 election 과정에 참여합니다. arbiter node가 primary node가 될 수는 없습니다.
-
Heartbeat
Replica set 내의 모든 노드들은 정해진 초(second)마다 서로에게 heartbeat(일종의 ping)를 보냅니다. 귀엽지 않나요? 이러한 Heartbeat가 특정 초 동안 수신되지 않으면 다른 node들이 Election을 주섬주섬 준비합니다.
구성
Replica Set은 동일한 데이터를 가진 여러 node(여기서는 서버)로 이루어져있으며, 선택적으로 하나의 Aribiter node를 포함시킬 수 있습니다. 데이터를 가진 node들 중에서는 반드시 하나의 primary node를 지정해줘야하며, 나머지 node들은 secondary node라고 칭합니다.
node들의 구성에 따라 대표적인 구성 방식을 소개하고자 하는데요, Replica Set을 구성하는 node의 개수는 최소 3개 이상입니다. 그 중에 대표적인 3개로 이루어진 경우는 다음과 같습니다.
-
P-S-S
(Primary + Secondary + Secondary)하나의 Primary와 두개의 Secondary node로 이루어진 구성입니다. Primary node에 문제가 생기더라도 Secondary node 2개나 그 자리를 대신할 수 있으니 든든(?)합니다. 높은 안정성( high availability )을 보장할 수 있습니다.
-
P-S-A
(Primary + Secondary + Arbiter)Primary, Secondary, Arbiter node 각 1개씩으로 이루어진 구성입니다. P-S-S 구성과는 다르게 Arbiter node가 추가되었는데요, 이 node는 Primary나 Secondary와는 다르게 서버의 리소스는 많이 필요하지 않지만 데이터를 실제로 담고있지는 않기 때문에 상대적으로는 구성의 안정성이 낮습니다(그렇다고 안정성이 좋지 않은 것은 아닙니다).
📌 구축하기
Step #1: 각 인스턴스에 MongoDB 설치 Step #2: Replica Set 설정
이번에는 실제로 MongoDB 서버를 3개 구축하고 이들을 P-S-A Replica Set으로 설정하는 방법을 알아보겠습니다. 전체 과정은 MongoDB의 공식 문서 를 참고하였습니다.
필자의 경우 Replica Set으로 운영중인 서비스를 마이그레이션하기 전, 개발 환경에서 동일한 환경을 구축하여 테스트하는 용도로 진행하였습니다.
Step #1 : 각 인스턴스에 MongoDB 설치
- 본 글에서 ‘인스턴스’는 AWS의 EC2 인스턴스를 의미합니다.
- EC2 인스턴스 생성과 초기 설정에 익숙한 독자를 기준으로 설명하였습니다.
- 아래와 같은 과정을 총 3번 진행해주세요.
(1) EC2 인스턴스에 MongoDB를 설치하기(이 문서 참고)
- Platform(x86_64, arm64 등)에 따른 MongoDB 버전을 잘 체크해야합니다.
- 인스턴스 OS(ex. Ubuntu, Debian 등)도 지원되는 버전을 잘 확인해야합니다.
-
설치 커맨드 예시(MongoDB 4 버전)
$wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add - $echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list $sudo apt-get update $sudo apt-get install -y mongodb-org
(2) 인스턴스의 27017번 포트를 열기
- Security Group의 inbound rule을 수정해야합니다.
(3) Primary node로 설정할 인스턴스를 정하고, admin 유저의 비밀번호 설정하기
$mongo
> use admin
> db.createUser({"user": "admin", "pwd":"{비밀번호}", "roles": [{"role":"root", "db": "admin"}]}) # 예시 권한
- Primary node에 비밀번호를 설정하지 않으면 불시에(?) 해킹을 당할 수 있으므로 반드시 진행해주세요.
Step #2: Replica Set 설정
고려해야할 것
- 공식 문서에도 나와있지만, 위의 Step#1 에서 생성한 인스턴스들은 ip보다는 host name(도메인 주소)로 접근하는 것을 권장하고있습니다. AWS의 경우 Route53을 통해 도메인을 설정할 수 있으니 먼저 진행해주세요.(ex.
{primary/secondary/arbitery}.mongo.db}
)
(1) 각 인스턴스의 MongoDB 설정 파일(/etc/mongo.conf) 수정하기
-
Replica Set으로 설정할 모든 인스턴스에서 동일하게 수정해줘야합니다.
-
설정값을 아래와 같이 수정해줍니다.
(수정1) net: localhost와 해당 인스턴스의 host name으로 설정
(수정2) security: Replica Set 내의 node들 간의 인증에 사용되는 키를 생성해줘야합니다.
만약 이 부분을 설정해주지 않으면DBClientConnection failed to receive message from 127.0.0.1:27017 - HostUnreachable: Connection closed by peer
에러가 발생할 수 있습니다. 이 문서를 참고하여 키파일을 생성하고, 키파일의 경로를 추가해주세요.(수정3) replication: 원하는 replica set 이름을 설정해주세요.
"/etc/mongod.conf" 43L, 637C 24,30 All # mongod.conf # for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/ # Where and how to store data. storage: dbPath: /var/lib/mongodb journal: enabled: true # engine: # mmapv1: # wiredTiger: # where to write logging data. systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log # 수정(1) net: port: 27017 bindIp: localhost,{해당 인스턴스의 host name} # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo # 수정(2) security: authorization: "enabled" clusterAuthMode: "keyFile" keyFile: "{키 파일 경로}" #operationProfiling: # 수정(3) replication: replSetName: "{원하는 이름}" #sharding: ## Enterprise-Only Options: #auditLog: #snmp: 37,36 All
(2) Primary node에서 replica set 생성하기
-
(1)에서 설정 파일을 잘 생성했다면, 이제는 각 인스턴스에서 mongod(mongo daemon)을 실행해줘야합니다.
// mongo daemon 실행 $sudo mongod --config /etc/mongod.conf --fork(백그라운드로 실행)
-
Primary node로 설정하고자하는 인스턴스의 mongo daemon에 접속합니다.
$mongo --port 27017 -u "admin" -p Enter password: {Step#1의 (3)에서 설정한 비밀번호 입력}
-
Replica set 생성
> rs.initiate()
(3) Replica Set에 Secondary, Arbiter node 추가하기
-
Secondary node 인스턴스에 접속해서 추가하기
$mongo > rs.add({host:"{Secondary node 도메인주소:27017}", priority:1})
-
Arbiter node 인스턴스에 접속해서 추가하기
$mongo > rs.add({host:"{Arbitrer node 도메인주소:27017}", priority:1, arbiterOnly:true})
(4) Replica set 생성 결과 확인(Primary node 인스턴스에서)
$mongo --port 27017 -u "admin" -p
Enter password: {Step#1의 (3)에서 설정한 비밀번호 입력}
> rs.status()
{
"set" : "{Replica set 이름}",
"date" : ISODate("2021-11-23T15:29:07.810Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1637681296, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-11-23T08:51:26.230Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1637657486, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2021-11-23T08:51:26.231Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-11-23T08:51:26.314Z")
},
"members" : [
{
"_id" : 0,
"name" : "{Primary node 도메인 주소:27017}",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 23873,
"optime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-23T15:29:06Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1637657486, 2),
"electionDate" : ISODate("2021-11-23T08:51:26Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "{Arbiter node 도메인 주소:27017}",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 193,
"lastHeartbeat" : ISODate("2021-11-23T15:29:07.005Z"),
"lastHeartbeatRecv" : ISODate("2021-11-23T15:29:07.013Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "{Secondary node 도메인 주소:27017}",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 160,
"optime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-23T15:29:06Z"),
"optimeDurableDate" : ISODate("2021-11-23T15:29:06Z"),
"lastHeartbeat" : ISODate("2021-11-23T15:29:06.943Z"),
"lastHeartbeatRecv" : ISODate("2021-11-23T15:29:06.251Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "test.primary.mongo.db:27017",
"syncSourceHost" : "test.primary.mongo.db:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
}
],
"ok" : 1,
"operationTime" : Timestamp(1637681346, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1637681346, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
위와같이 보인다면 Replica Set 설정이 완료된 것입니다. 이제 Primary node의 주소를 가지고 어플리케이션 코드에서 적용시켜보면서 테스트해보자구요!