成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一次奇幻的 docker libcontainer 代碼閱讀之旅

云計(jì)算
一直對(duì) docker 提供的容器感到好奇,不知道究竟是如何實(shí)現(xiàn)隔離盒保證安全的,之前 docker 本來(lái)是用 lxc 來(lái)提供容器功能的,但是由于對(duì)內(nèi)核代碼有一絲恐懼沒(méi)敢去看,后來(lái)聽(tīng)說(shuō) docker 為了實(shí)現(xiàn)跨平臺(tái)兼容自己實(shí)現(xiàn)了一套 native 的容器就是 libcontainer 。既然是新項(xiàng)目那么代碼量和復(fù)雜度應(yīng)該都不會(huì)太高吧,抱著這個(gè)想法我就翻看 libcontainer 的代碼讀一讀。

準(zhǔn)備工作

首先自然要下到代碼才能讀,建議去下完整的 docker 源碼,不要只下 libcontainer 的源碼。不然就會(huì)像我一樣讀的時(shí)候碰到一個(gè)坑掉里面爬了半天。

接下來(lái)就要有一個(gè)代碼閱讀器了,由于 go 語(yǔ)言還是個(gè)比較新的語(yǔ)言,配套的工具還不是很完善,不過(guò)可以用 liteide (自備梯子)這個(gè)輕量級(jí)的 golang ide 來(lái)兼職一下。

打開(kāi)之后可以看到 docker 的目錄結(jié)構(gòu)大致是這樣的。

alt

那么我們所關(guān)注的 libcontainer 在哪里呢?藏得還挺深的在 \verdor\src\github.com\libcontainer\。進(jìn)去之后就會(huì)發(fā)現(xiàn)有個(gè)顯眼的 container.go 在向你招手,嗯第一個(gè)坑馬上就要來(lái)了。
container

這段代碼初看起來(lái)還是很淺顯的。代碼縮水后如下

 

  1. type Container interface { 
  2. ID() string 
  3. RunState() (*RunState, Error) 
  4. Config() *Config 
  5. Start(config *ProcessConfig) (pid int, exitChan chan int, err Error) 
  6. Destroy() Error 
  7. Processes() ([]int, Error) 
  8. Stats() (*ContainerStats, Error) 
  9. Pause() Error 
  10. Resume() Error 

可以看出這段代碼只是定義了一個(gè)接口,任何實(shí)現(xiàn)這些方法的對(duì)象就會(huì)變成一個(gè) docker 認(rèn)可的 container。其中比較關(guān)鍵的一個(gè)函數(shù)就是 Start 了,他是在 container 里啟動(dòng)進(jìn)程的方法,可以看到接口的要求是傳進(jìn)一個(gè)所要啟動(dòng)進(jìn)程相關(guān)的配置,返回一個(gè)進(jìn)程 pid 和一個(gè)接受退出信息的 channel。

下一步自然就是去找這個(gè)接口的實(shí)現(xiàn)去看看究竟是怎么做的,然后一個(gè)坑就來(lái)了。由于 go 語(yǔ)言不要求對(duì)象向 java 那樣顯示的聲明自己實(shí)現(xiàn)哪個(gè)接口,只要自己默默實(shí)現(xiàn)了對(duì)應(yīng)的方法就默認(rèn)變成了哪個(gè)接口類型的對(duì)象。所以沒(méi)有什么直觀的方法來(lái)找到哪些對(duì)象實(shí)現(xiàn)了這個(gè)接口,翻了一下 libcontainer 文件夾下的文件感覺(jué)哪個(gè)都不像。感覺(jué)有些不詳?shù)念A(yù)兆,裝了個(gè) Cygwin 去 grep Start 這個(gè)函數(shù),結(jié)果意外的發(fā)現(xiàn)沒(méi)有,于是又在整個(gè) docker 目錄下去 grep 發(fā)現(xiàn)還是沒(méi)有。

我就奇怪了,不是說(shuō) docker 1.2 之后就支持 native 的 container 了么,他連 libcontainer 里的 container 接口都沒(méi)實(shí)現(xiàn)他是怎么調(diào)用 native 的 container 的。既然自底向上的找不到,那就只能自頂向下的從上層往下跟去找找怎么回事了。
driver

docker 支持 lxc 和 native 兩套容器實(shí)現(xiàn),是通過(guò) driver 這個(gè)接口的兩個(gè)實(shí)現(xiàn)來(lái)完成的。在 \daemon\execdriver 中可以看到有 lxc 和 native 兩個(gè)文件夾,里面就是相關(guān)的代碼。不過(guò)在 \daemon\ 目錄下可以看到還有一個(gè) container.go 里面是有個(gè) container 對(duì)象,可是并沒(méi)有實(shí)現(xiàn) libcontainer 里對(duì)應(yīng)的接口,難道 libcontainer 里的那個(gè) interface 只是一個(gè)幌子?

先看一下 driver 這個(gè)接口

 

  1. type Driver interface { 
  2. Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code 
  3. // Exec executes the process in a running container, blocks until the process exits and returns the exit code 
  4. Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error) 
  5. Kill(c *Command, sig int) error 
  6. Pause(c *Command) error 
  7. Unpause(c *Command) error 
  8. Name() string // Driver name 
  9. Info(id string) Info // "temporary" hack (until we move state from core to plugins) 
  10. GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container. 
  11. Terminate(c *Command) error // kill it with fire 
  12. Clean(id string) error// clean all traces of container exec 

有沒(méi)有感覺(jué)名字雖說(shuō)和上面的 container interface 不太一樣,不過(guò)意思是差不多的。resume 變成了 unpause, destory 變成了 teminate,processes 變成了 getpidsforcontainer,start 也變成了 run 和 exec 兩個(gè)函數(shù)。看到這不得不說(shuō) docker 的代碼的一致性和可讀性還是慘了點(diǎn),codereview 需要更嚴(yán)格一些呀。

再進(jìn)到 native 的 driver.go 就可以看到具體的實(shí)現(xiàn)了。在文件頭部發(fā)現(xiàn)了一長(zhǎng)串 import,其中有幾個(gè)比較抓眼球:

 

  1. import ( 
  2. .... 
  3. "github.com/docker/libcontainer" 
  4. "github.com/docker/libcontainer/apparmor" 
  5. "github.com/docker/libcontainer/cgroups/fs" 
  6. "github.com/docker/libcontainer/cgroups/systemd" 
  7. consolepkg "github.com/docker/libcontainer/console" 
  8. "github.com/docker/libcontainer/namespaces" 
  9. "github.com/docker/libcontainer/namespaces/nsenter" 
  10. "github.com/docker/libcontainer/system" 

從這里似乎可以看出一點(diǎn)端倪了。libcontainer 的目的是提供一個(gè)平臺(tái)無(wú)關(guān)的原生容器,這需要包括資源隔離,權(quán)限控制等一系列通用組件,所以 libcontainer 就來(lái)提供這些通用組件,所以他叫 "lib"。而每個(gè)平臺(tái)想實(shí)現(xiàn)自己的容器的話就可以借用這些組件,當(dāng)然可以只用一部分而不全用, docker 就相當(dāng)于用了包括 apparmor、cgroups、namespaces 等等組件,然后沒(méi)用 libcontainer 的 container 接口和其他一些組件,自己寫了其他部分完成的所謂 native 的容器。

還是看 run 函數(shù)

 

  1. func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error)  

其中 execdriver.Pipes 是一個(gè)定義標(biāo)準(zhǔn)輸入輸出和錯(cuò)誤指向的結(jié)構(gòu),startCallback 是在進(jìn)程結(jié)束或者退出時(shí)調(diào)用的一個(gè)回調(diào)函數(shù),最重要的結(jié)構(gòu)是 execdriver.Command 他定義了容器內(nèi)運(yùn)行程序的各種環(huán)境和約束條件。可以在 daemon 下的 driver.go 中找到對(duì)應(yīng)的定義。
Command

 

  1. type Command struct { 
  2. ID string `json:"id"
  3. Rootfs string `json:"rootfs"// root fs of the container 
  4. InitPath string `json:"initpath"// dockerinit 
  5. WorkingDir string `json:"working_dir"
  6. ConfigPath string `json:"config_path"// this should be able to be removed when the lxc template is moved into the driver 
  7. Network *Network `json:"network"
  8. Resources *Resources `json:"resources"
  9. Mounts []Mount `json:"mounts"
  10. AllowedDevices []*devices.Device `json:"allowed_devices"
  11. AutoCreatedDevices []*devices.Device `json:"autocreated_devices"
  12. CapAdd []string `json:"cap_add"
  13. CapDrop[]string `json:"cap_drop"
  14. ContainerPid int `json:"container_pid"// the pid for the process inside a container 
  15. ProcessConfig ProcessConfig `json:"process_config"// Describes the init process of the container. 
  16. ProcessLabel string `json:"process_label"
  17. MountLabel string `json:"mount_label"
  18. LxcConfig []string `json:"lxc_config"
  19. AppArmorProfile string `json:"apparmor_profile"

其中和進(jìn)程隔離相關(guān)的有 Resources 規(guī)定了 cpu 和 memory 的資源分配,可供 cgroups 將來(lái)調(diào)用。 CapAdd 和 CapDrop 這個(gè)和 linux Capability 相關(guān)來(lái)控制 root 的某些系統(tǒng)調(diào)用權(quán)限不會(huì)被容器內(nèi)的程序使用。ProcessLabel 為容器內(nèi)的進(jìn)程打上一個(gè) Lable 這樣的話 seLinux 將來(lái)就可以通過(guò)這個(gè) lable 來(lái)做權(quán)限控制。Apparomoprofile 指向 docker 默認(rèn)的 apparmor profile 路徑,一般為/etc/apparmor.d/docker,用來(lái)控制程序?qū)ξ募到y(tǒng)的訪問(wèn)權(quán)限。

可以看到,docker 對(duì)容器的隔離策略并不是自己開(kāi)發(fā)一套隔離機(jī)制而是把現(xiàn)有的能用的已有隔離機(jī)制全用上。甚至 AppArmor 和 seLinux 這兩個(gè)類似并且人家兩家還在相互競(jìng)爭(zhēng)的機(jī)制也都一股腦不管三七二十一全加上,頗有拿來(lái)主義的風(fēng)采。這樣的話萬(wàn)一惡意程序突破了一層防護(hù)還有另外一層擋著,而且這幾個(gè)隔離機(jī)制還相互保護(hù)要同時(shí)突破所有的防護(hù)才行。

而我們真正要在容器中執(zhí)行的程序在 ProcessConfig 這個(gè)結(jié)構(gòu)體中的 Entrypoint。由此可見(jiàn)所謂的容器就是一個(gè)穿著各種隔離外套的程序,用這些隔離外套保護(hù)這個(gè)程序可以活在自己的小天地里,不知有漢無(wú)論魏晉。
Exec

還是回到 run 里面看看究竟是怎么 run 的吧,看完了一系列的初始化和異常判斷后終于到了真正運(yùn)行的代碼,只有一行,長(zhǎng)得是這個(gè)樣子的:

 

  1. return namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd { 
  2. c.ProcessConfig.Path = d.initPath 
  3. c.ProcessConfig.Args = append([]string{ 
  4. DriverName, 
  5. "-console", console, 
  6. "-pipe""3"
  7. "-root", filepath.Join(d.root, c.ID), 
  8. "--"
  9. }, args...) 
  10.  
  11. // set this to nil so that when we set the clone flags anything else is reset 
  12. c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{ 
  13. Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)), 
  14. c.ProcessConfig.ExtraFiles = []*os.File{child} 
  15.  
  16. c.ProcessConfig.Env = container.Env 
  17. c.ProcessConfig.Dir = container.RootFs 
  18.  
  19. return &c.ProcessConfig.Cmd 
  20. }, func() { 
  21. if startCallback != nil { 
  22. c.ContainerPid = c.ProcessConfig.Process.Pid 
  23. startCallback(&c.ProcessConfig, c.ContainerPid) 
  24. }) 

 

看到這里整個(gè)人都不好了,我覺(jué)得 docker 這個(gè)項(xiàng)目要是這樣下去會(huì)出問(wèn)題的,就算你喜歡匿名函數(shù)也不要這么偏執(zhí)好么。我甚至懷疑 docker 在用什么黑科技來(lái)隱藏他的真實(shí)代碼了。于是我決定放棄這行代碼直接看 namespaces.Exec 去了。在\verdor\src\github.com\libcontainer\namespaces\exec.go里

 

  1. func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Writer, console, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error)  

不太確定一個(gè)函數(shù)8個(gè)參數(shù)真的好么,但是我更納悶的是在主項(xiàng)目里既然都有 pipe 這個(gè)結(jié)構(gòu)把 stdin,stdout,stderr 放在一起為啥到這里就要分開(kāi)寫了,6個(gè)雖然也不少,但是比8個(gè)要好點(diǎn)。回過(guò)頭來(lái)說(shuō)一下 namespace ,這又是另一種隔離機(jī)制。顧名思義,隔離的是名字空間,這要的話本來(lái)屬于全局可見(jiàn)的名字資源,如 pid,network,mountpoint 之類的資源虛擬出多份,每個(gè) namespace 一份,每組進(jìn)程占用一個(gè) namespace。這樣的話容器內(nèi)程序都看不到外部其他進(jìn)程,攻擊的難度自然也就加大了。

然后這里面最關(guān)鍵的執(zhí)行的一句倒是很簡(jiǎn)單了。

 

  1. if err := command.Start(); err != nil { 
  2. child.Close() 
  3. return -1, err 

其中的 command 是系統(tǒng)調(diào)用類 exec.Cmd 的一個(gè)對(duì)象,而之前的關(guān)于程序的配置信息已經(jīng)在那個(gè)一行的執(zhí)行代碼里都整合進(jìn) command 里了,在這里只要 start 一下程序就跑起來(lái)了。然后我就疑惑了,這個(gè)函數(shù)不是 namespaces 包下的么,咋沒(méi)有 namespaces 設(shè)置的相關(guān)代碼呢。其實(shí)你仔細(xì)看那一行的執(zhí)行代碼可以發(fā)現(xiàn) namespaces 的設(shè)置也在里面了,換句話說(shuō)這個(gè) namespaces 包下的 exec 其實(shí)沒(méi)有做什么和 namespaces 相關(guān)的事情,只是 start 了一下。這種代碼邏輯結(jié)構(gòu)可是給讀代碼的人帶來(lái)了不小的困惑啊。
總結(jié)

這次讀代碼的起點(diǎn)是想搞懂容器是如何做隔離和保證安全的。從代碼來(lái)看 docker 并沒(méi)有另起爐灶新開(kāi)發(fā)機(jī)制,而是將現(xiàn)有經(jīng)過(guò)考驗(yàn)的隔離安全機(jī)制能用的全用上,包括 cgroups,capability,namespaces,apparmor 和 seLinux。這樣一套組合拳打出來(lái)的效果理論上看還是很好的,即使其中一個(gè)機(jī)制出了漏洞,但是要利用這個(gè)漏洞的方法很可能會(huì)被其他機(jī)制限制住,要找到一種同時(shí)繞過(guò)所有隔離機(jī)制的方法難度就要大多了。

但是從讀代碼的角度來(lái)看,docker 的代碼的質(zhì)量就讓人很難恭維了,即使 libcontainer 是一個(gè)獨(dú)立的部分,但本是同根生的名字都不一致,不知道之后會(huì)不會(huì)更混亂。而一些代碼風(fēng)格和邏輯上也實(shí)在讓人讀起來(lái)很費(fèi)勁,代碼質(zhì)量要提高的地方還有很多。畢竟是開(kāi)源的項(xiàng)目,即使功能很強(qiáng)大,但是大家如果發(fā)現(xiàn)代碼質(zhì)量有問(wèn)題,恐怕也不大敢用在生產(chǎn)吧。

而至于 libcontainer 盡管從 docker 中獨(dú)立出去發(fā)展,但是可以看出和主項(xiàng)目還有一些沒(méi)有切分干凈的地方,而且 docker 主項(xiàng)目目前也沒(méi)有采用 libcontainer 中的 container 方式,只是在調(diào)用里面的一些機(jī)制方法,看樣子目前還處于一個(gè)逐步替換的過(guò)程中。libcontainer 和一個(gè)獨(dú)立完整的產(chǎn)品還有一段距離,諸位有興趣的也可以參與進(jìn)去,萬(wàn)一這就是下一個(gè)偉大的項(xiàng)目呢?

原文出自:https://docker.cn/p/docker-libcontainer-reading

 

責(zé)任編輯:Ophira 來(lái)源: Docker中文社區(qū)
相關(guān)推薦

2017-01-23 12:40:45

設(shè)計(jì)演講報(bào)表數(shù)據(jù)

2011-06-30 22:23:21

打印機(jī)常見(jiàn)問(wèn)題

2020-11-02 09:48:35

C++泄漏代碼

2016-01-07 12:40:02

機(jī)器學(xué)習(xí)權(quán)威定義

2021-04-02 06:18:27

Docker鏡像

2025-02-05 11:43:28

2024-09-24 10:36:29

2024-09-26 19:39:23

2011-06-28 10:41:50

DBA

2020-07-08 07:44:35

面試阿里加班

2017-03-22 15:38:28

代碼架構(gòu)Java

2021-12-27 10:08:16

Python編程語(yǔ)言庫(kù)

2020-10-24 13:50:59

Python庫(kù)編程語(yǔ)言

2023-09-04 09:12:10

設(shè)計(jì)業(yè)務(wù)耦合

2013-11-20 13:55:01

代碼提交優(yōu)秀

2024-09-26 10:41:31

2018-07-16 22:29:29

代碼迭代質(zhì)量

2016-06-23 14:19:59

DevOpsOpenStackIaaS

2012-08-28 09:21:59

Ajax查錯(cuò)經(jīng)歷Web

2021-11-01 17:29:02

Windows系統(tǒng)Fork
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久久久亚洲国产| 亚洲精品国产电影 | 亚洲欧美一区二区三区视频 | 黄色大片在线视频 | 老司机久久 | 国产精品久久99 | 欧美日韩高清免费 | 美女天堂 | 精品国产乱码久久久久久牛牛 | 中文字幕综合 | 男人的天堂中文字幕 | 亚洲精品乱码久久久久久蜜桃 | 国产精品一区二区三区久久 | 亚洲欧美在线观看 | 亚洲品质自拍视频网站 | 亚洲一区二区三区免费在线观看 | 久久久久久久91 | 久久精品国产99国产精品 | 国产欧美视频一区二区三区 | 欧美在线视频网 | 欧美亚洲一区二区三区 | 中文字幕av在线 | 国产一区视频在线 | 欧美日韩三区 | 玖玖玖在线| 日韩欧美黄色 | 欧美美女被c| 久久一二区 | 精品三区 | 欧美激情va永久在线播放 | 国产精品不卡视频 | 欧美在线播放一区 | 特级做a爰片毛片免费看108 | 精品一区二区三区在线视频 | 亚洲欧美日韩电影 | 欧美国产日韩在线 | 日韩在线免费看 | 精品九九 | 九热在线 | 天天天堂| 黄色大片在线免费观看 |