概述

在我们前面介绍的几种 Workload 中,每种都各有特色,用于不同的场景,但是,这些都有一个共性,就是创建出来的 Pod 都是一致的。所谓的一致的,就是说,假设我们是使用的 ReplicationSet,创建了 3 个 Pod,那么这 3 个 Pod 除了名字一定不一样之外,其他的属性可能都是一样的,包括运行的参数和模式以及数据存储。

虽然这个在普通的应用中是没问题的,例如是一个 Web Server,只是处理一下逻辑,然后保存到后段的 DB 中。但是,如果通过 ReplicationSet 来部署一个 DB 的多实例,那么可能就会存在一些问题了,通过 Kurbernetes 来运行多实例的 DB,很多时候出于数据持久化考虑,我们是使用 PVC(后续会说),那么当使用 PVC 和 PV 的时候,可能就会变成这个样子:

3 个节点的数据都写到同一个 PV 中去了,这样肯定是不行的。

StatefulSet介绍

为了解决 Pod 的状态性的问题,Kubernetes 引入了 StatefulSet 的概念,通过 StatefulSet,你可以得到这些好处:

是的,没错,每隔 Pod 都可以通过 DNS 访问到,而这种特性,在其他的 Workload 中是不能实现的。通过 StatefulSet,可以让 Pod 持有状态,即使因为故障 Pod 重建了,那么对应的 Pod 的名字和数据都会保留,和重建之前没有什么区别。

使用 StatefulSet

使用 StatefulSet 有一个硬性要求,那就是必须先建立一个 HeadlessService1,然后绑定这个 HeadlessService 到 StatefulSet。

下面我就以 MongoDB 为示例,演示一下如何创建 StatefulSet,因为我还没有介绍到关于 PVC 和 PV 的内容,所以这个 MongoDB 将使用本地存储卷:

  1. 创建 Headless Service
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: mongo
  5. labels:
  6. app: mongo
  7. spec:
  8. ports:
  9. - name: mongo
  10. port: 27017
  11. targetPort: 27017
  12. clusterIP: None
  13. selector:
  14. app: mongo
  1. 创建 StatefulSet
  1. ---
  2. apiVersion: apps/v1
  3. kind: StatefulSet
  4. metadata:
  5. name: mongo
  6. spec:
  7. selector:
  8. matchLabels:
  9. app: mongo
  10. serviceName: "mongo"
  11. replicas: 3
  12. podManagementPolicy: Parallel
  13. template:
  14. metadata:
  15. labels:
  16. app: mongo
  17. spec:
  18. affinity:
  19. podAntiAffinity:
  20. requiredDuringSchedulingIgnoredDuringExecution:
  21. - labelSelector:
  22. matchExpressions:
  23. - key: "app"
  24. operator: In
  25. values:
  26. - mongo
  27. topologyKey: "kubernetes.io/hostname"
  28. containers:
  29. - name: mongo
  30. image: mongodb:latest
  31. command:
  32. - mongod
  33. - "--bind_ip_all"
  34. - "--replSet"
  35. - rs0
  36. ports:
  37. - containerPort: 27017
  38. volumeMounts:
  39. - name: mongo-data
  40. mountPath: /data/db
  41. volumes:
  42. - name: mongo-data
  43. hostPath:
  44. path: /home/liqiang/mongodb/db

这里因为使用的是 Host 的数据目录,所以需要保证不会有两个 Pod 调度到同一个机器上:

  1. affinity:
  2. podAntiAffinity:
  3. requiredDuringSchedulingIgnoredDuringExecution:
  4. - labelSelector:
  5. matchExpressions:
  6. - key: "app"
  7. operator: In
  8. values:
  9. - mongo
  10. topologyKey: "kubernetes.io/hostname"

然后规定了所有的 Pod 可以同时运行:

  1. podManagementPolicy: Parallel

除了同时运行之外,还有另外一种形式是顺序执行,也就是说假设有 3 个 Pod,那么 StatefulSet 会等第一个 Pod 已经 Running 之后再运行第二个 Pod,依次到所有的 Pod 都运行完成。

通过 DNS 访问 Pod

前面说过了,在 StatefulSet 中,可以通过 DNS 直接访问 Pod,那么我们就试验一下:

  1. [root@liqiang.io]# kubectl exec -it mongo-0 /bin/sh
  2. # mongo --host mongo-1.mongo
  3. MongoDB shell version v4.2.0
  4. connecting to: mongodb://mongo-1.mongo:27017/?compressors=disabled&gssapiServiceName=mongodb
  5. Implicit session: session { "id" : UUID("bfc54171-aa31-4c87-bbf5-e4ddaf5897f8") }
  6. MongoDB server version: 4.2.0
  7. Server has startup warnings:

可以发现,我们可以通过 <pod-name>.<service> 的形式访问 Pod,这其实和 StatefulSet 的设计是有关系的,因为在类似于 Deployment 的 Controller 中,Pod 的名字是不固定的,而在 StatefulSet 中,Pod 的名字都是固定的,具体更多的区别,我们可以看下面一节。

和 ReplicaSet 对比

StatefulSet at-most-one

Kubernetes 必须保证两个拥有相同标记和绑定相同持久卷声明的有状态的 Pod 实例不会同时运行。一个 Statefulset 必须保证有状态的 pod 实例的 at-most-one 语义。也就是说,StatefulSet 必须保证一个 Pod 不再运行后,才会去创建它的替换 pod。这对于处理节点故障有很大影响。