如何在Kubernetes上扩展MongoDB

DanJill 发布于1年前

如何在Kubernetes上扩展MongoDB

原文

Kubernetes主要用于无状态应用程序。 但是,在1.3版本中引入了PetSets,之后它们演变为StatefulSets。 官方文档将StatefulSets描述为“StatefulSets旨在与有状态应用程序和分布式系统一起使用”。

对此最好的用例之一是对数据存储服务进行编排,例如MongoDB,ElasticSearch,Redis,Zookeeper等。

我们可以把StatefulSets的特性归纳如下:

  1. 有序索引Pods
  2. 稳定的网络ID
  3. 有序并行的Pods管理
  4. 滚动更新

这些细节可以在 这里 找到。

StatefulSets的一个非常明显的特征是提供稳定网络ID,与 headless-services 一起使用时,功能可以更加强大。

我们不花费很多时间在Kubernetes文档中随时可以查看的信息,让我们专注于运行和扩展MongoDB集群。

您需要一个可以运行的Kubernetes群集并启用RBAC(推荐)。 在本教程中,我将使用GKE集群,但是,AWS EKS或Microsoft的AKS或Kops管理的K8S也是可行的替代方案。

我们将为MongoDB集群部署以下组件:

  1. 配置HostVM的Daemon Set
  2. Mongo Pods的Service Account和ClusterRole Binding
  3. 为Pods提供永久性存储SSDs的Storage Class
  4. 访问Mongo容器的Headless Service
  5. Mongo Pods Stateful Set
  6. GCP Internal LB: 从kubernetes集群外部访问MongoDB(可选)
  7. 使用Ingress访问Pod(可选)

值得注意的是,每个MongoDB Pod都会运行一个sidecar,以便动态配置副本集。Sidecar每5秒检查一次新成员。

Daemon Set for HostVM Configuration:

```yaml

kind: DaemonSet

apiVersion: extensions/v1beta1

metadata:

name: hostvm-configurer

labels:

app: startup-script

spec:

template:

metadata:

labels:

app: startup-script

spec:

hostPID: true

containers:

- name: hostvm-configurer-container

image: gcr.io/google-containers/startup-script:v1

securityContext:

privileged: true

env:

- name: STARTUP_SCRIPT

value: |

#! /bin/bash

set -o errexit

set -o pipefail

set -o nounset

# Disable hugepages

echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled

echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag

```

Configuration for ServiceAccount, Storage Class, Headless SVC and StatefulSet:

yaml

apiVersion: v1

kind: Namespace

metadata:

name: mongo

apiVersion: v1

kind: ServiceAccount

metadata:

name: mongo

namespace: mongo

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRoleBinding

metadata:

name: mongo

subjects:

- kind: ServiceAccount

name: mongo

namespace: mongo

roleRef:

kind: ClusterRole

name: cluster-admin

apiGroup: rbac.authorization.k8s.io

apiVersion: storage.k8s.io/v1beta1

kind: StorageClass

metadata:

name: fast

provisioner: kubernetes.io/gce-pd

parameters:

type: pd-ssd

fsType: xfs

allowVolumeExpansion: true

apiVersion: v1

kind: Service

metadata:

name: mongo

namespace: mongo

labels:

name: mongo

spec:

ports:

- port: 27017

targetPort: 27017

clusterIP: None

selector:

role: mongo

apiVersion: apps/v1beta1

kind: StatefulSet

metadata:

name: mongo

namespace: mongo

spec:

serviceName: mongo

replicas: 3

template:

metadata:

labels:

role: mongo

environment: staging

replicaset: MainRepSet

spec:

affinity:

podAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:

- weight: 100

podAffinityTerm:

labelSelector:

matchExpressions:

- key: replicaset

operator: In

values:

- MainRepSet

topologyKey: kubernetes.io/hostname

terminationGracePeriodSeconds: 10

serviceAccountName: mongo

containers:

- name: mongo

image: mongo

command:

- mongod

- "--wiredTigerCacheSizeGB"

- "0.25"

- "--bind_ip"

- "0.0.0.0"

- "--replSet"

- MainRepSet

- "--smallfiles"

- "--noprealloc"

ports:

- containerPort: 27017

volumeMounts:

- name: mongo-persistent-storage

mountPath: /data/db

resources:

requests:

cpu: 1

memory: 2Gi

- name: mongo-sidecar

image: cvallance/mongo-k8s-sidecar

env:

- name: MONGO_SIDECAR_POD_LABELS

value: "role=mongo,environment=staging"

- name: KUBE_NAMESPACE

value: "mongo"

- name: KUBERNETES_MONGO_SERVICE_NAME

value: "mongo"

volumeClaimTemplates:

- metadata:

name: mongo-persistent-storage

annotations:

volume.beta.kubernetes.io/storage-class: "fast"

spec:

accessModes: [ "ReadWriteOnce" ]

storageClassName: fast

resources:

requests:

storage: 10Gi

关键点:

1. 应该使用适当的环境变量仔细配置Mongo的Sidecar,以及为pod提供的标签,和为deployment和service的命名空间。 有关sidecar容器的详细信息,请点击 此处

2. 默认缓存大小的指导值是:“50%的RAM减去1GB,或256MB”。 鉴于所请求的内存量为2GB,此处的WiredTiger缓存大小已设置为256MB。

3. Inter-Pod Anti-Affinity确保在同一个工作节点上不会安排2个Mongo Pod,从而使其能够适应节点故障。 此外,建议将节点保留在不同的可用区中,以便集群能够抵御区域故障。

4. 当前部署的Service Account具有管理员权限。 但是,它应该仅限于DB的命名空间。

上面提到的两个配置文件也可以在 这里 找到。

部署MongoDB集群

shell

kubectl apply -f configure-node.yml

kubectl apply -f mongo.yml

你可以通过以下命令查看所有组件状况:

shell

kubectl -n mongo get all
shell

root$ kubectl -n mongo get all

NAME                 DESIRED   CURRENT   AGE

statefulsets/mongo   3         3         3m

NAME         READY     STATUS    RESTARTS   AGE

po/mongo-0   2/2       Running   0          3m

po/mongo-1   2/2       Running   0          2m

po/mongo-2   2/2       Running   0          1m

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE

svc/mongo   ClusterIP   None         <none>        27017/TCP   3m

如您所见,该服务没有Cluster-IP,也没有External-IP,它是Headless服务。 此服务将直接解析为StatefulSets的Pod-IP。

让我们来验证一下DNS解析。 我们在集群中启动了一个交互式shell:

shell

kubectl run my-shell --rm -i --tty --image ubuntu -- bash

root@my-shell-68974bb7f7-cs4l9:/# dig mongo.mongo +search +noall +answer

; <<>> DiG 9.11.3-1ubuntu1.1-Ubuntu <<>> mongo.mongo +search +noall +answer

;; global options: +cmd

mongo.mongo.svc.cluster.local. 30 IN A 10.56.7.10

mongo.mongo.svc.cluster.local. 30 IN A 10.56.8.11

mongo.mongo.svc.cluster.local. 30 IN A 10.56.1.4

服务的DNS规则是<服务名称>.<服务的命名空间>,因此,在我们的例子中看到的是mongo.mongo。

IPs(10.56.6.17,10.56.7.10,10.56.8.11)是我们的Mongo StatefulSets的Pod IPs。 这可以通过在集群内部运行nslookup来测试。

shell

root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.6.17

17.6.56.10.in-addr.arpa name = mongo-0.mongo.mongo.svc.cluster.local.

root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.7.10

10.7.56.10.in-addr.arpa name = mongo-1.mongo.mongo.svc.cluster.local.

root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.8.11

11.8.56.10.in-addr.arpa name = mongo-2.mongo.mongo.svc.cluster.local.

如果您的应用程序部署在K8的群集中,那么它可以通过以下方式访问节点:

Node-0: mongo-0.mongo.mongo.svc.cluster.local:27017 

Node-1: mongo-1.mongo.mongo.svc.cluster.local:27017 

Node-2: mongo-2.mongo.mongo.svc.cluster.local:27017

如果要从集群外部访问mongo节点,你可以为每个pod部署内部负载平衡或使用Ingress Controller(如NGINX或Traefik)创建一个内部Ingress。

GCP Internal LB SVC Configuration (可选)

yaml

apiVersion: v1

kind: Service

metadata: 

  annotations: 

    cloud.google.com/load-balancer-type: Internal

  name: mongo-0

  namespace: mongo

spec: 

  ports: 

    - 

      port: 27017

      targetPort: 27017

  selector: 

    statefulset.kubernetes.io/pod-name: mongo-0

  type: LoadBalancer

为mongo-1和mongo-2也部署2个此类服务。

您可以将内部负载均衡的IP提供给MongoClient URI。

shell

root$ kubectl -n mongo get svc

NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)           AGE

mongo     ClusterIP      None            <none>        27017/TCP         15m

mongo-0   LoadBalancer   10.59.252.157   10.20.20.2    27017:30184/TCP   9m

mongo-1   LoadBalancer   10.59.252.235   10.20.20.3    27017:30343/TCP   9m

mongo-2   LoadBalancer   10.59.254.199   10.20.20.4    27017:31298/TCP   9m

mongo-0/1/2的外部IP是新创建的TCP负载均衡器的IP。 这些是您的子网或对等网络,如果有的话。

通过Ingress访问Pods(可选)

也可以使用诸如Nginx之类的Ingress Controller来定向到Mongo StatefulSets的流量。 确保ingress服务是内部服务,而不是通过PublicIP公开。 Ingress对象的配置看起来像这样:

yaml

...

spec:

  rules:

  - host: mongo.example.com

    http:

      paths:

      - path: '/mongo-0'

        backend:

          hostNames:

          - mongo-0

          serviceName: mongo # There is no extra service. This is the headless service.

          servicePort: '27017'

请务必注意,您的应用程序至少应该知道一个当前处于启动状态的mongo节点,这样可以发现所有其他节点。

我在本地mac上使用Robo 3T作为mongo客户端。 连接到其中一个节点后并运行rs.status(),您可以查看副本集的详细信息,并检查是否已配置其他2个Pods并自动连接到副本集。

rs.status()查看副本集名称和成员个数:

如何在Kubernetes上扩展MongoDB

每个成员都可以看到FQDN和状态。 此FQDN只能从群集内部访问。

如何在Kubernetes上扩展MongoDB

每个secondary成员正在同步到mongo-0,mongo-0是当前的primary。

如何在Kubernetes上扩展MongoDB

现在我们扩展mongo Pods的Stateful Set以检查新的mongo容器是否被添加到ReplicaSet。

shell

root$ kubectl -n mongo scale statefulsets mongo --replicas=4

statefulset "mongo" scaled

root$ kubectl -n mongo get pods -o wide

NAME      READY     STATUS    RESTARTS   AGE       IP           NODE

mongo-0   2/2       Running   0          25m       10.56.6.17   gke-k8-demo-demo-k8-pool-1-45712bb7-vfqs

mongo-1   2/2       Running   0          24m       10.56.7.10   gke-k8-demo-demo-k8-pool-1-c6901f2e-trv5

mongo-2   2/2       Running   0          23m       10.56.8.11   gke-k8-demo-demo-k8-pool-1-c7622fba-qayt

mongo-3   2/2       Running   0          3m        10.56.1.4    gke-k8-demo-demo-k8-pool-1-85308bb7-89a4

可以看出,所有四个pod都部署到不同的GKE节点,因此我们的Pod-Anti Affinity策略工作正常。

扩展操作还将自动提供持久卷,该卷将充当新pod的数据目录。

shell

root$ kubectl -n mongo get pvc

NAME                               STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE

mongo-persistent-storage-mongo-0   Bound     pvc-337fb7d6-9f8f-11e8-bcd6-42010a940024   11G        RWO            fast           49m

mongo-persistent-storage-mongo-1   Bound     pvc-53375e31-9f8f-11e8-bcd6-42010a940024   11G        RWO            fast           49m

mongo-persistent-storage-mongo-2   Bound     pvc-6cee0f97-9f8f-11e8-bcd6-42010a940024   11G        RWO            fast           48m

mongo-persistent-storage-mongo-3   Bound     pvc-3e89573f-9f92-11e8-bcd6-42010a940024   11G        RWO            fast           28m

要检查名为mongo-3的pod是否已添加到副本集,我们将在同一节点上再次运行rs.status()并观察其差异。

对于同一个的Replicaset,成员数现在为4。

如何在Kubernetes上扩展MongoDB

新添加的成员遵循与先前成员相同的FQDN方案,并且还与同一主节点同步:

如何在Kubernetes上扩展MongoDB

进一步的考虑

  1. 给Mongo Pod的Node Pool打上合适的label并确保在StatefulSets和HostVM配置的DaemonSets的Spec中指定适当的Node Affinity会很有帮助。 这是因为DaemonSet将调整主机操作系统的一些参数,并且这些设置应仅限于MongoDB Pod。 没有这些设置,对其他应用程序可能会更好。
  2. 在GKE中给Node Pool打Label非常容易,可以直接从GCP控制台进行。
  3. 虽然我们在Pod的Spec中指定了CPU和内存限制,但我们也可以考虑部署VPA(Vertical Pod Autoscaler)。
  4. 可以通过实施网络策略或服务网格(如Istio)来控制从集群内部到我们的数据库的流量。

如果你已经看到这里,我相信你已经浏览了整个博文。 我试图整理很多分散的信息并将其作为一个整体呈现。 我的目标是为您提供足够的信息,以便开始使用Kubernetes上的Stateful Sets,并希望你们中的许多人觉得它很有用。 我们非常欢迎您提出反馈、意见或建议。:)

查看原文: 如何在Kubernetes上扩展MongoDB

  • smallwolf
  • purplegorilla
  • redkoala
  • brownpanda
  • silvercat
  • yellowostrich
  • redpanda