Running MongoDB in Kubernetes: ReplicaSet HA solutions

104 阅读31分钟

Prerequisites

image.png

Deployment By Manifest

Preparation

IP AddressHostnameOS ReleaseRemark
10.2.102.241k8s-masterDebian 11.x主节点,负责管理集群
10.2.102.242k8s-node1Debian 11.x工作节点1,运行应用的 Pod
10.2.102.243k8s-node2Debian 11.x工作节点2,运行应用的 Pod
10.2.102.250nfs-serverDebian 11.xNFS 服务器,为集群提供持久化存储服务

Deploy NFS

部署 NFS 服务端

# 安装 NFS 服务端软件
root@localhost:~# apt-get install -y nfs-kernel-server

# 创建三个共享文件夹
root@localhost:~# mkdir -p /mnt/k8s/mongo/pv{1..3}

# 更改目录权限
root@localhost:~# chmod 777 /mnt/k8s/mongo/pv{1..3}

# 编辑 /etc/exports 文件
root@localhost:~# cat >> /etc/exports <<EOF
/mnt/k8s/mongo/pv1 10.2.102.0/24(rw,sync,no_subtree_check,no_root_squash)
/mnt/k8s/mongo/pv2 10.2.102.0/24(rw,sync,no_subtree_check,no_root_squash)
/mnt/k8s/mongo/pv3 10.2.102.0/24(rw,sync,no_subtree_check,no_root_squash)
EOF

# 应用新的设置
root@localhost:~# exportfs -a

# 重启 NFS 服务
root@localhost:~# systemctl restart nfs-kernel-server

# 检查可用的共享资源
root@localhost:~# exportfs -v
/mnt/k8s/mongo/pv1
                10.2.102.0/24(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/mnt/k8s/mongo/pv2
                10.2.102.0/24(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/mnt/k8s/mongo/pv3
                10.2.102.0/24(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)

参考挂载选项

选项描述
ro只读模式。客户端只可读取共享文件,不能进行写操作。
rw读写模式。客户端可以读取和写入共享文件。
sync同步模式。NFS 服务器将等待所有的写操作都写入磁盘才返回结果,保证了数据的一致性。
async异步模式。NFS 服务器在接收到写请求后立即返回结果,不等待数据写入磁盘,可能会导致数据不一致。
no_root_squash取消 Root Squash 。NFS 服务器将客户端的 root 用户视为本地的 root 用户。
root_squash启用 Root Squash。NFS 服务器将客户端的 root 用户视为名为 nobody 的普通用户。
subtree_check子目录检查。NFS 服务器将检查客户端对某个子目录的请求是否也在共享目录下。
no_subtree_check取消子目录检查。NFS 服务器不将进行子目录检查,有助于提高性能。

验证 NFS 客户端

在各节点安装 NFS 客户端,并进行本地测试:

# 安装 NFS 客户端软件
root@k8s-master:~# apt-get install -y nfs-common

# 查看 NFS 服务端可用于共享的目录
root@k8s-master:~# showmount -e 10.2.102.250
Export list for 10.2.102.250:
/mnt/k8s/mongo/pv3 10.2.102.0/24
/mnt/k8s/mongo/pv2 10.2.102.0/24
/mnt/k8s/mongo/pv1 10.2.102.0/24

# 尝试挂载一个共享目录到本地
root@k8s-master:~# mount -t nfs 10.2.102.250:/mnt/k8s/mongo/pv1 /mnt
Created symlink /run/systemd/system/remote-fs.target.wants/rpc-statd.service → /lib/systemd/system/rpc-statd.service.

# 检查已挂载的本地文件系统
root@k8s-master:~# df -h /mnt
Filesystem                       Size  Used Avail Use% Mounted on
10.2.102.250:/mnt/k8s/mongo/pv1   64G  4.8G   59G   8% /mnt

# 测试共享目录的读写权限
root@k8s-master:~# cd /mnt && touch test && rm test && cd ~

# 卸载文件系统
root@k8s-master:~# umount /mnt

Deploy K8S

K8S 集群搭建过程略,检查集群节点状态及信息:

# 查看集群节点信息
root@k8s-master:~# kubectl get nodes -o wide
NAME         STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION   CONTAINER-RUNTIME
k8s-master   Ready    control-plane   44d   v1.28.2   10.2.102.241   <none>        Debian GNU/Linux 12 (bookworm)   6.1.0-7-amd64    containerd://1.7.8
k8s-node1    Ready    <none>          44d   v1.28.2   10.2.102.242   <none>        Debian GNU/Linux 12 (bookworm)   6.1.0-7-amd64    containerd://1.7.8
k8s-node2    Ready    <none>          44d   v1.28.2   10.2.102.243   <none>        Debian GNU/Linux 12 (bookworm)   6.1.0-7-amd64    containerd://1.7.8

# 查看所有的命名空间
root@k8s-master:~# kubectl get ns
NAME               STATUS   AGE
calico-apiserver   Active   44d
calico-system      Active   44d
default            Active   44d
kube-node-lease    Active   44d
kube-public        Active   44d
kube-system        Active   44d
test               Active   39d
tigera-operator    Active   44d

# 在每个节点上添加主机名和IP地址映射
root@k8s-master:~# cat >> /etc/hosts <<EOF
10.2.102.241    k8s-master
10.2.102.242    k8s-node1
10.2.102.243    k8s-node2
10.2.102.250    nfs-server
EOF

Deploy ReplicaSet

  1. 创建mongo-replica 命名空间
# 创建命名空间
root@k8s-master:~# kubectl create ns mongo-replica
namespace/mongo-replica created

# 查看相关信息
root@k8s-master:~# kubectl describe ns mongo-replica
Name:         mongo-replica
Labels:       kubernetes.io/metadata.name=mongo-replica
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.
  1. 编写pv-mongo-repl.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-mongo-repl-1                       # 资源名称
  labels:
    app: mongo                                # 为资源添加标签,方便管理
spec:
  capacity:
    storage: 10Gi                             # 存储容量(注意:虽然 K8S 允许你声明超过实际存储量的存储容量,但为了避免可能的问题,建议你总是确保声明的存储容量不超过 NFS 服务器实际的可用空间)
  accessModes:
    - ReadWriteOnce                           # 存储访问模式,ReadWriteOnce 表示支持单个节点进行读写操作
  storageClassName: mongo-storage             # 存储类,PVC 所需 matched storageClass 才能绑定该 PV
  nfs:
    server: nfs-server                        # NFS 服务器的主机名或 IP 地址,注意确保该地址可以被所有 Kubernetes 集群节点解析
    path: /mnt/k8s/mongo/pv1                  # NFS 服务器上的共享目录路径
  persistentVolumeReclaimPolicy: Retain       # 取回策略,Retain 表示在 PVC 被删除后,对应的 PV 仍然保留,需要手动清理

---

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-mongo-repl-2
  labels:
    app: mongo
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: mongo-storage
  nfs:
    server: nfs-server
    path: /mnt/k8s/mongo/pv2
  persistentVolumeReclaimPolicy: Retain

---

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-mongo-repl-3
  labels:
    app: mongo
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: mongo-storage
  nfs:
    server: nfs-server
    path: /mnt/k8s/mongo/pv3
  persistentVolumeReclaimPolicy: Retain
  1. 创建pv持久卷
# 创建 PersistentVolume 资源
root@k8s-master:~/test/mongo-repl# kubectl apply -f pv-mongo-repl.yaml
persistentvolume/pv-mongo-repl-1 created
persistentvolume/pv-mongo-repl-2 created
persistentvolume/pv-mongo-repl-3 created

# 查看 PersistentVolume 的状态
root@k8s-master:~/test/mongo-repl# kubectl get pv
NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS      REASON   AGE
pv-mongo-repl-1   10Gi       RWO            Retain           Available           mongo-storage             102s
pv-mongo-repl-2   10Gi       RWO            Retain           Available           mongo-storage             102s
pv-mongo-repl-3   10Gi       RWO            Retain           Available           mongo-storage             102s

# Tips: 当有 PVC 去申请使用这些存储后,你可以在 NFS 服务器上看到这些挂载点了
  1. 编写sts-mongo-repl.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo                                 # 定义 StatefulSet 的名称
  namespace: mongo-replica                    # 定义使用的 Kubernetes 命名空间
spec:
  serviceName: mongo                          # 定义 Headless Service 的名称,用于服务发现
  replicas: 3                                 # 定义副本数量
  selector:
    matchLabels:
      app: mongo                              # 匹配 Pod 的标签,用于确定 StatefulSet 管理哪些 Pod
  template:
    metadata:
      labels:
        app: mongo                            # 在 Pod 模板中定义的标签,用于匹配 StatefulSet 的 selector
    spec:
      terminationGracePeriodSeconds: 10       # 定义 Pod 终止的优雅期限
      containers:
        - name: mongo
          image: mongo:4.0                    # 定义 MongoDB 容器使用的镜像版本
          imagePullPolicy: IfNotPresent
          command:
            - mongod
            - "--bind_ip_all"                 # 监听所有网络接口
            - "--replSet"
            - rs0                             # 启用 MongoDB 副本集,并指定副本集的名称
          ports:
            - containerPort: 27017            # 定义容器暴露的端口
          volumeMounts:
            - name: mongo-volume
              mountPath: /data/db             # 定义 MongoDB 数据存储的挂载路径
        - name: mongo-sidecar
          image: cvallance/mongo-k8s-sidecar  # 这个 sidecar 容器可以帮助在 MongoDB Pod 成功启动后,自动配置 MongoDB 的副本集
          env:
            - name: MONGO_SIDECAR_POD_LABELS
              value: "app=mongo"
  volumeClaimTemplates:
    - metadata:
        name: mongo-volume                    # 定义持久卷声明(PersistentVolumeClaim)的名称
        labels:
          app: mongo
      spec:
        accessModes: [ "ReadWriteOnce" ]      # 定义持久卷声明的访问模式
        storageClassName: mongo-storage       # 定义存储类的名称,用于动态分配存储
        resources:
          requests:
            storage: 10Gi                     # 定义请求的存储容量

---

apiVersion: v1
kind: Service
metadata:
  name: mongo-headless-svc                    # 定义 Headless Service 的名称
  namespace: mongo-replica                    # 定义使用的 Kubernetes 命名空间
  labels:
    app: mongo
spec:
  ports:
  - port: 27017
    targetPort: 27017
  clusterIP: None                             # 定义 Headless Service,clusterIP 设置为 None,表示没有 Cluster IP
  selector:
    app: mongo                                # 匹配 StatefulSet 管理的 Pod 的标签
  1. 编写svc-lb-mongo-repl.yaml
apiVersion: v1
kind: Service
metadata:
  name: mongo-lb
  namespace: mongo-replica
  annotations:
    metallb.universe.tf/address-pool: ip-pool
spec:
  ports:
  - port: 27017
    targetPort: 27017
  selector:
    app: mongo
  type: LoadBalancer
  1. 运行 MongoDB 服务,检查相关资源
# 应用配置
root@k8s-master:~/test/mongo-repl# kubectl apply -f sts-mongo-repl.yaml
statefulset.apps/mongodb created
service/mongodb-headless-svc created

# 查看全部资源(将会每一秒刷新一次,实时显示最新状态)
root@k8s-master:~/test/mongo-repl# watch -n 1 -x kubectl get all -n mongo-replica
Every 1.0s: kubectl get all -n mongo-replica                                                                                                                                                                     k8s-master: Thu Dec 28 14:59:04 2023

NAME          READY   STATUS              RESTARTS   AGE
pod/mongo-0   2/2     Running             0          9s
pod/mongo-1   2/2     Running             0          6s
pod/mongo-2   0/2     ContainerCreating   0          3s

NAME                         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
service/mongo-headless-svc   ClusterIP   None         <none>        27017/TCP   9s

NAME                     READY   AGE
statefulset.apps/mongo   2/3     9s

# 查看 statefulset 资源
root@k8s-master:~/test/mongo-repl# kubectl get sts -n mongo-replica
NAME    READY   AGE
mongo   3/3     81s

# 查看 pod 资源
root@k8s-master:~/test/mongo-repl# kubectl get pod -n mongo-replica -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
mongo-0   2/2     Running   0          98s   10.244.169.144   k8s-node2   <none>           <none>
mongo-1   2/2     Running   0          94s   10.244.36.93     k8s-node1   <none>           <none>
mongo-2   2/2     Running   0          84s   10.244.169.145   k8s-node2   <none>           <none>

root@k8s-master:~/test/mongo-repl# kubectl get pvc -n mongo-replica
NAME                   STATUS   VOLUME            CAPACITY   ACCESS MODES   STORAGECLASS    AGE
mongo-volume-mongo-0   Bound    pv-mongo-repl-1   10Gi       RWO            mongo-storage   114s
mongo-volume-mongo-1   Bound    pv-mongo-repl-2   10Gi       RWO            mongo-storage   110s
mongo-volume-mongo-2   Bound    pv-mongo-repl-3   10Gi       RWO            mongo-storage   100s

# 查看 headless service 资源
root@k8s-master:~/test/mongo-repl# kubectl get svc -n mongo-replica
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
mongo-headless-svc   ClusterIP   None         <none>        27017/TCP   2m12s
root@k8s-master:~/test/mongo-repl# kubectl exec -n mongo-replica -c mongo -it mongo-0 -- /bin/bash

root@mongo-0:/# mongo mongo-0.mongo.mongo-replica.svc.cluster.local:27017
MongoDB shell version v4.0.28
connecting to: mongodb://mongo-0.mongo.mongo-replica.svc.cluster.local:27017/test?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("a96a02e9-6a59-4a4d-9070-5af8a2087f46") }
MongoDB server version: 4.0.28
Server has startup warnings:
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten]
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten] ** WARNING: You are running this process as the root user, which is not recommended.
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten]
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten]
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2023-12-28T06:32:26.987+0000 I CONTROL  [initandlisten]
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---

>
  1. 手动创建副本集
// 方式1
use admin
var cfg = { _id: "rs0", members:[{ _id: 0, host: 'mongo-0.mongo.mongo-replica.svc.cluster.local:27017', priority: 3 }, { _id: 1, host: 'mongo-1.mongo.mongo-replica.svc.cluster.local:27017', priority: 2 },{ _id: 2, host: 'mongo-2.mongo.mongo-replica.svc.cluster.local:27017', priority: 1 }] }
rs.initiate(cfg)
rs.status()

// 方式2
rs.initiate()
var cfg = rs.conf()
cfg.members[0].host="mongo-0.mongo.mongo-replica.svc.cluster.local:27017"
rs.reconfig(cfg)
rs.status()
rs.add("mongo-1.mongo.mongo-replica.svc.cluster.local:27017")
rs.add("mongo-2.mongo.mongo-replica.svc.cluster.local:27017")
rs.status()

Using MongoDB Operator

www.mongodb.com/try/downloa…

MongoDB Community Operator 可创建 ReplicaSet 复制集,支持节点弹性扩缩,当前不支持备份功能。

Basic Introduction

Architecture

Documentation

Community Operator:

github.com/mongodb/mon…

Content:

More Config:

Configuration Scheme

  • 集群部署模式选型:
一主一从一选举一主两从
冗余性较低,如果从节点故障则无法提供服务高,即使一个节点故障,也能保持服务连续性
读能力一般,由两个数据节点提供较高,由三个数据节点提供
数据一致性很好,数据只需要在两个节点间同步较差,需要在三个节点间同步,可能存在一致性问题
维护成本较低,仲裁器不持有数据,资源需求低较高,需要维护三个数据节点
资源开销以 100% 为资源基准上浮到 150% - 200% 资源消耗
故障恢复一般,如果从节点故障,集群将无法提供服务较好,即使一个从节点故障,剩余节点仍可提供服务
  • 标准用户角色分配:
mindmap
    Permissions
        root
            readWriteAnyDatabase
            userAdminAnyDatabase
            clusterAdmin
                clusterManager
                clusterMonitor
                hostManager
                dbAdminAnyDatabase
            restore
            backup
        admin
            readWriteAnyDatabase
            dbAdminAnyDatabase
            userAdminAnyDatabase
        user
            readWrite
            read
          
    

Deployment Steps

Install using Helm charts

通过 Helm charts 离线安装 mongodb-community-operator

# 下载 MongoDB Community Operator 的 Helm chart 到本地
root@k8s-client:~# helm pull mongodb/community-operator --untar

root@k8s-client:~# ll community-operator/
total 24
-rw-r--r-- 1 root root  239 Dec 29 15:24 Chart.lock
drwxr-xr-x 3 root root 4096 Dec 29 15:24 charts
-rw-r--r-- 1 root root  564 Dec 29 15:24 Chart.yaml
-rw-r--r-- 1 root root 3181 Dec 29 15:24 README.md
drwxr-xr-x 2 root root 4096 Dec 29 15:24 templates
-rw-r--r-- 1 root root 3260 Dec 29 15:24 values.yaml

# 按需修改 Helm chart 的默认配置
root@k8s-client:~# vi community-operator/values.yaml

# 将整个 community-operator 文件夹打包,并安装到 Kubernetes 集群
root@k8s-client:~# helm install mongo-community-operator ./community-operator --namespace mongo-repl --create-namespace

通过 Helm charts 在线安装 mongodb-community-operator

# 将一个新的 Helm 仓库添加到 Helm 客户端
root@k8s-client:~# helm repo add mongodb https://mongodb.github.io/helm-charts

# 更新获取到仓库中最新的 charts
root@k8s-client:~# helm repo update

# 安装 mongo-community-operator 的 Helm chart 到 mongo-repl 命名空间下
root@k8s-client:~# helm install mongo-community-operator mongodb/community-operator --namespace mongo-repl --create-namespace
NAME: mongo-community-operator
LAST DEPLOYED: Fri Dec 29 13:13:42 2023
NAMESPACE: mongo-repl
STATUS: deployed
REVISION: 1
TEST SUITE: None

# 列出在命名空间 mongo-repl 中的所有 Helm releases
root@k8s-client:~# helm list -n mongo-repl
NAME                            NAMESPACE         REVISION        UPDATED                                        STATUS          CHART                           APP VERSION
mongo-community-operator        mongo-repl        1               2023-12-29 13:13:42.012360971 +0800 CST        deployed        community-operator-0.9.0        0.9.0

# 检查刚安装的 mongo-community-operator Helm chart 已创建了对应的 operator Deployment
root@k8s-client:~# kubectl get deployments -n mongo-repl
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
mongodb-kubernetes-operator   1/1     1            1           2m57s

# 检查刚安装的 mongo-community-operator Helm chart 已创建了对应的 operator Pod
root@k8s-client:~# kubectl get pods -n mongo-repl
NAME                                           READY   STATUS    RESTARTS   AGE
mongodb-kubernetes-operator-584c9fcb79-qg8xk   1/1     Running   0          3m8s

Create ReplicaSet

  1. 编写 mongo-community-operator.yaml
---
apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
  name: mongodb
  namespace: mongo-repl
spec:
  members: 2
  arbiters: 1
  type: ReplicaSet
  version: "6.0.5"
  security:
    authentication:
      modes: ["SCRAM"]

  statefulSet:
    spec:
      template:
        spec:
          # resources can be specified by applying an override
          # per container name.
          containers:
            - name: mongod
              resources:
                limits:
                  cpu: 1000m
                  memory: 1Gi
                requests:
                  cpu: 500m
                  memory: 512Mi
            - name: mongodb-agent
              resources:
                limits:
                  cpu: "0.2"
                  memory: 256Mi
                requests:
                  cpu: "0.2"
                  memory: 200Mi

      volumeClaimTemplates:
      - metadata:
          name: data-volume
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 10Gi
          storageClassName: longhorn
          volumeMode: Filesystem
      - metadata:
          name: logs-volume
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 10Gi
          storageClassName: longhorn
          volumeMode: Filesystem

  users:
    - name: root
      db: admin
      passwordSecretRef:
        name: root-password
      roles:
        - name: root
          db: admin
      scramCredentialsSecretName: root-scram
      additionalConnectionStringConfig:
        readPreference: secondary
    - name: admin
      db: admin
      passwordSecretRef:
        name: admin-password
      roles:
        - name: userAdminAnyDatabase
          db: admin
        - name: readWriteAnyDatabase
          db: admin
        - name: dbAdminAnyDatabase
          db: admin
      scramCredentialsSecretName: admin-scram
      additionalConnectionStringConfig:
        readPreference: secondary
    - name: appuser
      db: mydb  # assuming 'mydb' is your application database
      passwordSecretRef:
        name: appuser-password
      roles:
        - name: readWrite
          db: mydb
      scramCredentialsSecretName: appuser-scram

  additionalMongodConfig:
    # the additional config passed to the mongod process can be specified
    # either in nested or dot notation
    net:
      bindIp: 0.0.0.0
      port: 27017
    storage:
      wiredTiger:
        engineConfig:
          cacheSizeGB: 2
          journalCompressor: zlib
    security:
      authorization: enabled
    setParameter:
      failIndexKeyTooLong: false

  additionalConnectionStringConfig:
    readPreference: primary

# the user credentials will be generated from this secret
# once the credentials are generated, this secret is no longer required
---
apiVersion: v1
kind: Secret
metadata:
  name: root-password   # corresponds to spec.users.passwordSecretRef.name in the MongoDB CRD
  namespace: mongo-repl
type: Opaque
stringData:
  password: root@12345 # corresponds to spec.users.passwordSecretRef.key in the MongoDB CRD
---
apiVersion: v1
kind: Secret
metadata:
  name: admin-password
  namespace: mongo-repl
type: Opaque
stringData:
  password: admin@12345
---
apiVersion: v1
kind: Secret
metadata:
  name: appuser-password
  namespace: mongo-repl
type: Opaque
stringData:
  password: app@12345
  1. 应用mongo-community-operator.yaml
# 应用加载 mongo-community-operator.yaml
root@k8s-client:~# kubectl apply -f mongo-community-operator.yaml
mongodbcommunity.mongodbcommunity.mongodb.com/mongodb created
secret/root-password created
secret/admin-password created
secret/appuser-password created

# watch 是基于 linux 命令周期性地执行并显示后面命令的结果
root@k8s-client:~# watch -n 1 -x kubectl get pods -n mongo-repl
NAME                                           READY   STATUS     RESTARTS   AGE
mongodb-0                                      0/2     Init:0/2   0          11s
mongodb-arb-0                                  0/2     Init:0/2   0          11s
mongodb-kubernetes-operator-584c9fcb79-qg8xk   1/1     Running    0          7m3s

# k8s 中 --watch 选项是基于事件驱动,只有在有事件发生时才会有输出
root@k8s-client:~# kubectl get mongodbcommunity -n mongo-repl --watch
NAME      PHASE     VERSION
mongodb   Pending
mongodb   Running   6.0.5

# 可以看到创建了 PSA 模型的 mongodb replicaSet 集群
root@k8s-client:~# kubectl get pods -n mongo-repl
NAME                                           READY   STATUS    RESTARTS   AGE
mongodb-0                                      2/2     Running   0          2m8s
mongodb-1                                      2/2     Running   0          76s
mongodb-arb-0                                  2/2     Running   0          2m8s
mongodb-kubernetes-operator-584c9fcb79-qg8xk   1/1     Running   0          9m

# 社区 Kubernetes Operator 创建包含用户连接字符串和凭据secret
# 遵循以下命名约定:<metadata.name>-<auth-db>-<username>
root@k8s-client:~# kubectl get secret -n mongo-repl
NAME                                             TYPE                 DATA   AGE
admin-password                                   Opaque               1      3m32s
admin-scram-scram-credentials                    Opaque               6      3m32s
appuser-password                                 Opaque               1      3m32s
appuser-scram-scram-credentials                  Opaque               6      3m32s
mongodb-admin-admin                              Opaque               4      104s
mongodb-admin-root                               Opaque               4      104s
mongodb-agent-password                           Opaque               1      3m32s
mongodb-config                                   Opaque               1      3m32s
mongodb-keyfile                                  Opaque               1      3m32s
mongodb-mydb-appuser                             Opaque               4      104s
root-password                                    Opaque               1      3m32s
root-scram-scram-credentials                     Opaque               6      3m32s
sh.helm.release.v1.mongo-community-operator.v1   helm.sh/release.v1   1      9m

# 需在主机上提前下载 jq 命令:apt-get update && apt-get install -y jq
root@k8s-client:~# kubectl get secret mongodb-admin-root -n mongo-repl -o json | jq -r '.data | with_entries(.value |= @base64d)'
{
  "connectionString.standard": "mongodb://root:root%4012345@mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017/admin?replicaSet=mongodb&ssl=false&readPreference=secondary",
  "connectionString.standardSrv": "mongodb+srv://root:root%4012345@mongodb-svc.mongo-repl.svc.cluster.local/admin?replicaSet=mongodb&ssl=false&readPreference=secondary",
  "password": "root@12345",
  "username": "root"
}

Expose Service

  1. 编写 mongo-external-service.yaml
---
kind: Service
apiVersion: v1
metadata:
  name: mongo-svc0
  namespace: mongo-repl
  annotations:
    note: "NodePort is used for development and testing environments"
spec:
  type: NodePort
  selector:
    app: mongodb-svc
    statefulset.kubernetes.io/pod-name: mongodb-0
  ports:
    - protocol: TCP
      nodePort: 31181
      port: 31181
      targetPort: 27017

---

kind: Service
apiVersion: v1
metadata:
  name: mongo-svc1
  namespace: mongo-repl
  annotations:
    note: "NodePort is used for development and testing environments"
spec:
  type: NodePort
  selector:
    app: mongodb-svc
    statefulset.kubernetes.io/pod-name: mongodb-1
  ports:
    - nodePort: 31182
      port: 31182
      targetPort: 27017
  1. 应用mongo-external-service.yaml
# 服务加载 mongo-external-service.yaml
root@k8s-client:~# kubectl apply -f mongo-external-service.yaml
service/mongo-svc0 created
service/mongo-svc1 created

# 查看当前服务状态
root@k8s-client:~# kubectl get svc -n mongo-repl
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)           AGE
mongo-svc0    NodePort    10.43.208.111   <none>        31181:31181/TCP   20s
mongo-svc1    NodePort    10.43.20.130    <none>        31182:31182/TCP   20s
mongodb-svc   ClusterIP   None            <none>        27017/TCP         4h25m

# 查看Node的IP信息
root@k8s-client:~# kubectl get nodes -o wide
NAME        STATUS   ROLES                      AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
kube-dev01   Ready    controlplane,etcd,worker   127d   v1.26.7   10.93.40.51   <none>        CentOS Linux 7 (Core)   3.10.0-1160.92.1.el7.x86_64   docker://24.0.2
kube-dev02   Ready    controlplane,etcd,worker   127d   v1.26.7   10.93.40.52   <none>        CentOS Linux 7 (Core)   3.10.0-1160.92.1.el7.x86_64   docker://24.0.2
kube-dev03   Ready    controlplane,etcd,worker   127d   v1.26.7   10.93.40.53   <none>        CentOS Linux 7 (Core)   3.10.0-1160.92.1.el7.x86_64   docker://24.0.2
  1. 使用 K8S Node + Service Port 进行登录测试
# 其它服务器可联通 K8S Node 暴露出来的端口
[root@localhost ~]# telnet 10.93.40.51 31181
Trying 10.93.40.51...
Connected to 10.93.40.51.
Escape character is '^]'.
^C
[root@localhost ~]# telnet 10.93.40.51 31182
Trying 10.93.40.51...
Connected to 10.93.40.51.
Escape character is '^]'.
^C

[root@localhost ~]# mongosh --host 10.93.40.51 --port 31181 -u root -p 'root@12345' --authenticationDatabase admin
Current Mongosh Log ID: 658ecbdf3be8f6c4953bcf36
Connecting to:          mongodb://<credentials>@10.93.40.51:31181/?directConnection=true&authSource=admin&appName=mongosh+2.1.1
Using MongoDB:          6.0.5
Using Mongosh:          2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.

------
   The server generated these startup warnings when booting
   2023-12-29T08:54:52.230+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2023-12-29T08:54:52.230+00:00: The configured WiredTiger cache size is more than 80% of available RAM. See http://dochub.mongodb.org/core/faq-memory-diagnostics-wt
   2023-12-29T08:54:52.927+00:00: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options]
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: vm.max_map_count is too low
------

Deprecation warnings:
  - Using mongosh on the current operating system is deprecated, and support may be removed in a future release.
See https://www.mongodb.com/docs/mongodb-shell/install/#supported-operating-systems for documentation on supported platforms.
mongodb [direct: primary] test> 

在生产环境中为了简化 MongoDB 连接的需求,希望让客户端(用户)无需了解 MongoDB 的集群结构,只通过一个固定 IP+Port 就能连接上对应的服务。Kubernetes 提供了几种方式来满足这种需求:

  • 当你创建 LoadBalancer 类型的 Service 时,云提供商(例如 AWSGCPAzure等)会提供一个公共 IP 或者 DNS 名称,并设置一个负载均衡器,流量通过这个负载均衡器路由到你的服务。
  • Ingress 是一组路由规则,它定义了外部请求如何访问集群内部的服务。在你的情况下,你可以设置一个 Ingress Controller,将所有的请求指向你的 MongoDB 服务。
  • 如上述不支持,可以考虑使用 HAProxy 或者 Nginx 作为反向代理和负载均衡器。这样,所有的请求都会发送到这个代理,然后由代理转发到正确的服务。
  • 还可以考虑使用 MongoDB 的服务发现功能,通过连接字符串进行连接,当应用程序使用 MongoDB 的驱动连接到集群时,这个连接字符串只需包含一个集群中的任一节点地址,驱动程序会自动发现集群中其它的节点。Connection String 类似如下:
mongodb://<user>:<password>@<host1>:<port>,<host2>:<port>/<dbname>?replicaSet=<replica-name>

Cluster Operation

Client Connection

  1. 编写 mongo-client-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mongo-client
  name: mongo-client
  namespace: mongo-repl
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongo-client
  template:
    metadata:
      labels:
        app: mongo-client
    spec:
      containers:
      - image: mongo
        name: mongo-client
        env:
        - name: "CONNECTION_STRING"
          valueFrom:
            secretKeyRef:
              # name: <metadata.name>-<auth-db>-<username>
              name: mongodb-admin-root
              key: connectionString.standardSrv
  1. 应用mongo-client-deployment.yaml
# 应用加载 mongo-client-deployment.yaml
root@k8s-client:~# kubectl apply -f mongo-client-deployment.yaml
deployment.apps/mongo-client created

# 查看当前的 pod
root@k8s-client:~# kubectl get pods -n mongo-repl
NAME                                           READY   STATUS    RESTARTS   AGE
mongo-client-5879dfbfcd-p52zc                  1/1     Running   0          34s
mongodb-0                                      2/2     Running   0          2m18s
mongodb-1                                      2/2     Running   0          86s
mongodb-arb-0                                  2/2     Running   0          2m18s
mongodb-kubernetes-operator-584c9fcb79-qg8xk   1/1     Running   0          9m10s

# 进入 pod 内部 shell
root@k8s-client:~# kubectl exec -it mongo-client-5b74bfbbc8-qmjzh -n mongo-repl -- /bin/bash

# 列出客户端提供的 mongo 相关工具命令
root@mongo-client-5b74bfbbc8-qmjzh:/# ls `echo "$PATH" | sed 's/:/ /g'` | grep -i mongo
mongod
mongodump
mongoexport
mongofiles
mongoimport
mongorestore
mongos
mongosh
mongostat
mongotop
mongod
mongodump
mongoexport
mongofiles
mongoimport
mongorestore
mongos
mongosh
mongostat
mongotop

# 使用 MongoDB SRV connection string 进行登录
root@mongo-client-5b74bfbbc8-qmjzh:/# mongosh "mongodb+srv://root:root%4012345@mongodb-svc.mongo-repl.svc.cluster.local/admin?replicaSet=mongodb&ssl=false&readPreference=secondary"
Current Mongosh Log ID:        658e95dc23c0825f9f980a1a
Connecting to:                mongodb+srv://<credentials>@mongodb-svc.mongo-repl.svc.cluster.local/admin?replicaSet=mongodb&ssl=false&readPreference=secondary&appName=mongosh+2.1.1
Using MongoDB:                6.0.5
Using Mongosh:                2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.

------
   The server generated these startup warnings when booting
   2023-12-29T08:54:52.230+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2023-12-29T08:54:52.230+00:00: The configured WiredTiger cache size is more than 80% of available RAM. See http://dochub.mongodb.org/core/faq-memory-diagnostics-wt
   2023-12-29T08:54:52.927+00:00: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options]
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: vm.max_map_count is too low
------

mongodb [primary] admin> 
  1. 连接 Mongosh 验证配置和集群状态
// 查看数据库的当前连接状态,包括连接用户的信息和角色等
mongodb [primary] admin> db.runCommand({connectionStatus : 1})
{
  authInfo: {
    authenticatedUsers: [ { user: 'root', db: 'admin' } ],
    authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703843334, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('wtVTIZ9uuK36t3Km3jZyKimjxAU=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703843334, i: 1 })
}

// 列出 MongoDB 上的所有数据库
mongodb [primary] admin> show dbs
admin   172.00 KiB
config  176.00 KiB
local   484.00 KiB

// 切换当前的数据库到 admin 数据库
mongodb [primary] admin> use admin
already on db admin

// 查看所有数据库的用户信息
mongodb [primary] admin> db.system.users.find()
[
  {
    _id: 'admin.mms-automation',
    userId: UUID('03cc6896-7d21-4c15-9232-59b850037354'),
    user: 'mms-automation',
    db: 'admin',
    credentials: {
      'SCRAM-SHA-256': {
        iterationCount: 15000,
        salt: 'j+8GOsmttVvlXKtBwt7VQ0J9aAChc/RRwoSxwQ==',
        storedKey: '2JcvTsOAuxsM2Lgt7CcNNZROVr73IxJr+ZtyqVZx9eM=',
        serverKey: 'rCYjGVIILlYMObuZv7OH11I0oCax4022CjiPBGqZXUo='
      }
    },
    roles: [
      { role: 'backup', db: 'admin' },
      { role: 'readWriteAnyDatabase', db: 'admin' },
      { role: 'restore', db: 'admin' },
      { role: 'dbAdminAnyDatabase', db: 'admin' },
      { role: 'userAdminAnyDatabase', db: 'admin' },
      { role: 'clusterAdmin', db: 'admin' }
    ]
  },
  {
    _id: 'admin.root',
    user: 'root',
    db: 'admin',
    credentials: {
      'SCRAM-SHA-256': {
        iterationCount: 15000,
        salt: 'mCCf6in3BYhljlLs3q495Eo1xtfwpERLqoepLQ==',
        storedKey: 'nLiWzq1jrODdjPPgVqGQl9hE8PmBnuYvykXHZFpKBY4=',
        serverKey: 'SkzaF+2gGV3ZL5C3bWQTksF5IQdfVXYIuyH2L4EufLI='
      }
    },
    roles: [ { role: 'root', db: 'admin', minFcv: '' } ],
    authenticationRestrictions: [],
    userId: UUID('a7100736-2abc-404c-a5a1-e7d17233c13f')
  },
  {
    _id: 'admin.admin',
    user: 'admin',
    db: 'admin',
    credentials: {
      'SCRAM-SHA-256': {
        iterationCount: 15000,
        salt: '5wD7fQwKCAjNi34vrsq5ERHEqnIn117L5B3EOA==',
        storedKey: 'kfkGNAOZK3H+NUIgIbJ3fPSFSXfGG2TjaeBhiznxXxs=',
        serverKey: 'mWBKpWkUqCc6taTpG4tnOfiBdiPl9cjGWwBuhgrP1Yo='
      }
    },
    roles: [
      { role: 'dbAdminAnyDatabase', db: 'admin', minFcv: '' },
      { role: 'readWriteAnyDatabase', db: 'admin', minFcv: '' },
      { role: 'userAdminAnyDatabase', db: 'admin', minFcv: '' }
    ],
    authenticationRestrictions: [],
    userId: UUID('75a11c21-3029-4eef-88f6-47788e5bdc62')
  },
  {
    _id: 'mydb.appuser',
    user: 'appuser',
    db: 'mydb',
    credentials: {
      'SCRAM-SHA-256': {
        iterationCount: 15000,
        salt: 'hvmm+8ssMZHpdYFeGXER/XSICN1Lh+Fwd09Akw==',
        storedKey: 'uobhKe8VO1RQvEWfIAphcIc+jL46bUWQXKLwfjvY0bM=',
        serverKey: 'L1w/NwAIzIMKqLADDBZseMDjeLcYq05lcUXIyT/nbuI='
      }
    },
    roles: [ { role: 'readWrite', db: 'mydb', minFcv: '' } ],
    authenticationRestrictions: [],
    userId: UUID('522098c5-269e-46e0-9733-cab2df84db9b')
  }
]

// 返回副本集的状态信息
mongodb [primary] admin> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T10:00:46.780Z'),
  myState: 1,
  term: Long('1'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
    lastCommittedWallTime: ISODate('2023-12-29T10:00:44.718Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
    appliedOpTime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
    durableOpTime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
    lastAppliedWallTime: ISODate('2023-12-29T10:00:44.718Z'),
    lastDurableWallTime: ISODate('2023-12-29T10:00:44.718Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703843993, i: 6 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2023-12-29T08:56:04.371Z'),
    electionTerm: Long('1'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    numCatchUpOps: Long('0'),
    newTermStartDate: ISODate('2023-12-29T08:56:04.488Z'),
    wMajorityWriteAvailabilityDate: ISODate('2023-12-29T08:56:05.065Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 3954,
      optime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
      optimeDate: ISODate('2023-12-29T10:00:44.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T10:00:44.718Z'),
      lastDurableWallTime: ISODate('2023-12-29T10:00:44.718Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703840164, i: 1 }),
      electionDate: ISODate('2023-12-29T08:56:04.000Z'),
      configVersion: 1,
      configTerm: 1,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 3892,
      optime: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
      optimeDurable: { ts: Timestamp({ t: 1703844044, i: 1 }), t: Long('1') },
      optimeDate: ISODate('2023-12-29T10:00:44.000Z'),
      optimeDurableDate: ISODate('2023-12-29T10:00:44.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T10:00:44.718Z'),
      lastDurableWallTime: ISODate('2023-12-29T10:00:44.718Z'),
      lastHeartbeat: ISODate('2023-12-29T10:00:45.581Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T10:00:44.952Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      syncSourceId: 0,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 3892,
      lastHeartbeat: ISODate('2023-12-29T10:00:45.582Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T10:00:45.581Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703844044, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('MrGmoETGp3SW23/VZ+UnlYOQ2ms=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703844044, i: 1 })
}
  1. 其它账号验证连接
root@k8s-client:~# kubectl get pods -n mongo-repl -o wide
NAME                                           READY   STATUS    RESTARTS   AGE    IP            NODE        NOMINATED NODE   READINESS GATES
mongo-client-5b74bfbbc8-qmjzh                  1/1     Running   0          54m    10.42.1.148   kube-dev02   <none>           <none>
mongodb-0                                      2/2     Running   0          98m    10.42.1.103   kube-dev02   <none>           <none>
mongodb-1                                      2/2     Running   0          97m    10.42.0.129   kube-dev01   <none>           <none>
mongodb-arb-0                                  2/2     Running   0          98m    10.42.2.20    kube-dev03   <none>           <none>
mongodb-kubernetes-operator-584c9fcb79-qg8xk   1/1     Running   0          179m   10.42.1.17    kube-dev02   <none>           <none>

root@k8s-node1:~# kubectl exec -it mongo-client-5b74bfbbc8-qmjzh -n mongo-repl -- /bin/bash
root@mongo-client-5b74bfbbc8-qmjzh:/#

// 是用 root 用户密码作为凭证进行登录
root@mongo-client-5b74bfbbc8-qmjzh:/# mongosh --host 10.42.0.129 --port 27017 -u root -p 'root@12345' --authenticationDatabase admin
Current Mongosh Log ID:        658ea0827976736f70dc71ca
Connecting to:                mongodb://<credentials>@10.42.0.129:27017/?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:                6.0.5
Using Mongosh:                2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2023-12-29T08:55:50.137+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2023-12-29T08:55:50.137+00:00: The configured WiredTiger cache size is more than 80% of available RAM. See http://dochub.mongodb.org/core/faq-memory-diagnostics-wt
   2023-12-29T08:55:50.860+00:00: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options]
   2023-12-29T08:55:50.860+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2023-12-29T08:55:50.860+00:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
   2023-12-29T08:55:50.860+00:00: vm.max_map_count is too low
------

mongodb [direct: secondary] test>

// 连接业务普通用户时,需要注意要指定库名
root@mongo-client-5b74bfbbc8-qmjzh:/# mongosh --host 10.42.0.129 --port 27017 -u appuser -p 'app@12345' mydb
Current Mongosh Log ID:        658ea1770e38c2f427d201a6
Connecting to:                mongodb://<credentials>@10.42.0.129:27017/mydb?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:                6.0.5
Using Mongosh:                2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

mongodb [direct: secondary] mydb>

Data Consistency

主备节点数据同步一致性校验:

// 连接到 primary 节点
[root@localhost ~]# mongosh --host 10.93.40.51 --port 31181 -u appuser -p 'app@12345' mydb
Current Mongosh Log ID: 658eea9af9379d5e496cd74f
Connecting to:          mongodb://<credentials>@10.93.40.51:31181/mydb?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:          6.0.5
Using Mongosh:          2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

Deprecation warnings:
  - Using mongosh on the current operating system is deprecated, and support may be removed in a future release.
See https://www.mongodb.com/docs/mongodb-shell/install/#supported-operating-systems for documentation on supported platforms.

// 新建 tt 集合,并插入 10 条文档
mongodb [direct: primary] mydb> for (var i = 1; i <= 10; i++) { db.tt.insertOne({ field: i }); }
{
  acknowledged: true,
  insertedId: ObjectId('658eeaa5f9379d5e496cd759')
}

// 查看 tt 集合中的文档数量
mongodb [direct: primary] mydb> db.tt.countDocuments()
10

// 连接到 secondary 节点
[root@localhost ~]# mongosh --host 10.93.40.51 --port 31182 -u appuser -p 'app@12345' mydb
Current Mongosh Log ID: 658eead16f8283fb1a40a801
Connecting to:          mongodb://<credentials>@10.93.40.51:31182/mydb?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:          6.0.5
Using Mongosh:          2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

Deprecation warnings:
  - Using mongosh on the current operating system is deprecated, and support may be removed in a future release.
See https://www.mongodb.com/docs/mongodb-shell/install/#supported-operating-systems for documentation on supported platforms. 

// 默认备节点不允许读取数据,关闭其限制
mongodb [direct: secondary] mydb> db.getMongo().setReadPref("secondaryPreferred")

// 可以看到备节点也可以正常访问到从主节点同步过来的数据(当然在写入十分频繁的场景下,也可能会存在一些同步延迟)
mongodb [direct: secondary] mydb> db.tt.find().sort({field: 1});
[
  { _id: ObjectId('658eeaa4f9379d5e496cd750'), field: 1 },
  { _id: ObjectId('658eeaa5f9379d5e496cd751'), field: 2 },
  { _id: ObjectId('658eeaa5f9379d5e496cd752'), field: 3 },
  { _id: ObjectId('658eeaa5f9379d5e496cd753'), field: 4 },
  { _id: ObjectId('658eeaa5f9379d5e496cd754'), field: 5 },
  { _id: ObjectId('658eeaa5f9379d5e496cd755'), field: 6 },
  { _id: ObjectId('658eeaa5f9379d5e496cd756'), field: 7 },
  { _id: ObjectId('658eeaa5f9379d5e496cd757'), field: 8 },
  { _id: ObjectId('658eeaa5f9379d5e496cd758'), field: 9 },
  { _id: ObjectId('658eeaa5f9379d5e496cd759'), field: 10 }
]

HA Testing

NodePort 测试环境的限制和问题:

当我们在 Kubernetes 集群中部署 MongoDB 集群时,并使用 MongoDB Operator 自动创建 Secret,遇到的主要问题是 MongoDB Operator 生成的连接字符串仅包含内部服务名,不支持 NodePort。 在生产环境中,我们更常见的是使用 LoadBalancer 服务,因为 LoadBalancer 能够将流量智能地路由到集群中的多个 MongoDB 节点。然而,在开发环境中,我们可能会更倾向于使用 NodePort。 对于在开发环境中解决服务名与 IP 映射的问题,一种可行的方法是在 hosts 文件中手动添加映射。然而这并不是一种理想的解决方案,因为它没有解决端口映射的问题,并且可能在大规模部署或动态环境中引入更多问题。 在我们的 MongoDB 客户端,无论是 MongoDB Shell 还是 Go 驱动程序,连接 MongoDB 服务器时,服务器会返回包含集群成员信息的元数据。在这个元数据中,成员节点使用内部服务名称表示,这在外部环境中是无法解析的,即便我们更改了 hosts 文件。并且,元数据中的端口默认是 MongoDB 的标准端口 27017,而在 NodePort 服务中,我们需要为每个节点使用不同的端口。 要在开发环境中成功使用 Go 驱动程序连接 MondgoDB,我们可能需要做出一些调整。首先,在 Operator 配置时,我们可能需要为每个 MongoDB 节点设置一个唯一的内部端口并使其与节点的 NodePort 对应。其次,我们需要在 hosts 文件中添加相应的映射,将内部服务名映射到正确的 IP 地址。 然而请注意,这只是理论上的解决方案,还没有得到实际验证,可能并不适用于所有的环境。我们总是推荐在生产环境中使用 LoadBalancer 类型服务或 Service Mesh 来处理这种问题,而不是进行复杂而可能引入新问题的手动配置操作。

我们原计划在开发环境中模拟真实的业务场景,使用 Go 程序通过连接字符串持续连接 MongoDB,并且不断地向数据库插入数据。我们观察并模拟了在这个过程中 MongoDB 主节点和备份节点可能会遇到的故障,以探索其高可用性 (HA) 和故障转移 (Failover) 的特性。整个实验的运行结果呈现在 Go 程序的日志中,我们从中观察到,在故障切换的过程中,有一段时间内数据无法被正常写入。在这个过程中,我们利用了 MongoDB 提供的服务发现机制,这让开发者可以很方便地建立数据库连接,而无需关心具体连接到哪个节点。尽管存在少数情况下数据无法正常写入的情形,但在大多数情况下,MongoDB 复制集所展现出的数据强一致性和主备节点切换的特性无疑对业务透明。也就是说,对于业务来说,几秒钟内无法正常写入的数据几乎不会对整体业务产生影响。虽然由于环境的限制,我们并未能完全实施所有的模拟实验,但我们依然可以通过模拟故障现场的方式——即删除 Kubernetes 中的 Pod 资源,来观察和体验 MongoDB 复制集强大的高可用性能力。

image.png

  1. 模拟备节点故障的情况(体现自愈能力):
# 获取 mongo-repl 名称空间中所有 Pod 的详细信息,同时将各个 Pod 的 IP 地址,所在节点等信息展示出来
root@k8s-client:~# kubectl get pods -n mongo-repl -o wide
NAME                                           READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
mongo-client-5b74bfbbc8-qmjzh                  1/1     Running   0          8h      10.42.1.148   kube-dev02   <none>           <none>
mongodb-0                                      2/2     Running   0          9h      10.42.1.103   kube-dev02   <none>           <none>
mongodb-1                                      2/2     Running   0          3m46s   10.42.0.130   kube-dev01   <none>           <none>
mongodb-arb-0                                  2/2     Running   0          9h      10.42.2.20    kube-dev03   <none>           <none>
mongodb-kubernetes-operator-584c9fcb79-7t8wf   1/1     Running   0          10h     10.42.1.17    kube-dev02   <none>           <none>

# 删除 mongo-repl 名称空间中名为 mongodb-1 的 Pod,以模拟故障发生
root@k8s-client:~# kubectl delete pod -n mongo-repl mongodb-1
pod "mongodb-1" deleted

# 在删除 Pod 后,再次执行获取 Pod 信息的命令,以查看Pod删除后Kubernetes集群中的变化
root@k8s-client:~# kubectl get pods -n mongo-repl -o wide
NAME                                           READY   STATUS     RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
mongo-client-5b74bfbbc8-qmjzh                  1/1     Running    0          8h    10.42.1.148   kube-dev02   <none>           <none>
mongodb-0                                      2/2     Running    0          9h    10.42.1.103   kube-dev02   <none>           <none>
mongodb-1                                      0/2     Init:0/2   0          3s    <none>        kube-dev01   <none>           <none>
mongodb-arb-0                                  2/2     Running    0          9h    10.42.2.20    kube-dev03   <none>           <none>
mongodb-kubernetes-operator-584c9fcb79-7t8wf   1/1     Running    0          10h   10.42.1.17    kube-dev02   <none>           <none>

# 进入名为 mongo-client-5b74bfbbc8-qmjzh 的 Pod 中
root@k8s-client:~# kubectl -n mongo-repl exec -it mongo-client-5b74bfbbc8-qmjzh  -- /bin/bash

# 在 Pod 内部,使用 mongosh 连接到 MongoDB 主节点
root@mongo-client-5b74bfbbc8-qmjzh:/# mongosh --host 10.42.1.103 --port 27017 -u root -p 'root@12345'
Current Mongosh Log ID:        658f0f202f5261240ea405c7
Connecting to:                mongodb://<credentials>@10.42.1.103:27017/?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:                6.0.5
Using Mongosh:                2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2023-12-29T08:54:52.230+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2023-12-29T08:54:52.230+00:00: The configured WiredTiger cache size is more than 80% of available RAM. See http://dochub.mongodb.org/core/faq-memory-diagnostics-wt
   2023-12-29T08:54:52.927+00:00: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options]
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
   2023-12-29T08:54:52.927+00:00: vm.max_map_count is too low
------

# 查询复制集的的当前状态,可以看到 member1 的状态是 'not reachable' 的
mongodb [direct: primary] test> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T18:25:38.085Z'),
  myState: 1,
  term: Long('1'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703874316, i: 1 }), t: Long('1') },
    lastCommittedWallTime: ISODate('2023-12-29T18:25:16.004Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703874316, i: 1 }), t: Long('1') },
    appliedOpTime: { ts: Timestamp({ t: 1703874336, i: 1 }), t: Long('1') },
    durableOpTime: { ts: Timestamp({ t: 1703874336, i: 1 }), t: Long('1') },
    lastAppliedWallTime: ISODate('2023-12-29T18:25:36.005Z'),
    lastDurableWallTime: ISODate('2023-12-29T18:25:36.005Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703874316, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2023-12-29T08:56:04.371Z'),
    electionTerm: Long('1'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    numCatchUpOps: Long('0'),
    newTermStartDate: ISODate('2023-12-29T08:56:04.488Z'),
    wMajorityWriteAvailabilityDate: ISODate('2023-12-29T08:56:05.065Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 34246,
      optime: { ts: Timestamp({ t: 1703874336, i: 1 }), t: Long('1') },
      optimeDate: ISODate('2023-12-29T18:25:36.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:25:36.005Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:25:36.005Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703840164, i: 1 }),
      electionDate: ISODate('2023-12-29T08:56:04.000Z'),
      configVersion: 1,
      configTerm: 1,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 0,
      state: 8,
      stateStr: '(not reachable/healthy)',
      uptime: 0,
      optime: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') },
      optimeDurable: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') },
      optimeDate: ISODate('1970-01-01T00:00:00.000Z'),
      optimeDurableDate: ISODate('1970-01-01T00:00:00.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:25:16.004Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:25:16.004Z'),
      lastHeartbeat: ISODate('2023-12-29T18:25:29.478Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:25:18.632Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: "Couldn't get a connection within the time limit",
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 34183,
      lastHeartbeat: ISODate('2023-12-29T18:25:37.435Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:25:37.479Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703874336, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('3gp9QE+FspWGmMzg/zlU6PqhslM=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703874336, i: 1 })
}

# 等待一段时间,待 Pod 恢复,member1 自动加到集群成为 'SECONDARY'
mongodb [direct: primary] test> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T18:26:36.459Z'),
  myState: 1,
  term: Long('1'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703874396, i: 1 }), t: Long('1') },
    lastCommittedWallTime: ISODate('2023-12-29T18:26:36.007Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703874396, i: 1 }), t: Long('1') },
    appliedOpTime: { ts: Timestamp({ t: 1703874396, i: 1 }), t: Long('1') },
    durableOpTime: { ts: Timestamp({ t: 1703874396, i: 1 }), t: Long('1') },
    lastAppliedWallTime: ISODate('2023-12-29T18:26:36.007Z'),
    lastDurableWallTime: ISODate('2023-12-29T18:26:36.007Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703874386, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2023-12-29T08:56:04.371Z'),
    electionTerm: Long('1'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1703840154, i: 1 }), t: Long('-1') },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    numCatchUpOps: Long('0'),
    newTermStartDate: ISODate('2023-12-29T08:56:04.488Z'),
    wMajorityWriteAvailabilityDate: ISODate('2023-12-29T08:56:05.065Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 34304,
      optime: { ts: Timestamp({ t: 1703874396, i: 1 }), t: Long('1') },
      optimeDate: ISODate('2023-12-29T18:26:36.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:26:36.007Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:26:36.007Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703840164, i: 1 }),
      electionDate: ISODate('2023-12-29T08:56:04.000Z'),
      configVersion: 1,
      configTerm: 1,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 56,
      optime: { ts: Timestamp({ t: 1703874386, i: 1 }), t: Long('1') },
      optimeDurable: { ts: Timestamp({ t: 1703874386, i: 1 }), t: Long('1') },
      optimeDate: ISODate('2023-12-29T18:26:26.000Z'),
      optimeDurableDate: ISODate('2023-12-29T18:26:26.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:26:36.007Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:26:36.007Z'),
      lastHeartbeat: ISODate('2023-12-29T18:26:35.573Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:26:34.639Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      syncSourceId: 0,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 34242,
      lastHeartbeat: ISODate('2023-12-29T18:26:35.464Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:26:35.500Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703874396, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('eHNIOSIkAiIELxznL4pAalGWOlU=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703874396, i: 1 })
}
  1. 模拟主节点故障的情况(会发生 Failover):
# 查看 mongo-repl 命名空间下的所有 Pod,以及它们对应的 Node 和其他信息
root@k8s-client:~# kubectl get pods -n mongo-repl -o wide
NAME                                           READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
mongo-client-5b74bfbbc8-qmjzh                  1/1     Running   0          9h    10.42.1.148   kube-dev02   <none>           <none>
mongodb-0                                      2/2     Running   0          9h    10.42.1.103   kube-dev02   <none>           <none>
mongodb-1                                      2/2     Running   0          18m   10.42.0.131   kube-dev01   <none>           <none>
mongodb-arb-0                                  2/2     Running   0          9h    10.42.2.20    kube-dev03   <none>           <none>
mongodb-kubernetes-operator-584c9fcb79-7t8wf   1/1     Running   0          11h   10.42.1.17    kube-dev02   <none>           <none>

# 删除 mongo-repl 名称空间中名为 mongodb-0 的 Pod,以模拟故障发生
root@k8s-client:~# kubectl delete pod -n mongo-repl mongodb-0
pod "mongodb-0" deleted

# 在删除 Pod 后,再次执行获取 Pod 信息的命令,以查看Pod删除后Kubernetes集群中的变化
root@k8s-client:~# kubectl get pods -n mongo-repl -o wide
NAME                                           READY   STATUS     RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
mongo-client-5b74bfbbc8-qmjzh                  1/1     Running    0          9h    10.42.1.148   kube-dev02   <none>           <none>
mongodb-0                                      0/2     Init:0/2   0          3s    <none>        kube-dev02   <none>           <none>
mongodb-1                                      2/2     Running    0          18m   10.42.0.131   kube-dev01   <none>           <none>
mongodb-arb-0                                  2/2     Running    0          9h    10.42.2.20    kube-dev03   <none>           <none>
mongodb-kubernetes-operator-584c9fcb79-7t8wf   1/1     Running    0          11h   10.42.1.17    kube-dev02   <none>           <none>

# 进入名为 mongo-client-5b74bfbbc8-qmjzh 的 Pod 中
root@k8s-client:~# kubectl -n mongo-repl exec -it mongo-client-5b74bfbbc8-qmjzh  -- /bin/bash

# 使用 mongosh 连接到备份节点(mongodb-1)的 MongoDB 服务
root@mongo-client-5b74bfbbc8-qmjzh:/# mongosh --host 10.42.0.131 --port 27017 -u root -p 'root@12345'
Current Mongosh Log ID:        658f138c523d8c4ee90b8ea6
Connecting to:                mongodb://<credentials>@10.42.0.131:27017/?directConnection=true&appName=mongosh+2.1.1
Using MongoDB:                6.0.5
Using Mongosh:                2.1.1

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2023-12-29T18:25:37.256+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2023-12-29T18:25:37.256+00:00: The configured WiredTiger cache size is more than 80% of available RAM. See http://dochub.mongodb.org/core/faq-memory-diagnostics-wt
   2023-12-29T18:25:38.397+00:00: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options]
   2023-12-29T18:25:38.397+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2023-12-29T18:25:38.397+00:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
   2023-12-29T18:25:38.397+00:00: vm.max_map_count is too low
------

# 查询 MongoDB 复制集的状态。可以看到,主节点已经被成功删除,并且备份节点 mongodb-1 已经变成了新的主节点
mongodb [direct: primary] test> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T18:44:31.634Z'),
  myState: 1,
  term: Long('3'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703875443, i: 2 }), t: Long('3') },
    lastCommittedWallTime: ISODate('2023-12-29T18:44:03.052Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703875443, i: 2 }), t: Long('3') },
    appliedOpTime: { ts: Timestamp({ t: 1703875463, i: 1 }), t: Long('3') },
    durableOpTime: { ts: Timestamp({ t: 1703875463, i: 1 }), t: Long('3') },
    lastAppliedWallTime: ISODate('2023-12-29T18:44:23.054Z'),
    lastDurableWallTime: ISODate('2023-12-29T18:44:23.054Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703875416, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2023-12-29T18:44:03.028Z'),
    electionTerm: Long('3'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1703875426, i: 1 }), t: Long('1') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1703875426, i: 1 }), t: Long('1') },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    numCatchUpOps: Long('0'),
    newTermStartDate: ISODate('2023-12-29T18:44:03.052Z'),
    wMajorityWriteAvailabilityDate: ISODate('2023-12-29T18:44:03.592Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 0,
      state: 8,
      stateStr: '(not reachable/healthy)',
      uptime: 0,
      optime: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') },
      optimeDurable: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') },
      optimeDate: ISODate('1970-01-01T00:00:00.000Z'),
      optimeDurableDate: ISODate('1970-01-01T00:00:00.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:44:03.052Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:44:03.052Z'),
      lastHeartbeat: ISODate('2023-12-29T18:44:31.102Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:44:06.060Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: 'Error connecting to mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017 (10.42.1.230:27017) :: caused by :: Connection refused',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 3
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 1134,
      optime: { ts: Timestamp({ t: 1703875463, i: 1 }), t: Long('3') },
      optimeDate: ISODate('2023-12-29T18:44:23.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:44:23.054Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:44:23.054Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703875443, i: 1 }),
      electionDate: ISODate('2023-12-29T18:44:03.000Z'),
      configVersion: 1,
      configTerm: 3,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 1132,
      lastHeartbeat: ISODate('2023-12-29T18:44:31.063Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:44:31.065Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 3
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703875463, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('fq2GTCR9t8h0/DAHZendoFld5Vs=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703875463, i: 1 })
}

# 过段时间后,再次查询复制集的状态,可以看到原来主节点的 Pod 已经被 Kubernetes 自动重建,并以备份节点的身份加入到了复制集中。
  1. 执行手动重新选举操作(主备切换):
// 当前集群状态是 mongodb-0 为 'SECONDARY',mongodb-1 为 'PRIMARY'
mongodb [direct: primary] test> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T18:52:05.677Z'),
  myState: 1,
  term: Long('3'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
    lastCommittedWallTime: ISODate('2023-12-29T18:52:03.071Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
    appliedOpTime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
    durableOpTime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
    lastAppliedWallTime: ISODate('2023-12-29T18:52:03.071Z'),
    lastDurableWallTime: ISODate('2023-12-29T18:52:03.071Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703875893, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2023-12-29T18:44:03.028Z'),
    electionTerm: Long('3'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1703875426, i: 1 }), t: Long('1') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1703875426, i: 1 }), t: Long('1') },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    numCatchUpOps: Long('0'),
    newTermStartDate: ISODate('2023-12-29T18:44:03.052Z'),
    wMajorityWriteAvailabilityDate: ISODate('2023-12-29T18:44:03.592Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 452,
      optime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
      optimeDurable: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
      optimeDate: ISODate('2023-12-29T18:52:03.000Z'),
      optimeDurableDate: ISODate('2023-12-29T18:52:03.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:52:03.071Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:52:03.071Z'),
      lastHeartbeat: ISODate('2023-12-29T18:52:05.298Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:52:05.616Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      syncSourceId: 1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 3
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 1588,
      optime: { ts: Timestamp({ t: 1703875923, i: 1 }), t: Long('3') },
      optimeDate: ISODate('2023-12-29T18:52:03.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:52:03.071Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:52:03.071Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703875443, i: 1 }),
      electionDate: ISODate('2023-12-29T18:44:03.000Z'),
      configVersion: 1,
      configTerm: 3,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 1586,
      lastHeartbeat: ISODate('2023-12-29T18:52:05.154Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:52:05.153Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 3
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703875923, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('mjPWiuEvF6rzP+MA1njx3pL5koU=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703875923, i: 1 })
}

// 强制重新竞选主节点(且当前主节点120s内降级且不允许参与竞选)
mongodb [direct: primary] test> db.adminCommand({ replSetStepDown: 120 })
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703876033, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('40i0WrEIHFKFeLjKf+P3iMchFOc=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703876033, i: 1 })
}

// 由于总共就两个数据节点(1主1从1选举),因此强制竞选的结果就相当于主备切换,当然如果有更多的备节点,原先的主节点在恢复后仍具有重新参选的可能性,然而这并不是绝对的,因为选举结果取决于多种因素,如节点的优先级设定、系统的网络延迟等
mongodb [direct: secondary] test> rs.status()
{
  set: 'mongodb',
  date: ISODate('2023-12-29T18:54:20.087Z'),
  myState: 2,
  term: Long('4'),
  syncSourceHost: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
  syncSourceId: 0,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 2,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
    lastCommittedWallTime: ISODate('2023-12-29T18:54:10.591Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
    appliedOpTime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
    durableOpTime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
    lastAppliedWallTime: ISODate('2023-12-29T18:54:10.591Z'),
    lastDurableWallTime: ISODate('2023-12-29T18:54:10.591Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1703876013, i: 1 }),
  electionParticipantMetrics: {
    votedForCandidate: true,
    electionTerm: Long('4'),
    lastVoteDate: ISODate('2023-12-29T18:54:00.576Z'),
    electionCandidateMemberId: 0,
    voteReason: '',
    lastAppliedOpTimeAtElection: { ts: Timestamp({ t: 1703876033, i: 1 }), t: Long('3') },
    maxAppliedOpTimeInSet: { ts: Timestamp({ t: 1703876033, i: 1 }), t: Long('3') },
    priorityAtElection: 1,
    newTermStartDate: ISODate('2023-12-29T18:54:00.590Z'),
    newTermAppliedDate: ISODate('2023-12-29T18:54:01.582Z')
  },
  members: [
    {
      _id: 0,
      name: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 586,
      optime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
      optimeDurable: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
      optimeDate: ISODate('2023-12-29T18:54:10.000Z'),
      optimeDurableDate: ISODate('2023-12-29T18:54:10.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:54:10.591Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:54:10.591Z'),
      lastHeartbeat: ISODate('2023-12-29T18:54:19.604Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:54:18.595Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1703876040, i: 1 }),
      electionDate: ISODate('2023-12-29T18:54:00.000Z'),
      configVersion: 1,
      configTerm: 4
    },
    {
      _id: 1,
      name: 'mongodb-1.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 1723,
      optime: { ts: Timestamp({ t: 1703876050, i: 1 }), t: Long('4') },
      optimeDate: ISODate('2023-12-29T18:54:10.000Z'),
      lastAppliedWallTime: ISODate('2023-12-29T18:54:10.591Z'),
      lastDurableWallTime: ISODate('2023-12-29T18:54:10.591Z'),
      syncSourceHost: 'mongodb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      syncSourceId: 0,
      infoMessage: '',
      configVersion: 1,
      configTerm: 4,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 100,
      name: 'mongodb-arb-0.mongodb-svc.mongo-repl.svc.cluster.local:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 1720,
      lastHeartbeat: ISODate('2023-12-29T18:54:19.604Z'),
      lastHeartbeatRecv: ISODate('2023-12-29T18:54:18.604Z'),
      pingMs: Long('0'),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 4
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1703876050, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('Pvp/6ay28lbUNlIX7yM73lnjc90=', 0),
      keyId: Long('7317937781991276549')
    }
  },
  operationTime: Timestamp({ t: 1703876050, i: 1 })
}

Backup & Restore

用于实现 MongoDB 数据库的备份以及将备份文件上传到 S3 对象存储服务中。

image.png

One-time Tasks

执行流程图:

            +---------------+
            |    User ENV   |
            | (外部环境变量)  |
            +-------+-------+
                    |
                    |
            +-------v-------+
            |  MongoDB Pod  |
            | (mongo-client)|
            +-------+-------+
                    |
  Backup operation using `mongodump`
                    |
            +-------v-------+
            |     Volume    |
            |    (backup)   |
            +-------+-------+
                    |
  Check backup file & upload to S3 using aws-cli
                    |
            +-------v-------+
            |    S3 Pod     |
            | (aws-client)  |
            +-------+-------+
                    |
       Upload backup file to S3 bucket
                    |
         +----------v-----------+
         |  S3 Bucket (in AWS)  |
         +----------------------+

编写mongodb-backup-to-s3-pod.yaml

---
apiVersion: v1
kind: Pod
metadata:
  name: mongodb-backup
  namespace: mongo-repl
spec:
  restartPolicy: OnFailure
  volumes:
  - name: backup
    emptyDir: {}
  containers:
  - name: mongo-client
    image: mongo
    command: ["/bin/sh", "-c"]
    args:
    - >
      mongodump --uri="${CONNECTION_STRING}" --db="${MONGO_DB}" --out=/backup/mdb-${MONGO_DB}-$(date +'%Y-%m-%d') &&
      sleep 15 # 让aws-client容器有足够的时间来获取到备份文件
    env:
    - name: MONGO_DB
      value: $MONGO_DB
    - name: CONNECTION_STRING
      valueFrom:
        secretKeyRef:
          name: mongodb-admin-root
          key: connectionString.standardSrv
    volumeMounts:
    - name: backup
      mountPath: /backup
  - name: aws-client
    image: garland/aws-cli-docker:1.16.133
    command: ["/bin/sh", "-c"]
    args: 
    - >
      while true; do
      if [ -f /backup/mdb-${MONGO_DB}-$(date +'%Y-%m-%d') ]; then
        aws s3 cp /backup/mdb-${MONGO_DB}-$(date +'%Y-%m-%d') s3://mongo-backups/${USERNAME}/mdb-${MONGO_DB}-$(date +'%Y-%m-%d')
        break
      fi
      sleep 10
      done
    env:
    # 注意不是 $(VAR) 的形式,而是 $VAR 这并不是写错了,在下方进行解释!
    - name: MONGO_DB
      value: $MONGO_DB
    - name: USERNAME
      value: $USERNAME
    - name: AWS_ACCESS_KEY_ID
      valueFrom:
        secretKeyRef:
          name: aws-credentials
          key: AWS_ACCESS_KEY_ID
    - name: AWS_SECRET_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: aws-credentials
          key: AWS_SECRET_ACCESS_KEY
    - name: AWS_DEFAULT_REGION
      valueFrom:
        configMapKeyRef:
          name: aws-config
          key: AWS_DEFAULT_REGION
    - name: AWS_ENDPOINT_URL
      valueFrom:
        configMapKeyRef:
          name: aws-config
          key: AWS_ENDPOINT_URL
    volumeMounts:
    - name: backup
      mountPath: /backup

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-config
  namespace: mongo-repl
data:
  AWS_DEFAULT_REGION: us-east-1
  AWS_ENDPOINT_URL: http://<your-minio-service-url> 

---

apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: mongo-repl
type: Opaque
data:
  AWS_ACCESS_KEY_ID: bWluaW9hY2Nlc3NrZXk=      # Base64编码的MinIO access key
  AWS_SECRET_ACCESS_KEY: bWluaW9zZWNyZXRrZXk=  # Base64编码的MinIO secret key

执行方案设计

在执行备份过程中,我们的解决方案设计了两个动态变量,以满足 “谁(用户名)需要备份哪个库(数据库名称)”。这种设计提供了高度的灵活性,为后续 DAAS 云数据库服务平台中 “备份恢复功能模块” 奠定了基础,但这样也带来了额外操作挑战:如何有效处理数据库名称和用户名的动态变化。一种常见的解决方案是创建 ConfigMap,但这也引入了另外的问题,例如在完成 Pod 任务后,我们需要删除 ConfigMap,这在执行其他备份任务时会导致任务必须串行执行。

利用envsubst工具,我们能妥善处理上述问题。我们首先将原来的$(MONGO_DB)形式改为${MONGO_DB},这样便能让envsubst正确解析和替换相关变量。

测试方案的可行性:

# 设置 VAR 变量
root@k8s-client:~# export MONGO_DB="demo_db"
root@k8s-client:~# export USERNAME="demo_user"

# 替换掉原文本中的 VAR 变量
root@k8s-client:~# envsubst < mongodb-backup-to-s3-pod.yaml | egrep "MONGO_DB|USERNAME" -A 1
    - name: MONGO_DB
      value: demo_db
--
    - name: MONGO_DB
      value: demo_db
    - name: USERNAME
      value: demo_user

我们需要编写一个run.sh脚本作为执行器,通过该脚本,我们能实现环境变量的动态替换并执行备份任务,从而避免了额外的 ConfigMap 管理工作以及串行执行的限制。

#!/bin/bash

export MONGO_DB=$1
export USERNAME=$2

envsubst < mongodb-backup-to-s3-pod.yaml | kubectl apply -f -
# 提供执行的权限:
chmod +x run.sh

# 运行脚本并传入实际参数:
./run.sh "database" "@mystic"

这个方法使我们可以更高效地执行备份任务,而无需为额外的 ConfigMap 管理工作分心。此外,由于每个任务都在一个新的、具有独立环境变量的 Shell Session 中进行,因此我们可以同时处理多个备份任务,避免了串行执行的局限。这种方式极大地优化了工作流程,降低了心智负担,更方便于程序端的开发调用。

CronJob Backup

编写mongodb-backup-to-s3-cron.yaml 执行定时备份任务,方案逻辑同上:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: mongodb-backup-cron
  namespace: mongo-repl   
spec:
  schedule: "$SCHEDULE"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: mongo-client
            image: mongo
            command: ["/bin/sh", "-c"]
            args:
            - >
              mongodump --uri="${CONNECTION_STRING}" --db="$MONGO_DB" --out="/backup/mdb-$MONGO_DB-$(date +'%Y-%m-%d')" &&
              sleep 15
            env:
            - name: MONGO_DB
              value: $MONGO_DB
            - name: CONNECTION_STRING
              valueFrom:
                secretKeyRef:
                  name: mongodb-admin-root
                  key: connectionString.standardSrv
            volumeMounts:
            - name: backup
              mountPath: /backup
          - name: aws-client
            image: garland/aws-cli-docker:1.16.133
            command: ["/bin/sh", "-c"]
            args: 
            - >
              while true; do
              if [ -f "/backup/mdb-$MONGO_DB-$(date +'%Y-%m-%d')" ]; then
                aws s3 cp "/backup/mdb-$MONGO_DB-$(date +'%Y-%m-%d')" "s3://mongo-backups/$USERNAME/mdb-$MONGO_DB-$(date +'%Y-%m-%d')"
                break
              fi
              sleep 10
              done
            env:
            - name: MONGO_DB
              value: $MONGO_DB
            - name: USERNAME
              value: $USERNAME
            - name: AWS_ACCESS_KEY_ID
              valueFrom:
                secretKeyRef:
                  name: aws-credentials
                  key: AWS_ACCESS_KEY_ID
            - name: AWS_SECRET_ACCESS_KEY
              valueFrom:
                secretKeyRef:
                  name: aws-credentials
                  key: AWS_SECRET_ACCESS_KEY
            - name: AWS_DEFAULT_REGION
              valueFrom:
                configMapKeyRef:
                  name: aws-config
                  key: AWS_DEFAULT_REGION
            - name: AWS_ENDPOINT_URL
              valueFrom:
                configMapKeyRef:
                  name: aws-config
                  key: AWS_ENDPOINT_URL
            volumeMounts:
            - name: backup
              mountPath: /backup
          volumes:
          - name: backup
            emptyDir: {}

使用以下脚本来调用这个 CronJob

 #!/bin/bash

export MONGO_DB=$1
export USERNAME=$2
export SCHEDULE=$3

envsubst < mongodb-backup-to-s3-cron.yaml | kubectl apply -f -

运行脚本并传入实际参数:

./run-cron.sh "database" "@mystic" "*/5 * * * *"

Restore Dump

编写mongodb-``restore``-``from``-s3.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mongodb-restore
  namespace: mongo-repl
spec:
  restartPolicy: OnFailure
  volumes:
  - name: restore
    emptyDir: {}
  containers:
  - name: aws-client
    image: garland/aws-cli-docker:1.16.133
    command: ["/bin/sh", "-c"]
    args: 
    - >
      aws s3 cp s3://mongo-backups/${USERNAME}/${FILENAME} /restore/ &&
      sleep 15
    env:
    - name: USERNAME
      value: $USERNAME
    - name: FILENAME
      value: $FILENAME
    - name: AWS_ACCESS_KEY_ID
      valueFrom:
        secretKeyRef:
          name: aws-credentials
          key: AWS_ACCESS_KEY_ID
    - name: AWS_SECRET_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: aws-credentials
          key: AWS_SECRET_ACCESS_KEY
    - name: AWS_DEFAULT_REGION
      valueFrom:
        configMapKeyRef:
          name: aws-config
          key: AWS_DEFAULT_REGION
    - name: AWS_ENDPOINT_URL
      valueFrom:
        configMapKeyRef:
          name: aws-config
          key: AWS_ENDPOINT_URL
    volumeMounts:
    - name: restore
      mountPath: /restore
  - name: mongo-client
    image: mongo
    command: ["/bin/sh", "-c"]
    args:
    - >
      while true; do
      if [ -f /restore/${FILENAME} ]; then
        mongorestore --uri=${CONNECTION_STRING} --db=${TARGET_DBNAME} /restore/${FILENAME}
        break
      fi
      sleep 10
      done
    env:
    - name: TARGET_DBNAME
      value: $TARGET_DBNAME
    - name: FILENAME
      value: $FILENAME
    - name: CONNECTION_STRING
      valueFrom:
        secretKeyRef:
          name: mongodb-admin-root
          key: connectionString.standardSrv
    volumeMounts:
    - name: restore
      mountPath: /restore

使用以下脚本来执行恢复任务:

#!/bin/bash

export USERNAME=$1
export FILENAME=$2
export TARGET_DBNAME=$3

envsubst < mongodb-restore-from-s3.yaml | kubectl apply -

运行脚本并传入实际参数:

./run-restore.sh "@mystic" "mdb-database-2023-12-30" "newdatabase"

Other solutions

blog.palark.com/running-mon…

blog.min.io/acceleratin…