Kubernetes
对无状态服务有完善的支持,但是对于有状态的服务,是从1.3版本开始,才逐渐支持的。
有状态的应用程序
一般情况下,nginx
或者web server
(不包含MySQL
)自身都是不需要保存数据的,对于 web server
,数据会保存在专门做持久化的节点上。所以这些节点可以随意扩容或者缩容,只要简单的增加或减少副本的数量就可以。但是很多有状态的程序都需要集群式的部署,意味着节点需要形成群组关系,每个节点需要一个唯一的ID(例如Kafka BrokerId, Zookeeper myid)来作为集群内部每个成员的标识,集群内节点之间进行内部通信时需要用到这些标识。传统的做法是管理员会把这些程序部署到稳定的,长期存活的节点上去,这些节点有持久化的存储和静态的IP地址。这样某个应用的实例就跟底层物理基础设施比如某台机器,某个IP地址耦合在一起了。Kubernets
中StatefulSet
的目标是通过把标识分配给应用程序的某个不依赖于底层物理基础设施的特定实例来解耦这种依赖关系。(消费方不使用静态的IP
,而是通过DNS
域名去找到某台特定机器)
StatefulSet
StatefulSet
(1.5版本之前叫做PetSet
)为什么适合有状态的程序,因为它相比于Deployment
有以下特点:
- 稳定的,唯一的网络标识,可以用来发现集群内部的其他成员。比如
StatefulSet
的名字叫kafka
,那么第一个起来的Pet
叫kafka-0
,第二个叫kafk-1
,依次类推。 - 稳定的持久化存储:通过
Kubernetes
的PV/PVC
或者外部存储(预先提供的)
来实现 - 启动或关闭时保证有序:
优雅的部署
和伸缩性
: 操作第n个pod时,前n-1个pod已经是运行且准备好的状态。 有序的,优雅的删除和终止操作:从 n, n-1, ... 1, 0 这样的顺序删除
上述提到的“稳定”指的是Pod
在多次重新调度时保持稳定,即存储
,DNS名称
,hostname
都是跟Pod
绑定到一起的,跟Pod
被调度到哪个节点没关系。
所以Zookeeper
,Etcd
或Elasticsearch
这类需要稳定的集群成员的应用时,就可以用StatefulSet
。通过查询无头服务域名的A记录
,就可以得到集群内成员的域名信息。
StatefulSet
也有一些限制:
-
Pod
的存储必须是通过PersistentVolume Provisioner
基于storeage类
来提供,或者是管理员预先提供的外部存储。 - 删除或者缩容不会删除跟
StatefulSet
相关的卷,这是为了保证数据的安全 -
StatefulSet
现在需要一个无头服务(Headless Service)来负责生成Pods
的唯一网络标示,需要开发人员创建这个服务 - 对
StatefulSet
的升级是一个手工的过程
无头服务(Headless Service)
要定义一个服务(Service)
为无头服务(Headless Service)
,需要把Service
定义中的ClusterIP
配置项设置为空: spec.clusterIP:None
。和普通Service
相比,Headless Service
没有ClusterIP
(所以没有负载均衡),它会给一个集群内部的每个成员提供一个唯一的DNS域名
来作为每个成员的网络标识,集群内部成员之间使用域名通信。无头服务管理的域名是如下的格式:$(service_name).$(k8s_namespace).svc.cluster.local
。其中的"cluster.local"
是集群的域名,除非做了配置,否则集群域名默认就是cluster.local
。StatefulSet
下创建的每个Pod
,得到一个对应的DNS
子域名,格式如下:
$(podname).$(governing_service_domain)
,这里 governing_service_domain
是由StatefulSet
中定义的serviceName
来决定。举例子,无头服务管理的kafka
的域名是:kafka.test.svc.cluster.local
, 创建的Pod
得到的子域名是 kafka-1.kafka.test.svc.cluster.local
。注意这里提到的域名,都是由kuber-dns
组件管理的集群内部使用的域名,可以通过命令来查询:
$ nslookup my-nginx
Server: 192.168.16.53
Address 1: 192.168.16.53
Name: my-nginx
Address 1: 192.168.16.132
而普通Service
情况下,Pod
名字后面是随机数,需要通过Service
来做负载均衡。
当一个StatefulSet
挂掉,新创建的StatefulSet
会被赋予跟原来的Pod
一样的名字,通过这个名字来匹配到原来的存储,实现了状态保存。因为上文提到了,每个Pod
的标识附着在Pod
上,无论pod
被重新调度到了哪里。
成员发现
一个Pod
可以通过 Downward api机制来知道自己的pod
名字,也可以运行hostname
来发现自己的DNS名字
。StatefuleSet
的服务名(governing service
)在创建的时刻就已知了,所以只需要通过一个约定的环境变量把服务名传递给POD
就可以。
一点八卦
为什么从PetSet
改名字到StatefulSet
,也是很有意思的,感兴趣的同学可以去这里看看:
Please Consider changing the name of PetSet before General Availability