有状态应用允许用户重复返回该应用并恢复之前的操作,比如电子邮件或者网上银行应用。有状态的应用会记录之前事务的上下文,这些上下文可能会对当前或未来事务产生影响。所以,有状态的应用必须确保每个用户始终访问同一个应用程序实例,或者有某种在实例之间同步数据的机制。
本文通过在分布式数据库在kubernetes的实践,总结如下有状态应用的注意点。
稳定且唯一的标识符
kubernetes中支持稳定且唯一的标识符的workload是StatefulSet。StatefulSet 中的 Pod 都是有序号的,从 0 开始一直到定义的 replica 数量减一。配合headless service, 可以让StatefulSet的pod在整个k8s集群中有稳定且唯一的域名。
对于部分分布式数据库或者存储,除了要去pod有唯一的域名或者hostname外,还要求pod有不变的ip。解决ip不变的问题有如下两种方法:
- 选择hostnetwork,可以让pod使用node 的网络,可以保证ip不变,不过需要nodeSelector或者绑定pv保证pod不会在node间漂移;
- 选择pod的ip不变的网络插件:
- 开源网络插件Calico:支持IPIP和BGP两种协议,同时也支持给特定pod指定ip。
- 阿里巴巴开源网络插件hybridnet: 支持针对StatefulSet pod保留ip
数据可持久化
Kubernetes 中的常规存储卷会有一个确定的生命周期:每个卷都与 pod 的生命周期绑定。当 pod 处于活跃状态的时候,卷会保持在 pod 内,如果重启 pod 的话,卷会被重置。所以针对有状态的应用,需要pod能够挂载持久卷(Persistent Volume)。
针对持久卷,可以根据不同环境使用不同的StorageClass。在公有云环境,可以使用云厂商提供的云盘。如果没有云盘,可以使用本地盘,详解 云原生本地磁盘管理-天翼云开发者社区 - 天翼云 (ctyun.cn)。
Pod绑定PV,除了能保证数据的可持久化,另外可以通过PV的稳定性,保证pod不会因为重启、删除而出现漂移的现象。
驱逐管理
Kubernetes在节点收到压力后, kubelet 主动终止 Pod 以回收节点上资源。如果遇到流量高峰,可能出现雪崩效应。有状态应该由于无法漂移,可能会出现被驱逐后无法再被调度。
在针对分布式数据库或存储应用的pod,可以选择较高QoS的资源分配,而且其他无状态或者指标采集应用可以选择较低QoS的资源分配。
合理的分配节点资源,如docker 运行时的磁盘空间或者容器挂载的目录不要使用系统盘资源。
如果时对有状态应用独占情况,可以调高驱逐的条件或删除kubelet驱逐的特性。
kubelet 具有以下默认硬驱逐条件:
memory.available<100Mi
nodefs.available<10%
imagefs.available<15%
nodefs.inodesFree<5%
(Linux 节点)
容器生命周期管理
不同于无状态应用,有状态应用一般需要上下文信息需要在启动加载或者停止时分发。如分布式数据库需要在启动服务时加载分片信息,在停止服务时把分片的信息分发给其他节点。
Kubernetes 提供了两种容器钩子(hook)来实现容器启动后和容器关闭前需要执行任务:
PostStart: 这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起, 容器将不能达到running状态。
PreStop:这个钩子在容器终止之前立即被调用。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起, Pod阶段将停留在running状态并且永不会达到failed状态。
有两种方式来实现上面的钩子:
- Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。
- HTTP - 对容器上的特定的端点执行HTTP请求。