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
下面主要介紹下 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
在Docker官方文檔中列出的Volume Plugins有近 20個 。
OSS 是阿里雲的對象存儲服務,解決文件類存儲的需求。有一個基於 OSS 的文件系統的實現,叫 ossfs 。能夠把某個 OSS Bucket 掛載到OS中的一個分區上,這樣就好像訪問本地文件同樣,訪問 OSS 文件。
文件系統 1K-塊 已用 可用 已用% 掛載點
ossfs 256T 0 256T 0% /mnt/i/ossfs/mytestbucket
在這個基礎上,編寫了一個Docker數據卷的插件,使用該插件,能夠建立 ossfs 數據卷,並Bind到容器中使用。
爲了對插件有一個直觀的認識,咱們從實踐出發,拿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。
下面,使用插件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時,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
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 等。