Mongodb高可用模式

mongodb的部署方式有:

Standalone单节点部署

此种部署方式就是最简单易用并且常见的部署了,直接使用mongod起来一个进程。

Master-Slave主从结构

主从架构一般用于备份或者做读写分离。一般有一主一从设计和一主多从设计。

  • 主(Master)

可读可写,当数据有修改的时候,会将oplog同步到所有连接的salve上去。

  • 从(Slave)

只读不可写,自动从Master同步数据。

特别的,对于Mongodb来说,并不推荐使用Master-Slave架构,因为Master-Slave其中Master宕机后不能自动恢复,推荐使用Replica Set,后面会有介绍,除非Replica的节点数超过50,才需要使用Master-Slave架构,正常情况是不可能用那么多节点的。

还有一点,Master-Slave不支持链式结构,Slave只能直接连接Master。Redis的Master-Slave支持链式结构,Slave可以连接Slave,成为Slave的Slave。

Relica Set副本集

Mongodb的Replica Set即副本集方式主要有两个目的,一个是数据冗余做故障恢复使用,当发生硬件故障或者其它原因造成的宕机时,可以使用副本进行恢复。

另一个是做读写分离,读的请求分流到副本上,减轻主(Primary)的读压力。

Primary和Secondary搭建的Replica Set

Replica Set是mongod的实例集合,它们有着同样的数据内容。包含三类角色:

  • 主节点(Primary)

接收所有的写请求,然后把修改同步到所有Secondary。一个Replica Set只能有一个Primary节点,当Primary挂掉后,其他Secondary或者Arbiter节点会重新选举出来一个主节点。默认读请求也是发到Primary节点处理的,需要转发到Secondary需要客户端修改一下连接配置。

  • 副本节点(Secondary)

与主节点保持同样的数据集。当主节点挂掉的时候,参与选主。

  • 仲裁者(Arbiter)

不保有数据,不参与选主,只进行选主投票。使用Arbiter可以减轻数据存储的硬件需求,Arbiter跑起来几乎没什么大的硬件资源需求,但重要的一点是,在生产环境下它和其他数据节点不要部署在同一台机器上。

注意,一个自动failover的Replica Set节点数必须为奇数,目的是选主投票的时候要有一个大多数才能进行选主决策。

  • 选主过程

其中Secondary宕机,不受影响,若Primary宕机,会进行重新选主

使用Arbiter搭建Replica Set

偶数个数据节点,加一个Arbiter构成的Replica Set方式

  • shard Cluster数据分片

当数据量比较大的时候,我们需要把数据分片运行在不同的机器中,以降低CPU、内存和IO的压力,Sharding就是数据库分片技术。

MongoDB分片技术类似MySQL的水平切分和垂直切分,数据库主要由两种方式做Sharding:垂直扩展和横向切分。

垂直扩展的方式就是进行集群扩展,添加更多的CPU,内存,磁盘空间等。

横向切分则是通过数据分片的方式,通过集群统一提供服务:

MongoDB的Sharding架构

MongoDB分片架构中的角色

  • 数据分片(Shards)

用来保存数据,保证数据的高可用性和一致性。可以是一个单独的mongod实例,也可以是一个副本集。

在生产环境下Shard一般是一个Replica Set,以防止该数据片的单点故障。所有Shard中有一个PrimaryShard,里面包含未进行划分的数据集合:

  • 查询路由(Query Routers)

路由就是mongos的实例,客户端直接连接mongos,由mongos把读写请求路由到指定的Shard上去。

一个Sharding集群,可以有一个mongos,也可以有多mongos以减轻客户端请求的压力。

  • 配置服务器(Config servers)

保存集群的元数据(metadata),包含各个Shard的路由规则。

安装部署单节点Mongo

创建 NFS 存储

NFS 存储主要是为了给 MongoDB 提供稳定的后端存储,当 MongoDBPod 发生故障重启或迁移后,依然能获得原先的数据。

记得保证所有可部署pod的节点都要安装nfs服务端/客户端

mongo默认配置(小记)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
#security:
#authorization: enabled
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options:
#auditLog:
#snmp:

部署 MongoDB 应用服务

(1)首先创建一个名为 mongodb.yaml 的配置文件,文件里的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 创建PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
namespace: middleware
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-mongodb
nfs:
path: /data/k8s
server: 10.1.4.13
---
# 创建pvc
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mongodb-pvc
namespace: middleware
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-mongodb
---
# 创建Service
apiVersion: v1
kind: Service
metadata:
name: mongodb-svc
namespace: middleware
spec:
type: NodePort
ports:
- name: mongo
port: 27017
targetPort: 27017
nodePort: 30017
protocol: TCP
selector:
app: mongodb
---
# 创建Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-deploy
namespace: middleware
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:4.4.13
imagePullPolicy: IfNotPresent
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-pvc
mountPath: /data/db
subPath: mongo-data
- name: mongo-config
mountPath: /etc/mongod.conf
subPath: mongo-data
volumes:
- name: mongo-pvc
persistentVolumeClaim:
claimName: mongodb-pvc
- name: mongo-config
persistentVolumeClaim:
claimName: mongodb-pvc

(2)接着执行如下命令对这个 YAML 文件进行部署:

1
kubectl apply -f mongodb.yaml

(3)稍等片刻,执行如下命令可以查看是否创建成功:

1
2
3
kubectl get pv 
kubectl get pods
kubectl get service

访问测试

(1)我们使用客户端工具连接上我们刚刚创建的 MongoDB,然后创建一个 hangge 数据库以及 test 集合,并给集合中插入一些数据,说明 MongoDB 部署成功。

(2)接着执行如下命令强制重启 pod,重启后再次查看数据库可以发现数据没有丢失,说明数据持久化也是成功的。

1
kubectl replace --force -f mongodb.yaml

安装部署Mongo - Relica Set副本集(3节点)

Headless Service

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: mongodb-rs
namespace: middleware
spec:
publishNotReadyAddresses: false
clusterIP: None
selector:
app: mongodb-rs

ConfigMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-rs-cm
namespace: middleware
data:
keyfile: |
dGhpcyBpcyBycyBzdXBlciBzZWNyZXQga2V5Cg==
mongod_rs.conf: |+
systemLog:
destination: file
logAppend: true
path: /data/mongod.log
storage:
dbPath: /data
journal:
enabled: true
directoryPerDB: true
wiredTiger:
engineConfig:
cacheSizeGB: 2
directoryForIndexes: true
processManagement:
fork: true
pidFilePath: /data/mongod.pid
net:
port: 27017
bindIp: 0.0.0.0
maxIncomingConnections: 5000
security:
keyFile: /data/configdb/keyfile
authorization: enabled
replication:
oplogSizeMB: 1024
replSetName: rs_prod

PV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
apiVersion: v1
kind: PersistentVolume
metadata:
finalizers:
- kubernetes.io/pv-protection
labels:
mongo-pvname: mongodb-pv0
name: mongodb-pv0
namespace: middleware
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: mongodb-rs-storage
nfs:
path: /data/k8s/mongo-data0
server: 10.1.4.13
readOnly: false
---
apiVersion: v1
kind: PersistentVolume
metadata:
finalizers:
- kubernetes.io/pv-protection
labels:
mongo-pvname: mongodb-pv1
name: mongodb-pv1
namespace: middleware
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: mongodb-rs-storage
nfs:
path: /data/k8s/mongo-data1
server: 10.1.4.13
readOnly: false
---
apiVersion: v1
kind: PersistentVolume
metadata:
finalizers:
- kubernetes.io/pv-protection
labels:
mongo-pvname: mongodb-pv2
name: mongodb-pv2
namespace: middleware
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: mongodb-rs-storage
nfs:
path: /data/k8s/mongo-data2
server: 10.1.4.13
readOnly: false

Statefulset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb-rs
namespace: middleware
spec:
serviceName: mongodb-rs
replicas: 3
selector:
matchLabels:
app: mongodb-rs
template:
metadata:
labels:
app: mongodb-rs
spec:
containers:
- name: mongo
image: mongo:4.4.13
ports:
- containerPort: 27017
name: client
command: ["sh"]
args:
- "-c"
- |
set -ex
mongod --config /data/configdb/mongod_rs.conf
sleep infinity
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
resources:
limits:
cpu: '2'
memory: 2000Mi
volumeMounts:
- name: conf
mountPath: /data/configdb
readOnly: false
- name: data
mountPath: /data
readOnly: false
volumes:
- name: conf
configMap:
name: mongodb-rs-cm
defaultMode: 0600
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
storageClassName: mongodb-rs-storage

执行rs的初始化操作

上面已经完成了rs的部署,其实是通过StatefulSet建立了三个pod内运行mongod服务,此时三个mongod是没有任何联系的,需要进行一些设置

1
2
3
4
5
6
7
8
9
10
mongo

config = { _id:"rs_prod", members:[
{_id:0,host:"mongodb-rs-0.mongodb-rs.middleware.svc.cluster.local:27017",priority:90},
{_id:1,host:"mongodb-rs-1.mongodb-rs.middleware.svc.cluster.local:27017",priority:80},
{_id:2,host:"mongodb-rs-2.mongodb-rs.middleware.svc.cluster.local:27017",priority:70}
]
}

rs.initiate(config);

读写

  • PRIMARY

默认情况下,PRIMARY节点有读写权限,SECONDARY节点没有读写权限。

  • SECONDARY

SECONDARY节点可以成功使用账户密码登录,而进行查询时(报错NotMasterNoSlaveOk)提示不是master并且slaveOk也没打开。 此时使用rs.secondaryOk();可以临时打开读权限。

问题1:MongoSocketException: mongodb-rs-0.mongodb-rs.middleware.svc.cluster.local

解决1:

参考:https://stackoverflow.com/questions/60413876/kubernetes-mongodb-connection-issue-from-java

I have found the problem, when you deploy a repSet, you must launch this command:

1
2
3
4
5
rs.initiate({_id: "MainRepSet", version: 1, members: [
{ _id: 0, host : "ip1:27017" },
{ _id: 1, host : "ip2:27017" }
{ _id: 2, host : "ip3:27017" }
]})

And I launched this command with the following hostanmes:

1
2
3
mongod-0.mongodb-service.default.svc.cluster.local;
mongod-1.mongodb-service.default.svc.cluster.local;
mongod-2.mongodb-service.default.svc.cluster.local

解决2:

配合read_preference=pymongo.ReadPreference.PRIMARY, directConnection=True(DirectConnection类)解决

如下:

1
2
3
4
REPLICASET_URL = 'mongodb://root:root@xxx:27017/admin?replicaSet=rs_prod&slaveOk=true&connectTimeoutMS=3000&socketTimeoutMS=3000&maxPoolSize=1'

# 获取mongoclient
client = MongoClient(REPLICASET_URL, read_preference=pymongo.ReadPreference.PRIMARY, directConnection=True)

问题2:rs.status(): “Our replica set config is invalid or we are not a member of it

解决:

使用rs.status()查看下各host是否正确,不正确的话重新配置下

1
2
3
4
5
6
7
8
9
reConfig = { _id:"rs_prod", members:[
{_id:0,host:"mongodb-rs-0.mongodb-rs.middleware.svc.cluster.local:27017",priority:90},
{_id:1,host:"mongodb-rs-1.mongodb-rs.middleware.svc.cluster.local:27017",priority:80},
{_id:2,host:"mongodb-rs-2.mongodb-rs.middleware.svc.cluster.local:27017",priority:70}
]
}

# force -> 非主节点强制请求重新分配主节点
rs.reconfig(reConfig, { force:true })

附:开启用户权限认证

如果是集群部署,记得连接到primarty节点

rs.isMaster() #rs主节点判断

(1)注意如果之前已经使用没有鉴权的方式部署过 MongoDB,并且做了持久化,那么再改动 YMAL 文件重新部署是不会起作用的。这种情况我们可以进入执行如下命令进入容器:

1
kubectl -n middleware exec -it xxx /bin/bash

(2)进入 mongodb 客户端:

1
mongo

(3)执行如下命令创建用户即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use admin

# 创建root账户
db.createUser({"user":"root","pwd":"EIEU#@(#234werq","roles":["root"]})

# 登录root
db.auth("root","EIEU#@(#234werq")

# 创建admin账户
db.createUser({user:"admin", pwd:"EIEU#@(#234werq", roles:[{role: "userAdminAnyDatabase", db:"admin" }]})

# 创建日常使用账户
db.createUser(
{
user: "mongouser",
pwd: "EIEU#@(#234werq",
roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
}
)

# 补充 - 删除用户
use {账号数据所在的数据库}
db.dropUser('python')