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实现。

  • Creating a StatefulSet

参考