Kubernetes部署MongoDB集群(三):用户数据库服务配置公网访问

本文为系列第三部分,使用第二部分创建好的userdb可以从公网访问,方法是通过Kubernetes Service暴露userdb pod到公网上。

整个系列:

  1. 安装MongoDB Ops Manager
  2. 创建用户数据库(replicaset)
  3. 用户数据库服务配置公网访问
  4. openssl生成自签名CA证书和server证书
  5. 打开用户数据库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

$ 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一行去掉:

...
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文件:

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:

$ kubectl apply -f userdb0service.yaml
service/userdb-0-svc-ext created
过一会儿,userdb-0-svc-ext就会被分配一个公网IP,然后可以在域名服务商那里将此IP绑定到域名。此处假设域名为userdb<X>.com。同理,我们将另外两个pod userdb1userdb2也通过LoadBalancer暴露给公网。

客户端访问和getaddrinfo ENOTFOUND错误

至此,我们可以通过mongo shell来访问userdb了,查看下三个service的IP(以<ip0>``<ip1>``<ip2>代替):

$ 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连接,则会出现连接失败的问题,错误是:
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()印证了这一点:

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

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证书