searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

一次编译,到处运行?容器口号切勿盲信

2023-12-25 08:18:04
113
0

1. 问题的提出

  长期以来,对于容器的“build once, run anywhere”的口号深信不疑。在生产环境中随意拉取公网上docker images,直接使用或在此基础上继续构建自己业务的现象比比皆是。今天我们先不谈镜像的仓库及安全更新问题,只就性能和兼容性问题,带大家看看这样做会给我们带来多大的麻烦。

2. 事故案例

现网中出现数十台物理机反应很慢,偶发性死机问题。经top发现普遍存在crond进程CPU占用高且数量庞大的问题。然而奇怪的是这种问题只出现在宿主机系统为ctyunos2的环境中。而最终我们可以把复现环境缩小到如下情况:

  操作系统: CTyunOS2.0.1 vs CentOS7.9

  容器环境: docker

  容器镜像: CTyunos2 vs CentOS8.4 (容器内只运行一个crond)

对比后如图所示,在加载同样的基于centos8.4的业务镜像情况下,左图为CtyunOS2.0.1操作系统,有大量crond进程占用CPU资源,容易导致系统卡死;右图为CentOS7.9操作系统,则没有任何问题。

但在使用基于ctyunos2容器基础镜像构建的业务镜像时,两边都没有发现cpu 负载过高的问题。

3. 根因分析

(1) 为什么会造成性能损失,为什么ctyunos镜像没问题

打开容器环境中的/var/log/message, 可以看到很多文件关系相关的错误,直观上是因为大量fd需要关闭导致的。

利用在top中查看到的crond的pid使用gdb -p <crond pid>可以看到进程最后的调用堆栈:

根据do_command child_process 和close,推测到应该与这个github的PR有关:

cronie-crond/cronie/commit/584911514ce6aa2f16e1d79431bac816ea62cb2c

fdmax是在老版本的cronie中是由getdtablesize直接设定,而执行关闭文件关闭时会一直遍历到fdmax,可以想见,如果fdmax过大,性能将有极大损失。

新版本中fdmax将被限制在MAX_CLOSE_FD之内,但这个PR是从cronie-1.5.5版本才引入。线上正在使用的centos8.4镜像中正是在使用低于此版本的cronie-1.4.11-23,而基于我们ctyunos2的镜像使用的是高于此版本的cronie-1.5.5-2,所以ctyunos2的镜像是做过问题修复的,而centos的没有。

结论1:使用基于其它系统构建的镜像,存在安全或性能更新不及时的风险

(2) 为什么有问题的镜像在centos7.9上没问题

对比发现,centos7.9的gettabelesize()值比ctyunos2的要小很多。而这个值的来源是/proc/1/limits中的Max open files的值。

在ctyunos2上soft Limit和Hard Limit都是一个很大的数:1073741816。

而这个值在centos7.9上是1024*1024 = 1048576,足足差了1000多倍

也就是说在使用含有低版本cronie包的镜像的条件下,在遍历关闭文件过程中,ctyunos2要比centos7.9多花1000多倍的时间。当然我们知道这些重要的系统值是牵一发而动全身的,简单的改小系统的Max open files绝不是我们想要的解决方案,也许这也是当初cronie提PR修复的原因。因此根据系统情况选择正确的镜像就显得犹为重要。

结论2:宿主机系统与镜像系统的版本在某些情况下是相互挑剔的,因此业务镜像最好使用同一系统的base image进行构建,以确保业务的万无一失。

(3) 为什么ctyunos2的max open files这么大?

根据Redhat的官方文档solutions/1479623:

Note: The value of "Max open files"(ulimit -n) is limited to fs.nr_open value.

通过这个实验可以清楚的看到ulimit -n的值不能设置的大于fs.nr_open

而fs.nr_open这个值最初定义于kernel:kernel/fs/file.c

取__read_mostly和sysctl_nr_open_max相比中最大的:

  27 unsigned int sysctl_nr_open __read_mostly = 1024*1024;

  28 unsigned int sysctl_nr_open_min = BITS_PER_LONG;

  29 /* our min() is unusable in constant expressions ;-/ */

  30 #define __const_min(x, y) ((x) < (y) ? (x) : (y))

  31 unsigned int sysctl_nr_open_max =

  32         __const_min(INT_MAX, ~(size_t)0/sizeof(void *)) & -BITS_PER_LONG;

我们发现不只在CTyunOS2上,在RHEL9上ulimit -n(Max open files)值也都比较大。直接设置原因在于fs.nr_open被某个神秘程序更新为了很大的数:

# cat /proc/sys/fs/nr_open

1073741816

而这背后的原因应该是大规模商用系统需要更多的可打开文件数,这显然不能被随意设小。那么这个神秘程序是谁呢?

(4) 是谁更新了fs.nr_open?

简而言之,是新版本的systemd。请看github的PR

systemd/systemd/commit/a8b627aaed409a15260c25988970c795bf963812

这个merge是在systemd v240里加入的,我们ctyunos2中的版本是v243,而centos7.9的版本是v219. 这也就解释了为什么centos7.9的fs.nr_open小,而RHEL9及CTyunOS2的大

结论3:即使是同系列系统,重要系统值也会有所不同,所以要慎重选择系统。

4. 总结

基于上面的三条结论:

  • 使用基于其它系统构建的镜像,存在安全或性能更新不及时的风险
  • 宿主机系统与镜像系统的版本在某些情况下是相互挑剔的,因此业务镜像最好使用同一系统的base image进行构建,以确保业务的万无一失。
  • 即使是同系列系统,重要系统值也会有所不同,所以要慎重选择系统。

我们的总结如下:

宿主机和容器系统版本差异较大时会存在风险,虽然这种问题概率较低,以至于我们长期没有重视,但从商业稳定角度来看,推荐从自己的业务开始推行centos/ubuntu/alpine/busybox等镜像替换工作。

使用其它系统的镜像不仅有安全漏洞隐患(尤其是centos这样的镜像),后续无法保障安全更新,还可能造成重大性能问题,潜在造成很多不明原因的系统卡死问题。推荐使用构建流水线,基于自己的base image全面构建自研业务镜像,并配套使用类似于k8s-install(可直接从gitee中搜索)的工具安装并更新相关软件包及容器镜像,全面达成安全漏洞封堵、业务100%兼容及性能优化的需要。

另一方面我们也意识到,我们无法强制客户全部使用ctyunos重构自己的镜像,无法阻止客户拉取网上centos镜像,这就和当前公有云主机我们还提供centos一样,客户习惯我们要尊重,但是风险提示我们要给到位,出现问题客户要么自行解决,要么接受建议使用我们提供的ctyunos镜像。

总之,容器层面的centos替换,应首先从存在问题的镜像做起,然后逐步推广。

0条评论
0 / 1000
w****n
4文章数
2粉丝数
w****n
4 文章 | 2 粉丝
原创

一次编译,到处运行?容器口号切勿盲信

2023-12-25 08:18:04
113
0

1. 问题的提出

  长期以来,对于容器的“build once, run anywhere”的口号深信不疑。在生产环境中随意拉取公网上docker images,直接使用或在此基础上继续构建自己业务的现象比比皆是。今天我们先不谈镜像的仓库及安全更新问题,只就性能和兼容性问题,带大家看看这样做会给我们带来多大的麻烦。

2. 事故案例

现网中出现数十台物理机反应很慢,偶发性死机问题。经top发现普遍存在crond进程CPU占用高且数量庞大的问题。然而奇怪的是这种问题只出现在宿主机系统为ctyunos2的环境中。而最终我们可以把复现环境缩小到如下情况:

  操作系统: CTyunOS2.0.1 vs CentOS7.9

  容器环境: docker

  容器镜像: CTyunos2 vs CentOS8.4 (容器内只运行一个crond)

对比后如图所示,在加载同样的基于centos8.4的业务镜像情况下,左图为CtyunOS2.0.1操作系统,有大量crond进程占用CPU资源,容易导致系统卡死;右图为CentOS7.9操作系统,则没有任何问题。

但在使用基于ctyunos2容器基础镜像构建的业务镜像时,两边都没有发现cpu 负载过高的问题。

3. 根因分析

(1) 为什么会造成性能损失,为什么ctyunos镜像没问题

打开容器环境中的/var/log/message, 可以看到很多文件关系相关的错误,直观上是因为大量fd需要关闭导致的。

利用在top中查看到的crond的pid使用gdb -p <crond pid>可以看到进程最后的调用堆栈:

根据do_command child_process 和close,推测到应该与这个github的PR有关:

cronie-crond/cronie/commit/584911514ce6aa2f16e1d79431bac816ea62cb2c

fdmax是在老版本的cronie中是由getdtablesize直接设定,而执行关闭文件关闭时会一直遍历到fdmax,可以想见,如果fdmax过大,性能将有极大损失。

新版本中fdmax将被限制在MAX_CLOSE_FD之内,但这个PR是从cronie-1.5.5版本才引入。线上正在使用的centos8.4镜像中正是在使用低于此版本的cronie-1.4.11-23,而基于我们ctyunos2的镜像使用的是高于此版本的cronie-1.5.5-2,所以ctyunos2的镜像是做过问题修复的,而centos的没有。

结论1:使用基于其它系统构建的镜像,存在安全或性能更新不及时的风险

(2) 为什么有问题的镜像在centos7.9上没问题

对比发现,centos7.9的gettabelesize()值比ctyunos2的要小很多。而这个值的来源是/proc/1/limits中的Max open files的值。

在ctyunos2上soft Limit和Hard Limit都是一个很大的数:1073741816。

而这个值在centos7.9上是1024*1024 = 1048576,足足差了1000多倍

也就是说在使用含有低版本cronie包的镜像的条件下,在遍历关闭文件过程中,ctyunos2要比centos7.9多花1000多倍的时间。当然我们知道这些重要的系统值是牵一发而动全身的,简单的改小系统的Max open files绝不是我们想要的解决方案,也许这也是当初cronie提PR修复的原因。因此根据系统情况选择正确的镜像就显得犹为重要。

结论2:宿主机系统与镜像系统的版本在某些情况下是相互挑剔的,因此业务镜像最好使用同一系统的base image进行构建,以确保业务的万无一失。

(3) 为什么ctyunos2的max open files这么大?

根据Redhat的官方文档solutions/1479623:

Note: The value of "Max open files"(ulimit -n) is limited to fs.nr_open value.

通过这个实验可以清楚的看到ulimit -n的值不能设置的大于fs.nr_open

而fs.nr_open这个值最初定义于kernel:kernel/fs/file.c

取__read_mostly和sysctl_nr_open_max相比中最大的:

  27 unsigned int sysctl_nr_open __read_mostly = 1024*1024;

  28 unsigned int sysctl_nr_open_min = BITS_PER_LONG;

  29 /* our min() is unusable in constant expressions ;-/ */

  30 #define __const_min(x, y) ((x) < (y) ? (x) : (y))

  31 unsigned int sysctl_nr_open_max =

  32         __const_min(INT_MAX, ~(size_t)0/sizeof(void *)) & -BITS_PER_LONG;

我们发现不只在CTyunOS2上,在RHEL9上ulimit -n(Max open files)值也都比较大。直接设置原因在于fs.nr_open被某个神秘程序更新为了很大的数:

# cat /proc/sys/fs/nr_open

1073741816

而这背后的原因应该是大规模商用系统需要更多的可打开文件数,这显然不能被随意设小。那么这个神秘程序是谁呢?

(4) 是谁更新了fs.nr_open?

简而言之,是新版本的systemd。请看github的PR

systemd/systemd/commit/a8b627aaed409a15260c25988970c795bf963812

这个merge是在systemd v240里加入的,我们ctyunos2中的版本是v243,而centos7.9的版本是v219. 这也就解释了为什么centos7.9的fs.nr_open小,而RHEL9及CTyunOS2的大

结论3:即使是同系列系统,重要系统值也会有所不同,所以要慎重选择系统。

4. 总结

基于上面的三条结论:

  • 使用基于其它系统构建的镜像,存在安全或性能更新不及时的风险
  • 宿主机系统与镜像系统的版本在某些情况下是相互挑剔的,因此业务镜像最好使用同一系统的base image进行构建,以确保业务的万无一失。
  • 即使是同系列系统,重要系统值也会有所不同,所以要慎重选择系统。

我们的总结如下:

宿主机和容器系统版本差异较大时会存在风险,虽然这种问题概率较低,以至于我们长期没有重视,但从商业稳定角度来看,推荐从自己的业务开始推行centos/ubuntu/alpine/busybox等镜像替换工作。

使用其它系统的镜像不仅有安全漏洞隐患(尤其是centos这样的镜像),后续无法保障安全更新,还可能造成重大性能问题,潜在造成很多不明原因的系统卡死问题。推荐使用构建流水线,基于自己的base image全面构建自研业务镜像,并配套使用类似于k8s-install(可直接从gitee中搜索)的工具安装并更新相关软件包及容器镜像,全面达成安全漏洞封堵、业务100%兼容及性能优化的需要。

另一方面我们也意识到,我们无法强制客户全部使用ctyunos重构自己的镜像,无法阻止客户拉取网上centos镜像,这就和当前公有云主机我们还提供centos一样,客户习惯我们要尊重,但是风险提示我们要给到位,出现问题客户要么自行解决,要么接受建议使用我们提供的ctyunos镜像。

总之,容器层面的centos替换,应首先从存在问题的镜像做起,然后逐步推广。

文章来自个人专栏
CTyunOS
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
4
2