在Kubernetes上部署有状态服务
2017-01-20
Kubernetes对于无状态服务的支持已经十分成熟,有状态服务需要保存状态,Kubernetes提供了Volume和Persistent Volume用于服务的状态保存。 很多有状态服务都是以集群形式部署的,因此在在Kubernetes上部署有状态服务时还需要考虑集群管理的需求。针对这个需求Kubernetes提供了StatefulSet(在Kubernetes 1.5之前是PetSet)。
状态保存 #
Kubernetes上的存储有以下两个抽象:
- Volume
- Persistent Volume
Volume #
Volume包含两种:单节点Volume和跨节点Volume
单节点Volume和某个具体Node节点绑定,使用Pod所在Node的本地目录,包含emptyDir和hostPath两种(Kubernetes Pod的Volume:emptyDir和hostPath)。emptyDir是一个匿名的空目录,在Pod创建时创建,在Pod删除时删除。hostPath相对于Pod独立,用户指定一个路径,在Pod被删除后里面的数据可以得以保留,在Pod被创建前也可以放一些初始化的数据。和Node绑定的Volume在一个Pod从一个Node迁移到另一个Node之后里面的数据就会丢失。因此emptyDir和hostPath适合存储一些临时的数据或者用在一个Pod内的多个容器之间共享数据。
跨节点Volume不再和某个具体的Node节点绑定,其存储集群和Kubernetes是两个集群。Kubernetes Volume的实现通过插件的方式,目前主流的存储系统在Kubernetes都有对应的Volume插件,例如Google的gcePersistentDisk,Amazon的awsElasticBlockStore,这类Volume一般要与相对应的云主机结合使用。另外其他开源的分布式存储GlusterFS,Ceph也都有对应的Volume插件(在Kubernetes Pod中使用Ceph RBD Volume)。
Persistent Volume #
Persistent Volume和Volume的区别是:Volume和使用它的Pod之间是一种静态绑定关系,即在定义一个Pod的地方就会定义Volume;而PV是一个独立的Kubernetes资源对象,可以单独创建一个PV,而是通过PVC(Persistent Volume Claim)来实现动态绑定。
PV的访问模式有以下三种:
- ReadWriteOnce: 可读可写,只能被一个Node挂载。
- ReadOnlyMany:只读的,可以被多个Node挂载
- ReadWriteMany:可读可写,可以被多个Node共享。
不是每种存储类型都支持这三种访问模式,具体查看Access Modes。
实际使用场景PV和PVC的创建者一般是不同的人,比如由一个管理员创建一个PV池,由开发人员创建Pod和PVC,在PVC中指定所需要的PV的大小、访问模式,这个PVC就会根据要求去PV池里面绑定一个满足要求的PV(Kubernetes资源对象之Persistent Volumes)。
Dynamic Storage Provision #
PV的另一种使用场景是使用StorageClass来动态创建,节省管理员手动创建PV的时间,同时可以封装不同的存储类型给PVC去绑定。PVC可以根据StorageClass的名字去绑定。
1 apiVersion: storage.k8s.io/v1beta1
2 kind: StorageClass
3 metadata:
4 name: fast
5 provisioner: kubernetes.io/rbd
6 parameters:
7 monitors: 10.16.153.105:6789
8 adminId: kube
9 adminSecretName: ceph-secret
10 adminSecretNamespace: kube-system
11 pool: kube
12 userId: kube
13 userSecretName: ceph-secret-user
集群管理 #
Init-Container #
Init Container就是做初始化工作的容器。 一个Pod中可以有一个或多个Init Container,这些Init Container会依次执行,直到所有的Init Container被执行完后,业务Container才会被执行。一个Pod中的容器是共享网络和存储的,因此在Init Container中产生的数据就可以被业务Container使用。
Init Container可以用来等待其他服务就绪,例如服务A需要访问数据服务B,可以在服务A的Pod里面用一个Init Container检测数据服务B是否Ready,直到服务B Ready后Init Container才会退出,业务容器服务A启动。
1apiVersion: v1
2kind: Pod
3metadata:
4 name: myapp-pod
5 labels:
6 app: myapp
7 annotations:
8 pod.beta.kubernetes.io/init-containers: '[
9 {
10 "name": "init-myservice",
11 "image": "busybox",
12 "command": ["sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;"]
13 },
14 {
15 "name": "init-mydb",
16 "image": "busybox",
17 "command": ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"]
18 }
19 ]'
20spec:
21 containers:
22 - name: myapp-container
23 image: busybox
24 command: ['sh', '-c', 'echo The app is running! && sleep 3600']
Init Container还可以用来做集群的初始化配置工作,例如新节点在加入集群时可以在Init Container中获取集群其他节点的信息,生成初始化配置文件,当业务容器启动后就可以使用这个配置文件来加入集群。
Init Container在Kubernetes 1.5中使用annotation定义,还不是正式版本。
StatefulSet #
很多大型有状态服务需要以集群方式部署,因此每个服务实例需要有唯一的标识,各个实例可能还会对启动顺序有要求。针对于此,Kubernetes提供了一个名为StatefulSet的资源对象。
有状态服务具有以下特征:
要求稳定的网络身份。Headless Service为集群内部每个节点提供统一的DNS名称,同时不需要ClusterIP,通过
ClusterIp:None
指定。要求稳定的存储,这个可以通过PV实现。