StatefulSet 是 Kubernetes(k8s)中的一个重要概念,主要用于管理有状态的应用程序。

以下是关于 StatefulSet 的详细解释:

定义与用途

  • StatefulSet 是一种用于管理有状态应用的资源对象。
  • 它常被用于部署需要持久化存储和唯一标识的应用,如数据库、分布式存储系统等。
  • 与 Deployment 等无状态应用部署方式相比,StatefulSet 能够保证每个 Pod 实例具有稳定的网络标识符和持久化存储。

特性

  • 稳定的网络标识符:StatefulSet 为每个 Pod 分配一个唯一的标识符(例如,Redis-Sentinel-0、Redis-Sentinel-1 等),这些标识符在 Pod 生命周期内保持不变,即使在重新调度时也会保留。
  • 持久化存储:StatefulSet 通常与 PersistentVolumeClaims (PVCs) 结合使用,为每个 Pod 提供持久化存储。PVCs 是对底层存储资源的请求,可以动态或静态地分配给 Pod。
  • 有序部署和扩缩:StatefulSet 在创建和删除 Pod 实例时,会按照一定的顺序进行操作,确保每个 Pod 实例的唯一标识符和网络标识符的稳定性。这使得有状态应用可以保持持久化存储的连接,并且具备可靠的网络标识符,以便于应用之间的通信。
  • 滚动更新:StatefulSet 支持有序的滚动更新,可以在更新过程中保持应用的高可用性。
  • DNS 解析:StatefulSet 提供了有状态服务的 DNS 解析,使得应用可以通过稳定的网络标识符进行通信。

组成

  • StatefulSet 定义:描述应用的基本信息,如副本集大小、容器镜像等。
  • Pod 模板:定义 Pod 的配置,包括容器、存储卷等。
  • VolumeClaimTemplate:自动为每个 Pod 创建 PVC 的模板。

关于Headless

StatefulSet经常与Headless Service搭配使用,主要是因为它们在Kubernetes中各自的特点和优势能够相互补充,以更好地支持有状态应用的部署和管理。以下是几个关键原因:

  1. 有状态应用的需求
  • StatefulSet主要用于管理有状态应用程序的工作负载API对象。这些应用程序通常需要稳定的网络标识符、持久的存储以及与其他Pod之间有序、可靠的通信。
  • 例如,在部署如ElasticSearch集群、MongoDB集群、RabbitMQ集群、Redis集群等需要持久化的服务时,StatefulSet能够确保每个Pod都有唯一的标识符,并且这些Pod在重新调度时也会保留其标识符。
  1. Headless Service的特性
  • Headless Service(无头服务)与普通的Service不同,它不分配ClusterIP,而是使用Endpoints进行Pod之间的通信。
  • Headless Service可以通过解析Service的DNS返回所有Pod的地址和DNS,这使得StatefulSet中的Pod能够直接通过DNS名称进行通信,而无需依赖ClusterIP。
  1. 结合使用的优势
  • 当StatefulSet与Headless Service结合使用时,可以为每个Pod创建一个唯一的DNS名称,这使得其他Pod或服务可以通过该DNS名称直接访问到特定的Pod。
  • 这种直接访问的方式对于需要稳定网络标识符的有状态应用来说非常重要,因为它确保了Pod之间的通信是可靠和有序的。
  • 此外,由于Headless Service不使用ClusterIP,因此它不会受到ClusterIP可能带来的网络延迟或故障的影响,从而提高了通信的效率和稳定性。
  1. 说明
  • 假设我们有一个名为Redis-Sentinel的StatefulSet,它创建了三个Pod:Redis-Sentinel-0、Redis-Sentinel-1和Redis-Sentinel-2。
  • 当我们为这个StatefulSet创建一个Headless Service时,这三个Pod将分别获得一个唯一的DNS名称,如Redis-Sentinel-0.redis-sentinel-svc.NAMESPACE_NAME等。
  • 其他服务或Pod可以通过这些DNS名称直接访问到特定的Redis-Sentinel Pod,从而实现高效、稳定的有状态应用通信。

如果不使用Headless Service与StatefulSet搭配,而是使用普通的ClusterIP类型的Service,可能会带来以下几个问题:

  1. 不稳定的网络标识符
  • 对于StatefulSet管理的有状态应用,Pod通常需要具有稳定的网络标识符以便进行通信。如果使用ClusterIP类型的Service,Pod之间的通信将依赖于Service的ClusterIP,而不是Pod的直接地址。然而,ClusterIP并不是为Pod之间通信而设计的,它主要用于集群外部访问集群内部的服务。
  1. 无法直接访问Pod
  • 当使用ClusterIP类型的Service时,你无法直接通过Service的DNS名称访问到特定的Pod。Service的DNS解析将返回Service的ClusterIP,而不是Pod的IP地址。这限制了Pod之间直接通信的能力,尤其是在需要基于Pod身份进行通信的场景中。
  1. 无法处理Pod的重新调度
  • 如果StatefulSet中的Pod因为某种原因被重新调度到集群中的另一个节点上,其IP地址可能会发生变化。如果使用ClusterIP类型的Service,并且服务消费者通过Service的ClusterIP进行通信,那么它们将无法感知到Pod的重新调度,因为ClusterIP始终保持不变。这可能导致通信中断或数据不一致的问题。
  1. 扩展性和灵活性受限
  • 使用Headless Service可以允许StatefulSet中的Pod之间通过DNS进行发现和通信,而无需知道它们的实际IP地址。这使得扩展和缩容StatefulSet变得更加容易,因为Pod的IP地址可以自动更新到DNS记录中。而使用ClusterIP类型的Service则需要在扩展或缩容时手动更新Service的配置或依赖其他机制来管理Pod的IP地址。

实例

目标使用PVC模板的statefulSet,创建一个6节点redis集群,实现k8s内部,外部访问。

简要步骤

使用cm构建redis配置文件。使用statefulSet构建6个redis节点,挂载cm作为配置文件,使用调用storageClass nfs的PVC模板,使用headless实现Pod间通信。

redis配置


使用cm创建一个redis配置

cat > redis.conf << EOF
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
EOF

kubectl create cm redis-conf --from-file=redis.conf

创建headless服务

apiVersion: v1
kind: Service
metadata:
  name: redis-headless-svc
  labels:
    app: redis-cluster
spec:
  clusterIP: None
  ports:
  - name: redis-port
    port: 6379
  selector:
    app: redis-cluster

创建statefulset

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-app
spec:
  serviceName: redis-headless-svc
  replicas: 6
  selector:
    matchLabels:
      app: redis-cluster
  volumeClaimTemplates:
  - apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: nfs-client
      resources:
        requests:
          storage: 1Gi

  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      volumes:
      - name: conf
        configMap:
          name: redis-conf
      containers:
      - name: redis-node
        image: redis:3.2.8
        command:
        - "redis-server"
        args:
        - "/etc/redis/redis.conf"

        readinessProbe:
          tcpSocket:
            port: 6379
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          tcpSocket:
            port: 6379
          initialDelaySeconds: 5
          periodSeconds: 10


        ports:
        - name: redis
          containerPort: 6379
          protocol: "TCP"
        - name: cluster
          containerPort: 16379
          protocol: "TCP"
        volumeMounts:
        - name: conf
          mountPath: /etc/redis/

        - name: redis-data
          mountPath: /var/lib/redis

可以看到statefulSet创建pod是一个一个来的,上一个pod处于ready状态后,才会进行下一个的创建

从0开始搞K8S:statefulset_statefulset

再看PVC 和PV

yaml文件中只是定义了一个pvc的模板,下面的均是自动生成。

从0开始搞K8S:statefulset_statefulset_02

此时删除一个pod,可以观察重启后的pod IP没有变化

从0开始搞K8S:statefulset_statefulset_03

以上是验证statefulSet相关功能,接下来继续配置reids

构建redis

使用redis源码中自带的 redis-trib.rb 构建集群

 redis-trib.rb create --replicas 1 10.244.1.219:6379 10.244.2.145:6379 10.244.4.37:6379 10.244.3.30:6379 10.244.1.220:6379 10.244.2.146:6379

结果如下

从0开始搞K8S:statefulset_statefulset_04

验证

从0开始搞K8S:statefulset_statefulset_05