Kubernetes主要用于无状态应用程序。 但是,在1.3版本中引入了PetSets,之后它们演变为StatefulSets。 官方文档将StatefulSets描述为“StatefulSets旨在与有状态应用程序和分布式系统一起使用”。
对此最好的用例之一是对数据存储服务进行编排,例如MongoDB,ElasticSearch,Redis,ZooKeeper等。
我们可以把StatefulSets的特性归纳如下:
有序索引Pod
稳定的网络ID
有序并行的Pod管理
滚动更新
这些细节可以在这里[1]找到。
StatefulSets的一个非常明显的特征是提供稳定网络ID,与Headless Services[2]一起使用时,功能可以更加强大。
我们在Kubernetes文档中随时可以查看的信息上不会花费很多时间,让我们专注于运行和扩展MongoDB集群。
你需要一个可以运行的Kubernetes群集并启用RBAC(推荐)。 在本教程中,我将使用GKE集群,但是,AWS EKS或Microsoft的AKS或Kops管理的Kubernetes也是可行的替代方案。
我们将为MongoDB集群部署以下组件:
配置HostVM的Daemon Set
Mongo Pod的Service Account和ClusterRole Binding
为Pod提供永久性存储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
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
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:mongosubjects: -
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: - mongo -"--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
关键点:
应该使用适当的环境变量仔细配置Mongo的Sidecar,以及为Pod提供的标签,和为deployment和service的命名空间。 有关Sidecar容器的详细信息,请点击此处[3]。
默认缓存大小的指导值是:“50%的RAM减去1GB,或256MB”。 鉴于所请求的内存量为2GB,此处的WiredTiger缓存大小已设置为256MB。
Inter-Pod Anti-Affinity确保在同一个工作节点上不会安排2个Mongo Pod,从而使其能够适应节点故障。 此外,建议将节点保留在不同的可用区中,以便集群能够抵御区域故障。
当前部署的Service Account具有管理员权限。 但是,它应该仅限于DB的命名空间。
上面提到的两个配置文件也可以在这里[4]找到。
部署MongoDB集群
kubectl apply -f configure-node.yml
kubectl apply -f mongo.yml
你可以通过以下命令查看所有组件状况:
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:
kubectl run my-shell --rm -i --tty --image ubuntu -- bash
root@my-shell-68974bb7f7-cs4l9:/# dig mongo.mongo +search +noall +answer
; <<>>DiG9.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.local30 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来测试。
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.1010.7.56.10.in-addr.arpa name = mongo-1.mongo.
mongo.svc.cluster.local.
root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.8.1111.8.
56.10.in-addr.arpa name = mongo-2.mongo.mongo.svc.cluster.local.
如果你的应用程序部署在Kubernetes的群集中,那么它可以通过以下方式访问节点:
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(可选)
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。
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访问Pod(可选)
也可以使用诸如Nginx之类的Ingress Controller来定向到Mongo StatefulSets的流量。 确保Ingress服务是内部服务,而不是通过PublicIP公开。 Ingress对象的配置看起来像这样:
...
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个Pod并自动连接到副本集。
rs.status()查看副本集名称和成员个数
每个成员都可以看到FQDN和状态。 此FQDN只能从群集内部访问。
每个secondary成员正在同步到mongo-0,mongo-0是当前的primary。
现在我们扩展mongo Pods的Stateful Set以检查新的Mongo容器是否被添加到ReplicaSet。
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的数据目录。
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
要检查名为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,并希望你们中的许多人觉得它很有用。 我们非常欢迎您提出反馈、意见或建议。:)
原文发布时间为:2018-12-13
本文作者: kelvinji2009 译
本文来自云栖社区合作伙伴“ 数据和云”,了解相关信息可以关注“OraNews”微信公众号