溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Docker容器實現原理及容器隔離性踩坑介紹

發布時間:2020-08-11 07:43:16 來源:ITPUB博客 閱讀:334 作者:小米運維 欄目:云計算

正如Docker官方的口號:“Build once,Run anywhere,Configure once,Run anything”,Docker被貼上了如下標簽:輕巧、秒級啟動、版本管理、可移植性等等,這些優點讓它出現之初就收到極大的關注?,F在,Docker已經不僅僅是開發測試階段使用的工具,大家已經在生產環境中大量使用。今天我們給大家介紹關于容器隔離性的一個“坑”。在此之前,我們先來回顧一下Docker容器的底層實現原理。

容器底層實現

Docker容器實現原理及容器隔離性踩坑介紹 我們都知道,虛擬機與容器的底層實現原理是不同的,正如下圖對比:

Docker容器實現原理及容器隔離性踩坑介紹

虛擬機實現資源隔離的方法是利用一個獨立的Guest OS,并利用Hypervisor虛擬化CPU、內存、IO設備等實現的。例如,為了虛擬化內存,Hypervisor會創建一個shadow page table,正常情況下,一個page table可以用來實現從虛擬內存到物理內存的翻譯。相比虛擬機實現資源和環境隔離的方案,Docker就顯得簡練很多,它不像虛擬機一樣重新加載一個操作系統內核,引導、加載操作系統內核是一個比較耗時而又消耗資源的過程,Docker是利用Linux內核特性實現的隔離,運行容器的速度幾乎等同于直接啟動進程。

關于Docker實現原理,簡單總結如下:

  • 使用Namespaces實現了系統環境的隔離,Namespaces允許一個進程以及它的子進程從共享的宿主機內核資源(網絡棧、進程列表、掛載點等)里獲得一個僅自己可見的隔離區域,讓同一個Namespace下的所有進程感知彼此變化,對外界進程一無所知,仿佛運行在一個獨占的操作系統中;

  • 使用CGroups限制這個環境的資源使用情況,比如一臺16核32GB的機器上只讓容器使用2核4GB。使用CGroups還可以為資源設置權重,計算使用量,操控任務(進程或線程)啟停等;

  • 使用鏡像管理功能,利用Docker的鏡像分層、寫時復制、內容尋址、聯合掛載技術實現了一套完整的容器文件系統及運行環境,再結合鏡像倉庫,鏡像可以快速下載和共享,方便在多環境部署。

正因為Docker不像虛機虛擬化一個Guest OS,而是利用宿主機的資源,和宿主機共用一個內核,所以會存在下面問題:

注意:存在問題并不一定說就是安全隱患,Docker作為最重視安全的容器技術之一,在很多方面都提供了強安全性的默認配置,其中包括:容器root用戶的 Capability 能力限制,Seccomp系統調用過濾,Apparmor的 MAC 訪問控制,ulimit限制,鏡像簽名機制等。

1、Docker是利用CGroups實現資源限制的,只能限制資源消耗的最大值,而不能隔絕其他程序占用自己的資源;

2、Namespace的6項隔離看似完整,實際上依舊沒有完全隔離Linux資源,比如/proc 、/sys 、/dev/sd*等目錄未完全隔離,SELinux、time、syslog等所有現有Namespace之外的信息都未隔離。

容器隔離性踩過的坑

Docker容器實現原理及容器隔離性踩坑介紹

在使用容器的時候,大家很可能遇到過這幾個問題:

1、在Docker容器中執行 top、free 等命令,會發現看到的資源使用情況都是宿主機的資源情況,而我們需要的是這個容器被限制了多少CPU,內存,當前容器內的進程使用了多少;

2、在容器里修改/etc/sysctl.conf,會收到提示”sysctl: error setting key ‘net.ipv4….’: Read-only file system”;

3、程序運行在容器里面,調用API獲取系統內存、CPU,取到的是宿主機的資源大??;

4、對于多進程程序,一般都可以將worker數量設置成auto,自適應系統CPU核數,但在容器里面這么設置,取到的CPU核數是不正確的,例如Nginx,其他應用取到的可能也不正確,需要進行測試。

這些問題的本質都一樣,在Linux環境,很多命令都是通過讀取/proc 或者 /sys 目錄下文件來計算資源使用情況,以free命令為例:

lynzabo@ubuntu:~$ strace free
execve("/usr/bin/free", ["free"], [/* 66 vars */]) = 0
...
statfs("/sys/fs/selinux", 0x7ffec90733a0) = -1 ENOENT (No such file or directory)
statfs("/selinux", 0x7ffec90733a0) = -1 ENOENT (No such file or directory)
open("/proc/filesystems", O_RDONLY) = 3
...
open("/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3
...
open("/proc/meminfo", O_RDONLY) = 3
+++ exited with 0 +++
lynzabo@ubuntu:~$

包括各個語言,比如Java,NodeJS,這里以NodeJS為例:



const

 os = 

require

(

'os'

);

const total = os.totalmem();
const free = os.freemem();
const usage = (free - total) / total * 100 ;

NodeJS的實現,也是通過讀取/proc/meminfo文件獲取內存信息。Java也是類似。

我們都知道,JVM默認的最大Heap大小是系統內存的1/4,假若物理機內存為10G,如果你不手動指定Heap大小,則JVM默認Heap大小就為2.5G。JavaSE8(<8u131) 版本前還沒有針對在容器內執行高度受限的Linux進程進行優化,JDK1.9以后開始正式支持容器環境中的CGroups內存限制,JDK1.10這個功能已經默認開啟,可以查看相關Issue (Issue地址: https://bugs.openjdk.java.net/browse/JDK-8146115 ) 。熟悉JVM內存結構的人都清楚,JVM Heap是一個只增不減的內存模型,Heap的內存只會往上漲,不會下降。在容器里面使用Java,如果為JVM未設置Heap大小,Heap取得的是宿主機的內存大小,當Heap的大小達到容器內存大小時候,就會觸發系統對容器OOM,Java進程會異常退出。常見的系統日志打印如下:

memory: usage 2047696kB, limit 2047696kB, failcnt 23543
memory+swap: usage 2047696kB, limit 9007199254740991kB, failcnt 0
......
Free swap = 0kB
Total swap = 0kB
......
Memory cgroup out of memory: Kill process 18286 (java) score 933 or sacrifice child

對于Java應用,下面提供兩個辦法來設置Heap

1、對于JavaSE8(<8u131)版本,手動指定最大堆大小。

docker run的時候通過環境變量傳參確切限制最大heap大?。?

docker run -d -m 800M -e JAVA_OPTIONS='-Xmx300m' openjdk:8-jdk-alpine

2、對于JavaSE8(>8u131)版本,可以使用上面手動指定最大堆大小,也可以使用下面辦法,設置自適應容器內存限制。

docker run的時候通過環境變量傳參確切限制最大heap大小

docker run -d -m 800M -e JAVA_OPTIONS='-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1' openjdk:8-jdk-alpine

對比這兩種方式,第一種方式缺乏靈活性,在確切知道內存限制大小的情況下可以使用,第二種方法必須在JavaSE8(>8u131)版本才能使用。

當你啟動一個容器時候,Docker會調用libcontainer實現對容器的具體管理,包括創建UTS、IPS、Mount等Namespace實現容器之間的隔離和利用CGroups實現對容器的資源限制,在其中,Docker會將宿主機一些目錄以只讀方式掛載到容器中,其中包括/proc、/dev、/dev/shm、/sys目錄,同時還會建立以下幾個鏈接

  • /proc/self/fd->/dev/fd

  • /proc/self/fd/0->/dev/stdin

  • /proc/self/fd/1->/dev/stdout

  • /proc/self/fd/2->/dev/stderr 

保證系統IO不會 出現問題, 這也是為什么在容器里面取到的是宿主機資源原因。

了解了這些,那么我們在容器里該如何獲取實例資源使用情況呢,下面介紹兩個方法。

從CGroups中讀取

Docker容器實現原理及容器隔離性踩坑介紹

Docker 在 1.8 版本以后會將分配給容器的CGroups信息掛載進容器內部,容器里面的程序可以通過解析CGroups信息獲取到容器資源信息。

在容器里面可以運行mount 命令查看這些掛載記錄

...
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (ro,nosuid,nodev,noexec,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
...

在這里我們不講解CGroups對CPU和內存的限制都有哪些,只介紹基于Kubernetes編排引擎下的計算資源管理,對容器CGroups都做了哪些支持:

  • 當為Pod指定了requests,其中requests.cpu會作為--cpu-shares 參數值傳遞給docker run 命令,當一個宿主機上有多個容器發生CPU資源競爭時這個參數就會生效,參數值越大,越容易被分配到CPU,requests.memory不會作為參數傳遞給Docker,這個參數在Kubernetes的資源QoS管理時使用;

  • 當為Pod指定了limits,其中limits.cpu會作為--cpu-quota 參數的值傳遞給docker run 命令,docker run命令中另外一個參數--cpu-period 默認設置為100000,通過這兩個參數限制容器最多能夠使用的CPU核數,limits.memory會作為--memory 參數傳遞給docker run 命令,用來限制容器內存,目前Kubernetes不支持限制Swap大小,建議在部署Kubernetes時候禁用Swap。

Kubernetes 1.10以后支持為Pod指定固定CPU編號,我們在這里不詳細介紹,就以常規的計算資源管理為主,簡單講一下以Kubernetes作為編排引擎,容器的CGroups資源限制情況:

1、讀取容器CPU核數

# 這個值除以100000得到的就是容器核數
~ # cat  /sys/fs/cgroup/cpu/cpu.cfs_quota_us 
400000

2、獲取容器內存使用情況(USAGE / LIMIT)

~ # cat /sys/fs/cgroup/memory/memory.usage_in_bytes 
4289953792
~ # cat /sys/fs/cgroup/memory/memory.limit_in_bytes 
4294967296

將這兩個值相除得到的就是內存使用百分比。

3、獲取容器是否被設置了OOM,是否發生過OOM

~ # cat /sys/fs/cgroup/memory/memory.oom_control 
oom_kill_disable 0
under_oom 0
~ #
~ #

這里需要解釋一下:

  • oom_kill_disable默認為0,表示打開了oom killer,就是當內存超時會觸發kill進程??梢栽谑褂胐ocker run時候指定disable oom,將此值設置為1,關閉oom killer;

  • under_oom 這個值僅僅是用來看的,表示當前的CGroups的狀態是不是已經oom了,如果是,這個值將顯示為1。

4、獲取容器磁盤I/O

~ # cat /sys/fs/cgroup/blkio/blkio.throttle.io_service_bytes
253:16 Read 20015124480
253:16 Write 24235769856
253:16 Sync 0
253:16 Async 44250894336
253:16 Total 44250894336
Total 44250894336

5、獲取容器虛擬網卡入/出流量

~ # cat /sys/class/net/eth0/statistics/rx_bytes 
10167967741
~ # cat /sys/class/net/eth0/statistics/tx_bytes 
15139291335
~ #

如果你對從容器中讀取CGroups感興趣,可以點擊最下方 閱讀原文 了解 docker stats源碼實現。


使用LXCFS

Docker容器實現原理及容器隔離性踩坑介紹

由于習慣性等原因,在容器中使用top、free等命令仍然是一個較為普遍存在的需求,但是容器中的/proc、/sys目錄等還是掛載的宿主機目錄,有一個開源項目:LXCFS。LXCFS是基于FUSE實現的一套用戶態文件系統,使用LXCFS,讓你在容器里面繼續使用top、free等命令變成了可能。但需要注意,LXCFS可能會存在很多問題,建議在線上環境先不要使用。

總結

容器給大家帶來了很多便利,很多公司已經或正在把業務往容器上遷移。在遷移過程中,需要清楚上面介紹的這個問題是不是會影響應用的正常運行,并采取相應的辦法繞過這個坑。

這篇文章的分享就到這里,希望對大家有所幫助。

Docker容器實現原理及容器隔離性踩坑介紹


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女