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.java
”
用人话解释一下就是:node
xcfs 是一个开源的 FUSE(用户态文件系统)实现来支持 LXC 容器,它也能够支持 Docker 容器。让容器内的应用在读取内存和 CPU 信息的时候经过 lxcfs 的映射,转到本身的经过对 cgroup 中容器相关定义信息读取的虚拟数据上。nginx
”
容器技术提供了不一样于传统虚拟机技术的环境隔离方式。一般的 Linux 容器对容器打包和启动进行了加速,但也下降了容器的隔离强度。其中 Linux 容器最为知名的问题就是资源视图隔离问题。git
容器能够经过 cgroup 的方式对资源的使用状况进行限制,包括: 内存,CPU 等。可是须要注意的是,若是容器内的一个进程使用一些经常使用的监控命令,如: free, top 等命令其实看到仍是物理机的数据,而非容器的数据。这是因为容器并无作到对 /proc
, /sys
等文件系统的资源视图隔离。github
从容器的视角来看,一般有一些业务开发者已经习惯了在传统的物理机,虚拟机上使用 top
, free
等命令来查看系统的资源使用状况,可是容器没有作到资源视图隔离,那么在容器里面看到的数据仍是物理机的数据。golang
从应用程序的视角来看,在容器里面运行进程和在物理机虚拟机上运行进程的运行环境是不一样的。而且有些应用在容器里面运行进程会存在一些安全隐患:web
对于不少基于 JVM 的 java 程序,应用启动时会根据系统的资源上限来分配 JVM 的堆和栈的大小。而在容器里面运行运行 JAVA 应用因为 JVM 获取的内存数据仍是物理机的数据,而容器分配的资源配额又小于 JVM 启动时须要的资源大小,就会致使程序启动不成功。docker
对于须要获取 host cpu info 的程序,好比在开发 golang 服务端须要获取 golang中 runtime.GOMAXPROCS(runtime.NumCPU())
或者运维在设置服务启动进程数量的时候( 好比 nginx 配置中的 worker_processes auto ),都喜欢经过程序自动判断所在运行环境CPU的数量。可是在容器内的进程总会从/proc/cpuinfo
中获取到 CPU 的核数,而容器里面的/proc
文件系统仍是物理机的,从而会影响到运行在容器里面服务的运行状态。ubuntu
如何作容器的资源视图隔离?api
lxcfs 是经过文件挂载的方式,把 cgroup 中关于系统的相关信息读取出来,经过 docker 的 volume 挂载给容器内部的 proc 系统。 而后让 docker 内的应用读取 proc 中信息的时候觉得就是读取的宿主机的真实的 proc。
下面是 lxcfs 的工做原理架构图:
解释一下这张图,当咱们把宿主机的 /var/lib/lxcfs/proc/memoinfo
文件挂载到 Docker 容器的 /proc/meminfo
位置后,容器中进程读取相应文件内容时,lxcfs 的 /dev/fuse
实现会从容器对应的 Cgroup 中读取正确的内存限制。从而使得应用得到正确的资源约束。 cpu 的限制原理也是同样的。
wget <https://copr-be.cloud.fedoraproject.org/results/ganto/lxc3/epel-7-x86_64/01041891-lxcfs/lxcfs-3.1.2-0.2.el7.x86_64.rpm> rpm -ivh lxcfs-3.1.2-0.2.el7.x86_64.rpm 复制代码
检查一下安装是否成功
[root@ifdasdfe2344 system]# lxcfs -h
Usage: lxcfs [-f|-d] [-p pidfile] mountpoint -f running foreground by default; -d enable debug output Default pidfile is /run/lxcfs.pid lxcfs -h 复制代码
直接后台启动
lxcfs /var/lib/lxcfs &
复制代码
经过 systemd 启动(推荐)
touch /usr/lib/systemd/system/lxcfs.service
cat > /usr/lib/systemd/system/lxcfs.service <<EOF [Unit] Description=lxcfs [Service] ExecStart=/usr/bin/lxcfs -f /var/lib/lxcfs Restart=on-failure #ExecReload=/bin/kill -s SIGHUP $MAINPID [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl start lxcfs.service 复制代码
检查启动是否成功
[root@ifdasdfe2344 system]# ps aux | grep lxcfs
root 3276 0.0 0.0 112708 980 pts/2 S+ 15:45 0:00 grep --color=auto lxcfs root 18625 0.0 0.0 234628 1296 ? Ssl 14:16 0:00 /usr/bin/lxcfs -f /var/lib/lxcfs 复制代码
启动成功。
咱们首先在未开启 lxcfs 的机器上运行一个容器,进入到容器中观察 cpu, memory 的信息。为了看出明显的差异,咱们用了一台高配置服务器(32c128g)。
# 执行如下操做
systemctl stop lxcfs docker run -it ubuntu /bin/bash # 进入到 nginx 容器中 free -h 复制代码
经过上面的结果咱们能够看到虽然是在容器中查看内存信息,可是显示的仍是宿主机的 meminfo。
# 看一下 CPU 的核数
cat /proc/cpuinfo| grep "processor"| wc -l 复制代码
结果符合咱们的猜测,没有开启 lxcfs ,容器所看到的 cpuinfo 就是宿主机的。
systemctl start lxcfs
# 启动一个容器,用 lxcfs 的 /proc 文件映射到容器中的 /proc 文件,容器内存设置为 256M: docker run -it -m 256m \\ -v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \\ -v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \\ -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \\ -v /var/lib/lxcfs/proc/stat:/proc/stat:rw \\ -v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \\ -v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \\ ubuntu:latest /bin/bash free -h 复制代码
能够看到容器自己的内存被正确获取到了,对于内存的资源视图隔离是成功的。
# --cpus 2,限定容器最多只能使用两个逻辑CPU
docker run -it --rm -m 256m --cpus 2 \\ -v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \\ -v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \\ -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \\ -v /var/lib/lxcfs/proc/stat:/proc/stat:rw \\ -v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \\ -v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \\ ubuntu:latest /bin/sh 复制代码
cpuinfo 也是咱们所限制容器所能使用的逻辑 cpu 的数量了。指定容器只能在指定的 CPU 数量上运行应当是利大于弊的,就是在建立容器的时候须要额外作点工做,合理分配 cpuset。
在kubernetes中使用lxcfs须要解决两个问题:
第一个问题是每一个node上都要启动 lxcfs;
第二个问题是将 lxcfs 维护的 /proc 文件挂载到每一个容器中;
针对第一个问题,咱们使用 daemonset 在每一个 k8s node 上都安装 lxcfs。
直接使用下面的 yaml 文件:
apiVersion: apps/v1
kind: DaemonSet metadata: name: lxcfs labels: app: lxcfs spec: selector: matchLabels: app: lxcfs template: metadata: labels: app: lxcfs spec: hostPID: true tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: lxcfs image: registry.cn-hangzhou.aliyuncs.com/denverdino/lxcfs:3.0.4 imagePullPolicy: Always securityContext: privileged: true volumeMounts: - name: cgroup mountPath: /sys/fs/cgroup - name: lxcfs mountPath: /var/lib/lxcfs mountPropagation: Bidirectional - name: usr-local mountPath: /usr/local volumes: - name: cgroup hostPath: path: /sys/fs/cgroup - name: usr-local hostPath: path: /usr/local - name: lxcfs hostPath: path: /var/lib/lxcfs type: DirectoryOrCreate 复制代码
kubectl apply -f lxcfs-daemonset.yaml
复制代码
能够看到 lxcfs 的 daemonset 已经部署到每一个 node 上。
针对第二个问题,咱们两种方法来解决。
第一种就是简单地在 k8s deployment 的 yaml 文件中声明对宿主机 /var/lib/lxcfs/proc
一系列文件的挂载。
第二种方式利用Kubernetes的扩展机制 Initializer,实现对 lxcfs 文件的自动化挂载。可是 InitializerConfiguration
的功能在 k8s 1.14 以后就再也不支持了,这里再也不赘述。可是咱们能够实现 admission-webhook (准入控制(Admission Control)在受权后对请求作进一步的验证或添加默认参数, https://kubernetes.feisky.xyz/extension/auth/admission)来达到一样的目的。
# 验证你的 k8s 集群是否支持 admission
$ kubectl api-versions | grep admissionregistration.k8s.io/v1beta1 admissionregistration.k8s.io/v1beta1 复制代码
关于 admission-webhook 的编写不属于本文的讨论范围,能够到官方文档中深刻了解。
这里有一个实现 lxcfs admission webhook 的范例,能够参考:https://github.com/hantmac/lxcfs-admission-webhook
本文介绍了经过 lxcfs 提供容器资源视图隔离的方法,能够帮助一些容器化应用更好的识别容器运行时的资源限制。
同时,在本文中咱们介绍了利用容器和 DaemonSet 的方式部署 lxcfs FUSE,这不但极大简化了部署,也能够方便地利用 Kubernetes 自身的容器管理能力,支持 lxcfs 进程失效时自动恢复,在集群伸缩时也能够保证节点部署的一致性。这个技巧对于其余相似的监控或者系统扩展都是适用的。
另外咱们介绍了利用 Kubernetes 的 admission webhook,实现对 lxcfs 文件的自动化挂载。整个过程对于应用部署人员是透明的,能够极大简化运维复杂度。