如何在Ubuntu 20.04上配置MongoDB副本集

本教程的早期版本是由Justin Ellingwood编写的。

简介

MongoDB,也被称为_Mongo_,是一个面向文档的数据库,用于许多现代网络应用。它被归类为NoSQL数据库,因为它不依赖于传统的基于表的关系数据库结构。相反,它使用类似于JSON的文档,具有动态模式。这意味着,与关系型数据库不同,MongoDB在向数据库添加数据之前不需要预定义模式。

在使用数据库时,拥有多个数据副本往往是有用的。这在其中一个数据库服务器发生故障的情况下提供了冗余,可以提高数据库的可用性和可扩展性,并减少读取延迟。在多个独立的数据库中同步数据的做法被称为_复制_。在MongoDB中,通过复制维护相同数据集的一组服务器被称为_复制集_。

本教程简要介绍了复制在MongoDB中的工作原理,并概述了如何配置和启动一个有三个成员的复制集。在这个例子的配置中,复制集的每个成员将是一个独立的MongoDB实例,分别运行在Ubuntu 20.04服务器上。

注意:请注意,本指南中概述的程序旨在演示如何快速建立和运行一个副本。完成本教程后,你将拥有一个正常运行的副本集,但它不会启用任何安全功能。这种设置不建议用于生产环境

MongoDB的社区版本带有两种认证方法,可以帮助保持数据库的安全,即_密钥文件认证_和_x.509认证_。对于采用复制的生产部署,MongoDB文档建议使用x.509认证,它将密钥文件描述为 “最低限度的安全形式”,”最适合于测试或开发环境。”然而,获得和配置x.509证书的过程有许多注意事项和决定,必须在个案的基础上进行,这超出了DigitalOcean教程的范围。

如果你打算使用你的副本集进行测试或开发,我们强烈建议你遵循我们的教程:如何配置Ubuntu 20.04上MongoDB副本集的密钥文件认证

先决条件

要完成本指南,你将需要。

  • 三台服务器,分别运行Ubuntu 20.04。这三台服务器都应该有一个非root管理用户和一个配置了UFW的防火墙。要设置这些,请遵循我们的Ubuntu 20.04的初始服务器设置指南
  • 在你的每台Ubuntu服务器上安装MongoDB。为此,请遵循我们的教程:如何在Ubuntu 20.04上安装MongoDB,确保在你的三个服务器上完成每一步。

请注意,为清晰起见,本指南将把三台服务器称为mongo0mongo1mongo2。任何显示在mongo0上执行的命令或文件变化的例子都会有一个蓝色背景,就像这样。

mongo1上执行的命令和文件更改将有一个粉红色的背景。

mongo2上执行的例子将有一个绿色背景。

最后,在每台服务器上必须运行的命令或必须进行的文件修改将有一个标准的灰色背景,像这样。

了解MongoDB复制集

正如在介绍中提到的,MongoDB通过一种叫做_复制集_的实现来处理复制。属于某个复制集的MongoDB的每个运行实例都被称为其_成员_之一_。_每个复制集必须有一个_主要_成员和至少一个_辅助_成员。

主要成员是与副本集交易的主要访问点,是唯一可以接受写操作的成员。每个复制集一次只能有一个主要成员,因为复制是通过复制主要成员的OPL_OG(_”操作日志 “的缩写)并在第二成员各自的数据集上重复记录的变化而发生的。多个主站接受写操作会导致数据冲突。

默认情况下,应用程序将只查询主成员的读和写操作。你可以将你的设置配置为从一个或多个辅助成员中读取,但由于数据是异步传输的,从辅助节点读取可能导致旧数据被提供。因此,这样的配置并不是每个用例的理想选择。

MongoDB的复制集区别于其他复制实现方式的一个特点是其自动故障转移机制。在主要成员不可用的情况下,次要节点之间会发生一个自动选举过程,以选择一个新的主要成员。一个复制集最多可以有50个成员,但在选举中最多只能有7个成员投票。

然而,如果二级成员池包含偶数的节点,可能会导致由于投票僵局而无法选出一个新的主要成员。这就需要在副本集中加入第三类成员:仲裁员。仲裁者是副本集的一个可选的成员,在这种情况下投票,以确保副本集能够达成决定。但要注意的是,仲裁者没有数据集的副本,他们被禁止成为复制集的主要成员。如果一个复制集只有一个二级成员,那么就需要一个仲裁者。

有些时候,你可能不希望所有的次级成员都遵循副本集次级成员的标准规则。MongoDB允许你配置副本集的次级成员来承担以下非标准的角色。

  • 优先级为0的复制成员。在某些情况下,将某些集合成员选为主要位置会对你的应用程序的性能产生负面影响。例如,如果你正在将数据复制到一个远程数据中心,或者某个次级成员的硬件不足以让它作为该集的主要访问点,将其优先级设置为0 ,可以确保该成员不会成为主要成员,但可以继续复制数据。
  • 隐藏的复制成员。有些情况需要你保持一组成员对客户的访问和可见,同时隐藏有独立用途的后台成员,不应该用于读取操作。例如,你可能需要一个二级成员作为分析工作的基础,这将受益于一个最新的数据集,但会对工作成员造成压力。通过将这个成员设置为隐藏,它将不会干扰复制集的一般操作。隐藏成员必须被设置为优先级为0 ,以避免成为主要成员,但他们可以在选举中投票。
  • 延迟复制的成员。通过为次要成员设置延迟选项,你可以控制次要成员等待多长时间来执行它从主要成员的OPLOG中复制的每个动作。如果你想防止意外删除或从破坏性的操作中恢复,这很有用。例如,如果你把一个次级成员延迟半天,它就不会立即对自己的数据集进行意外操作,可以用来恢复变化。延迟的成员不能成为主要成员,但可以在选举中投票。在大多数情况下,它们也应该被隐藏,以防止应用程序读取过时的数据。

第1步 – 配置DNS解析

当在步骤4中初始化副本集的时候,你需要提供一个地址,在这个地址中每个副本集的成员都可以被其他两个副本集的成员联系到。MongoDB文档建议在配置副本集时不要使用IP地址,因为IP地址会意外改变。相反,MongoDB建议在配置副本集时使用逻辑DNS主机名。

一种方法是为每个复制成员配置子域。尽管配置子域对于生产环境或其他长期解决方案来说是理想的,但本教程将概述如何通过编辑每个服务器各自的hosts 文件来配置DNS解析。

hosts 是一个特殊的文件,它允许你将人类可读的主机名分配给数字的IP地址。这意味着,如果你的任何服务器的IP地址发生变化,你只需要更新三个服务器上的hosts 文件,而不是重新配置副本集。

在Linux和其他类似Unix的系统中,hosts 被存储在/etc/ 目录中。在你的三个服务器上,用你喜欢的文本编辑器编辑该文件。这里,我们将使用nano

sudo nano /etc/hosts
复制代码

在配置本地主机的前几行之后,为复制集的每个成员添加一个条目。这些条目的形式是一个IP地址,后面是你选择的可读名称,如本例中。

/etc/hosts

IP_address   any_hostname
复制代码

你可以将你的服务器配置成你想要的任何主机名,但使每个主机名具有描述性可能会有帮助。在本指南的例子中,三个服务器将使用这些主机名。

  • mongo0.replset.member
  • mongo1.replset.member
  • mongo2.replset.member

使用这些主机名,你的/etc/hosts 文件将看起来类似于以下突出显示的行。

/etc/hosts

. . .
127.0.0.1 localhost

203.0.113.0 mongo0.replset.member
203.0.113.1 mongo1.replset.member
203.0.113.2 mongo2.replset.member
. . .
复制代码

如果你不知道你的服务器的IP地址,你可以在每台服务器上运行下面的curl 命令来检索它们。icanhazip.com 是一个网站,显示用于访问它的任何计算机的IP地址。通过提供它的URL作为curl 命令的参数,该命令将把你运行它的服务器的IP地址打印到标准输出。

curl -4 icanhazip.com
复制代码

如果你使用DigitalOcean Droplets,你也可以在控制面板中找到你的服务器的IP地址

你在这里添加的新行在你的三个主机上都应该是相同的。保存并关闭你每个服务器上的文件。如果你用nano 来编辑这些文件,按CTRL + XY ,然后按ENTER

在编辑、保存和关闭每个服务器上的hosts 文件后,你就完成了对复制集的DNS解析配置。现在你可以继续更新每个服务器的防火墙规则,以允许它们相互通信。

第2步 – 用UFW更新每个服务器的防火墙配置

假设你遵循先决条件的初始服务器设置指南,你将在每个安装了MongoDB的服务器上设置一个防火墙,并为OpenSSH UFW配置文件启用访问权限。这是一项重要的安全措施,因为这些防火墙目前阻止了对你服务器上任何端口的连接,除了ssh ,这些连接提出的密钥与每个服务器各自的authorized_keys 文件中的密钥一致。

然而,这些防火墙也会阻止每台服务器上的MongoDB实例相互通信,使你无法启动复制集。为了纠正这个问题,你需要添加新的防火墙规则,允许每台服务器访问其他两台服务器上的端口,而MongoDB正在监听这些端口的连接。

mongo0上,运行以下ufw 命令,允许mongo1访问mongo0上的端口27017

sudo ufw allow from mongo1_server_ip to any port 27017
复制代码

请务必改变 mogno1_server_ip以反映你的mongo1服务器的实际 IP 地址。请注意,ufw 命令将不会与hosts 文件中配置的主机名一起工作,所以请确保在这个命令和下面的命令中使用你的服务器的实际 IP 地址。另外,如果你已经更新了该服务器上的Mongo实例,使其使用非默认的端口,请确保改变27017 ,以反映你的MongoDB实例实际使用的端口。

然后添加另一个防火墙规则,让mongo2访问同一端口。

sudo ufw allow from mongo2_server_ip to any port 27017
复制代码

接下来,更新其他两个服务器的防火墙规则。在mongo1上运行以下命令,确保改变IP地址,以分别反映mongo0mongo2的IP地址。

sudo ufw allow from mongo0_server_ip to any port 27017
sudo ufw allow from mongo2_server_ip to any port 27017
复制代码

最后,在mongo2上运行这两条命令。同样,要确保你为每台服务器输入正确的IP地址。

sudo ufw allow from mongo0_server_ip to any port 27017
sudo ufw allow from mongo1_server_ip to any port 27017
复制代码

在添加这些UFW规则后,你的三个MongoDB服务器中的每一个都将被允许访问其他两个服务器上的MongoDB所使用的端口。然而,你还不能测试这一点,因为每台服务器上的Mongo实例目前都阻止了任何外部连接。在下一步中通过更新每个MongoDB实例的配置文件来启用复制后,你就可以进行这个测试了。

第3步 – 在每个服务器的MongoDB配置文件中启用复制功能

在这一点上,你已经编辑了你的服务器的/etc/hosts 文件,以配置主机名,这些主机名将解析到每个服务器的 IP 地址。你还打开了每个服务器的防火墙,允许另外两个服务器访问默认的MongoDB端口,27107 。现在你准备开始配置每台服务器上的MongoDB安装,以启用复制。

本步骤概述了如何通过编辑MongoDB的配置文件(/etc/mongod.conf )来做到这一点。你必须在每台服务器上完成本步骤中的每一个程序,但为了演示,我们将在例子中使用mongo0

mongo0上,用你喜欢的文本编辑器打开MongoDB的配置文件。

sudo nano /etc/mongod.conf
复制代码

尽管你已经打开了每台服务器的防火墙,允许其他服务器访问端口27017 ,但MongoDB目前被绑定到127.0.0.1 ,即本地回环网络接口。这意味着MongoDB只能接受来自安装它的服务器的连接。

为了允许远程连接,除了127.0.0.1 ,你必须将MongoDB绑定到你的服务器的可公开路由的IP地址。这样,你的MongoDB安装将能够监听来自远程机器对你的MongoDB服务器的连接。

找到network interfaces 部分。默认情况下,它看起来像这样。

/etc/mongod.conf

. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1
. . .
复制代码

在以bindIp: 开始的行中添加一个逗号,后面是mongo0的主机名或公共IP地址。本例使用步骤1中配置的主机名。

/etc/mongod.conf

. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo0.replset.member
. . .
复制代码

接下来,在文件底部找到写着#replication: 的那一行。它将看起来像这样。

/etc/mongod.conf

. . .
#replication:
. . . 
复制代码

去掉这一行的磅符号(#),取消注释。然后在这行下面添加一个replSetName 指令,后面跟一个MongoDB用来识别副本集的名字。

/etc/mongod.conf

. . .
replication:
  replSetName: "rs0"
. . . 
复制代码

在这个例子中,replSetName 指令的值是"rs0" 。你可以在这里提供任何你想要的名字,但是使用一个描述性的名字可能会有帮助。但请记住,每台服务器的mongod.conf 文件必须在replSetName 指令后有相同的名称,以便它们的MongoDB实例成为同一复制集的成员。

请注意,在replSetName 指令前有两个空格,并且该名称被引号包裹("),这两点对于该配置被正确读取是必要的。

在更新文件的这两部分后,netreplication ,它们将看起来像这样。

/etc/mongod.conf

. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo0.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 
复制代码

保存并关闭该文件。然后对mongo1mongo2上的/etc/mongod.conf 文件做同样的修改。这样做之后,在mongo1的配置文件中,这些更新的部分将看起来像这样。

/etc/mongod.conf

. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo1.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 
复制代码

下面是这些部分在mongo2的配置文件中的样子。

/etc/mongod.conf

. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo2.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 
复制代码

重申一下,你在每个服务器的bindIp 指令中添加的IP地址或主机名必须是你正在编辑的服务器的mongod.conf 文件。

在对每个服务器的mongod.conf 文件进行这些修改后,保存并关闭每个文件。然后,通过发出以下命令在每台服务器上重新启动mongod 服务。

sudo systemctl restart mongod
复制代码

这样,你就为每台服务器的MongoDB实例启用了复制功能。

注意:在这一点上,你可以使用nc 命令来测试你在步骤2中添加的防火墙规则是否正确。nc ,_netcat_的简称,是一个用来建立TCP或UDP网络连接的工具。在这种情况下,它对测试很有用,因为它允许你在建立连接时同时指定一个IP地址和一个端口号。

下面的例子nc 命令包括-z 选项,该选项限制该工具只扫描目标服务器上的一个监听守护程序,而不向其发送任何数据。回顾一下先决条件的安装教程,MongoDB是作为一个服务守护程序运行的,因此这个选项对于测试连接性很有用。它还包括v 选项,该选项增加了命令的粗略性,导致它比其他方式返回更多的信息。

这个例子nc ,显示了试图从mongo0到达mongo1的情况。

nc -zv mongo1.replset.member 27017
复制代码

下面的输出表明,mongo0能够在MongoDB使用的端口上到达mongo1

OutputConnection to mongo1.replset.member 27017 port [tcp/*] succeeded!
复制代码

你可以通过在每台服务器上重复这个命令并指定适当的主机名或IP地址来测试每对服务器之间的连接。

在编辑每个服务器的mongod.conf 文件以启用复制并重新启动mongod 服务后,你就可以启动复制集并将每个Mongo实例添加为成员了。

第4步 – 启动复制集并添加成员

现在你已经配置了三个MongoDB的安装,你可以打开MongoDB的外壳来启动复制,并将每个成员添加为成员。

出于演示目的,本步骤中的例子将使用mongo0上的MongoDB实例来启动复制集。然而,你可以从任何服务器上启动复制,这些服务器的mongod.conf 文件已经被适当配置。

mongo0上,打开MongoDB shell。

mongo
复制代码

从提示符中,你可以通过运行rs.initiate() 方法从mongo shell中启动一个复制集。然而,运行这个方法本身只会为你运行这个方法的机器启动复制,然后你需要通过为每个成员发布rs.add() 方法来添加你的其他Mongo实例。

回顾一下,MongoDB将其数据存储在类似JSON的结构中,称为_文档_。因为你已经在你的每台服务器上编辑了mongod.conf 文件来配置三个Mongo实例的复制,你可以在rs.initiate 方法中包含一个保存每个成员的配置细节的文档。这将允许你启动复制集并一次性添加每个成员,而不是运行多个单独的方法。

要做到这一点,通过输入以下内容并按ENTER ,开始一个rs.initiate() 方法。

rs.initiate(
复制代码

在你输入结束括号之前,Mongo不会将rs.initiate 方法注册为完成。在你这样做之前,提示将从一个大于号(> )变成一个省略号(... )。

与JSON中的对象一样,MongoDB中的文档以大括号开始和结束({})。要开始添加副本集的配置文档,请输入一个开头的大括号。

{
复制代码

MongoDB文档是由任意数量的_字段和值_对组成的,其形式为 field: value.这个特定文档的第一个字段和值对必须是一个_id: 字段,该字段提供了一个用于识别副本集的名称;该字段的值必须与你在mongod.conf 文件中设置的replSetName 指令相同,在我们的例子中是"rs0"

输入这个字段和值对,后面加一个逗号,然后按ENTER ,开始一个新行。

_id: "rs0",
复制代码

接下来,添加一个members: 字段。不过,在这个members: 字段之后,要用一个包含多个文档的数组来代替一个单一的值,每个数组代表一个要添加的复制集成员。在MongoDB文档中,数组总是被放在一对方括号内([] )。

添加members: 字段,后面有一个开头的方括号来开始数组,然后按ENTER 来移动到下一行。

members: [
复制代码

现在添加一个有两个字段和值对的文档,用逗号隔开,代表复制集的第一个成员。这个文档的第一个字段是另一个_id: 字段,它接受一个用于内部识别成员的整数。第二个是host: 字段,后面必须有一个包含主机名的字符串,该主机名将被解析为可以到达成员Mongo实例的一个地址。

{ _id: 0, host: "mongo0.replset.member" },
复制代码

注意:如果你的任何Mongo实例运行在MongoDB默认的端口–27017 ,你必须在主机名后面加上冒号(:),然后是端口号,如本例。

{ _id: 0, host: "mongo0.replset.member:27018" },
复制代码

在输入第一个文件后,为复制集的其他成员输入其他文件。确保每个文件用逗号分开。

{ _id: 1, host: "mongo1.replset.member" },
{ _id: 2, host: "mongo2.replset.member" }
复制代码

接下来,通过输入一个方括号结束阵列。

]
复制代码

最后,用一个结尾的大括号结束配置文件,然后用一个结尾的小括号结束方法。

})
复制代码

所有这些,rs.initiate() 方法将看起来像这样。

> rs.initiate(
... {
... _id: "rs0",
... members: [
... { _id: 0, host: "mongo0.replset.member" },
... { _id: 1, host: "mongo1.replset.member" },
... { _id: 2, host: "mongo2.replset.member" }
... ]
... })
复制代码

假设你正确输入了所有的细节,一旦你在输入结束括号后按下ENTER ,该方法将运行并启动复制集。如果该方法在输出中返回"ok" : 1 ,这意味着复制集被正确启动。

Output{
    "ok" : 1,
    "$clusterTime" : {
        "clusterTime" : Timestamp(1612389071, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    },
    "operationTime" : Timestamp(1612389071, 1)
}
复制代码

如果复制集如期启动,你会注意到MongoDB客户端的提示将从只有一个大于号(> )变为以下内容。

MongoDB安装了一些内置的方法,你可以用它们来管理和检索复制集的信息。在这些方法中,rs.help() 方法特别有帮助,因为它可以返回这些复制集方法的列表以及对它们所做工作的描述。

rs.help()
复制代码
Output    rs.status()                                { replSetGetStatus : 1 } checks repl set status
    rs.initiate()                              { replSetInitiate : null } initiates set with default settings
    rs.initiate(cfg)                           { replSetInitiate : cfg } initiates set with configuration cfg
    rs.conf()                                  get the current configuration object from local.system.replset
    rs.reconfig(cfg)                           updates the configuration of a running replica set with cfg (disconnects)
    rs.add(hostportstr)                        add a new member to the set with default attributes (disconnects)
    rs.add(membercfgobj)                       add a new member to the set with extra attributes (disconnects)
    rs.addArb(hostportstr)                     add a new member which is arbiterOnly:true (disconnects)
    rs.stepDown([stepdownSecs, catchUpSecs])   step down as primary (disconnects)
    rs.syncFrom(hostportstr)                   make a secondary sync from the given member
    rs.freeze(secs)                            make a node ineligible to become primary for the time specified
    rs.remove(hostportstr)                     remove a host from the replica set (disconnects)
    rs.secondaryOk()                               allow queries on secondary nodes

    rs.printReplicationInfo()                  check oplog size and time range
    rs.printSecondaryReplicationInfo()             check replica set members and replication lag
    db.isMaster()                              check who is primary
    db.hello()                              check who is primary

    reconfiguration helpers disconnect from the database so the shell will display
    an error, even if the command succeeds.
复制代码

在运行rs.help() 或这些方法中的另一个之后,你可能会看到客户端的提示又变成了以下内容。

这意味着你所连接的MongoDB实例被选为主集成员。

请注意,如果你有额外的节点,你想在未来添加到复制集,你可以在配置它们之后用rs.add() ,就像你在前面的步骤中配置当前复制集成员一样。

rs.add( "mongo3.replset.member" )
复制代码

现在你可以通过按CTRL + C 或运行exit 命令来关闭MongoDB客户端。

exit
复制代码

你的复制集现在已经启动并运行了,你可以开始将它与你的应用程序集成。

警告。当你打开MongoDB提示来启动复制集时,你可能已经注意到一个类似于这样的警告信息。

. . .
        2021-02-03T21:45:48.379+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
. . .
复制代码

这条信息表明你还没有为你的数据库启用访问控制。根据MongoDB的文档。

MongoDB使用基于角色的访问控制(RBAC)来管理对MongoDB系统的访问。一个用户被授予一个或多个角色,这些角色决定了该用户对数据库资源和操作的访问。

因为访问控制没有在你的任何MongoDB实例上启用,任何对复制集中的三台服务器有访问权的人也可以获得对该服务器上Mongo实例的访问权。这构成了一个重要的安全风险,因为这意味着他们也可以获得对你的应用程序数据的访问。

消除这种警告并为你的复制集增加一层安全的方法是配置_密钥文件认证_。正如介绍中提到的,MongoDB文档将密钥文件描述为 “最低限度的安全形式”,”最适合于测试或开发环境”。

请注意,对于生产部署,MongoDB文档反而建议使用x.509证书进行内部成员认证。获取和配置x.509证书的过程有许多注意事项和决定,必须在个案的基础上做出,这超出了本教程的范围。

如果你打算将副本集用于测试或开发,我们强烈建议你遵循我们的教程:如何在Ubuntu 20.04上配置MongoDB副本集的密钥文件认证

结论

数据库复制作为一种提高性能、可用性和数据安全性的策略已被广泛使用,以至于人们建议在生产环境中使用的任何数据库都要启用某种形式的复制。复制也是通用的,可以在数据架构中扮演许多不同的角色,如报告或灾难恢复。在MongoDB的复制集中发现的自动故障转移功能使其特别有价值,有助于确保你的数据在发生故障时保持高度可用。

如果你想了解更多关于MongoDB的信息,我们鼓励你查看我们整个MongoDB教程集。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享