最近遇到一个问题:statefulset 部署能否只是升级部分的 pod?问题本身不难,但我发现没有说过有关 statefulset 的介绍,于是本文将介绍一些有关 statefulset 的点。
问题的答案则放在文章的最后,这也是 statefulset 一个很有用的特性。

statefulset 特点

首先让我们来简单说下 statefulset 的一些基本特点

特点

statefulset 顾名思义是有状态的,它与 deployment 最大的不同就是这个。

  • 稳定的、唯一的网络标识符。
  • 稳定的、持久的存储。
  • 有序的、优雅的部署和扩缩。
  • 有序的、自动的滚动更新。
    由于特点是为了满足很多有状态的服务或者中间件:如 Zookeeper、Kafka 等,他们一般有一些数据是不希望随着服务的重启而消失的,所以就需要 statefulset 了。

顺序&唯一

在 statefulset 中每个 pod 会有一个唯一标识符,故这些 pod 之间是不一样的,也就是唯一的,所以 pod 在重启或者是调度后是没有办法互相替换的。

1
2
3
4
5
6
7
8
❯ kubectl get pods
NAME READY STATUS RESTARTS AGE
consul-0 1/1 Running 10 52d
consul-1 1/1 Running 3 52d
consul-2 1/1 Running 7 51d
consul-3 1/1 Running 4 52d
consul-4 1/1 Running 4 53d
consul-5 1/1 Running 7 51d

我们可以看到每个 pod 的名称后面还跟了一个递增的数字用于区分

  • 创建的时候顺序是 0,1,2…. 5
  • 终止的时候顺序是 5….2,1,0
  • 并且前一个没有 running&ready 后一个是不会被创建的

数据持久化

由于 statefulset 的特性,我们往往需要持久化数据,也就是需要对应的 PV 和 PVC,当然与之更好的配合是 StorageClass,因为我们往往需要动态为每个 pod 去申请对应的资源,这部分详细可以参考:k8s StorageClass使用攻略

由于 statefulset 中每个 pod 都是唯一的,所以他们对应的持久化的 PV 和 PVC 也是一一对应的,pod-1 不会也不能用到 pod-2 所对应的 PV。

滚动更新

什么是滚动更新

当我们重启一个 deployment 的时候,或者是修改了 deployment 的时候,pod 会几个几个进行更新,它默认确保 75% 的处于可用状态。

而 statefulset 不一样,它一定是一个一个更新的,按照顺序(从最大序号到最小序号)进行,每次更新一个 Pod。

如何实现

其实实现方式和 deployment 一样,都是通过控制器模式来实现的,并且 k8s 中都运用到了状态维护的设计思想,也就是:描述我需要的状态,如果当前状态不是我需要的,则进行改变,直到成为我描述的状态。

1
2
3
4
5
6
7
8
9
for {
实际状态 := 获取集群中对象 X 的实际状态(Actual State)
期望状态 := 获取集群中对象 X 的期望状态(Expectation State)
if 实际状态 == 期望状态{
什么都不做
} else {
执行编排动作,将实际状态调整为期望状态
}
}

步骤

  1. 获取所有 pod 副本
  2. 进行分组,不需要的 pod 进入 condemned
  3. 需要的 pod 进入 replicas
  4. 根据规则顺序的序列好检查副本状态
  5. 按照降序删除 condemned
  6. 按照降序更新 replicas

具体代码在 updateStatefulSet 方法中:
https://github.com/kubernetes/kubernetes/blob/9f82d81e55cafdedab619ea25cabf5d42736dacf/pkg/controller/statefulset/stateful_set_control.go

能否只是升级部分的 pod?

我们经常会遇到需要蓝绿发布的场景,也就是说,我们只想升级一个应用中部分的 pod,保持新老版本共存,来验证一些业务或者升级所带来的影响,如果遇到问题可以快速相应。那么在 statefulset 中,实现起来很简单。

在 statefulset 的配置中有个 partition

1
2
3
4
5
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2

你可以通过配置这个参数来实现升级部分的 pod

  • 序号 >= partition 的 pod 会更新
  • 序号 < partition 的 pod 不会被更新

然后当你验证业务通过后,可以跳转这个参数为 0,就可以将剩余的 pod 也更新完成了