一、 背景介绍
为了满足扩展性需求,Docker (1.7 及以后版本)提供了插件支持
用户能够根据自己的需要编写自定义插件来增强 Docker 的功能
一般而言,各类插件与 docker daemon 守护进程的交互原理都是一样的
为了减轻开发者负担,docker 官方提供了 go-plugins-helpers 基础工具包
借助该工具包,我们只需关注实际的业务逻辑,按照接口规范(需要实现哪些方法)编写插件实体即可
二、 开发步骤
2.1 环境准备
操作系统:ubuntu 16.04 LTS
Go:1.9.2
Docker:1.12.6
2.2 在 $GOPATH/src 目录下新建一个文件夹,如 docker-volume-plugin-example
2.3 在 $GOPATH/src/docker-volume-plugin-example 目录下创建 driver.go 源文件
package main
import (
"os"
"path/filepath"
"sync"
"errors"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/go-plugins-helpers/volume"
)
type ExampleDriver struct {
volumes map[string]string
mutex *sync.Mutex
mountPoint string
}
func NewExampleDriver(mount string) volume.Driver {
var d = ExampleDriver{
volumes: make(map[string]string),
mutex: &sync.Mutex{},
mountPoint: mount,
}
filepath.Walk(mount, func(path string, f os.FileInfo, err error) error {
if ( f == nil ) {return err}
if (strings.Compare(path,mount) == 0){ return nil}
if f.IsDir() {d.volumes[f.Name()] = path}
return nil
})
return d
}
func (d ExampleDriver) Create(r *volume.CreateRequest) error {
logrus.Infof("Create volume: %s", r.Name)
d.mutex.Lock()
defer d.mutex.Unlock()
if _, ok := d.volumes[r.Name]; ok {
return nil
}
volumePath := filepath.Join(d.mountPoint, r.Name)
os.MkdirAll(volumePath, os.ModePerm)
_, err := os.Lstat(volumePath)
if err != nil {
logrus.Errorf("Error %s %v", volumePath, err.Error())
return err
}
d.volumes[r.Name] = volumePath
return nil
}
func (d ExampleDriver) List() (*volume.ListResponse,error) {
logrus.Info("Volumes list... ")
logrus.Info(d.volumes)
var res = &volume.ListResponse{}
volumes := make([]*volume.Volume,0)
for name, path := range d.volumes {
volumes = append(volumes, &volume.Volume{
Name: name,
Mountpoint: path,
})
}
res.Volumes = volumes
return res, nil
}
func (d ExampleDriver) Get(r *volume.GetRequest) (*volume.GetResponse,error) {
logrus.Infof("Get volume: %s", r.Name)
var res = &volume.GetResponse{}
if path, ok := d.volumes[r.Name]; ok {
res.Volume = &volume.Volume{
Name: r.Name,
Mountpoint: path,
}
return res, nil
}
return &volume.GetResponse{}, errors.New(r.Name + " not exists")
}
func (d ExampleDriver) Remove(r *volume.RemoveRequest) error {
logrus.Info("Remove volume ", r.Name)
d.mutex.Lock()
defer d.mutex.Unlock()
if _, ok := d.volumes[r.Name]; ok {
os.RemoveAll(filepath.Join(d.mountPoint, r.Name))
delete(d.volumes, r.Name)
return nil
}
return errors.New(r.Name + " not exists")
}
func (d ExampleDriver) Path(r *volume.PathRequest) (*volume.PathResponse,error) {
logrus.Info("Get volume path ", r.Name)
var res = &volume.PathResponse{}
if path, ok := d.volumes[r.Name]; ok {
res.Mountpoint = path
return res,nil
}
return &volume.PathResponse{},errors.New(r.Name + " not exists")
}
func (d ExampleDriver) Mount(r *volume.MountRequest) (*volume.MountResponse,error) {
logrus.Info("Mount volume ", r.Name)
var res = &volume.MountResponse{}
if path, ok := d.volumes[r.Name]; ok {
res.Mountpoint = path
return res,nil
}
return &volume.MountResponse{},errors.New(r.Name + " not exists")
}
func (d ExampleDriver) Unmount(r *volume.UnmountRequest) error {
logrus.Info("Unmount ", r.Name)
if _, ok := d.volumes[r.Name]; ok {
return nil
}
return errors.New(r.Name + " not exists")
}
func (d ExampleDriver) Capabilities() *volume.CapabilitiesResponse {
logrus.Info("Capabilities. ")
return &volume.CapabilitiesResponse{}
}
2.4 在 $GOPATH/src/docker-volume-plugin-example 目录下创建 main.go 源文件
package main
import (
"log"
"github.com/docker/go-plugins-helpers/volume"
)
func main() {
driver := NewExampleDriver("/tmp/example-volume-mount-root")
handler := volume.NewHandler(driver)
if err := handler.ServeUnix("example-driver",0); err != nil {
log.Fatalf("Error %v", err)
}
for {
}
}
2.5 编译插件源码
# cd $GOPATH/src/docker-volume-plugin-example
# go build
2.6 启动插件
# cd $GOPATH/src/docker-volume-plugin-example
# ./docker-volume-plugin-example
2.7 测试插件
# docker run -it -v c1:/data --volume-driver=example-driver ubuntu:14.04 /bin/bash
执行该命令后,插件会自动在 /tmp/example-volume-mount-root 目录下创建一个名称为 c1 的文件夹,
并挂载到容器中(映射为 /data 路径)
# docker volume ls
执行该命令后,将显示所有 volume,包含通过插件创建的卷
# docker volume rm c1
执行该命令,将删除数据卷 c1,注意对应 /tmp/example-volume-mount-root 下的 c1 文件夹也会被删除