憨豆说安全 · 2020年08月10日

云芯一号教程 - MongoDB Replicaset集群搭建教程

  MongoDB是一个跨平台的,面向文档的数据库,是当前NoSQL数据库产品中最热门的一种,是一种介于关系数据库和非关系数据库之间的产品。它支持的数据结构非常松散,类似json格式,可以存储比较复杂的数据类型。
  Mongodb的集群模式有三种:Master-Slaver模式、Replica Set模式、sharding模式,其中Replica set应用最为广泛。
  Replica Set是一组MongoDB实例组成的集群,由一个主(Primary)节点和多个备份(Secondary)节点构成。通过Replication,由Primary将更新的数据同步到其他实例上,这样每个MongoDB实例都有相同的数据集副本。当主节点崩溃,备份节点会自动将其中权重最高的成员升级为新的主节点;当集群为偶数台时,特别是一主一从架构时,还需要加入一个仲裁(Arbiter)节点,参与升级投票。Replica Set操作中一般读写数据都是在主(Primary)节点上,需要手动指定读库的备份(Secondary)节点,从而实现负载均衡。
  Replica Set通过维护冗余的数据库副本,能够实现数据的异地备份,读写分离和自动故障转移。
  下面我们搭建MongoDB一主一从架构的Replica Set集群。

1.配置多实例
  Replica Set需要3个实例,我们来配置MongoDB的多实例。
1.1 创建3个实例目录

  jishu@Jishu:~$ mkdir -p mongodb/mongo1/db
  jishu@Jishu:~$ mkdir -p mongodb/mongo2/db
  jishu@Jishu:~$ mkdir -p mongodb/mongo3/db

1.2 创建3个实例的配置文件
  在mongodb/mongo1目录下创建配置文件mongod.conf。

  systemLog:
     destination: file
     path: "/home/jishu/mongodb/mongo1/mongod.log"
     logAppend: true
  storage:
     dbPath: /home/jishu/mongodb/mongo1/db
     journal:
        enabled: true
  processManagement:
     fork: true
  security:
     authorization: disabled
  net:
     bindIp: 127.0.0.1
     port: 27017
  setParameter:
     enableLocalhostAuthBypass: false
  replication:
     replSetName: test

  在mongodb/mongo2目录下创建配置文件mongod.conf。

  systemLog:
     destination: file
     path: "/home/jishu/mongodb/mongo2/mongod.log"
     logAppend: true
  storage:
     dbPath: /home/jishu/mongodb/mongo2/db
     journal:
        enabled: true
  processManagement:
     fork: true
  security:
     authorization: disabled
  net:
     bindIp: 127.0.0.1
     port: 27018
  setParameter:
     enableLocalhostAuthBypass: false
  replication:
     replSetName: test

  在mongodb/mongo3目录下创建配置文件mongod.conf。

  systemLog:
     destination: file
     path: "/home/jishu/mongodb/mongo3/mongod.log"
     logAppend: true
  storage:
     dbPath: /home/jishu/mongodb/mongo3/db
     journal:
        enabled: true
  processManagement:
     fork: true
  security:
     authorization: disabled
  net:
     bindIp: 127.0.0.1
     port: 27019
  setParameter:
     enableLocalhostAuthBypass: false
  replication:
     replSetName: test

1.2 启动3个实例

  jishu@Jishu:~/mongodb$ sudo mongod --config mongo1/mongod.conf 
  about to fork child process, waiting until server is ready for       connections.
  forked process: 1415
  child process started successfully, parent exiting
  jishu@Jishu:~/mongodb$ sudo mongod --config mongo2/mongod.conf 
  about to fork child process, waiting until server is ready for connections.
  forked process: 1497
  child process started successfully, parent exiting
  jishu@Jishu:~/mongodb$ sudo mongod --config mongo3/mongod.conf 
  about to fork child process, waiting until server is ready for connections.
  forked process: 1542
  child process started successfully, parent exiting
  jishu@Jishu:~/mongodb$ sudo ps -A | grep "mongod"
   1415 ?        00:00:01 mongod
   1497 ?        00:00:00 mongod
   1542 ?        00:00:00 mongod

  3个实例启动完成。

2.配置ReplicaSet集群
  参照ReplicaSet集群的角色,把mongo1作为主(Primary)节点,mongo2作为备份(Secondary)节点,mongo3作为仲裁(Arbiter)节点。
  集群需要在主(Primary)节点上进行。
  连接主(Primary)节点的MongoDB:

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017
  MongoDB shell version v4.2.6
  connecting to: mongodb://127.0.0.1:27017/test?      compressors=disabled&gssapiServiceName=mongodb
  ......
  To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
  ---
  
  > 

  配置集群参数:

  > use admin
  switched to db admin
  > cfg={ _id:"test", members:[ {_id:0,host:'127.0.0.1:27017',priority:2}, {_id:1,host:'127.0.0.1:27018',priority:1}, {_id:2,host:'127.0.0.1:27019',arbiterOnly:true}] };
  {
          "_id" : "test",
          "members" : [
                  {
                          "_id" : 0,
                          "host" : "127.0.0.1:27017",
                          "priority" : 2
                  },
                  {
                          "_id" : 1,
                          "host" : "127.0.0.1:27018",
                          "priority" : 1
                  },
                  {
                          "_id" : 2,
                          "host" : "127.0.0.1:27019",
                          "arbiterOnly" : true
                  }
          ]
  }
  > rs.initiate(cfg)
  {
          "ok" : 1,
          "$clusterTime" : {
                  "clusterTime" : Timestamp(1590067297, 1),
                  "signature" : {
                          "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                          "keyId" : NumberLong(0)
                  }
          },
          "operationTime" : Timestamp(1590067297, 1)
  }

  备注:
  集群配置的cfg名字可自定义,只要跟mongodb参数不冲突,_id参数为Replica Set名字,members里面的优先级priority值高的为主(Primary)节点,对于仲裁(Arbiter)点一定要加上arbiterOnly:true,否则主备模式不生效。
  集群cfg配置生效:rs.initiate(cfg)

  集群配置生效后,我们查看一下集群状态:rs.status()

  test:PRIMARY> rs.status()
  {
          "set" : "test",
          "date" : ISODate("2020-05-21T13:24:42.872Z"),
          "myState" : 1,
          "term" : NumberLong(1),
          "syncingTo" : "",
          "syncSourceHost" : "",
          "syncSourceId" : -1,
          "heartbeatIntervalMillis" : NumberLong(2000),
          "majorityVoteCount" : 2,
          "writeMajorityCount" : 2,
          ......
          "members" : [
                  {
                          "_id" : 0,
                          "name" : "127.0.0.1:27017",
                          "health" : 1,
                          "state" : 1,
                          "stateStr" : "PRIMARY",
                          "uptime" : 1458,
                           ......
                  },
                  {
                          "_id" : 1,
                          "name" : "127.0.0.1:27018",
                          "health" : 1,
                          "state" : 2,
                          "stateStr" : "SECONDARY",
                          "uptime" : 185,
                          ......
                  },
                  {
                          "_id" : 2,
                          "name" : "127.0.0.1:27019",
                          "health" : 1,
                          "state" : 7,
                          "stateStr" : "ARBITER",
                          "uptime" : 185,
                          .....
                  }
          ],
          "ok" : 1,
          "$clusterTime" : {
                  "clusterTime" : Timestamp(1590067478, 1),
                  "signature" : {
                          "hash" :       BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                          "keyId" : NumberLong(0)
                  }
          },
          "operationTime" : Timestamp(1590067478, 1)
  }

  可以看到,集群状态正常,其中"stateStr" : "PRIMARY"表示主(Primary)节点, "stateStr" : "SECONDARY"表示备份(Secondary)节点, "stateStr" : "ARBITER,表示仲裁(Arbiter)节点。

2.5 集群验证
  集群搭建好了,接下来我们用停掉/重启主(Primary)节点的方法来看一下集群是否能正常切换主备节点。
  直接kill掉主(Primary)节点mongo1的进程,然后连接mongo2的备份(Secondary)节点,查询集群状态。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27018
  MongoDB shell version v4.2.6
  ......
  To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
  ---
  
  test:PRIMARY> rs.status()
  {
          "set" : "test",
          "date" : ISODate("2020-05-21T13:34:35.681Z"),
          "myState" : 1,
          "term" : NumberLong(2),
          "syncingTo" : "",
          "syncSourceHost" : "",
          "syncSourceId" : -1,
          "heartbeatIntervalMillis" : NumberLong(2000),
          "majorityVoteCount" : 2,
          "writeMajorityCount" : 2,
          ......
          "members" : [
                  {
                          "_id" : 0,
                          "name" : "127.0.0.1:27017",
                          "health" : 0,
                          "state" : 8,
                          "stateStr" : "(not reachable/healthy)",
                          "uptime" : 0,
                          ......
                  },
                  {
                          "_id" : 1,
                          "name" : "127.0.0.1:27018",
                          "health" : 1,
                          "state" : 1,
                          "stateStr" : "PRIMARY",
                          "uptime" : 2030,
                          ......
                  },
                  {
                          "_id" : 2,
                          "name" : "127.0.0.1:27019",
                          "health" : 1,
                          "state" : 7,
                          "stateStr" : "ARBITER",
                          "uptime" : 778,
                           ......
                  }
          ],
          "ok" : 1,
          "$clusterTime" : {
                  "clusterTime" : Timestamp(1590068075, 1),
                  "signature" : {
                          "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                          "keyId" : NumberLong(0)
                  }
          },
          "operationTime" : Timestamp(1590068075, 1)
  }

  可以看到,集群中原先的主(Primary)节点状态变为"stateStr" : "(not reachable/healthy)", 原先的备份(Secondary)节点状态变为"stateStr" : "PRIMARY",变为新的主(Primary)节点。
  重启主(Primary)节点后,再查询集群状态,又恢复成原先的状态。
  从上述验证中,可以看到集群的主备节点切换正常。

2.6 配置keyfile
  为了保证集群之间的安全性,需要增加KeyFile安全认证机制。

  使用keyfile认证,副本集中的每个mongod实例使用keyfile内容认证其他成员。只有有正确的keyfile的mongod实例可以加入副本集。keyfile的内容必须是6到1024个字符的长度,且副本集所有成员的keyfile内容必须相同。

  开启keyfile认证会默认开启auth认证,需要用户的auth认证才能连接MongoDB数据库,所以还需要创建用户。

  在集群中先创建用户。

  test:PRIMARY> use admin
  switched to db admin
  #创建数据库管理员
  test:PRIMARY> db.createUser({user:"admin",pwd:"admin",roles:[{role:"readWriteAnyDatabase",db:"admin"},{role:"dbAdminAnyDatabase",db:"admin"},{role:"userAdminAnyDatabase",db:"admin"}]})
  #创建集群管理员
  test:PRIMARY> db.createUser({user:"cluster",pwd:"cluster",roles:[{role:"clusterAdmin",db:"admin"},{role:"clusterManager",db:"admin"},{role:"clusterMonitor",db:"admin"}]})

  用户创建好之后,关闭主备节点和arbiter节点的服务,可以直接kill掉进程。

  创建keyfile文件,并修改权限。

  jishu@Jishu:~/mongodb$ sudo openssl rand -base64 128 > ./mongodb.key
  jishu@Jishu:~/mongodb$ sudo chmod 600 ./mongodb.key

  修改3个实例的配置文件,添加认证机制。

  security:
     authorization: enabled
     keyFile: /home/jishu/mongodb/mongodb.key

  重新启动3个实例。

  重新连接主(Primary)节点。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin
  MongoDB shell version v4.2.6
  connecting to: mongodb://127.0.0.1:27017/admin?      compressors=disabled&gssapiServiceName=mongodb
  Implicit session: session { "id" : UUID("188cd807-0a9d-46f1-bab9-c485c4e7e919") }
  MongoDB server version: 4.2.6
  test:PRIMARY> rs.status()
  {
          "operationTime" : Timestamp(1590072034, 1),
          "ok" : 0,
          "errmsg" : "command replSetGetStatus requires authentication",
          "code" : 13,
          "codeName" : "Unauthorized",
          "$clusterTime" : {


                  "clusterTime" : Timestamp(1590072034, 1),
                  "signature" : {
                          "hash" : BinData(0,"sUrYE7ATx1FC8rBeWBKkOEY61wk="),
                          "keyId" : NumberLong("6829287086298759171")
                  }
          }
  }

  如果不进行用户认证连接MongoDB,将无法查看集群状态。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u admin -p admin
  MongoDB shell version v4.2.6
  connecting to: mongodb://127.0.0.1:27017/admin?      compressors=disabled&gssapiServiceName=mongodb
  Implicit session: session { "id" : UUID("188cd807-0a9d-46f1-bab9-c485c4e7e919") }
  MongoDB server version: 4.2.6
  test:PRIMARY> rs.status()
  {
          "operationTime" : Timestamp(1590074047, 1),
          "ok" : 0,
          "errmsg" : "command replSetGetStatus requires authentication",
          "code" : 13,
          "codeName" : "Unauthorized",
          "$clusterTime" : {
                  "clusterTime" : Timestamp(1590074047, 1),
                  "signature" : {
                          "hash" : BinData(0,"sUrYE7ATx1FC8rBeWBKkOEY61wk="),
                          "keyId" : NumberLong("6829287086298759171")
                  }
          }
  }               

  使用admin账号,也无法查看集群状态。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u cluster -p cluster
  MongoDB shell version v4.2.6
  connecting to: mongodb://127.0.0.1:27017/admin?compressors=disabled&gssapiServiceName=mongodb
  ......
  
  test:PRIMARY> rs.status()
  {
          "set" : "test",
          "date" : ISODate("2020-05-21T15:12:19.580Z"),
          "myState" : 1,
          "term" : NumberLong(9),
          "syncingTo" : "",
          ......
  }

  使用cluster账号,可以正常参看集群状态。查看集群状态,需要clusterMonitor权限。如果需要用其它用户账号来查看,需要给账号授权。

  db.grantRolesToUser(id, ["clusterAdmin"])

  至此,MongoDB的副本集搭建完成。

3.副本集验证
  我们在主(Primary)节点插入数据来验证功能。
  根据账号的权限,cluster账号只能用于管理集群,无法修改数据。插入数据时需要用admin账号登录。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u admin -p admin
  MongoDB shell version v4.2.6
  ......
  test:PRIMARY> use test
  switched to db test
  #创建集合
  test:PRIMARY> db.createCollection("ttcollection")
  {
          "ok" : 1,
          "$clusterTime" : {
                 "clusterTime" : Timestamp(1590132227, 1),
                  "signature" : {
                          "hash" : BinData(0,"U/atfhk+a6gmBUKuwktlR7f1kv0="),
                          "keyId" : NumberLong("6829287086298759171")
                  }
          },
          "operationTime" : Timestamp(1590132227, 1)
  }
  #插入测试数据
  test:PRIMARY> db.ttcollection.insert({title: 'MongoDB 教程',description: 'MongoDB 是一个 Nosql 数据库',tags: ['mongodb', 'database', 'NoSQL']})
  WriteResult({ "nInserted" : 1 })

  到备份(Secondary)节点查看数据是否同步成功。

  jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27018/admin  -u admin -p admin
  MongoDB shell version v4.2.6
  ......
  test:SECONDARY> use test
  switched to db test
  test:SECONDARY> show tables;
  2020-05-22T15:20:55.534+0800 E  QUERY    [js] uncaught exception: Error: listCollections failed: {
          "operationTime" : Timestamp(1590132053, 1),
          "ok" : 0,
          "errmsg" : "not master and slaveOk=false",
          "code" : 13435,
          "codeName" : "NotMasterNoSlaveOk",
          ......
          }
  }

  出现上面的错误是因为备份(Secondary)节点不允许读写,需要用命令开启。

  test:SECONDARY> rs.slaveOk()
  test:SECONDARY> show tables
  ttcollection
  test:SECONDARY> db.ttcollection.find()
  { "_id" : ObjectId("5ec77f0535251ff83c7cc399"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "tags" : [ "mongodb", "database", "NoSQL" ] }

  数据同步成功。

推荐阅读
关注数
4271
内容数
71
低成本Arm微服务器开发平台“云芯1号”教程及应用,欢迎关注
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息