سلام. در این قسمت میخوام راه اندازی replica set در mongo رو با هم بررسی کنیم. تا حالا همیشه از virtualbox برای شبیه سازی استفاده می کردم. اما در این مورد به مشکلی برخوردم که از نسخه 4 به بعد mongo امکان راه اندازی mongo رو نداشتم. مشکل مربوط به موردی در cpu میشد که بصورت زیر در راه اندازی سرویس mongo خودش رو نشون میداد:
Process: 1170 ExecStart=/usr/bin/mongod $OPTIONS (code=dumped, signal=ILL)
وقتی جستجو کردم متوجه شدم مربوطه به خصوصیتی در cpu با نام Advanced Vector Extensions یا به اختصار avx هست که یا هنوز نشده یا من موفق نشدم که در virtualbox حلش کنم. بنابراین مهاجرت کردم به vmware . برای اطلاعات بیشتر در این مورد میتونید به https://en.wikipedia.org/wiki/Advanced_Vector_Extensions مراجعه کنید.
سه ماشین مجازی نصب کردم (https://vahiddb.com/fa/os/os-infra/install-oracle-linux-8-9) و به هر کدام دو کارت شبکه اختصاص دادم. یکی از کارت شبکه ها رو nat کردم و دیگری رو hostonly قرار دادم . اولی برای اتصال به اینترنت و دومی برای اتصال از ماشین local و بین خودشون.
اگر از ایران اینکار رو انجام میدید، می تونید از سایت https://shecan.ir/ برای رفع مشکلات اتصال استفاده کنید.
برای تنظیمات شبکه در vmware اینکار رو انجام بدید. virtual network editor رو بصورت run as administrator اجرا کنید.
و برای کارت شبکه nat هم بصورت زیر:
و در تنظمیات ماشینها هم کارت شبکه ها رو در قسمت custom ، کارتهای مربوطه رو انتخاب کنید.
در داخل ماشین می تونید با استفاده از لینک زیر کارت شبکه host-only رو با استفاده از لینک زیر آدرس دهی کنید. حواسمون باشه که در رنج آدرسهای 192.168.188.0 می تونید آدرس دهی کنید.
https://vahiddb.com/fa/os/os-infra/set-ip-via-nmcli-on-just-one-network-card-fa
حالا می رویم برای کانفیگ repository ها. یک پوشه به اسم /cdrom می سازیم و بعد از اینکه دوباره iso را در نرم افزار vmware به ماشین کانکت کردیم، آنرا در سیستم عامل با دستور زیر mount می کنیم.
mount /dev/cdrom /cdrom/
سپس هر آن چیزی که در حال حاضر در مسیر etc/yum.repos.d هست را به پوشه backup انتقال می دهیم و repository های زیر را می سازیم:
[root@mongo1 yum.repos.d]# ls
backup media.repo mongodb-enterprise-7.0.repo
[root@mongo1 yum.repos.d]# cat media.repo
[dvd-BaseOS]
name=DVD for RHEL - BaseOS
baseurl=file:///cdrom/BaseOS
enabled=1
gpgcheck=0
[dvd-AppStream]
name=DVD for RHEL - AppStream
baseurl=file:///cdrom/AppStream
enabled=1
gpgcheck=0
[root@mongo1 yum.repos.d]# cat mongodb-enterprise-7.0.repo
[mongodb-enterprise-7.0]
name=MongoDB Enterprise Repository
baseurl=https://repo.mongodb.com/yum/redhat/8/mongodb-enterprise/7.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-7.0.asc
در تنظیمات هر سه سرور در سیستم عامل پورت مربوط به mongo رو وارد میکنیم.
firewall-cmd --add-port=27017/tcp --permanent
firewall-cmd --reload
همچنین selinux رو با تنظیمات زیر بصورت موقت و بعد با تغییر فایل بصورت دائم غیر فعال می کنیم.
setenforce 0
برای تغییر دائمی، فایل /etc/selinux/config
را ویرایش کنید:
SELINUX=permissive
بر روی هر کدوم از سرورها متناسب با آدرس و اسمی که در نظر گرفتیم تنظیم می کنیم.
در /etc/hosts هم نام سرورها و آدرسشون رو اضافه می کنیم:
hostnamectl set-hostname mongo1.vahiddb.com
[root@mongo1 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.188.11 mongo1 mongo1.vahiddb.com
192.168.188.12 mongo2 mongo2.vahiddb.com
192.168.188.13 mongo3 mongo3.vahiddb.com
سرورها رو ریست می کنیم و حالا می توانیم نصب را انجام دهیم. بعد از ریستارت، یکبار دیگه iso نصب رو به هر سه سرور از طریق vmware دوباره mount می کنیم و در سیستم عامل دستور mount /dev/cdrom/ /cdorm رو می زنیم. در صورتی که از ایران برای اینکار اقدام می کنید، به سایت https://shecan.ir/ مراجعه کنید و آدرس dns رو در قسمت /etc/resolve.conf وارد کنید.
dnf install -y mongodb-enterprise
...
Installed:
cyrus-sasl-2.1.27-6.el8_5.x86_64
cyrus-sasl-gssapi-2.1.27-6.el8_5.x86_64
cyrus-sasl-plain-2.1.27-6.el8_5.x86_64
mongodb-database-tools-100.10.0-1.x86_64
mongodb-enterprise-7.0.15-1.el8.x86_64
mongodb-enterprise-cryptd-7.0.15-1.el8.x86_64
mongodb-enterprise-database-7.0.15-1.el8.x86_64
mongodb-enterprise-database-tools-extra-7.0.15-1.el8.x86_64
mongodb-enterprise-mongos-7.0.15-1.el8.x86_64
mongodb-enterprise-server-7.0.15-1.el8.x86_64
mongodb-enterprise-tools-7.0.15-1.el8.x86_64
mongodb-mongosh-2.3.3-1.el8.x86_64
python3-pip-9.0.3-24.el8.noarch
python3-setuptools-39.2.0-7.el8.noarch
python36-3.6.8-39.module+el8.10.0+90274+07ba55de.x86_64
تمام اینکار ها رو روی هر سه ماشین انجام می دهیم.
در صورتی که در زمانی که سیستم عامل رو نصب می کردید، پوشه جدا گانه ای برای داده در نظر گرفته باشید، ابتدا ownership پوشه data را در سیسیتم عامل به mognod تغییر می دهیم:
[root@mongo1 ~]# chown -R mongod:mongod /data/
سپس با تغییر زیر در تنظیمات /etc/mongod.conf اون رو اعمال کنید:
storage:
dbPath: /data
حالا تنظیم bindip جایی که هر کدام از node ها رو میخواهیم تنظیم کنیم که روی چه آدرسی سرویس دهی کنند:
برای mongo1
:
net:
port: 27017
bindIp: 127.0.0.1,mongo1
برای mongo2
:
net:
port: 27017
bindIp: 127.0.0.1,mongo2
برای mongo3
:
net:
port: 27017
bindIp: 127.0.0.1,mongo3
همینطور در فایل کانفیگ نام replicaset را بصورت زیر وارد می کنیم:
replication:
replSetName: rs0
دقت کنید که با هر تغییری در فایل کانفیگ دستور زیر رو بزنید که مطمئن بشید که مشکلی نداشته باشه
mongosh --eval 'db.adminCommand({ getCmdLineOpts: 1 })'
سرویس مونگودیبی را روی هر سرور راهاندازی کنید:
systemctl start mongod
بعد از این قسمت حتما دستور زیر رو اجرا کنید که ببینید درست بالا اومدن یا نه
systemctl status mongod
اگر این قسمت failed نشون داد حتماً لاگ رو چک کنید و مشکل رو برطرف کنید.
/var/log/mongodb/mongod.log
نمونه فایل کانفیگ بعد از تنظیمات انجام شده:
[root@mongo1 ~]# cat /etc/mongod.conf
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# Where and how to store data.
storage:
dbPath: /data
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1,mongo1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
#security:
#operationProfiling:
replication:
replSetName: rs0
#sharding:
## Enterprise-Only Options
#auditLog:
حالا باید replicaset رو با متصل شدن به یکی از سرورها فعال کنیم:
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017" },
{ _id: 1, host: "mongo2:27017" },
{ _id: 2, host: "mongo3:27017" }
]
})
با زدن دستور rs.status() متوجه وضعیت سرور می شویم.
Enterprise rs0 [direct: other] test> rs.status()
{
set: 'rs0',
...
},
lastStableRecoveryTimestamp: Timestamp({ t: 1732361077, i: 1 }),
members: [
{
_id: 0,
name: 'mongo1:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
...
},
{
_id: 1,
name: 'mongo2:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
...
},
{
_id: 2,
name: 'mongo3:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
...
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1732361077, i: 1 }),
signature: {
hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
keyId: Long('0')
}
},
operationTime: Timestamp({ t: 1732361077, i: 1 })
}
Enterprise rs0 [direct: secondary] test>
برای افزایش امنیت دو کار انجام می دهیم. یک کاربر ادمین بصورت زیر ایجاد می کنیم. و همچنین با توجه به اینکه authentication را اجباری می کنیم، برای ارتباط node ها با همدیگر، باید یک keyfile ایجاد کنیم. کاربرد keyfile :
-
امنیت ارتباط بین اعضای Replica Set:
- در حالت پیشفرض (بدون keyFile)، MongoDB فرض میکند که تمام اعضا در یک محیط امن هستند و نیازی به احراز هویت داخلی نیست. اما وقتی احراز هویت (authentication) فعال میشود، اعضای Replica Set برای ارتباط داخلی نیاز به تأیید اعتبار دارند.
-
فعالسازی احراز هویت داخلی:
- با فعال کردن authentication در MongoDB (پارامتر
auth: true
) و تنظیمkeyFile
، تمامی اعضای Replica Set باید از کلید مشترک برای تعامل استفاده کنند. این باعث میشود که اعضای غیرمجاز نتوانند به Replica Set ملحق شوند.
- با فعال کردن authentication در MongoDB (پارامتر
-
پشتیبانی از امنیت بالا:
keyFile
اطمینان میدهد که Replica Set فقط با اعضای قابل اعتماد کار میکند. این مخصوصاً زمانی که Replica Set در محیطهای غیرامن یا شبکههای عمومی اجرا میشود، بسیار حیاتی است.
با mongosh متصل می شویم :
use admin;
db.createUser({
user: "root",
pwd: "mySecurePass",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" },
{ role: "clusterAdmin", db: "admin" }
]
});
سرویس mongo را stop می کنیم
systemctl stop mongod
همجنین در کانفیگ هم الزام استفاده از پسورد را اضافه میکنیم:
security:
authorization: "enabled"
keyFile: /etc/mongo-keyfile
حالا باید یک فایل keyfile برای احراز هویت بین نودها ایجاد کنیم و در هر سه سرو کپی کنیم.
openssl rand -base64 756 > /etc/mongo-keyfile
chmod 400 /etc/mongo-keyfile
chown mongod:mongod /etc/mongo-keyfile
scp /etc/mongo-keyfile mongo2:/etc/
scp /etc/mongo-keyfile mongo3:/etc/
بعد از انتقال دستور زیر را در هر کدام از سرورهای 2و 3 می زنیم تا مالکیت فایلها به mongo انتقال یابد.
chmod 400 /etc/mongo-keyfile
chown mongod:mongod /etc/mongo-keyfile
سرویس mongod را start می کنیم.
systemctl start mongod
با نام کاربری و پسوردی که تعریف کردیم وصل میشیم.
mongosh --host 192.168.188.11 --port 27017 -u "root" -p "mySecurePass" --authenticationDatabase "admin"
مشکلاتی که در حین نصب باهاش مواجه شدم اینها بود:
مشکل 1: خطای SocketException
راهحل: اطمینان حاصل کنید که آدرسهای IP در تنظیمات bindIp
موجود و صحیح هستند.
مشکل 2: خطای احراز هویت SCRAM authentication failed
راهحل: بررسی کنید که فایل keyFile
در هر سرور یکسان و با دسترسی مناسب باشد.
حالا برای اینکه ببینیم همه چی درست هست یا نه دوباره به یکی از نودها وصل می شویم و دستور زیر را می زنیم:
[root@mongo2 ~]# mongosh --host 192.168.188.11 --port 27017 -u "root" -p "shahrtech" --authenticationDatabase "admin"
Current Mongosh Log ID: 67344f62a2965befb2c1c18b
Connecting to: mongodb://<credentials>@192.168.188.11:27017/?directConnection=true&authSource=admin&appName=mongosh+2.3.3
Using MongoDB: 7.0.15
Using Mongosh: 2.3.3
For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/
------
The server generated these startup warnings when booting
2024-11-13T01:24:11.603-05:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never' in this binary version
2024-11-13T01:24:11.603-05:00: vm.max_map_count is too low
------
Enterprise rs0 [direct: secondary] test> rs.status()
{
set: 'rs0',
date: ISODate('2024-11-13T07:04:08.314Z'),
myState: 2,
term: Long('7'),
syncSourceHost: 'mongo2:27017',
syncSourceId: 1,
heartbeatIntervalMillis: Long('2000'),
majorityVoteCount: 2,
writeMajorityCount: 2,
votingMembersCount: 3,
writableVotingMembersCount: 3,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
lastCommittedWallTime: ISODate('2024-11-13T07:04:04.512Z'),
readConcernMajorityOpTime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
appliedOpTime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
durableOpTime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
lastAppliedWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastDurableWallTime: ISODate('2024-11-13T07:04:04.512Z')
},
lastStableRecoveryTimestamp: Timestamp({ t: 1731417971, i: 1 }),
members: [
{
_id: 0,
name: 'mongo1:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 2400,
optime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
optimeDate: ISODate('2024-11-13T07:04:04.000Z'),
lastAppliedWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastDurableWallTime: ISODate('2024-11-13T07:04:04.512Z'),
syncSourceHost: 'mongo2:27017',
syncSourceId: 1,
infoMessage: '',
configVersion: 1,
configTerm: 7,
self: true,
lastHeartbeatMessage: ''
},
{
_id: 1,
name: 'mongo2:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 14,
optime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
optimeDurable: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
optimeDate: ISODate('2024-11-13T07:04:04.000Z'),
optimeDurableDate: ISODate('2024-11-13T07:04:04.000Z'),
lastAppliedWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastDurableWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastHeartbeat: ISODate('2024-11-13T07:04:07.531Z'),
lastHeartbeatRecv: ISODate('2024-11-13T07:04:07.046Z'),
pingMs: Long('0'),
lastHeartbeatMessage: '',
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1731481434, i: 1 }),
electionDate: ISODate('2024-11-13T07:03:54.000Z'),
configVersion: 1,
configTerm: 7
},
{
_id: 2,
name: 'mongo3:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 14,
optime: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
optimeDurable: { ts: Timestamp({ t: 1731481444, i: 1 }), t: Long('7') },
optimeDate: ISODate('2024-11-13T07:04:04.000Z'),
optimeDurableDate: ISODate('2024-11-13T07:04:04.000Z'),
lastAppliedWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastDurableWallTime: ISODate('2024-11-13T07:04:04.512Z'),
lastHeartbeat: ISODate('2024-11-13T07:04:07.531Z'),
lastHeartbeatRecv: ISODate('2024-11-13T07:04:07.531Z'),
pingMs: Long('0'),
lastHeartbeatMessage: '',
syncSourceHost: 'mongo2:27017',
syncSourceId: 1,
infoMessage: '',
configVersion: 1,
configTerm: 7
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1731481444, i: 1 }),
signature: {
hash: Binary.createFromBase64('5fdxvqSMElKHHKp0X7c8WYFLfO8=', 0),
keyId: Long('7436308128641056774')
}
},
operationTime: Timestamp({ t: 1731481444, i: 1 })
}