Q1: 为什么需要对容器的资源进行可视化隔离?

A1: 容器技术提供了不同于传统虚拟机技术的环境隔离方式。通常的Linux容器对容器打包和启动进行了加速,但也降低了容器的隔离强度。其中Linux容器最为知名的问题就是资源视图隔离问题。

容器可以通过cgroup的方式对资源的使用情况进行限制,包括: 内存,CPU等。但是需要注意的是,如果容器内的一个进程使用一些常用的监控命令,如: free, top等命令其实看到还是物理机的数据,而非容器的数据。这是由于容器并没有做到对/proc,/sys等文件系统的隔离。

Q2: 容器资源视图隔离主要应用在哪些场景?

A1:

  1. 在容器生产环境,通常有一些业务已经习惯了在传统的物理机,虚拟机上使用top,free等命令来查看系统的资源使用情况,但是容器没有做到资源视图隔离,那么在容器里面看到的数据还是物理机的数据。
  2. 在应用程序的视角来看,在容器里面运行进程和在物理机虚拟机上运行进程的运行环境是不同的。并且有些应用在容器里面运行进程会存在一些安全隐患:

    对于很多基于JVM的java程序,应用启动时会根据系统的资源上限来分配JVM的堆和栈的大小。而在容器里面运行运行JAVA应用由于JVM获取的内存数据还是物理机的数据,而容器分配的资源配额又小于JVM启动时需要的资源大小,就会导致程序启动不成功。并且在java应用里,一些java库也会根据资源视图分配堆和栈的大小,这同样会存在安全隐患。

    在CPU上也会存在问题,大对数的应用程序,比如nginx或者一些其它的中间件服务会根据其视图的cpuinfo文件信息设定默认的启动线程数。但是在容器内的进程总会从/proc/cpuinfo中获取到CPU的核数,而容器里面的/proc文件系统还是物理机的,从而会影响到运行在容器里面服务的性能。

Q3: 针对容器资源视图没有完全解决的问题,如何做?

使用 lxcfs 和 kubernetes admission webhook机制来实现容器资源视图隔离的效果。

1.lxcfs官方介绍:

1
LXCFS is a small FUSE filesystem written with the intention of making Linux containers feel more like a virtual machine. It started as a side-project of LXC but is useable by any runtime.

关于lxcfs的详细内容,请戳:https://github.com/lxc/lxcfs

在应用Lxcfs在线上环境需要注意以下几点:

  1. 当前(lxcfs release 3.1.2)版本只对procfs文件系统进行了虚拟化,并没有对/sys/devices/system/cpu/online文件进行虚拟化,但是对/sys/devices/system/cpu/online的虚拟化工作已经被合并到master分支了,如果想要对其进行隔离操作的话,请单独对lxcfs进行编译在使用。

插个话题: 为什么需要对/sys/devices/system/cpu/online文件进行视图虚拟化?
主要问题是由于一些语言在启动runtime时,会从该文件获取cpu的数量,来启动默认的线程数,比如: Java JVM或者Nginx。如果在容器里面获取的还是物理机的数据,会影响到应用程序的性能。

关于/sys/devices/system/cpu/online文件虚拟化的工作的issues:https://github.com/lxc/lxcfs/issues/301

2.lxcfs需要部署在k8s集群的各个Node节点上,当Lxcfs服务重启或者creash时,之前已经挂载在容器/proc的挂载点会失效,导致在容器中执行free,top命令会失效。为了解决这个问题,现在的做法是使用systemd的方式在各个节点启动lxcfs服务,当lxcfs服务crash之后重启时,在lxcfs服务启动成功之后,会通过ExecStartPost的方式执行一个/usr/local/bin/container_remount_lxcfs.sh来重新对之前已经挂载过的容器进行重新的挂载操作。

lxcfs.service的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=FUSE filesystem for LXC
ConditionVirtualization=!container
Before=lxc.service
Documentation=man:lxcfs(1)

[Service]
ExecStart=/usr/bin/lxcfs -l /var/lib/lxc/lxcfs/
KillMode=process
Restart= always
Delegate=yes
ExecStopPost=-/bin/fusermount -u /var/lib/lxc/lxcfs
ExecReload=/bin/kill -USR1 $MAINPID

# add remount script
ExecStartPost=/usr/local/bin/container_remount_lxcfs.sh


[Install]
WantedBy=multi-user.target

lxcfs重启成功之后,执行的重新挂载脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#! /bin/bash

PATH=$PATH:/bin
LXCFS="/var/lib/lxc/lxcfs"
LXCFS_ROOT_PATH="/var/lib/lxc"

containers=$(docker ps | grep -v pause | grep -v calico | awk '{print $1}' | grep -v CONTAINE)

for container in $containers;do
mountpoint=$(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination "/var/lib/lxc" }}{{ .Source }}{{ end }}{{ end }}' $container)
if [ "$mountpoint" = "$LXCFS_ROOT_PATH" ];then
echo "remount $container"
PID=$(docker inspect --format '{{.State.Pid}}' $container)
# mount /proc
for file in meminfo cpuinfo loadavg stat diskstats swaps uptime;do
echo nsenter --target $PID --mount -- mount -B "$LXCFS/proc/$file" "/proc/$file"
nsenter --target $PID --mount -- mount -B "$LXCFS/proc/$file" "/proc/$file"
done
# mount /sys
for file in online;do
echo nsenter --target $PID --mount -- mount -B "$LXCFS/sys/devices/system/cpu/$file" "/sys/devices/system/cpu/$file"
nsenter --target $PID --mount -- mount -B "$LXCFS/sys/devices/system/cpu/$file" "/sys/devices/system/cpu/$file"
done
fi
done

2.基于kuernetes可扩展的admission webhook机制对发送创建POD的请求数据进行拦截,并对其请求的Body进行修改之后(在lxcfs中主要是对pod进行procfs文件系统的挂载操作),在放行到具体的handler中并持久化数据到etcd。

关于kubernetes admission webhook的详细介绍,请参考下面的文档:

在配一张图更能体现adminssion webhook在请求中的哪个位置进行工作。


lxcfs

我基于开源的lxcfs-admission-webhook尽进行了修改,增加了对/sys/devices/system/cpu/online文件的虚拟化。详细的代码请戳: https://github.com/xigang/lxcfs-admission-webhook/tree/dev

在使用lxcfs-admission-webhook服务时,需要注意的几点如下:

  1. kubernetes的版本需要大于等于1.9。
  2. 配置kube-apiserver配置文件,对–admission-control增加MutatingAdmissionWebhook,ValidatingAdmissionWebhook参数,已使kubernetes开启admissionregistration.k8s.io/v1beta1 API。
  3. 如果kubernetes master节点没有部署kube-proxy需要为kube-apiserver增加--enable-aggregator-routing=true的参数。
  4. 如果使用的runc版本比较低,需要升级各个Node节点的runc,已允许容器对procfs文件系统的挂载操作。

最终体现容器资源可视化隔离的效果如下所示:


lxcfs-top

lxcfs-free