乔克视界 乔克视界
首页
  • 运维
  • 开发
  • 监控
  • 安全
  • 随笔
  • Docker
  • Golang
  • Python
  • AIOps
  • DevOps
  • Kubernetes
  • Prometheus
  • ELK
  • 心情杂货
  • 读书笔记
  • 面试
  • 实用技巧
  • 博客搭建
友链
关于
收藏
  • 分类
  • 标签
  • 归档

乔克

云原生爱好者
首页
  • 运维
  • 开发
  • 监控
  • 安全
  • 随笔
  • Docker
  • Golang
  • Python
  • AIOps
  • DevOps
  • Kubernetes
  • Prometheus
  • ELK
  • 心情杂货
  • 读书笔记
  • 面试
  • 实用技巧
  • 博客搭建
友链
关于
收藏
  • 分类
  • 标签
  • 归档
  • Docker

  • Golang

  • AIOps

  • Python

  • DevOps

  • Kubernetes

    • 什么是云原生?
    • Docker容器技术
    • Kubernetes简介
    • Kubernetes核心对象
    • Kubernetes集群管理
    • Kubernetes权限管理
    • Kubernetes工作负载管理
    • Kubernetes调度管理
    • Kubernetes应用质量管理
    • Kubernetes数据持久化管理
    • Kubernetes应用访问管理
    • Kubernetes应用配置管理
    • Kubernetes有状态应用管理
      • 什么是有状态应用
      • 如何使用 StatefulSet
      • 总结
    • Kubernetes 网络管理
    • Helm 应用包管理
  • Prometheus

  • ELK

  • 专栏
  • Kubernetes
乔克
2025-07-19
目录

Kubernetes有状态应用管理

我们在《Kubernetes 工作负载管理》中主要介绍了无状态应用的管理,当时也有提到有状态应用,但是由于那时候还没有解释数据如何持久化就没有做深度的介绍,而在这章,我们会着重介绍如何进行有状态应用的管理。

# 什么是有状态应用

实例之间的不等关系以及实例对外数据有依赖关系的应用,就被称为"有状态应用"。

所谓实例之间的不等关系即对分布式应用来说,各实例,各应用之间往往有比较大的依赖关系,比如某个应用必须先于其他应用启动,否则其他应用将不能启动等。

对外数据有依赖关系的应用,最显著的就是数据库应用,对于数据库应用,我们是需要持久化保存其数据的,如果是无状态应用,在数据库重启数据和应用就失去了联系,这显然是违背我们的初衷,不能投入生产的。

所以,为了解决 Kubernetes 中有状态应用的有效支持,Kubernetes 使用 StatefulSet 来编排管理有状态应用。

StatefulSet 类似于 ReplicaSet,不同之处在于它可以控制 Pod 的启动顺序,它为每个 Pod 设置唯一的标识。其具有一下功能:

  • 稳定的,唯一的网络标识符
  • 稳定的,持久化存储
  • 有序的,优雅部署和缩放
  • 有序的,自动滚动更新

StatefulSet 的设计很容易理解,它把现实世界抽象为以下两种情况:

(1)、拓扑状态。这就意味着应用之间是不对等关系,应用要按某种顺序启动,即使应用重启,也必须按其规定的顺序重启,并且重启后其网络标识必须和原来的一样,这样才能保证原访问者能通过同样的方法访问新的 Pod;

(2)、存储状态 。这就意味着应用绑定了存储数据,不论什么时候,不论什么情况,对应用来说,只要存储里的数据没有变化,读取到的数据应该是同一份;

所以 StatefulSet 的核心功能就是以某种方式记录 Pod 的状态,然后在 Pod 被重新创建时,通过某种方法恢复其状态。

# 如何使用 StatefulSet

在《Kubernetes 应用访问管理》中,我们介绍了 Service,它是为一组 Pod 提供外部访问的一种方式。通常,我们使用 Service 访问 Pod 有一下两种方式:

(1)、通过 Cluster IP,这个 Clustre IP 就相当于 VIP,我们访问这个 IP,就会将请求转发到后端 Pod 上;

(2)、通过 DNS 方式,通过这种方式首先得确保 Kubernetes 集群中有 DNS 服务。这个时候我们只要访问"my-service.my-namespace.svc,cluster.local",就可以访问到名为 my-service 的 Service 所代理的后端 Pod;

而对于第二种方式,有下面两种处理方法:

(1)、Normal Service,即解析域名,得到的是 Cluster IP,然后再按照方式一访问;

(2)、Headless Service,即解析域名,得到的是后端某个 Pod 的 IP 地址,这样就可以直接访问;

而在使用 StatefulSet 的时候,主要用到 Headless Service,还记得 Headless Service 怎么定义的吗?

我们只需要把 ClusterIP 设置为 None 即可,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless-service
  labels:
    name: nginx-headless-service
spec:
  clusterIP: None
  selector:
    name: nginx
  ports:
    - port: 8000
      targetPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13

了解了 Headless Service,还需要了解 PV、PVC 是怎么使用的,如果忘记了,可以移步《Kubernetes 数据持久化管理》回顾,这里就不再赘述了。

下面,我们开始使用 StatefulSet。

首先,我们创建两个个 PV,因为准备为有状态应用创建两个副本,如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv01
  labels:
    storage: pv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/k8s
    server: 192.168.205.128
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv02
  labels:
    storage: pv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/k8s
    server: 192.168.205.128
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

然后编写 StatefulSet 需要的 YAML 文件,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None
  selector:
    app: nginx
    role: stateful

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
        role: stateful
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        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

注意上面的 YAML 文件中和 volumeMounts 进行关联的是一个新的属性:volumeClaimTemplates,该属性会自动声明一个 pvc 对象和 pv 进行管理,而 serviceName: "nginx"表示在执行控制循环的时候,用 nginx 这个 Headless Service 来保存 Pod 的可解析身份。

创建完成后,可以看到会起两个 Pod:

$ kubectl get pod | grep web
web-0                                  1/1     Running   0             2m45s
web-1                                  1/1     Running   0             2m41s
1
2
3

从这两个 Pod 的命令可以看到,它们的名字不像 Deployment 那样随机生成的字符串,而是 0,1 这样的序号。这是因为 StatefulSet 要保证每个 Pod 顺序,确保每次重启或者更新,每个 Pod 依然保持以前的数据,不会错乱。所以 StatefulSet 会以[statefulset-name]-[index]规则进行命名,其中 index 从 0 开始。而且每个 Pod 的创建是有顺序的,如上只有 web-0 进入 running 状态后,web-1 才创建。

当两个 Pod 都进入 running 状态后,就可以查看其各自的网络身份了,我们通过 kubectl exec 来查看,如下:

$ kubectl exec web-0 -- sh -c 'hostname'
web-0
$ kubectl exec web-1 -- sh -c 'hostname'
web-1
1
2
3
4

可以看到这两个 pod 的 hostname 和 pod 的名字是一致的,都被分配为对应的编号,接下来我们用 DNS 的方式来访问 Headless Service。

我们先启动一个调试 Pod,如下:

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  namespace: default
spec:
  containers:
    - name: dnsutils
      image: lansible/dnstools
      command:
        - sleep
        - "3600"
      imagePullPolicy: IfNotPresent
  restartPolicy: Always
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后进入 dnsutils 容器进行解析,如下:

$ kubectl exec -it dnsutils -- /bin/sh
/ # nslookup web-0.nginx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-0.nginx.default.svc.cluster.local
Address: 172.16.51.247

/ # nslookup web-1.nginx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-1.nginx.default.svc.cluster.local
Address: 172.16.51.251

/ #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

从 nslookup 的结果分析,在访问 web-0.nginx 的时候解析的是 web-0 这个 Pod 的 IP,另一个亦然。这表示,如果我们在应用中配置 web-0.nginx,则只会调用 web-0 这个 Pod,在配置有状态应用,比如 Zookeeper 的时候,我们需要在配置文件里指定 zkServer,这时候就可以指定类似:zk-0.zookeeper,zk-1.zookeeper。

如果我们现在更新 StatefuleSet,起更新顺序是怎么样的呢?

首先,我们新开一个终端,输入以下命令用以观察:

$ kubectl get pods -w -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          67m
web-1   1/1     Running   0          67m
1
2
3
4

然后使用以下命令更新应用的镜像,如下:

kubectl set image statefulset/web nginx=nginx:1.8
1

然后观察 web 应用的更新顺序,如下:

$ kubectl get pods -w -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          67m
web-1   1/1     Running   0          67m
web-1   1/1     Terminating   0          68m
web-1   1/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Pending       0          0s
web-1   0/1     Pending       0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   0/1     ContainerCreating   0          1s
web-1   1/1     Running             0          10s
web-0   1/1     Terminating         0          69m
web-0   1/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Pending             0          0s
web-0   0/1     Pending             0          0s
web-0   0/1     ContainerCreating   0          0s
web-0   0/1     ContainerCreating   0          1s
web-0   1/1     Running             0          9s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

从整个顺序可以看到,起更新是从后往前进行更新的,也就是先更新 web-1 的 pod,再更新 web-0 的 pod。通过这种严格的对应规则,StatefulSet 就保证了 Pod 的网络标识的稳定性,通过这个方法,就可以把 Pod 的拓扑状态按照 Pod 的名字+编号的方式固定起来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即这个 Pod 的 DNS 记录。

由此,我们对 StatefulSet 梳理如下:

(1)、StatefulSet 直接管理的是 Pod。这是因为 StatefulSet 里的 Pod 实例不像 ReplicaSet 中的 Pod 实例完全一样,它们是有细微的区别,比如每个 Pod 的名字、hostname 等是不同的,而且 StatefulSet 区分这些实例的方式就是为 Pod 加上编号;

(2)、Kubernetes 通过 Headless Service 为这个编号的 Pod 在 DNS 服务器中生成带同样编号的记录。只要 StatefulSet 能保证这个 Pod 的编号不变,那么 Service 中类似于 web-0.nginx.default.svc.cluster.local 这样的 DNS 记录就不会变,而这条记录所解析的 Pod IP 地址会随着 Pod 的重新创建自动更新;

(3)、StatefulSet 还可以为每个 Pod 分配并创建一个和 Pod 同样编号的 PVC。这样 Kubernetes 就可以通过 Persitent Volume 机制为这个 PVC 绑定对应的 PV,从而保证每一个 Pod 都拥有独立的 Volume。这种情况下即使 Pod 被删除,它所对应的 PVC 和 PV 依然会保留下来,所以当这个 Pod 被重新创建出来过后,Kubernetes 会为它找到同样编号的 PVC,挂载这个 PVC 对应的 Volume,从而获取到以前 Volume 以前的数据;

# 总结

StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序完成作业,当 StatefulSet 的控制循环发现 Pod 的实际状态和期望状态不一致的时候,也会按着顺序对 Pod 进行操作。

当然 StatefulSet 还拥有其他特性,在实际的项目中,我们还是很少回去直接通过 StatefulSet 来部署我们的有状态服务的,除非你自己能够完全能够 hold 住,对于一些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator 等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用一个 StatefulSet 来部署一个 Pod 就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等。

上次更新: 2025/07/19, 21:23:02
Kubernetes应用配置管理
Kubernetes 网络管理

← Kubernetes应用配置管理 Kubernetes 网络管理→

最近更新
01
elastic 账户认证 401 问题
07-20
02
使用 helm 安装 es 和 kibana
07-20
03
elastic stack 搭建
07-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 乔克 | MIT License | 渝ICP备20002153号 |
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式