k8s集群内的节点,可能没你想象的那么健壮!(磁盘管理篇)

节点是组成 k8s 集群的基本单位,Pod 的容器最终是需要在节点上创建并运行起来,因此节点健康状态直接影响到了 k8s 集群和用户容器的健康。
在每个人入门容器的第一课,都会了解到容器在节点上是基于 namespace 和 cgroup 来做隔离,可是仅仅是相互之间做隔离,就足够了吗?
在容器应用落地和长期的运维过程中,会面临比隔离更多的实际需要面对的问题。归结起来,有两大类:

  • 当众多的容器在节点上运行起来,如何能保证容器的行为不会影响到节点的其他容器,或者甚至把节点搞挂?
    这个问题,是长期的 k8s 运维中会经常面对的一个问题,容器的某些行为,会影响到其他的容器和节点,在 k8s 的版本演进中,有些已经被修复,有些却一直存在并且会长期存在。

  • 如何能保证节点可以承载众多的应用?
    在应用独自占用节点的时代,节点的资源属于特定应用独享,所以在大多数时候,系统的默认配置,可以完全满足应用的需要。可是在容器时代,一个节点上会跑各种各样的应用,操作系统面临更复杂的业务情况,很多配置的默认值很已经不能满足需求了,进而影响到应用和节点的稳定性。

在 k8s 的落地实践中,这两个问题都是不能避免的,有时候甚至是一个问题。上甘岭不是一下子拿下来的,而是一个阵地一个阵地的争夺。节点稳定性维护也类似,不会是个一蹴而就的过程,而是各个细节点的优化再优化,一个点一个点地逐步解决。

本文选自新书 《Kubernetes 生产化实践之路》
,将从 节点磁盘
这个点来深入探讨。

1. 磁盘的分区

kubelet 的工作目录

在 kubelet 的默认设计上,是使用/var/lib/kubelet 作为工作目录的,并且默认使用/var/log 作为日志的存储目录,而且默认/var/lib/kubelet 和/var/log 是在 root 分区上的,所以不建议用户将这两个目录使用非 root 分区。Pod empytDir 的卷,就位于 kubelet 的工作目录下属于 Pod 的某个子目录, 例如:

1 /var/lib/kubelet/pods/a64f37ba8c5add4c01a106b1680248f9/volumes/kubernetes.io~empty-dir/tmp

运行时工作目录

用户会一般选择 docker,或者 containerd 作为运行时。运行时的工作目录,会存储容器的镜像,还有容器的可写层数据。运行时目录可以同样位于 root 分区上,也可以单独做个分区,避免容器的数据影响 root 分区。

磁盘其他分区

磁盘的其他分区可以作为 local volume 提供给 Pod 使用。至于如何有效管理这些磁盘,后面有机会可以再写个文章展开讲解。

2. 容器的存储驱动

docker 和 containerd 运行时都支持多种的容器存储驱动,例如 overlay2,devicemapper 等。overlay2 是目前广泛推荐的存储驱动,如果没有特殊的需求,基本上用户都会选择 overlay2 作为存储驱动。因为数据的存储,推荐都放在 Pod 的外挂卷上,而不要存储在容器的可写层,所以在容器的存储驱动上,稳定性是超过读写性能的更需要考虑的因素。

https://docs.docker.com/storage/storagedriver/select-storage-driver/#docker-engine—community
[1]
基于以上两个官方推荐的节点配置,我们来看下磁盘满这个问题。
Kubelet 具有针对磁盘的 eviction 机制,当磁盘 root 分区和运行时分区(如果存在)的 inode 或者空间少于一定的门限,会进行相关资源的释放,例如进行镜像的回收,删除已经退出的容器,或者删除相关正在运行的 pod。所以如果用户使用磁盘空间或者 inode 不恰当,可能会造成正在运行的其他 pod 被删除的情况,这是集群的管理者所不愿意看到的。
容器可以通过多种方式来使用节点上的磁盘空间,主要有 emptyDir,容器镜像,容器的可写层,以及容器日志。如果没有对这几种容器使用节点磁盘的方式进行限制,当容器往 emptyDir 或者容器可写层写大量数据,很容易将节点的 root 分区和运行时分区(如果存在)磁盘用满,进而导致节点不能正常工作。在 kubernetes 1.8 之前,这是经常发现的导致磁盘满的原因。尽管可以通过对 emptyDir 增加 sizeLimit 来限制对 emptyDir 的使用,但是因为这样的 sizeLimit 没有被调度处理,所以依然存在问题。

kubernetes 1.8 引入了 ephemeral-storage 的特性,来针对容器使用磁盘进行管理。emptyDir,容器日志,容器的可写层都被归结为 ephemeral-storage。类似 CPU,Memory,容器可以通过 spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
来申明对节点磁盘的空间使用。

但是这个特性直接打开就可以了吗?这个特性的使能,是有前提条件的。因为 emptyDir,日志,和容器可写层都被归结为统一的 ephemeral-storage,因此这三者需要在同一个磁盘分区(推荐是 root 分区)上才可以被统一管理。
当你费了九牛二虎之力将集群上的机器的磁盘分区都更改为支持该特性的模式,是否意味着全部解决问题了呢?用户的这些使用本地磁盘的行为,真的都能被限制住吗?
我们可以看 kubelet 如何来检测下 emptyDir,容器的可写层,和容器日志的使用率的,都采用了 du 或者类似 du 的方式。
当我们将一个文件打开,然后再将文件删除,因为文件描述符被打开,所以文件并不会真正删除,依旧占用磁盘空间,而 du 却检测不出来。因此如果有用户通过不断打开文件并删除的方式来占用磁盘空间,是可以导致节点的磁盘被写满,可是 kubelet 并不能进行有效控制。另外通过 du 的计算方式在文件大量存在的情况下非常低效,有时候当计算结果还没出来,磁盘就已经满了。

既然问题来了,聪明的开发者想到,可以通过文件系统的 quota 特性来对 emptyDir 卷的使用率进行监控,该方式可以检测到真正的磁盘使用率。而容器的可写层(只针对 overlayfs),如果是使用 docker 作为运行时,也是可以通过文件系统的 quota 来进行限制的( https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container
[2]
)。如果使用 containerd 作为运行时,那么目前还是不支持。
于是你需要将节点相关磁盘的 mount option 修改为支持文件系统的 quota 特性,然后在 kubelet,docker 上使能 quota 相关的特性,辛苦一番后,我们能翘起二郎腿,觉得万事大吉了吗?

当然还是不能,难道容器只能通过这三种方式往磁盘上写数据吗?

在 Dockerfile 中,我们能看到一条指令,叫 VOLUME 。

https://docs.docker.com/engine/reference/builder/#volume
[3]

意思是给容器提供一个卷来使用。很多开源的镜像,为了支持多种部署方式,都会在 Dockerfile 里面定义这样都卷来存储数据。当将这样的容器部署到集群里后,如果没有在 Pod Spec 里指定特定的卷 mount 到容器内 VOLUME 指令指定的目录,那么运行时会在运行时的工作目录下,创建一个本地目录,然后再 mount 给容器使用。当用户往该目录写数据使用磁盘,而该行为并不会被 kubelet 监控到。因此你只能修改相关的运行时代码,将该行为在相关节点上禁掉。
当经过了前面的阶段,磁盘的问题算彻底解决了吗?很明显,还是不能,因为磁盘不仅仅是空间占用问题,还有 inode 资源,IO 资源等问题。
① 当某个容器往 emptyDir 里面创建大量的问题,是否可以将磁盘的根分区的 inode 耗尽?
目前对容器 inode 资源的使用,并没有做相应的限制。
② 当某个容器往 emptyDir 里面大量读写数据,是否会将磁盘的 IO 耗尽,影响其他用户或者系统进程对磁盘的使用?
目前容器普遍使用的 cgroup v1 版本,还只能限制 direct IO,而无法限制 buffer IO。理论上 cgroup V2 可以解决这个问题。目前各大社区也在推进 cgroup v2 版本的支持工作,但是还并没有大规模使用。另外 cgroup V2 目前也才刚开始支持 XFS。

上面我们讲了 emptyDir 和容器可写层的限制,那么 日志
呢?

在我们的长期实践中,日志也是经常会导致节点空间被占用的原因之一,这里的日志包含系统服务的日志和容器的日志。日志的控制,非常依赖于节点 logrotate 服务。可是你节点上的 logrotate 服务真的配置正确了?更多内容 《Kubernetes 生产化实践之路》
一书中详细讲解。



新书上市






▊《Kubernetes生产化实践之路》


孟凡杰 等 著

  • 规模化K8s生产最佳实践
  • 基于K8s 1.18,所有案例来自eBay一线实践

本书从设计层面剖析了Kubernetes的设计原理,并阐述了其设计背后的生产系统问题。从互联网公司的视角出发,分享了如何构建高可用的多租户集群,如何确保集群的稳定性和高性能。

此外,本书阐述了数据面优化的重要性,并介绍了各个关键点,以确保使用物理机或虚拟机的应用在迁移至容器平台后能够获得最佳性能。

赠书福利

关注公众号 云原生实验室
,后台回复:
lovek8s

获取抽奖入口

:point_up_2:长按上方二维码关注,回复
lovek8s

即可参与

本次将随机抽取5位,每人获得 《Kubernetes生产化实践之路》
书籍一本,感谢电子工业出版社博文视点提供的书籍

活动截止时间: 2月1日18:00

没抽中的小伙伴也可以通过如下入口直接购买~

参考资料

[1]

https://docs.docker.com/storage/storagedriver/select-storage-driver/#docker-engine—community: https://docs.docker.com/storage/storagedriver/select-storage-driver/#docker-engine—community

[2]

https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container: https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container

[3]

https://docs.docker.com/engine/reference/builder/#volume: https://docs.docker.com/engine/reference/builder/#volume