# StatefulSet

<mark style="color:blue;">**StatefulSet（有状态集，缩写为 sts）常用于部署有状态的且需要有序启动的应用程序**</mark>，比如在进行Spring Cloud 项目容器化时，Eureka 的部署是比较适合用 StatefulSet 部署的，可以给每个 Eureka 实例创建一个唯一且固定的标识符，并且每个 Eureka 实例无须配置多余的 Service，其余 Spring Boot 应用可以直接通过 Eureka 的 Headless Service 进行注册。

## 简介

**在 Kubernetes 系统中，Pod 的管理对象 RC、Deployment、DaemonSet 和 Job 都面向无状态的服务**。但现实中有很多服务是有状态的，特别是一些复杂的中间件集群，例如 MySQL 集群、MongoDB 集群、Akka 集群、ZooKeeper 集群等，这些应用集群有 4 个共同点：

* 每个节点都有**固定的身份 ID**，通过这个 ID，集群中的成员可以相互发现并通信。
* 集群的**规模是比较固定的**，集群规模不能随意变动。
* 集群中的**每个节点都是有状态的**，通常会持久化数据到永久存储中。
* 如果磁盘损坏，则集群里的某个节点无法正常运行，集群功能受损。

{% hint style="success" %}
**如果通过 RC 或 Deployment 控制 Pod 副本数量来实现上述有状态的集群，就会发现第 1 点是无法满足的，因为 Pod 的名称是随机产生的，Pod 的 IP 地址也是在运行期才确定且可能有变动的，事先无法为每个 Pod 都确定唯一不变的 ID**。
{% endhint %}

另外，**为了能够在其他节点上恢复某个失败的节点，这种集群中的 Pod 需要挂接某种共享存储**。

为了解决这个问题，Kubernetes 从 1.4 版本开始引入了 PetSet 这个新的资源对象，并且在 1.5 版本时更名为 <mark style="color:blue;">**StatefulSet**</mark>，StatefulSet 从本质上来说，可以看作 Deployment/RC 的一个特殊变种，它有如下特性：

* <mark style="color:blue;">**StatefulSet 里的每个 Pod 都有稳定、唯一的网络标识，可以用来发现集群内的其他成员**</mark>。假设StatefulSet 的名称为 kafka，那么第 1 个 Pod 叫 kafka-0，第 2 个叫 kafka-1，以此类推。
* <mark style="color:blue;">**StatefulSet 控制的 Pod 副本的启停顺序是受控的，操作第 n 个 Pod 时，前 n-1 个 Pod 已经是运行且准备好的状态。**</mark>
* <mark style="color:blue;">**StatefulSet 里的 Pod 采用稳定的持久化存储卷，通过 PV 或 PVC 来实现，删除 Pod 时默认不会删除与 StatefulSet 相关的存储卷**</mark>**（为了保证数据的安全）**。

{% hint style="warning" %}

## <mark style="color:orange;">注意</mark>

<mark style="color:orange;">**删除一个 StatefulSet 时，不保证对 Pod 的终止！**</mark>

要在 StatefulSet 中实现 Pod 的有序和正常终止，可以在删除之前将 StatefulSet 的副本缩减为 0。
{% endhint %}

## **Headless Service**

StatefulSet 除了要与 PV 卷捆绑使用以存储 Pod 的状态数据，还要与 <mark style="color:blue;">**Headless Service**</mark> 配合使用，即<mark style="color:orange;">**在每个 StatefulSet 定义中都要声明它属于哪个 Headless Service**</mark>。

{% hint style="success" %} <mark style="color:blue;">**Headless Service 与普通 Service 的关键区别在于，它没有 Cluster IP，如果解析 Headless Service 的 DNS 域名，则返回的是该 Service 对应的全部 Pod 的 Endpoint 列表。**</mark>**Headless Service 使用 Endpoint 进行互相通信。**
{% endhint %}

**StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个 Pod 实例都创建了一个 DNS 域名，这个域名的格式为：**<mark style="color:blue;">**${podName}.${serviceName}.${namespace}.svc.cluster.local**</mark>。

其中：

* **podName=${statefulSetName}-number**
  * **statefulSetName** 是 StatefulSet 的名称
  * **number** 是一个整数，表示 Pod 的序号，以 0 作为起始值
* **serviceName 是 Headless Service 的名字，创建 StatefulSet 时必须指定 Headless Service 名称**
* **namespace** 是服务所在的命名空间。

  > 如果访问的 Pod 位于同一命名空间下，可以省略不写
* **cluster.local** 为 Cluster Domain（集群域）

{% hint style="info" %}
假如公司某个项目需要在 Kubernetes 中部署一个**主从模式的 Redis**，此时使用 StatefulSet 部署就极为合适，因为 **StatefulSet 启动时，只有当前一个容器完全启动时，后一个容器才会被调度，并且每个容器的标识符是固定的**，那么就**可以通过标识符来断定当前 Pod 的角色**。

比如用一个**名为 redis-cluster 的 StatefulSet** 部署主从架构的 Redis：

* 第一个容器启动时，它的标识符为 **redis-cluster-0**，并且 Pod 内主机名也为 **redis-cluster-0。**
* **将主机名为 redis-cluster-0 的容器作为 Redis 的主节点，其余为从节点**，那么**从节点连接 主节点时就可以使用不会更改的 Master 的 Headless Service。**

此时 Redis 从节点（Slave）配置文件如下：

```properties
 port 6379
 slaveof redis-cluster-0.redis-ms.public-service.svc.cluster.local 6379
 tcp-backlog 511
 timeout 0
 tcp-keepalive 0
 ...
```

其中 **redis-cluster-0.redis-ms.public-service.svc.cluster.local 是 Redis Master 的 Headless Service。**

> <mark style="color:blue;">**在同一命名空间下只需要写 redis-cluster-0.redis-ms 即可**</mark><mark style="color:blue;">，</mark><mark style="color:blue;">**后面的 public-service.svc.cluster.local 可以省略**</mark><mark style="color:blue;">。</mark>
> {% endhint %}
