kubernetes快速入门10-StatefulSet控制器
有状态副本集。
使用要求:
- 稳定且唯一的网络标识符;
- 稳定且持久的存储;
- 有序、平滑地部署和扩展;
- 有序、平滑地终止和删除;
- 有序的滚动更新
StatefulSet由三个组件组成:
- Headless services
- StatefulSet控制器
- volumeClaimTemplate 存储卷申请模板
StatefulSet帮助信息,statefulset可简写为sts
KIND: StatefulSet
VERSION: apps/v1
spec <Object>
selector <Object> -required- 选择器
matchLabels <map[string]string> 匹配相应的标签,用于关联属于sts控制器管理的pod,主要是实现pod数量与replicas数量趋同
serviceName <string> -required- 指定关联的serviceName,实现sts中pod名称的解析,
解析格式为:pod-specific-string.serviceName.default.svc.cluster.local
template <Object> -required- 当副本数不足时创建pod时使用的模板
updateStrategy <Object> pod的更新策略
rollingUpdate <Object> 滚动更新
partition <integer> 按分区滚动更新,<integer>表示滚动更新的边界,即pod中编号大于等于integer的pod将会被更新,如myapp-0,myapp-1,myapp-2,myapp-3这4个pod,如果integer设置为2,那myapp-2,myapp3将会被更新,并且更新先更新编号大的,再更新编号较小的,创建时则是先创建编号小的,再创建编号大的。partition默认值为0,表示全部pod滚动更新
volumeClaimTemplates <[]Object> 声明式挂载存储卷的模板,会以此模板定义的要求创建pvc,并绑定符合要求的pv,供给pod的volumeMounts使用
replicas <integer> statefulset控制器管理的pod运行的副本数
先准备存储和pv,存储利用NFS实现
k8s@node01:~/my_manifests/volumes$ cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata: # PV资源不设置namespace
name: pv001
labels:
name: pv001
speed: slow
spec:
nfs:
path: /data/nfs/volume/v1
server: node01.k8s.com
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata: # PV资源不设置namespace
name: pv002
labels:
name: pv002
speed: medium
spec:
nfs:
path: /data/nfs/volume/v2
server: node01.k8s.com
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata: # PV资源不设置namespace
name: pv003
labels:
name: pv003
speed: medium
spec:
nfs:
path: /data/nfs/volume/v3
server: node01.k8s.com
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
k8s@node01:~/my_manifests/volumes$ kubectl apply -f pv-nfs.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
k8s@node01:~/my_manifests/volumes$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 2Gi RWO,RWX Retain Available 3s
pv002 2Gi RWO,RWX Retain Available 3s
pv003 2Gi RWO Retain Available 3s
准备配置清单
k8s@node01:~/my_manifests/statefullset$ cat sts-myapp-pods.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
namespace: default
labels:
app: myapp-svc
spec:
clusterIP: None # 无头service,使用selector对应的标签选择相应的pods
selector:
app: myapp-pods
ports:
- name: http
port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp
spec:
replicas: 2
serviceName: myapp-svc
selector:
matchLabels:
app: myapp-pods
template:
metadata:
labels:
app: myapp-pods # pods使用的标签
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: web
containerPort: 80
volumeMounts:
- name: myappdata # pod中挂载卷的名称,与volumeClaimTemplates中的名称对应
mountPath: /usr/share/nginx/html/
volumeClaimTemplates: # 卷申请模板,当pod启动时会以此模板创建相应的pvc
- metadata:
name: myappdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
k8s@node01:~/my_manifests/statefullset$ kubectl apply -f sts-myapp-pods.yaml
service/myapp-svc created
statefulset.apps/myapp created
# pod的名称为配置清单中“pod名称+编号”
k8s@node01:~/my_manifests/statefullset$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-0 1/1 Running 0 4m28s 10.244.2.105 node03 <none> <none>
myapp-1 1/1 Running 0 4m25s 10.244.1.82 node02 <none> <none>
# pvc的名称为 volumeClaimTemplates.metadata.name的值+pod名称
k8s@node01:~/my_manifests/statefullset$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
myappdata-myapp-0 Bound pv003 2Gi RWO 11m
myappdata-myapp-1 Bound pv001 2Gi RWO,RWX 11m
k8s@node01:~/my_manifests/statefullset$ kubectl get pv # 相应的pv已bound
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 2Gi RWO,RWX Retain Bound default/myappdata-myapp-1 72m
pv002 2Gi RWO,RWX Retain Available 72m
pv003 2Gi RWO Retain Bound default/myappdata-myapp-0 72m
# 在sts控制中使用了headless的service,pod的名称不能直接进行解析,解析名称应为:pod名称.service名称.名称空间.svc.cluster.local,对应
k8s@node01:~/my_manifests/statefullset$ dig -t A myapp-0.myapp-svc.default.svc.cluster.local @10.96.0.10
...
;; ANSWER SECTION:
myapp-0.myapp-svc.default.svc.cluster.local. 30 IN A 10.244.2.105
k8s@node01:~/my_manifests/statefullset$ dig -t A myapp-1.myapp-svc.default.svc.cluster.local @10.96.0.10
;; ANSWER SECTION:
myapp-1.myapp-svc.default.svc.cluster.local. 30 IN A 10.244.1.82
每一个pod的名称是固定的,删除一个pod后重建后的名称也是不会发生变化,pvc的名称里隐含了pod的名称,如:myappdata-myapp-0,所以可持续的为同一个pod提供存储卷服务。
StatefulSet资源也可以进行扩容和缩容,只要有空闲的pv就可进行扩容。
# 两种方式都可以实现扩容和缩容
k8s@node01:~$ kubectl scale sts myapp --replicas=3
k8s@node01:~$ kubectl patch sts myapp -p '{"spec":{"replicases":2}}'
StatefulSet中pod更新
先设置升级分区边界
k8s@node01:~$ kubectl get sts
NAME READY AGE
myapp 3/3 10h
k8s@node01:~$ kubectl describe sts myapp
Name: myapp
Namespace: default
CreationTimestamp: Wed, 29 Jul 2020 22:34:06 +0800
Selector: app=myapp-pods
Labels: <none>
Annotations: Replicas: 3 desired | 3 total
Update Strategy: RollingUpdate
Partition: 0 # 默认的更新边界为 0, 表示全部pod滚动更新
...
# 修改更新边界,这里只有3个Pod,可以只让更新编号为1和2的
k8s@node01:~$ kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":1}}}}'
statefulset.apps/myapp patched
k8s@node01:~$ kubectl describe sts myapp
Name: myapp
Namespace: default
CreationTimestamp: Wed, 29 Jul 2020 22:34:06 +0800
Selector: app=myapp-pods
Labels: <none>
Annotations: Replicas: 3 desired | 3 total
Update Strategy: RollingUpdate
Partition: 1 # 已被设置为1
# 修改pod中的image
k8s@node01:~$ kubectl set image sts/myapp myapp=ikubernetes/myapp:v2
statefulset.apps/myapp image updated
# 监视更新过程,先更新编号为2的,再更新编号为1的
k8s@node01:~/my_manifests/statefullset$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 9m56s
myapp-1 1/1 Running 1 10h
myapp-2 1/1 Running 0 8m59s
myapp-2 1/1 Terminating 0 10m
myapp-2 0/1 Terminating 0 10m
myapp-2 0/1 Terminating 0 10m
myapp-2 0/1 Terminating 0 10m
myapp-2 0/1 Pending 0 0s
myapp-2 0/1 Pending 0 0s
myapp-2 0/1 ContainerCreating 0 0s
myapp-2 1/1 Running 0 2s
myapp-1 1/1 Terminating 1 10h
myapp-1 0/1 Terminating 1 10h
myapp-1 0/1 Terminating 1 10h
myapp-1 0/1 Terminating 1 10h
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 ContainerCreating 0 0s
myapp-1 1/1 Running 0 3s
# 确认pod是否已更新
k8s@node01:~$ kubectl get pods myapp-0 -o yaml | grep image
f:image: {}
f:imagePullPolicy: {}
- image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
image: ikubernetes/myapp:v1 # myapp-0 保值v1版本
imageID: docker-pullable://ikubernetes/myapp@sha256:9c3dc30b5219788b2b8a4b065f548b922a34479577befb54b03330999d30d513
k8s@node01:~$ kubectl get pods myapp-1 -o yaml | grep image
f:image: {}
f:imagePullPolicy: {}
- image: ikubernetes/myapp:v2 # v2版本
imagePullPolicy: IfNotPresent
image: ikubernetes/myapp:v2
imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358
k8s@node01:~$ kubectl get pods myapp-2 -o yaml | grep image
f:image: {}
f:imagePullPolicy: {}
- image: ikubernetes/myapp:v2 # v2版本
imagePullPolicy: IfNotPresent
image: ikubernetes/myapp:v2
imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358
# 如果更新后无问题,就可以把边界设置为0,让剩下的也一并更新
在实际生产环境中把有状态的应用迁移到k8s要慎重,可以多参考google上已实现方案,这里有个redis的事例:https://github.com/CommercialTribe/kube-redis