lxcfs 是什么? lxcfs 实现对容器资源视图隔离的最佳实践

 

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

为何要作容器的资源视图隔离?

  1. 从容器的视角来看,一般有一些业务开发者已经习惯了在传统的物理机,虚拟机上使用 top, free 等命令来查看系统的资源使用状况,可是容器没有作到资源视图隔离,那么在容器里面看到的数据仍是物理机的数据。golang

  2. 从应用程序的视角来看,在容器里面运行进程和在物理机虚拟机上运行进程的运行环境是不一样的。而且有些应用在容器里面运行进程会存在一些安全隐患: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

    如何作容器的资源视图隔离?

    lxcfs 横空出世就是为了解决这个问题。

    lxcfs 是经过文件挂载的方式,把 cgroup 中关于系统的相关信息读取出来,经过 docker 的 volume 挂载给容器内部的 proc 系统。而后让 docker 内的应用读取 proc 中信息的时候觉得就是读取的宿主机的真实的 proc。api

    下面是 lxcfs 的工做原理架构图:

    解释一下这张图,当咱们把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到 Docker 容器的 /proc/meminfo 位置后,容器中进程读取相应文件内容时,lxcfs 的 /dev/fuse 实现会从容器对应的 Cgroup 中读取正确的内存限制。从而使得应用得到正确的资源约束。cpu 的限制原理也是同样的。

    经过 lxcfs 实现资源视图隔离

    安装 lxcfs

    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

    直接后台启动

    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 效果

    未开启 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 就是宿主机的。

    开启 lxcfs

    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。

    lxcfs 的 Kubernetes 实践

    在kubernetes中使用lxcfs须要解决两个问题:

    第一个问题是每一个node上都要启动 lxcfs;

    第二个问题是将 lxcfs 维护的 /proc 文件挂载到每一个容器中;

    DaemonSet方式来运行 lxcfs FUSE 文件系统

    针对第一个问题,咱们使用 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 上。

    映射 lxcfs 的 proc 文件到容器

    针对第二个问题,咱们两种方法来解决。

    第一种就是简单地在 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 文件的自动化挂载。整个过程对于应用部署人员是透明的,能够极大简化运维复杂度。


官方资讯*最新技术*独家解读


本文分享自微信公众号 - Go Official Blog(Go_Official_Blog)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索