在实际使用的过程中,Deployment
并不能编排所有类型的应用,它适用于编排无状态服务的 Pod,StatefulSet
适用于编排有状态服务的 Pod。
-
无状态服务(Stateless Service)
:该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的,比如 WordPress 实例,我们可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的。 -
有状态服务(Stateful Service)
:该服务运行的实例需要在本地存储持久化数据,比如 Etcd 实例,Etcd 集群启动有先后顺序,他们会把数据持久化存储,当启动一个新的 Pod 时,他们是按照持久化里数据顺序加入集群。
Kubernetes 引入了 StatefulSet 这种资源对象来支持这种复杂的需求。StatefulSet 类似于 ReplicaSet,但是它可以处理 Pod 的启动顺序,为保留每个 Pod 的状态设置唯一标识,具有以下几个功能特性:
- 稳定的、唯一的网络标识符
- 稳定的、持久化的存储
- 有序的、优雅的部署和缩放
- 有序的、优雅的删除和终止
- 有序的、自动滚动更新
Headless Service
在介绍 StatefulSet 之前需要先介绍一下无头服务 Headless Service
,就是 Service 没有 ClusterIP。
-
有头服务:部署一个 3 副本的服务,会通过 Service 使其达到负载均衡,这个时候 Service 会有一个 ClusterIP。比如:创建了一个 Service 名为:mysvc;ClusterIP 为:10.250.33.99;命名空间为 default。通过 DNS 访问到 Pod 服流程为:
mysvc.default.svc.cluster.local -> 10.250.33.99 -> Pod
-
无头服务:部署一个 3 副本的服务,不给 Service 创建 ClusterIP。比如:创建了一个 Service 名为:mysvc;没有 ClusterIP;命名空间为 default。通过 DNS 访问到 Pod 服流程为:
mypod1.mysvc.default.svc.cluster.local -> Pod
,这个样子省去一个负载均衡的环节,使用无头服务服务的用途在需要固定 Pod 名称的场景
Headless Service 资源类型:
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
ports:
- name: http
port: 80
clusterIP: None #此处 ClusterIP 设置为 None
selector:
app: nginx
StatefulSet
先准备两个 1G 的存储卷(PV), PV 是实际的存储卷,PVC 是挂载卷的逻辑
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv001
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv002
直接创建 PV
[root@kube01 sts]# kubectl apply -f pv.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
[root@kube01 sts]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO Retain Available 4s
pv002 1Gi RWO Retain Available 4s
新建StatefulSet 资源:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
serviceName: "nginx" # 为通过 DNS 访问固定 service 名称
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 通过 PVC 自动获取适用的 PV 并进行绑定
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
在 StatefulSet 资源中发现了 serviceName
属性,这是因为此处设置的 Pod 的子域名,设置后可以通过mypod1.mysvc.default.svc.cluster.local
访问
通过 describe 查看到 Pod 的 Controlled By 属于 StatefulSet,与 Deployment 还是不一样的,Deployment 是通过控制 ReplicaSet 控制 Pod,StatefulSet 是直接控制 Pod
[root@kube01 sts]# kubectl describe pod web-0
Name: web-0
Namespace: default
Priority: 0
Node: kube02/192.168.17.6
Start Time: Tue, 13 Jun 2023 11:15:28 +0800
Labels: app=nginx
controller-revision-hash=web-7466694c86
statefulset.kubernetes.io/pod-name=web-0
Annotations: <none>
Status: Running
IP: 172.30.1.42
IPs:
IP: 172.30.1.42
Controlled By: StatefulSet/web
Containers:
nginx:
Container ID: docker://a4c4b0e27886ce2ba62a472c5a296b1fa12beca2e006523427d3ee7af309fc84
Image: nginx:1.7.9
Image ID: docker-pullable://nginx@sha256:e3456c851a152494c3e4ff5fcc26f240206abac0c9d794affb40e0714846c451
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Tue, 13 Jun 2023 11:15:29 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jhtxv (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
www:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: www-web-0
ReadOnly: false
kube-api-access-jhtxv:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events: <none>
#一个简单的快捷启动Pod方式
kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ #
滚动更新
在 StatefulSet 中同样也支持两种升级策略:onDelete 和 RollingUpdate,同样可以通过设置 .spec.updateStrategy.type 进行指定。
OnDelete
:该策略表示当更新了 StatefulSet 的模板后,只有手动删除旧的 Pod 才会创建新的 Pod。RollingUpdate
:该策略表示当更新 StatefulSet 模板后会自动删除旧的 Pod 并创建新的Pod,如果更新发生了错误,这次“滚动更新”就会停止。不过需要注意 StatefulSet 的 Pod 在部署时是顺序从 0n 的,而在滚动更新时,这些 Pod 则是按逆序的方式即 n0 一次删除并创建。