如何在Kubernetes上擴充套件MongoDB
如何在Kubernetes上擴充套件MongoDB
ofollow,noindex" target="_blank">原文
Kubernetes主要用於無狀態應用程式。 但是,在1.3版本中引入了PetSets,之後它們演變為StatefulSets。 官方文件將StatefulSets描述為“StatefulSets旨在與有狀態應用程式和分散式系統一起使用”。
對此最好的用例之一是對資料儲存服務進行編排,例如MongoDB,ElasticSearch,Redis,Zookeeper等。
我們可以把StatefulSets的特性歸納如下:
- 有序索引Pods
- 穩定的網路ID
- 有序並行的Pods管理
- 滾動更新
這些細節可以在 這裡 找到。
StatefulSets的一個非常明顯的特徵是提供穩定網路ID,與 headless-services 一起使用時,功能可以更加強大。
我們不花費很多時間在Kubernetes文件中隨時可以檢視的資訊,讓我們專注於執行和擴充套件MongoDB叢集。
您需要一個可以執行的Kubernetes群集並啟用RBAC(推薦)。 在本教程中,我將使用GKE叢集,但是,AWS EKS或Microsoft的AKS或Kops管理的K8S也是可行的替代方案。
我們將為MongoDB叢集部署以下元件:
- 配置HostVM的Daemon Set
- Mongo Pods的Service Account和ClusterRole Binding
- 為Pods提供永久性儲存SSDs的Storage Class
- 訪問Mongo容器的Headless Service
- Mongo Pods Stateful Set
- GCP Internal LB: 從kubernetes叢集外部訪問MongoDB(可選)
- 使用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 NAMEDESIREDCURRENTAGE statefulsets/mongo333m NAMEREADYSTATUSRESTARTSAGE po/mongo-02/2Running03m po/mongo-12/2Running02m po/mongo-22/2Running01m NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE svc/mongoClusterIPNone<none>27017/TCP3m
如您所見,該服務沒有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 NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE mongoClusterIPNone<none>27017/TCP15m mongo-0LoadBalancer10.59.252.15710.20.20.227017:30184/TCP9m mongo-1LoadBalancer10.59.252.23510.20.20.327017:30343/TCP9m mongo-2LoadBalancer10.59.254.19910.20.20.427017:31298/TCP9m
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()檢視副本集名稱和成員個數:

每個成員都可以看到FQDN和狀態。 此FQDN只能從群集內部訪問。

每個secondary成員正在同步到mongo-0,mongo-0是當前的primary。

現在我們擴充套件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 NAMEREADYSTATUSRESTARTSAGEIPNODE mongo-02/2Running025m10.56.6.17gke-k8-demo-demo-k8-pool-1-45712bb7-vfqs mongo-12/2Running024m10.56.7.10gke-k8-demo-demo-k8-pool-1-c6901f2e-trv5 mongo-22/2Running023m10.56.8.11gke-k8-demo-demo-k8-pool-1-c7622fba-qayt mongo-32/2Running03m10.56.1.4gke-k8-demo-demo-k8-pool-1-85308bb7-89a4
可以看出,所有四個pod都部署到不同的GKE節點,因此我們的Pod-Anti Affinity策略工作正常。
擴充套件操作還將自動提供持久卷,該卷將充當新pod的資料目錄。
shell root$ kubectl -n mongo get pvc NAMESTATUSVOLUMECAPACITYACCESS MODESSTORAGECLASSAGE mongo-persistent-storage-mongo-0Boundpvc-337fb7d6-9f8f-11e8-bcd6-42010a94002411GRWOfast49m mongo-persistent-storage-mongo-1Boundpvc-53375e31-9f8f-11e8-bcd6-42010a94002411GRWOfast49m mongo-persistent-storage-mongo-2Boundpvc-6cee0f97-9f8f-11e8-bcd6-42010a94002411GRWOfast48m mongo-persistent-storage-mongo-3Boundpvc-3e89573f-9f92-11e8-bcd6-42010a94002411GRWOfast28m
要檢查名為mongo-3的pod是否已新增到副本集,我們將在同一節點上再次執行rs.status()並觀察其差異。
對於同一個的Replicaset,成員數現在為4。

新新增的成員遵循與先前成員相同的FQDN方案,並且還與同一主節點同步:

進一步的考慮
- 給Mongo Pod的Node Pool打上合適的label並確保在StatefulSets和HostVM配置的DaemonSets的Spec中指定適當的Node Affinity會很有幫助。 這是因為DaemonSet將調整主機作業系統的一些引數,並且這些設定應僅限於MongoDB Pod。 沒有這些設定,對其他應用程式可能會更好。
- 在GKE中給Node Pool打Label非常容易,可以直接從GCP控制檯進行。
- 雖然我們在Pod的Spec中指定了CPU和記憶體限制,但我們也可以考慮部署VPA(Vertical Pod Autoscaler)。
- 可以通過實施網路策略或服務網格(如Istio)來控制從叢集內部到我們的資料庫的流量。
如果你已經看到這裡,我相信你已經瀏覽了整個博文。 我試圖整理很多分散的資訊並將其作為一個整體呈現。 我的目標是為您提供足夠的資訊,以便開始使用Kubernetes上的Stateful Sets,並希望你們中的許多人覺得它很有用。 我們非常歡迎您提出反饋、意見或建議。:)