Docker 的插件式設計


http://www.tuicool.com/articles/MnIRZvJ

http://uzhima.com/2016/08/02/what-is-docker-volume-plugin/

 

在之前的『閑話雲計算』一文中曾提到過:構成雲計算的『干細胞』是 計算、存儲和網絡 。Docker 作為雲計算領域的新生力量,自然少不了對這三要素的關注,我們來具體看看他是怎么設計的。

眾所周知,Docker 提供了容器的運行環境,通過LXC對CPU計算資源進行隔離,運行在每一台OS之上, 計算資源 是他最容易控制和管理的,但對 網絡存儲 的支持在之前一直不是特別理想,直到1.7之后的版本推出了 插件(Plugin) 的概念才有所改善。有了插件系統提供的能力,開發者可以根據Docker的運行周邊環境,定制屬於自己的網絡和存儲方案,對Engine進行擴展,滿足自己容器化的需求。比如,阿里雲上的 Docker 容器就可以使用 VPC網絡插件 ,給容器分配虛擬IP,實現容器在VPC內的網絡互聯;還可以使用 OSS 存儲插件 ,實現容器對 OSS Bucket 的訪問,擴展容器的存儲能力。

這就是Docker,對『計算』的完全掌控,對『存儲』和『網絡』的基本支持,同時通過插件擴展存儲和網絡的邊界,與現有雲計算的成果無縫融合。 除了有存儲Volume、網絡Network,Docker還提供了認證Authorization、IP地址管理IPAM等插件類型。

存儲插件(Volume Plugin)

下面主要介紹下 Docker存儲插件 的設計和實現。

圖片來源: slideshare

Docker Plugin 是以Web Service的服務運行在每一台Docker Host上的,通過HTTP協議傳輸RPC風格的JSON數據完成通信。

Plugin的啟動和停止,並不歸Docker管理,Docker Daemon依靠在缺省路徑下查找Unix Socket文件,自動發現可用的插件。

當客戶端與Daemon交互,使用插件創建數據卷時,Daemon會在后端找到插件對應的 socket 文件,建立連接並發起相應的API請求,最終結合Daemon自身的處理完成客戶端的請求。

VolumePlugin所定義的 API接口 有8個:

  • /VolumeDriver.Create
  • /VolumeDriver.Remove
  • /VolumeDriver.Mount
  • /VolumeDriver.Path
  • /VolumeDriver.Unmount
  • /VolumeDriver.Get
  • /VolumeDriver.List
  • /VolumeDriver.Capabilities

在Docker官方文檔中列出的Volume Plugins有近 20個

OSS存儲插件(OSSFS Volume Plugin)

OSS 是阿里雲的對象存儲服務,解決文件類存儲的需求。有一個基於 OSS 的文件系統的實現,叫 ossfs 。可以把某個 OSS Bucket 掛載到OS中的一個分區上,這樣就好像訪問本地文件一樣,訪問 OSS 文件。

文件系統          1K-塊    已用     可用 已用% 掛載點
ossfs           256T 0 256T 0% /mnt/i/ossfs/mytestbucket

在這個基礎上,編寫了一個Docker數據卷的插件,使用該插件,可以創建 ossfs 數據卷,並Bind到容器中使用。

認識插件接口(Volume Plugin API)

為了對插件有一個直觀的認識,我們從實踐出發,拿OSSFS插件舉例,追蹤接口調用,來幫助我們理解整個過程,加深理解。

OSSFS插件並不是這次的重點,因此它的啟動過程以及內部實現細節並不在這次討論范圍,會逐一略過。我用的是阿里雲容器服務自帶的OSSFS插件,插件啟動后會生成一個unix socket文件 /run/docker/plugins/ossfs.sock

為了便於監控插件API的日志,我們通過socat搭建一個代理,偽造一個新的socket文件 ossfs2.sock ,對這個代理的請求會被輸出到終端上。

sudo socat -t100 -v UNIX-LISTEN:/run/docker/plugins/ossfs2.sock,mode=777,reuseaddr,fork UNIX-CONNECT:/run/docker/plugins/ossfs.sock

這時,在Docker Daemon看來我們就擁有了兩個插件,ossfs和ossfs2,不過二者本質上是同一個web service。

創建Volume

下面,使用插件ossfs2創建數據卷 ff001 ,目的是為了獲取請求日志。

[root@node1 ~]# docker volume create -d ossfs2 --name=ff001 -o bucket=mytestbucket -o ak_id=xxx -o ak_secret=xxxxxx -o url=vpc100-oss-cn-hangzhou.aliyuncs.com ff001

在剛才的socat命令下,可以看到打印日志,摘取如下:

2016/08/02 17:04:49.780139 length=143 from=0 to=142 POST /Plugin.Activate HTTP/1.1\r Host: \r

User-Agent: Go-http-client/1.1\r

Content-Length: 1\r

Accept: application/vnd.docker.plugins.v1.2+json\r

\r < 2016/08/02 17:04:49.780763 length=165 from=0 to=164

HTTP/1.1 200 OK\r

{"Implements": ["VolumeDriver"]}

2016/08/02 17:04:49.781068 length=162 from=143 to=304 POST /VolumeDriver.Get HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 17:04:49.781364 length=229 from=165 to=393

HTTP/1.1 500 Internal Server Error\r

{"Mountpoint":"","Err":"Invalid ossfs options.","Volumes":null,"Volume":null}

2016/08/02 17:04:49.781708 length=316 from=305 to=620 POST /VolumeDriver.Create HTTP/1.1\r {"Name":"ff001","Opts":{"ak id":"xxxx","ak secret":"xxxxxx","bucket":"mytestbucket","url":"vpc100-oss-cn-hangzhou.aliyuncs.com"}} < 2016/08/02 17:04:49.849440 length=188 from=394 to=581

HTTP/1.1 200 OK\r

{"Mountpoint":"","Err":"","Volumes":null,"Volume":null}

2016/08/02 17:04:49.867860 length=163 from=621 to=783 POST /VolumeDriver.Path HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 17:04:49.872552 length=220 from=582 to=801

HTTP/1.1 200 OK\r

{"Mountpoint":"/mnt/i/ossfs/mytestbucket","Err":"","Volumes":null,"Volume":null}

2016/08/02 17:04:51.984118 length=148 from=784 to=931 POST /VolumeDriver.List HTTP/1.1\r {} < 2016/08/02 17:04:51.985414 length=316 from=802 to=1117

{"Mountpoint":"","Err":"","Volumes":[{"Name":"ff001","Mountpoint":"/mnt/i/ossfs/mytestbucket"}],"Volume":null}

所有的請求都是POST方法,URI、request body和response body我都已經加粗。簡單解釋下, /Plugin.Activate 接口是Plugin的公共接口,是做第一次Handshake,Plugin會返回類型是Volume還是Network等。一個Volume的創建過程,會先通過 Get 判斷名稱是否存在,然后執行 Create 做數據卷的初始化,接着通過 Path 接口獲取Volume的本地路徑。最后的 List 是用來查詢這個插件下有哪些Volume的,是被其他客戶端發起的,跟這次的創建過程並無關系。

至此,一個Volume就創建完成了,但還沒有被容器使用。

使用Volume

下面我們看看,在容器使用Volume時,Plugin提供了哪些接口。執行下面命令:

[root@node1 ~]# docker run -ti --volume-driver=ossfs2 -v ff001:/oss busybox ls /oss 1.jpeg

創建並啟動一個容器,掛載數據卷 ff001 到容器內的 /oss 目錄,然后執行命令 ls /oss 查看目錄下文件,命令退出后容器停止。

在ossfs2.sock處抓取到請求日志如下:

2016/08/02 17:35:18.680407 length=164 from=13610 to=13773 POST /VolumeDriver.Mount HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 17:35:18.680923 length=220 from=24348 to=24567

HTTP/1.1 200 OK\r

{"Mountpoint":"/mnt/i/ossfs/mytestbucket","Err":"","Volumes":null,"Volume":null}

2016/08/02 17:35:19.143549 length=166 from=13774 to=13939 POST /VolumeDriver.Unmount HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 17:35:19.144880 length=188 from=24568 to=24755

HTTP/1.1 200 OK\r

{"Mountpoint":"","Err":"","Volumes":null,"Volume":null}

啟動容器時,Daemon會請求Plugin的 Mount 接口,然后與容器內的目錄綁定,容器停止時會請求 Unmount 接口。

刪除Volume

我們使用以下命令刪除Volume

docker volume rm ff001

當有容器在使用Volume時,刪除會失敗(Daemon中有記錄)。成功刪除時,會打印以下日志:

2016/08/02 18:29:23.833416 length=162 from=34688 to=34849 POST /VolumeDriver.Get HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 18:29:23.833928 length=249 from=59892 to=60140

HTTP/1.1 200 OK\r

{"Mountpoint":"","Err":"","Volumes":null,"Volume":{"Name":"ff001","Mountpoint":"/mnt/i/ossfs/mytestbucket"}}

2016/08/02 18:29:23.834274 length=165 from=34850 to=35014 POST /VolumeDriver.Remove HTTP/1.1\r {"Name":"ff001"} < 2016/08/02 18:29:24.027498 length=188 from=60141 to=60328

HTTP/1.1 200 OK\r

{"Mountpoint":"","Err":"","Volumes":null,"Volume":null}

一個 Get ,一個 Remove 就完成了刪除時的接口調用。

自己寫一個?

根據上面的介紹,相比你已經大概清楚一個Volume Plugin需要做哪些事情,提供哪些接口,以及在什么時候使用這些接口了。 如果,你想自己寫一個Volume Plugin,也是很簡單的事情。Docker已經提供了Go語言的 Plugin SDK 『go-plugins-helpers』 ,內置了基礎API接口,你只需要引用它們,並根據自己存儲的特征具體實現這些接口就可以了。同樣可以參考這些 公開插件 的文檔或代碼,比如 Flocker , Glusterfs 等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM