本文为系列第三部分,使用第二部分创建好的userdb可以从公网访问,方法是通过Kubernetes Service暴露userdb pod到公网上。
整个系列:
- 安装MongoDB Ops Manager
- 创建用户数据库(replicaset)
- 用户数据库服务配置公网访问
- openssl生成自签名CA证书和server证书
- 打开用户数据库TLS通信加密和Auth授权
官方文档是通过NodePort的方式暴露给公网,而我们这里是通过创建service的方式完成,好处是这种方式不必管理pod和node之间的端口映射,也不必担心pod被调度到不同node上之后IP的改变。不知道官方使用NodePort进行服务暴露是基于怎样的考虑。
# Connect to a MongoDB Database Resource from Outside Kubernetes
在此之前,先保证replicaset中spec.security.tls.enabled为false,否则各server之间的auth可能会让配置变得复杂。
命令行创建LoadBalancer Service
1
2
3
4
5
6
| $ kubectl expose pod/userdb-0 --type="LoadBalancer" --port 27017 -n mongodb
service/userdb-0 exposed
$ kubectl expose pod/userdb-1 --type="LoadBalancer" --port 27017 -n mongodb
service/userdb-1 exposed
$ kubectl expose pod/userdb-2 --type="LoadBalancer" --port 27017 -n mongodb
service/userdb-2 exposed
|
可以在kubernetes dashboard看到我们创建了三个service: userdb-0, userdb-1, userdb-2,同时有三个公网IP。
这种暴露方式在pod更新之后可能会出现service匹配不到pod的情况,因为statefulset是用controller-revision-hash来标记pod的版本。所以要编辑一下这几个service中spec.selector部分,把controller-revision-hash一行去掉:
1
2
3
4
5
6
7
8
9
10
11
12
13
| ...
spec:
ports:
- protocol: TCP
port: 27017
targetPort: 27017
nodePort: 30560
selector:
app: userdb-svc
controller: mongodb-enterprise-operator
controller-revision-hash: userdb-676c9c444 # DELETE THIS LINE!
pod-anti-affinity: userdb
statefulset.kubernetes.io/pod-name: userdb-0
|
通过yaml创建Service
新建userdb0service.yaml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| apiVersion: v1
kind: Service
metadata:
name: userdb-0-svc-ext
namespace: mongodb
labels:
app: userdb-svc
controller: mongodb-enterprise-operator
pod-anti-affinity: userdb
statefulset.kubernetes.io/pod-name: userdb-0
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 27017
targetPort: 27017
selector:
app: userdb-svc
controller: mongodb-enterprise-operator
pod-anti-affinity: userdb
statefulset.kubernetes.io/pod-name: userdb-0
|
直接apply:
1
2
| $ kubectl apply -f userdb0service.yaml
service/userdb-0-svc-ext created
|
过一会儿,userdb-0-svc-ext就会被分配一个公网IP,然后可以在域名服务商那里将此IP绑定到域名。此处假设域名为userdb<X>.com。同理,我们将另外两个pod userdb1和userdb2也通过LoadBalancer暴露给公网。
客户端访问和getaddrinfo ENOTFOUND错误
至此,我们可以通过mongo shell来访问userdb了,查看下三个service的IP(以<ip0>``<ip1>``<ip2>代替):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| $ mongo "mongodb://<ip0>:27017,<ip1>:27017,<ip2>:27017/"
MongoDB shell version v4.4.2
connecting to: mongodb://<ip0>:27017,<ip1>:27017,<ip2>:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("6b780874-6ecb-4510-b7e9-61bff04a4711") }
MongoDB server version: 4.2.2
WARNING: shell and server versions do not match
---
The server generated these startup warnings when booting:
2020-12-10T08:32:10.037+0000 I STORAGE [initandlisten]
2020-12-10T08:32:10.037+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2020-12-10T08:32:10.037+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2020-12-10T08:32:10.848+0000 I CONTROL [initandlisten]
2020-12-10T08:32:10.848+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-12-10T08:32:10.848+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2020-12-10T08:32:10.848+0000 I CONTROL [initandlisten]
---
MongoDB Enterprise userdb:SECONDARY>
|
似乎有些问题,为什么connect的是SECONDARY节点?mongo默认的readPreference是primary。如果用MongoDB Compass连接,则会出现连接失败的问题,错误是:
1
| getaddrinfo ENOTFOUND userdb-0.userdb-svc.mongodb.svc.cluster.local
|
搜索了很久,这篇文章
# Connecting from external sources to MongoDB replica set in Kubernetes fails with getaddrinfo ENOTFOUND error but standalone works
解释了这种现象。
client会逐个连接connectionstring指定的这三个ip:port地址(种子列表)直到成功,然后使用isMaster确定这个节点他是否是master,并且得到replica set的成员列表。然后,client会弃用connectionstring中的种子列表,而使用获取的成员列表连接到每个成员。这个成员列表一般来说与rs.conf()命令获取的列表一致。而在Kubernetes集群中,获取的地址都是内网地址(xxx.svc.cluster.local),因此client连接不成功,会报错getaddrinfo ENOTFOUND。在登入的shell中运行rs.conf()印证了这一点:
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
| MongoDB Enterprise userdb:SECONDARY> rs.conf()
{
"_id" : "userdb",
"version" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "userdb-0.userdb-svc.mongodb.svc.cluster.local:27017",
...
},
{
"_id" : 1,
"host" : "userdb-1.userdb-svc.mongodb.svc.cluster.local:27017",
...
},
{
"_id" : 2,
"host" : "userdb-2.userdb-svc.mongodb.svc.cluster.local:27017",
...
}
],
"settings" : {
...
}
}
|
此外,从client的行为分析,Linux下的mongo shell和MongoDB Compass的实现行为不一致:mongo shell可以连接MongoDB成功,但可能显示为SECONDARY;而MongoDB Compass会报错,连接失败。
解决这个问题,需要在userdb.yaml中使用
spec.connectivity.replicaSetHorizons
指定公网访问的地址。而使用此选项又必须打开spec.security.tls:
1
2
3
4
5
6
7
8
| security:
tls:
enabled: true
connectivity:
replicaSetHorizons:
- "userdb": "userdb0.com:27017"
- "userdb": "userdb1.com:27017"
- "userdb": "userdb2.com:27017"
|
打开spec.security.tls比较麻烦,需要先生成每个server的证书,证书上绑定了自己的域名或IP,然后才能再更新userdb replicaset的yaml文件打开TLS。
我们先来生成MongoDB所需要的证书:
Kubernetes部署MongoDB集群(四):openssl生成自签名CA证书和server证书