Docker 的插件式設計

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

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

 

在以前的『閒話雲計算』一文中曾提到過:構成雲計算的『幹細胞』是 計算、存儲和網絡 。Docker 做爲雲計算領域的新生力量,天然少不了對這三要素的關注,咱們來具體看看他是怎麼設計的。node

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

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

存儲插件(Volume Plugin)

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

圖片來源: slidesharedocker

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

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

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

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

  • /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 等。

相關文章
相關標籤/搜索