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 文件的自動化掛載。整個過程對於應用部署人員是透明的,能夠極大簡化運維複雜度。