30.StatefulSet
StatefulSet
使用案例参考:kubernetes contrib - statefulsets,其中包含
zookeeper 和kakfa 的statefulset 设置和使用说明。
- 稳定的持久化存储,即
Pod 重新调度后还是能访问到相同的持久化数据,基于PVC 来实现 - 稳定的网络标志,即
Pod 重新调度后其PodName 和HostName 不变,基于Headless Service (即没有Cluster IP 的Service )来实现 - 有序部署,有序扩展,即
Pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0 到N-1 ,在下一个Pod 运行之前所有之前的Pod 必须都是Running 和Ready 状态) ,基于init containers 来实现 - 有序收缩,有序删除(即从
N-1 到0 )
从上面的应用场景可以发现,
- 用于定义网络标志(DNS domain)的
Headless Service - 用于创建
PersistentVolumes 的volumeClaimTemplates - 定义具体应用的
StatefulSet
statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
,其中
serviceName
为Headless Service 的名字0..N-1
为Pod 所在的序号,从0 开始到N-1 statefulSetName
为StatefulSet 的名字namespace
为服务所在的namespace ,Headless Servic 和StatefulSet 必须在相同的namespace .cluster.local
为Cluster Domain
使用StatefulSet
- 稳定,唯一的网络标志。
- 稳定,持久化存储。
- 有序,优雅地部署和
scale 。 - 有序,优雅地删除和终止。
- 有序,自动的滚动升级。
在上文中,稳定是
限制
StatefulSet 是beta 资源,Kubernetes 1.5 以前版本不支持。- 对于所有的
alpha/beta 的资源,您都可以通过在apiserver 中设置--runtime-config
选项来禁用。 - 给定
Pod 的存储必须由PersistentVolume Provisioner 根据请求的storage class
进行配置,或由管理员预先配置。 - 删除或
scale StatefulSet 将不会删除与StatefulSet 相关联的volume 。这样做是为了确保数据安全性,这通常比自动清除所有相关StatefulSet 资源更有价值。 StatefulSets 目前要求 Headless Service 负责Pod 的网络身份。您有责任创建此服务。
组件
下面的示例中描述了
- 一个名为
nginx 的headless service ,用于控制网络域。 - 一个名为
web 的StatefulSet ,它的Spec 中指定在有3 个运行nginx 容器的Pod 。 volumeClaimTemplates 使用PersistentVolume Provisioner 提供的 PersistentVolumes 作为稳定存储。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: gcr.io/google_containers/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
annotations:
volume.beta.kubernetes.io/storage-class: anything
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
Pod 身份
序数
对于一个有
稳定的网络ID
$(statefulset名称)-$(序数)
。上面的例子将创建三个名为web-0,web-1,web-2
的
$(服务名称).$(namespace).svc.cluster.local
,其中 “cluster.local” 是集群域。
在创建每个$(pod 名称).$(管理服务域)
,其中管理服务由serviceName
字段定义。
以下是
Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
注意cluster.local
除非进行了其他配置。
稳定存储
anything
存储类创建的volumeMounts
将挂载与
部署和Scale 保证
- 对于有
N 个副本的StatefulSet ,Pod 将按照{0..N-1} 的顺序被创建和部署。 - 当 删除
Pod 的时候,将按照逆序来终结,从{N-1..0} - 对
Pod 执行scale 操作之前,它所有的前任必须处于Running 和Ready 状态。 - 在终止
Pod 前,它所有的继任者必须处于完全关闭状态。
不应该将pod.Spec.TerminationGracePeriodSeconds
设置为
上面的
如果用户通过修补replicas=1
,则
Pod 管理策略
在.spec.podManagementPolicy
字段保证身份的唯一性。
OrderedReady Pod 管理
OrderedReady
并行Pod 管理
Parallel
更新策略
在.spec.updateStrategy
字段允许您配置和禁用
删除
OnDelete
更新策略实现了遗留(spec.updateStrategy
未指定时,这是默认策略。当.spec.updateStrategy.type
设置为 OnDelete
时,StatefulSet
中的.spec.template
进行的修改。
滚动更新
RollingUpdate
更新策略在.spec.updateStrategy.type
设置为 RollingUpdate
时,
分区
可以通过指定 .spec.updateStrategy.rollingUpdate.partition
来对 RollingUpdate
更新策略进行分区。如果指定了分区,则当.spec.template
更新时,具有大于或等于分区序数的所有.spec.updateStrategy.rollingUpdate.partition
大于其 .spec.replicas
,则其 .spec.template
的更新将不会传播到
在大多数情况下,您不需要使用分区,但如果您想要进行分阶段更新,使用金丝雀发布或执行分阶段发布,它们将非常有用。
简单示例
以一个简单的
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: gcr.io/google_containers/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
$ kubectl create -f web.yaml
service "nginx" created
statefulset "web" created
# 查看创建的headless service和statefulset
$ kubectl get service nginx
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx None <none> 80/TCP 1m
$ kubectl get statefulset web
NAME DESIRED CURRENT AGE
web 2 2 2m
# 根据volumeClaimTemplates自动创建PVC(在GCE中会自动创建kubernetes.io/gce-pd类型的volume)
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-d064a004-d8d4-11e6-b521-42010a800002 1Gi RWO 16s
www-web-1 Bound pvc-d06a3946-d8d4-11e6-b521-42010a800002 1Gi RWO 16s
# 查看创建的Pod,他们都是有序的
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 5m
web-1 1/1 Running 0 4m
# 使用nslookup查看这些Pod的DNS
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
/ # nslookup web-0.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.2.10
/ # nslookup web-1.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.3.12
/ # nslookup web-0.nginx.default.svc.cluster.local
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx.default.svc.cluster.local
Address 1: 10.244.2.10
还可以进行其他的操作
# 扩容
$ kubectl scale statefulset web --replicas=5
# 缩容
$ kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
# 镜像更新(目前还不支持直接更新image,需要patch来间接实现)
$ kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.7"}]'
# 删除StatefulSet和Headless Service
$ kubectl delete statefulset web
$ kubectl delete service nginx
# StatefulSet删除后PVC还会保留着,数据不再使用的话也需要删除
$ kubectl delete pvc www-web-0 www-web-1
zookeeper
另外一个更能说明
---
apiVersion: v1
kind: Service
metadata:
name: zk-headless
labels:
app: zk-headless
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zk
---
apiVersion: v1
kind: ConfigMap
metadata:
name: zk-config
data:
ensemble: "zk-0;zk-1;zk-2"
jvm.heap: "2G"
tick: "2000"
init: "10"
sync: "5"
client.cnxns: "60"
snap.retain: "3"
purge.interval: "1"
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-budget
spec:
selector:
matchLabels:
app: zk
minAvailable: 2
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: zk
spec:
serviceName: zk-headless
replicas: 3
template:
metadata:
labels:
app: zk
annotations:
pod.alpha.kubernetes.io/initialized: "true"
scheduler.alpha.kubernetes.io/affinity: >
{
"podAntiAffinity": {
"requiredDuringSchedulingRequiredDuringExecution": [{
"labelSelector": {
"matchExpressions": [{
"key": "app",
"operator": "In",
"values": ["zk-headless"]
}]
},
"topologyKey": "kubernetes.io/hostname"
}]
}
}
spec:
containers:
- name: k8szk
imagePullPolicy: Always
image: gcr.io/google_samples/k8szk:v1
resources:
requests:
memory: "4Gi"
cpu: "1"
ports:
- containerPort: 2181
name: client
- containerPort: 2888
name: server
- containerPort: 3888
name: leader-election
env:
- name: ZK_ENSEMBLE
valueFrom:
configMapKeyRef:
name: zk-config
key: ensemble
- name: ZK_HEAP_SIZE
valueFrom:
configMapKeyRef:
name: zk-config
key: jvm.heap
- name: ZK_TICK_TIME
valueFrom:
configMapKeyRef:
name: zk-config
key: tick
- name: ZK_INIT_LIMIT
valueFrom:
configMapKeyRef:
name: zk-config
key: init
- name: ZK_SYNC_LIMIT
valueFrom:
configMapKeyRef:
name: zk-config
key: tick
- name: ZK_MAX_CLIENT_CNXNS
valueFrom:
configMapKeyRef:
name: zk-config
key: client.cnxns
- name: ZK_SNAP_RETAIN_COUNT
valueFrom:
configMapKeyRef:
name: zk-config
key: snap.retain
- name: ZK_PURGE_INTERVAL
valueFrom:
configMapKeyRef:
name: zk-config
key: purge.interval
- name: ZK_CLIENT_PORT
value: "2181"
- name: ZK_SERVER_PORT
value: "2888"
- name: ZK_ELECTION_PORT
value: "3888"
command:
- sh
- -c
- zkGenConfig.sh && zkServer.sh start-foreground
readinessProbe:
exec:
command:
- "zkOk.sh"
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
exec:
command:
- "zkOk.sh"
initialDelaySeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: datadir
mountPath: /var/lib/zookeeper
securityContext:
runAsUser: 1000
fsGroup: 1000
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
kubectl create -f zookeeper.yaml
详细的使用说明见zookeeper stateful application。
关于
集群外部访问StatefulSet 的Pod
我们设想一下这样的场景:在
方法是为kubectl expose
将其以
kubectl label pod zk-0 zkInst=0
kubectl label pod zk-1 zkInst=1
kubectl expose po zk-0 --port=2181 --target-port=2181 --name=zk-0 --selector=zkInst=0 --type=NodePort
kubectl expose po zk-1 --port=2181 --target-port=2181 --name=zk-1 --selector=zkInst=1 --type=NodePort
这样在
查看zk-0
这个
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
zk-0 10.254.98.14 <nodes> 2181:31693/TCP 5m
集群外部就可以使用所有的