小橘子大叔

  • 首页
  • nginx
  • Linux
  • docker
  • Kubernetes
  • Prometheus
  • 生活
  • 文章归档
  • 友情链接
  • Instagram
  • TikTok
  • X
欢迎随时联系本人
  • Mail

深入理解StatefulSet

  • luxy
  • 2023-10-17
  • 4

        Ladies and gentlemen!在前一篇k8s的文章中,我提了一嘴deployment的弊端,在本篇中将详细讲讲一个更好玩的api对象,叫做StatefulSet

          Deployment下的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod。但是,在实际的场景中,并不是所有的应用都可以满足这样的要求。尤其是分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。所以,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)。

          下面将从拓扑状态和存储状态依次对StatefulSet进行分析。

          为了实现在部署“有状态应用”的时候,应用的每个实例拥有唯一并且稳定的“网络标识”,在创建StatefulSet前往往会先创建一个Headless Service,如下:

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

这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。之后这个service代理的pod我们都可以用dns的方式去访问:<pod-name>.<svc-name>.<namespace>.svc.cluster.local 下面再编写一个简单的StatefulSet文件

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web

可以看到与Deployment不同的是多出来了一个serviceName的字段,这正是部署有状态应用的精髓!create后我们将看到如下信息:

创建出的pod名字以StatefulSet+编号的格式出现。如果在create后你手速过快,使用watch进行观看时,你会发现是web-0先进行创建,然后再进行创建web-1。我们可以在集群用dns来访问一下它们

nslookup web-0.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

注意,如果你无法看见正确的dns解析的话,不妨试试nslookup web-0.nginx.default.svc.cluster.local ,我的k8s版本为v1.17.4。

通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。用一句话来说这个过程:StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。

在k8s中提供了一种叫做PV和PVC的机制,它们以“接口”的方式大大简化了持久化存储的过程。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。下面来编写一个类似的yaml文件:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

这里的volumeClaimTemplates 字段是我们着重应该了解的。它会为我们的每一个pod分配一个专属的PVC。些 PVC,都以<PVC名字>- <StatefulSet名字>-<编号>的方式命名.当这个PVC与PV绑定后,即使你现在手误删除了pod,StatefulSet控制器会通过控制循环自动帮你生成新的pod,该pod就会直接找到旧 Pod 遗留下来的同名的 PVC,进而找到跟这个 PVC 绑定在一起的 PV,实现持久化存储。但要注意,在之前需要手动创建PV,但创建PV时会出现一些问题,k8s为我们提供了storageClass来解决一些问题,在之后会讲到。

         这么一看,原本非常复杂的 StatefulSet,是不是也很容易理解了呢?

© 2025 小橘子大叔
Theme by Wing
  • {{ item.name }}
  • {{ item.name }}