StatefulSet和Deployment控制器的区别

  • statefulSet下的Pod有DNS地址,通过解析Pod的DNS可以返回Pod的IP
  • deployment下的Pod没有DNS

通过StatefulSet和headless service部署的服务效果

为什么要用headless service+statefulSet部署有状态应用?

使用headless service+statefulSet可以实现

  • StatefulSet会为关联的Pod保持一个不变的Pod Name,其hostname格式为
    $(StatefulSet name)-$(序号【从0开始】)
  • StatefulSet关联到的每一个Pod分配一个dnsName
    $<Pod Name>.$<service name>.$<namespace name>.svc.cluster.local
  • headless service会为关联的statefulSet分配一个域,通过dns解析该域能获取到每个pod的ip+port
    <service name>.$<namespace name>.svc.cluster.local

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: rs-storage
hostPath:
path: /tmp/data
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
publishNotReadyAddresses: true #这个决定了未准备好的endpoints是否在DNS的记录中
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
# 指定StatefulSet控制器要使用这个Headless Service, 此service必须在 StatefulSet 之前存在
serviceName: nginx
replicas: 2
#默认更新策略:
updateStrategy:
rollingUpdate:
partition: 0 # 具有大于或等于分区序数的所有 Pod 将被更新。具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于其 .spec.replicas,则其 .spec.template 的更新将不会传播到 Pod。在大多数情况下,您不需要使用分区,但如果您想要进行分阶段更新、执行金丝雀或执行分阶段上线,则这些分区会非常有用
maxUnavailable: 1 # 控制更新期间不可用的 Pod 的最大数量。 该值可以是绝对值(例如,“5”)或者是期望 Pod 个数的百分比(例如,10%)。 绝对值是根据百分比值四舍五入计算的。 该字段不能为 0。默认设置为 1。
type: RollingUpdate # statefulset默认更新策略 滚动更新,有一个更新失败就不会继续更新,deployment是随机更新模式
#statefulset更新策略:
# 1. RollingUpdate 滚动更新 [从下往上更新,倒序更新]
# 2. OnDelete 手动更新 [需要删除一个pod才会触发更新策略]
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: cnych/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
# volumeClaimTemplates,仅可使用在statefulset, 称为卷申请模板,当StatefulSet使用VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC,每个PVC的编号为`{volumeClaimTemplates.metadata.name}-{pod.name}`。每个Pod都拥有独立的存储
volumeClaimTemplates:
- metadata:
# 和volumeMounts下的name匹配上
name: www
spec:
storageClassName: rs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

查看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[root@k3s-master2 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-30abfb58-55b2-4632-afc0-c2b5c2f97558 1Gi RWO local-path 114s
www-web-1 Bound pvc-0789bbd0-d61f-4c4a-897b-c9f9ce1f8a5f 1Gi RWO local-path 92s

# ------------------------查看存储卷------------------------
[root@k3s-master2 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO Retain Available 2m37s
pvc-30abfb58-55b2-4632-afc0-c2b5c2f97558 1Gi RWO Delete Bound default/www-web-0 local-path 2m47s
pvc-0789bbd0-d61f-4c4a-897b-c9f9ce1f8a5f 1Gi RWO Delete Bound default/www-web-1 local-path 2m3s

# ------------------------查看pod------------------------
[root@k3s-master2 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m1s
web-1 1/1 Running 0 2m39s

# ------------------------查看statefulsets------------------------
[root@k3s-master2 ~]# kubectl get statefulsets.apps
NAME READY AGE
web 2/2 3m32s

# ------------------------查看hostname------------------------
[root@k8s-master ~]# kubectl exec web-0 -- hostname
web-0
[root@k8s-master ~]# kubectl exec web-1 -- hostname
web-1

# ------------------------查看pv和pvc------------------------
[root@VM-33-122-centos ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-4a24ab1c-9cd2-4460-b8d2-ff773d562f48 1Gi RWO local-path 4m10s
www-web-1 Bound pvc-becd76fa-4199-441b-bdf6-9a8c63133e9d 1Gi RWO local-path 4m3s
[root@VM-33-122-centos ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-4a24ab1c-9cd2-4460-b8d2-ff773d562f48 1Gi RWO Delete Bound default/www-web-0 local-path 7m31s
pvc-becd76fa-4199-441b-bdf6-9a8c63133e9d 1Gi RWO Delete Bound default/www-web-1 local-path 7m26s

# ------------------------查看解析service的结果------------------------
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: nginx
Address 1: 10.244.1.175 web-1.nginx.default.svc.cluster.local
Address 2: 10.244.4.83 web-0.nginx.default.svc.cluster.local
/ # ping nginx
PING nginx (10.244.1.175): 56 data bytes
64 bytes from 10.244.1.175: seq=0 ttl=62 time=1.076 ms
64 bytes from 10.244.1.175: seq=1 ttl=62 time=1.029 ms
64 bytes from 10.244.1.175: seq=2 ttl=62 time=1.075 ms

# 我们直接解析 Headless Service 的名称,可以看到得到的是两个 Pod 的解析记录,但实际上如果我们通过nginx这个 DNS 去访问我们的服务的话,并不会随机或者轮询背后的两个 Pod,而是访问到一个固定的 Pod,所以不能代替普通的 Service

特点总结

  • 每个pod的hostname是固定的:<statefulset名称>-<序号>

  • 每个pod有一个独立的DNS:<statefulset名称-序号>.<headless-service-name>.<namespace>.svc.cluster.local

  • 当以此格式访问时不会提供负载效果:<headless-service-name>.<namespace>.svc.cluster.local

  • 之前的service会自动的帮你填充cluster ip,这里将字段设置为none,不会分配,和deployment不同,deployment都是提供相同的统一的入口。有状态的都是提供独立的访问。

  • 对于一个拥有 N 个副本的 StatefulSet 来说,Pod 在部署时按照 {0 …… N-1} 的序号顺序创建的,而删除的时候按照逆序逐个删除

  • 主机名是固定的,即使pod被删除掉了,重新启动也是这个名称上面体现了稳定的主机名

  • 当 Pod 所在的节点发生故障导致 Pod 飘移到其他节点上,或者 Pod 因故障被删除重建,Pod 的 IP 都会发生变化,但是 Pod 的域名不会有任何变化,这也就意味着服务间可以通过不变的 Pod 域名来保障通信稳定,而不必依赖 Pod IP。

  • spec.serviceName这个字段,保证了 StatefulSet 关联的 Pod 可以有稳定的网络身份标识,即 Pod 的序号、主机名、DNS 记录名称等。

  • 对有状态的服务来说,每个副本可能都会用到持久化存储,且各自使用的数据是不一样的。每个独立的Pod都有独立的pv(利用volumeClaimTemplates)

  • 删除pod的时候,pv pvc不会被删除,并且删除重建之后还是可以读取之前的数据。当pod被重建之后,k8s还是根据pod编号找对应的pvc进行挂载