MongoDB replica set 구성하기

수하·2021년 9월 29일
1

MongoDB

목록 보기
1/2

오늘은 서버 세 대로 PSA 구조의 mongoDB replica set 구성을 하려고 한다.
회사에서 맡고 있는 로그 수집 시스템을 ELK Stack에서 Kafka + MongoDB 로 변경하려고 하여, MongoDB 서버 클러스터 구성이 필요했다.

MongoDB replica set이란

MongoDB의 Replica set이란 같은 데이터셋을 가지고 있는 여러 mongod 프로세스 그룹이다. 여러 DB 서버에 복수 개의 데이터 copy를 둠으로써, 단일 DB 서버로 구축하는 것보다 fault-tolerant하다.

Replica set은 크게 세 가지 역할로 나눌 수 있다.

  • Primary: 클라이언트에서 DB로 읽기 및 쓰기 작업을 한다.
  • Secondary: 프라이머리로부터 데이터를 동기화 한다. Primary에 장애가 발생한 경우, 투표를 통해 Primary가 될 수 있다. 클라이언트 단에서 read preference 설정을 하면 secondary도 read operation을 수행할 수 있다.
  • Arbiter: 데이터를 동기화하지는 않으며 primary 선정을 위한 투표권만 주어진다.

보다 자세한 설명은 이 블로그 참조

위 세 가지 역할을 조합하여, PSS 구조나 PSA 구조로 서버를 구성할 수 있다.
그러나 Arbiter는 데이터를 쌓지 않으며 적은 리소스로 사용 가능하기 때문에, 3개의 서버로 PSA 구조로 가되 두번째 Secondary 서버에 Arbiter도 같이 띄울 것이다.

  • 1번 서버: primary
  • 2번 서버: secondary
  • 3번 서버: secondary + arbiter (mongod 2개 실행)


MongoDB replica set 설치

서버 생성

kt cloud 에서 서버 3대를 생성하였다.
MongoDB는 메모리 용량에 따라 성능이 크게 좌우되기 때문에, 메모리는 최소 32GB에 SSD를 마운팅해야한다.

  • OS: CentOS 7.0 x86_64
  • spec: 8Core 32GB, 100GB SSD

mongodb 설치

삽질 1 - CentOS 7.0에서 MongoDB 5.0 버전이 잘 호환되지 않는다.

설치까지는 되는데, mongod 명령어가 먹지 않고, systemctl start mongod도 안된다.
OS를 바꾸기엔 기존 회사 서버가 CentOS7.0 이어서, 그냥 MongoDB 4.4 버전을 사용하기로 했다.

먼저 공식 문서에 나와있는 .repo 파일을 만들기 전, 아래와 같은 작업을 해줘야 한다. 그렇지 않으면 mongodb 설치 시, "[Errorno 14] curl#35 - "Peer reports incompatible or unsupported protocol version" 이라는 오류가 발생한다.

yum update -y nss curl libcurl

이후로는 모든 서버에서 아래를 참고하여 해당 서버에 맞는 명령어를 입력해주면 된다.
Arbiter가 있는 3번 서버는 추가 설정이 있으니 조심해야 한다.

  1. repository 추가
    vi /etc/yum.repos.d/mongodb-org-4.4.repo 하고 아래 내용을 입력한다.
[mongodb-org-4.4]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc
  1. yum install
    sudo yum install -y mongodb-org 로 최신 stable 버전의 mongodb 4.4를 설치한다.
    여기서, yum이 패키지 새 버전이 나왔을 때 자동 업그레이드하는 것을 막으려면 /etc/yum.conf 파일에 exclude=mongodb-org,mongodb-org-server,mongodb-org-shell,mongodb-org-mongos,mongodb-org-tools 를 입력해줘야 한다.

  2. ulimit 설정 변경
    ulimit -a 를 통해 시스템 리소스 사용 제한을 확인할 수 있다.
    MongoDB에서 recommend 하는 설정대로 변경하자. ulimit -n 64000 와 같이 입력하여 변경하면 된다. 특히 MongoDB 4.4에서는 open files 개수 (-n) 이 64000 보다 적으면 실행 시 오류가 발생하니 꼭 바꿔줘야 한다.

-f (file size): unlimited
-t (cpu time): unlimited
-v (virtual memory): unlimited
-l (locked-in-memory size): unlimited
-n (open files): 64000 // 중요한 설정이므로 꼭 변경
-m (memory size): unlimited
-u (processes/threads): 64000
  1. 데이터, 로그 디렉토리 변경
    mongoDB 기본 설정은 데이터 디렉토리는 /var/lib/mongo, 로그 디렉토리는 /var/log/mongodb 이다.
    그런데 난 /data 에 SSD 마운팅을 하였고, mongoDB 관련 파일은 모두 /var이 아닌 /opt 아래로 옮기려고 한다. 따라서 아래와 같이 데이터 디렉토리와 로그 디렉토그 모두 새로 생성하고 mongod로 사용자를 변경해야한다.
sudo mkdir -p /data/mongodata
sudo mkdir -p /opt/db/mongodb/logs
sudo chown -R mongod:mongod /data/mongodata
sudo chown -R mongod:mongod /opt/db/mongodb/logs
  1. Arbiter용 데이터, 로그 디렉토리 추가 (3번 서버)
    3번 서버는 위 4번 단계를 진행하고, 추가적으로 Arbiter용 데이터, 로그 디렉토리를 추가해줘야 한다. Arbiter는 DB 데이터를 적재하는 노드가 아니므로 /data가 아닌 /opt 에다 데이터 디렉토리를 설정해도 된다.
sudo mkdir -p /opt/db/mongoArbiter/data
sudo mkdir -p /opt/db/mongoArbiter/logs
sudo chown -R mongod:mongod /opt/db/mongoArbiter/data
sudo chown -R mongod:mongod /opt/db/mongoArbiter/logs
  1. SELinux 설정 (필요 시. 난 안함)
    나는 3대의 서버를 개발 서버로 사용할 목적이므로 모두 SELinux 설정을 껐다. Production 용도로는 SELinux를 enforcing 모드로 변경해야 한다고 하는데, 개발용 서버이기도 하고 추가 설정들이 많아서 disable 시켰다.
    각자 서버 설정을 확인한 후에, enforcing 모드면 추가 설정 하자.

mongod config 파일 설정

mongodb를 실행하기 전, mongod.conf 파일을 수정해야 한다.
vi /etc/mongod.conf 하여 아래와 같이 conf 파일을 수정하자.

1, 2번 서버는 conf 파일이 한개이므로 바로 수정해도 된다.
그러나 3번 서버는 secondary와 arbiter가 각각 다른 conf로 각각 떠있어야 하기 때문에, secondary용 conf는 /opt/db/mongodb/mongodb.conf에, arbiter용 conf는 /opt/db/mongoArbiter/mongod_arbiter.conf에 적는 등 conf를 분리하여 적어야 한다.

Primary, Secondary

systemLog:
   destination: file #
   path: "/opt/db/mongodb/logs/mongod.log"
   logAppend: true # 
   logRotate: rename # 
   timeStampFormat: iso8601-utc
   quiet: true # 
storage:
   engine: wiredTiger #
   dbPath: "/data/mongodata"
   directoryPerDB: true
   wiredTiger:
      engineConfig:
         journalCompressor: snappy
         cacheSizeGB: 0.4 #
      collectionConfig:
         blockCompressor: snappy
      indexConfig:
         prefixCompression: true
   journal:
       enabled: true #
       commitIntervalMs: 300 #
processManagement:
   fork: true
   pidFilePath: "/opt/db/mongodb/mongod.pid" #
   timeZoneInfo: /usr/share/zoneinfo
net:
   bindIpAll: true
   port: 27017
   wireObjectCheck : false #
   unixDomainSocket: #
       enabled : true
replication:
  replSetName: mongo_repl_set #
setParameter:
   enableLocalhostAuthBypass: false #
#security:
#   authorization: enabled
  • systemLog
    • destination: file 또는 syslog. syslog는 timestamp가 mongoDB가 메시지를 발행할 때가 아니라 실제 로그를 남길 때 생성되므로 추천하지 않는다.
    • logAppend: mongod가 재시작됐을 때 기존 로그 파일에 append.
    • logRotate: mongoDB는 로그 파일이 자동으로 rotation되지 않으며, 한 로그 파일 사이즈가 계속 커지게 된다. 참고
      따라서 별도의 리눅스 logrotate 설정이 필요하다. logRotate는 logrotate의 behavior를 정의한다.
      rename은 같은 파일을 이름만 바꿔서 로깅을 지속하고 reopen은 다른 Linux log rotation과 같이 로그 파일을 닫고 다시 여는 것이다.
    • quiet: 시스템 로그 아웃풋 양을 제한. Production에서는 connection 오류 트래킹을 위해 사용하지 않는 것이 좋다.
  • storage
    • engine: wiredTiger 또는 inMemory. wiredTiger가 기본 설정이다.
    • directoryPerDB: wiredTiger 엔진에서만 사용 가능하다. true이면 각 DB 마다 디렉토리를 분리한다.
    • wiredTiger.engineConfig.cacheSizeGB: wiredTiger 내부 cache의 최대 크기를 제한한다. 서버에 1개의 mongod가 떠있을 때를 기준으로 기본값이 max(0.5 of (RAM - 1GB), 256MB) 이므로, 2개 이상의 mongod가 떠있다면 줄여야 한다. 또한 wiredTiger는 내부 cache 뿐만 아니라 filesystem cache도 사용하기 때문에 기본값 이상으로 설정하는 것은 지양해야 한다.
    • journal.enabled: wiredTiger 엔진에서만 사용 가능하다. 서버의 저널 로그 (트랜잭션 로그)를 활성화할건지 결정한다. false로 하면 저널 로그를 디스크에 기록하지 않는다.
      MongoDB의 journaling이란 시스템 이상이나 크래시 등으로 mongodb가 비정상 종료된 경우라도 데이터가 깨지지 않도록 하기 위한 기능이다. 참고
      MongoDB는 메모리에 데이터를 올려놓고 메모리에 write 작업을 하는데, 바로 disk 데이터가 변경되는 것이 아니라 60초의 interval 마다 disk로 flush를 한다. 즉, 디스크에 데이터가 저장되는 데에 60초의 지연이 있으며 shutdown 시 데이터가 복구되지 않을 수 있다는 의미이다. 따라서 journaling은 enable 해야 한다.
    • journal.commitIntervalMs: MongoDB는 트랜잭션 단위로 저널 로그를 디스크에 동기화 하지 않기 때문에 이 옵션에서 설정한 millisec 단위로 저널 로그를 디스크에 동기화한다. 기본값은 100이며, 자주 commit할수록 journal의 durability는 증가하겠지만 디스크 IO 비용은 감안해야 한다.
  • processManagement
    • pidFilePath: processManagement.fork 설정과 같이 사용해야 유용하다. 단 리눅스에서 systemctl로 서비스를 시작하는 경우 PID file 관리는 /etc/init.d에서 이루어지기 때문에 사용하지 말 것.
  • net
    • wireObjectCheck: true면 클라이언트 요청이 잘못된 BSON인지 유효성 검사를 하게 된다. 요청하는 object가 많이 nesting 되어있는 경우 성능에 영향을 줄 수 있다.
    • unixDomainSocket: IPC (Inter Process Communication) 소켓이라고도 하며, localhost 프로세스 간 local file 기반의 소켓 통신이다. 기본값은 true이며 false면..? 검색해도 안나온다.. 좀 더 찾아보고 쓰겠다.
  • replication
    • replSetName: mongod가 속해있는 replica set의 이름. Replica set에 속해있는 모든 host는 동일한 값을 적어야 한다.
      wiredTiger 엔진의 경우 위의 storage.journal.enabled: true로 설정 해야 한다.
  • setParameter
    • MongoDB 서버 파라미터를 <parameter 이름> : <값> 형태로 전달할 수 있다.
    • enableLocalhostAuthBypass: localhost에서 인증을 bypass할지에 대한 설정이다. 기본값은 true이며, bypass를 해제하기 위해 false로 설정했다.
  • security: 다음 포스트에서 사용할 예정이다.

Arbiter

systemLog:
   verbosity: 0
   destination: file
   path: "/opt/db/mongoArbiter/logs/mongod.log"
   logAppend: true
   logRotate: rename
   timeStampFormat: iso8601-utc
   quiet: true
   component: #
    replication:
      verbosity: 3
storage:
   dbPath: "/opt/db/mongoArbiter/data"
   directoryPerDB: true
   wiredTiger:
      engineConfig:
         journalCompressor: snappy
         cacheSizeGB: 0.4
      collectionConfig:
         blockCompressor: snappy
      indexConfig:
         prefixCompression: true
   journal:
       enabled: true
processManagement:
   fork: true
   pidFilePath: "/opt/db/mongoArbiter/mongod.pid"
   timeZoneInfo: /usr/share/zoneinfo
net:
   bindIpAll: true
   port: 27027
   unixDomainSocket:
      enabled : false
replication:
  replSetName: mongo_repl_set
setParameter:
   enableLocalhostAuthBypass: false
  • systemLog
    • component: MongoDB 시스템 로그의 이벤트 카테고리를 component라고 한다. (NETWORK는 네트워크 연결과 관련된 로그, COMMAND는 DB 커맨드와 관련된 로그) component.<name>.verbosity 를 통해 각 component의 시스템 로그 verbosity를 설정할 수 있다.



mongod start 및 replica 서버 설정

start

Primary와 secondary는 mongod --port 27017 --config [conf 경로]로,
arbiter는 mongod --port 27027 --config [arbiter conf 경로]로 띄우면 된다.

특히 3번 서버는 실행 후 ps -ef | grep mongo에서 mongod 두 개가 떠있는 것을 꼭 확인해야 한다.

replica 서버 설정

Primary 서버에서 mongo 명령어를 입력한 후, 아래 설정을 통해 replica set을 초기화시킨다.

rs.initiate(
   {
      _id: “mongo_repl_set”,
      version: 1,
      members: [
         { _id: 0, host : "[1번 서버 ip주소:27017]" },
         { _id: 1, host : "[2번 서버 ip주소:27017]" },
         { _id: 2, host : "[3번 서버 ip주소:27017]" },
      ]
   }
)

rs.addArb("[3번 서버 ip주소:27027]")

삽질 2. 클라우드 콘솔에서 방화벽 해제 설정을 했는데도 포트가 열리지 않아 오류가 발생했다.

firewall-cmd --zone=public --permanent --add-port=27017/tcp 로 포트를 열고.
firewall-cmd --reload 로 새로고침 한 후
firewall-cmd --zone=public --list-all 로 public zone 의 열린 포트를 확인할 수 있다.

삽질 3. mongod.conf의 replication 설정은 절대 주석처리하고 실행하면 안된다.

Stackoverflow에서 rs.initiate() 시에는 mongod.confreplication 설정을 주석처리하고 실행하라는 글을 보고 해당 부분을 주석처리 하고 실행했더니, 아래와 같은 에러가 발생하였다.

Sep 29 16:16:52 aicc-mongodb-cluster-02.localdomain systemd[1]: Unit mongod.service entered failed state.
Sep 29 16:16:52 aicc-mongodb-cluster-02.localdomain systemd[1]: mongod.service failed.
Sep 29 16:16:52 aicc-mongodb-cluster-02.localdomain polkitd[1410]: Unregistered Authentication Agent for unix-process:13018:1496936 (system bus name :1.37, object path /org/freedesktop/Pol
{
	“ok” : 0,
	“errmsg” : “No host described in new configuration with {version: 1, term: 0} for replica set mongo_repl_set maps to this node”,
	“code” : 93,
	“codeName” : “InvalidReplicaSetConfig”
}

"mongo_repl_set 이라는 replica set의 설정에 있는 어떤 호스트도 현재 노드에 매핑이 되지 않는다." 라는 건데, rs.initiate() 설정의 _id 값과 mongod.confreplication.replSetName 이 서로 연동이 되지 않아서 발생하였다.
모든 mongod 설정의 replication.replSetName를 주석 해제하고 실행하였더니 정상적으로 연동되었다.

rs.status() 명령어를 입력한 후에 replica set 설정한 정보가 쭉 뜨면 성공이다!


테스트

Primary 에서 insert 후 secondary에서 read 테스트

먼저 primary 서버에 접속해서 테스트 DB, collection을 생성한 후 아무 document나 insert 해보겠다.

테스트 DB를 만들기 위해 use testdb 명령어를 입력한다.
이후 show dbs를 해봐도 방금 생성한 testdb 는 보이지 않는다. collection을 하나 이상 추가해야 보이므로, db.createCollection("testcollection") 명령어 수행 후 show dbs를 다시 해보면 아래와 같이 보인다.

db.testcollection.insert({"name":"수"}) 로 insert 를 해보고, db.testcollection.find()로 조회하면 primary 서버에서는 잘 보인다.

이제 secondary 서버에서 잘 조회되는지 확인해봐야 한다. secondary 서버에 접속하여 show dbs 명령어를 수행하면.. 아래와 같은 에러가 발생한다..

mongo_repl_set:SECONDARY> show dbs
uncaught exception: Error: listDatabases failed:{
	"topologyVersion" : {
		"processId" : ObjectId("6155714cd819dce17a0cda49"),
		"counter" : NumberLong(5)
	},
	"operationTime" : Timestamp(1633094786, 1),
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotPrimaryNoSecondaryOk",
	"$clusterTime" : {
		"clusterTime" : Timestamp(1633094786, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1

삽질 4. Secondary에서는 read operation이 막혀있다.

mongo shell에서 DB 조회를 하기 위해서는 rs.secondaryOk()를 실행해야 한다.

Secondary 서버에서 rs.secondaryOk() 수행 후 show dbs를 하면 아래와 같이 잘 보인다.


Fin!


다음 포스트에선 replica set에 security 설정을 추가할 계획이다.

0개의 댓글