Kubernetes如何支持有状态服务的部署?
作者:Jack47
转载请保留作者和原文出处
PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源。
Kubernetes对无状态服务有完善的支持,但是对于有状态的服务,是从1.3版本开始,才逐渐支持的。
有状态的应用程序
一般情况下,nginx或者web server(不包含MySQL)自身都是不需要保存数据的,对于 web server,数据会保存在专门做持久化的节点上。所以这些节点可以随意扩容或者缩容,只要简单的增加或减少副本的数量就可以。但是很多有状态的程序都需要集群式的部署,意味着节点需要形成群组关系,每个节点需要一个唯一的ID(例如Kafka BrokerId, Zookeeper myid)来作为集群内部每个成员的标识,集群内节点之间进行内部通信时需要用到这些标识。传统的做法是管理员会把这些程序部署到稳定的,长期存活的节点上去,这些节点有持久化的存储和静态的IP地址。这样某个应用的实例就跟底层物理基础设施比如某台机器,某个IP地址耦合在一起了。Kubernets中StatefulSet
的目标是通过把标识分配给应用程序的某个不依赖于底层物理基础设施的特定实例来解耦这种依赖关系。(消费方不使用静态的IP,而是通过DNS域名去找到某台特定机器)
StatefulSet
前提
使用StatefulSet
的前提:
- Kubernetes集群的版本 >=1.5
- 安装好DNS集群插件,版本 >=15
特点
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
实例
Kubernetes官方博客上有一个在Kubernetes上运行MogoDB的例子,见这里。
借用一下里面的架构图:
在StatefulSet之上运行MongoDB
如果您看了本篇博客,觉得对您有所收获,请点击右下角的“推荐”,让更多人看到!