10、Statefulset
无状态的服务我们可以采用rc、rsdeployment来进行创建与管理,对于有状态的服务,我们就要采用StatefulSet。
StatefulSet 有以下特点:
- 稳定的、唯一的网络标识符
- 稳定的、持久化的存储
- 有序的、优雅的部署和缩放
- 有序的、优雅的删除和终止
- 有序的、自动滚动更新
StatefulSet创建的每个pod都有从零开始的顺序索引,这个体现在名称与主机名上 ,同样还会体现在pod对应的固定存储上,这些名字是可知的。与无状态的pod不一样的是,有状态的pod需要有时候通过其主机名来进行定位。
一个StatefulSet通常需要创建一个用来记录每个pod网络标记的headless Service,通过这个service,每个pod将拥有独立的DNS记录,这样集群里的其他伙伴或客户端就可以通过主机名方便的找到它。比如说在default命名空间中有一个名为foo的控制服务,它的一个pod叫A-0,name可以通过:A-0.foo.default.svc.cluster.local 找到它。另外还可以通过DNS服务,查找域名foo.default.svc.cluster.local 对应的所有SRV记录,获取一个StatefulSet中所有的pod。
扩索容StatefulSet
扩容一个StatefulSet是使用下一个还没用到的顺序索引值创建一个新的pod,当缩容一个StatefulSet时,也会先删除索引值最高的pod,需要注意的是,不管是扩容还是缩容,StatefulSet 操作pod时,都是有序的,不会同时扩展与收缩。且当StatefulSet存在不健康的实例时,是不允许进行收缩的。
每个有状态的pod的存储必须是持久的,并且与pod解耦。因为持久化声明与持久卷时一一对应的,所以每个StatefulSet的pod都需要关联到不同的持久卷声明,与独自的持久卷对应。因为每个pod都需要根据pod模板进行创建,又要关联到不同的数据卷,所以引入了数据卷声明模板。每个StatefulSet都可以拥有一个或多个数据卷声明模板,在 pod创建前,这些持久卷声明会在之前创建好,然后绑定到一个pod实例上。
持久的数据卷可以被预先创建好,也可以由持久卷动态供应机制实时创建。
当StatefulSet增加一个副本时,会创建至少两个或更多的API对象(一个pod,与之关联一个或更多的持久声明),但缩容时,只会删除一个pod,与之关联的持久声明不会被删除。因为当持久声明被删除时,与之绑定的持久卷就会被回收或删除。因为有状态的pod是用来运行有状态应用的,所以其在数据卷上的数据非常重要,在StatefulSet缩容时删除这个声明是灾难性的。当后续扩容时,该持久声明将会被再次使用。如果要释放特定持久卷时,你需要手动删除对应的持久声明。
使用StatefulSet
我们来创建一个简单的StatefulSet,当应用收到一个POST请求时,它会把请求中的body写入/var/data/kubia.txt 文件中。当收到一个GET请求时,它会返回其主机名和存储的数据。
部署一个StatefulSet 需要准备三个对象
存储数据的持久数据卷,可以手动创建,也可以是动态供应
一个headless Service
StatefulSet本身
创建持久卷 persistent-volume-gcepd.yaml
kind: List # 可以通过--- 来区分定义多个资源。这里采用另一种方法,
apiVersion: v1 # 这里定义一个List对象,然后把各个资源作为List对象的各个项目
items:
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-a
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
gcePersistentDisk: # 需要注意的是,这里采用谷歌的GCE持久数据卷,需要谷歌引擎支持
pdName: pv-a # 如果集群不支持的话,请采用其他的数据卷类型
fsType: nfs4
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-b
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
gcePersistentDisk:
pdName: pv-a
fsType: nfs4
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-c
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
gcePersistentDisk:
pdName: pv-a
fsType: nfs
创建一个 headless Service, kubia-service-deadless.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-headless
spec:
clusterIP: None
selector:
app: kubia
role: stateful
ports:
- name: http
port: 80
创建StatefulSet 本身, kubia-statefulset.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: kubia
spec:
serviceName: kubia-headless # 指定service的名称
replicas: 2
template:
metadata:
labels: # pod需要具有service 标签选择器筛选的标签
app: kubia
tole: stateful
spec:
containers:
- image: luksa/kubia-pet
name: kubia
ports:
- name: http
containerPort: 8080
volumeMounts:
- name: data
mountPath: /var/data
volumeClaimTemplates: # 这是持久数据卷声明模板,这是新引入的
- metadata:
name: data
spec:
resources:
requests:
storage: 1Mi
accessModes:
- ReadWriteOnce
使用pod
由于service是headless类型的,所以服务时没有地址的,因此不能通过它来访问pod。我们可以通过API服务器与pod通信。
由于挨批服务器是有安全保障的,所以每次与其连接,都比较繁琐,因为要认证与授权。我们可以采用kubectl proxy 来与API服务器通信,而不用授权和SSL证书。
在终端中执行 kubectl proxy,从而开启kubectl代理来与API服务器通信,将使用localhost:8001 来替代实际的服务器主机地址与端口。通过以下命令来发送请求
curl localhost:8001/api/v1/namespace/default/pods/kubia-0/prosy/
注意:如果收到一个空的回应,请确保URL最后的 / 没有忘记
此时通信,每个请求都会经过两个代理,第一个是kubectl的代理,第二个是把请求代理到pod的API服务器
当我们删除一个pod时,StatefulSet会创建一个具有相同名称的新的pod。需要注意的是,新的pod可能被调度到其他的节点,并不一定会与旧的的pod一致。旧的pod的全部标记(名称、主机名和存储)都会转移到新的pod(pod的IP会发生改变)。
更新StatefulSet
使用 kubectl edit statefulset kubia 命令,会用默认编辑器打开StatefulSet的定义,然后再定义中对其进行修改,保存后退出。需要注意的是StatefulSet的更新更像ReplicaSet,而不是Deployment。所以再更新时,新创建的pod会才有新的模板,而已运行的副本是不会更新的,除非手动的删除已运行的副本。
注意:kubernetes1.7 版本开始,StatefulSet可以向Deployment与DaemonSet一样支持滚动更新。通过 kubectl explain可以查看相关文档。
StatefulSet 处理节点失效
当一个节点突然失效,Kubernetes 并不知道节点或者它上面运行的pod状态,不知道还有哪些pod在运行,也不知道它们是否还能被客户端访问,或者仅仅是kubelet停止向主节点上报节点状态。以为一个StatefulSet要保证不会有两个拥有相同标记和存储的pod同时运行,当一个节点失效时,在StatefulSet在明确知道一个pod不再运行之前,它是不会也不能再创建一个替换者。
只有当集群管理员告诉它,它才能明确知道,为了做到这一点,需要管理员删除这个pod,或删除整个节点。
手动模拟节点网络断开
进入一个节点,将网卡关闭,节点不再像master上报状态,此时查看node的状态,会发现为NotReady状态。该节点上的pod状态,都将变为unknown状态。
当一个pod处于Unknown状态时,会发生什么?
若该节点过段时间正常联通,则该pod将变为running状态。但如果这个pod的位置状态持续几分钟后(这个时间可以设置),这个pod将会自动从该节点上驱逐。这是由主节点处理的,它通过删除pod资源来把它从节点上驱逐。
当kubelet发现该pod状态被标记为删除时,它开始终止运行该pod。但是当节点网络断开时,这一切都不会发生,所以该pod还将一直运行。
手动删除pod,kubectl delete pod kubia-0,然后再次查看pod状态
此时通过AGE列中会发现,还是原来那个pod,该pod并没有被删除。这是因为在删除之前,这个pod已经被标记为删除,控制组件已经删除了它(把它从节点驱逐)。只要它所在节点上的kubelet通知API服务器,告知该pod容器已终止,那它将会被清除。但是由于节点的网络不通,所以产生了该现象。
此时可以强制删除pod,告诉API服务器不用等待kubelet来确认该pod不再运行,可以直接删除它。
kubectl delete pod kubia-0 --fore --grace-period 0
(本文章是学习《kubernetes in action》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)