一、docker plugin
Docker Plugin 是 Docker 社区提供的一种扩展机制,可以通过插件来增强 Docker 引擎的功能。Docker Plugin 可以让我们快速、轻松地扩展 Docker 引擎的功能,将其变得更加灵活和可定制化。
参考文档:docker plugin | Docker Documentation
插件是在与docker守护程序相同或不同的主机上运行的进程,该进程通过将文件放置《插件发现》章节所描述的插件目录之一中的相同docker主机上进行注册。
插件具有易于理解的名称,它们是简短的小写字符串。例如, flocker
或weave
。
插件可以在容器内部或外部运行。目前建议在容器外运行它们。
1、插件目录“plugin discovery”
每当用户或容器尝试按名称使用插件时,Docker都会通过在插件目录中查找插件来发现插件。
插件目录:
可以将三种类型的文件放在插件目录中。
.sock
文件是UNIX域套接字。.spec
文件是包含URL的文本文件,例如unix:///other.sock
或tcp://localhost:8080
。.json
文件是包含插件完整json规范的文本文件。
具有UNIX域套接字文件的插件必须在同一docker主机上运行,而具有spec或json文件的插件可以在不同的主机上运行(如果指定了远程URL)。
.sock(UNIX域套接字文件必须位)文件一般放置在/run/docker/plugins下;
.spec/.json文件一般放置在/etc/docker/plugins或者/usr/lib/docker/plugins下。
插件名称
文件名(不包括扩展名)确定插件名称。
例如, flocker
插件可能会产生UNIX socket 放在: /run/docker/plugins/flocker.sock
。
如果要彼此隔离定义,可以将每个插件定义到一个单独的子目录中。例如,您可以创建flocker
socket 放在/run/docker/plugins/flocker/flocker.sock
,然后挂载到flocker
容器里面/run/docker/plugins/flocker
。
插件搜索顺序
Docker始终总是首先在/run/docker/plugins
搜索Unix套接字。如果套接字不存在,它将检查/etc/docker/plugins
和/usr/lib/docker/plugins
下的spec或json文件。一旦找到具有给定名称的第一个插件定义,目录扫描就会停止。
2、JSON规范
这是插件的JSON格式:
{
"Name": "plugin-example",
"Addr": "https://example.com/docker/plugin",
"TLSConfig": {
"InsecureSkipVerify": false,
"CAFile": "/usr/shared/docker/certs/example-ca.pem",
"CertFile": "/usr/shared/docker/certs/example-cert.pem",
"KeyFile": "/usr/shared/docker/certs/example-key.pem"
}
}
TLSConfig
字段是可选的,并且只有存在此配置时,才会验证TLS。
2、插件生命周期
插件应在Docker之前启动,而在Docker之后停止。例如,为支持systemd
的平台打包插件时,可以使用systemd依赖项来管理启动和关闭顺序。
升级插件时,您应该首先停止Docker守护进程,升级插件,然后再次启动Docker。
Plugin需要在docker启动前启动;
更新plugin时需要先停止docker daemon,更新后再启动docker daemon。
插件在第一次使用时激活。docker会根据指定的插件名字,在插件目录下查找。(感觉docker应该增加一个接口,查询本机插件列表)
Docker与plugin间使用,json格式基于Http的RPC消息,消息类型为post。
3、插件激活
当插件首次被引用时 - 无论是由用户引用它的名称(例如docker run --volume-driver=foo
)还是已经配置为使用插件的容器 -- Docker在插件目录中查找指定的插件,并通过握手激活它。请参阅下面的握手API。
在Docker守护程序启动时插件不会自动激活。相反,它们只在需要时才会被懒惰地或按需地激活。
4、系统套接字激活
插件也可以通过systemd
套接字激活。官方的插件助手本身就支持套接字激活。为了使插件通过套接字激活,它需要一个service
文件和一个socket
文件。
service
文件(例如/lib/systemd/system/your-plugin.service
):
[Unit]
Description=Your plugin
Before=docker.service
After=network.target your-plugin.socket
Requires=your-plugin.socket docker.service
[Service]
ExecStart=/usr/lib/docker/your-plugin
[Install]
WantedBy=multi-user.target
socket
文件(例如/lib/systemd/system/your-plugin.socket
):
[Unit]
Description=Your plugin
[Socket]
ListenStream=/run/docker/plugins/your-plugin.sock
[Install]
WantedBy=sockets.target
当Docker守护进程连接到它们正在监听的套接字(例如守护进程第一次使用它们或者其中一个插件意外关闭)时,这将允许插件实际启动。
5、API设计
插件API是基于HTTP的RPC样式的JSON,非常类似于webhooks。
请求从 Docker守护程序(Docker daemon)到 该插件。因此插件需要实现HTTP服务器,并将其绑定到“插件发现”(plugin discovery)部分中提到的UNIX套接字。
所有请求都是HTTP POST
请求。
该API通过Accept标头进行版本控制,当前始终将其设置为application/vnd.docker.plugins.v1+json
。
6、Handshake API (握手API)
通过以下“握手” (handshake) API调用激活插件。
插入启用
/Plugin.Activate
Request: empty body
Response:
{
"Implements": ["VolumeDriver"]
}
响应此插件实现的Docker子系统列表。激活后,插件将从这个子系统发送事件。
可能的值为:
- authz
- NetworkDriver
- VolumeDriver
插件重试
尝试在插件上调用方法的尝试将以指数补偿的方式重试长达30秒。当将插件打包为容器时,这可能会有所帮助,因为它使插件容器有机会在依赖于它们的任何用户容器失败之前启动。
6、远程网络驱动插件 Handshake API
Remote Driver的出现为业界提供了自定义网络的可能。远程驱动作为服务端,libnetwork作为客户端,两者通过一系列带Json格式的http POST请求进行交互。
这种形式对网络实现的可维护性,可扩展性有很大好处。对于千差万别的网络实施方案而言,docker算是解放了双手,把更多精力放在自己擅长的容器方面。
代码位于libnetwork/drivers/remote。对应的远端驱动,只要实现了以下接口,就能支持docker网络相关的生命周期。
dispatchMap := map[string]func(http.ResponseWriter, *http.Request){ "/Plugin.Activate": activate(hostname), "/Plugin.Deactivate": deactivate(hostname), "/NetworkDriver.GetCapabilities": getCapability, "/NetworkDriver.CreateNetwork": createNetwork, "/NetworkDriver.DeleteNetwork": deleteNetwork, "/NetworkDriver.CreateEndpoint": createEndpoint(hostname), "/NetworkDriver.DeleteEndpoint": deleteEndpoint(hostname), "/NetworkDriver.EndpointOperInfo": endpointInfo, "/NetworkDriver.Join": join, "/NetworkDriver.Leave": leave,
}
详细参考:
https://github.com/docker/libnetwork/blob/master/docs/remote.md
二、docker plugin 命令行语法
插件主要有以下三种类型:
-
日志记录——这些插件添加了新的日志记录驱动程序,让您可以将日志存储在 Docker 引擎和主机之外的位置。
-
网络- 网络插件可以添加对新网络类型和相关功能的支持。
-
卷– 存储插件为 Docker 带来了额外的文件系统和卷驱动程序,为您提供了更多持久数据的选择。
还可以使用具有这些核心领域之外的功能的插件。插件由社区供应商编写;您可以使用公共插件 API和Go SDK编写自己的插件。
插件通过 Docker Hub分发。您可以通过前往“探索”页面并选择顶部的“插件”选项卡来找到可用的插件。目前有超过 600 个插件可用。
如果你自己写插件,可以用docker plugin push
命令提交到Docker Hub 。这类似于将图像推送到公共注册表。您需要先运行docker plugin create
以将插件的清单和文件系统转换为准备提交的包。您可以在Docker 文档 中获取有关创作插件的更多信息。
命令 | 描述 |
docker plugin create | 从根文件系统和配置文件创建插件。 插件数据目录必须包含config.json和 rootfs目录。 |
docker plugin disable | 禁用一个插件。 |
docker plugin enable | 启用一个插件。 |
docker plugin inspect | 显示一个或多个插件的详细信息。 |
docker plugin install | 安装一个插件。 |
docker plugin ls | 列出所有安装的插件。 |
docker plugin push | 将一个插件推送到registry。 |
docker plugin rm | 删除一个或多个插件。 |
docker plugin set | 更改插件的设置。 |
docker plugin upgrade | 升级一个已经存在的插件。 |
1、安装插件
插件是用docker plugin install
命令安装的。这接受 Docker Hub 插件的名称作为其参数:
docker plugin install store/example/example-plugin:version
插件引用看起来像以 开头的图像引用store/
。指定要安装的插件版本时适用相同的标记原则。与图像不同,您不能省略版本以自动拉取latest
标签。
2、查看插件详细信息
安装后,您的插件将在运行时显示docker plugin ls
:
docker plugin ls
可以从docker inspect
命令中获取有关单个插件的更多详细信息。这接受插件 ID 或标签并显示描述插件清单的详细 JSON:
docker inspect eccffc
3、例子:docker plugin 命令添加一个自定义的网络插件
docker plugin create
命令用于从rootfs
和配置config.json
创建一个插件。插件数据目录必须包含config.json
和rootfs
目录。
1)使用 Docker Plugin 来添加一个自定义的网络插件my-network-plugin
我们需要创建一个名为my-network-plugin 的目录,
/home/pluginDir/my-network-plugin
并在其中创建一个
config.json
文件,用于存储插件的配置信息。
{ "Name": "my-network-plugin", "Description": "A custom network plugin for Docker", "Type": "network", "Interface": { "Types": ["*"] } }
2)需要编写一个 Python 脚本作为插件的实现。
#!/usr/bin/env python import argparse import os def create_network(network_id, options): print("Creating network: %s" % (network_id)) def delete_network(network_id): print("Deleting network: %s" % (network_id)) def main(): parser = argparse.ArgumentParser(description='Custom Docker network plugin') parser.add_argument('--docker-plugin-mode', type=int) parser.add_argument('--network-id') parser.add_argument('--create', nargs='?', const=True) parser.add_argument('--delete', nargs='?', const=True) parser.add_argument('--option', action='append') args = parser.parse_args() if args.create: create_network(args.network_id, args.option) elif args.delete: delete_network(args.network_id) if __name__ == '__main__': main()
3)需要将插件加载到 Docker 引擎中
$ docker plugin install my-network-plugin . 或者: docker plugin create plugin /home/pluginDir/my-network-plugin $ docker plugin enable my-network-plugin
4)使用自定义网络插件来创建和删除 Docker 网络
$ docker network create --driver=my-network-plugin my-network $ docker network rm my-network
4、官方以开发一个sshfs的volume plugin为例。
执行docker plugin create命令的目录下必须包含以下内容:
config.json文件,里面是插件的配置信息,plugin config参考文档
rootfs目录,插件镜像解压后的目录。v2版本的docker plugin都是以docker镜像的方式包装的。
rootfs 目录:该rootfs目录代表插件的根文件系统。在此示例中,它是从 Dockerfile 创建的:
注意:该/run/docker/plugins目录在插件的文件系统中是强制性的,以便 docker 与插件进行通信。
config.json 文件
该config.json文件描述了插件。请参阅插件配置参考。
考虑以下config.json文件。
这个插件是一个卷驱动程序。它需要host网络和 CAP_SYS_ADMIN能力。它依赖于/docker-volume-sshfs 入口点并使用/run/docker/plugins/sshfs.sock套接字与 Docker 引擎进行通信。这个插件没有运行时参数。
注意官网上的这个文档有问题,config.json与代码里的不符,尤其是Entrypoint的二进制文件的位置不对。
注意socket配置的地址不要写详细地址,默认会在/run/docker/plugins目录下生成socket文件。
创建插件
可以通过运行来创建新插件, docker plugin create./path/to/plugin/data其中插件数据包含插件配置文件config.json和子目录中的根文件系统rootfs。
之后插件将显示在docker plugin ls. 插件可以推送到远程注册表 docker plugin push。
三、卷插件:跨节点存储
docker 卷默认使用的是local类型的驱动,只能存在宿主机,跨主机的volume就需要使用第三方的驱动,可以查看以下链接: https://docs.docker.com/engine/extend/legacy_plugins/#volume-plugins
docker官方只提供了卷插件的api,开发者可以根据实际需求定制卷插件驱动。 https://docs.docker.com/engine/extend/plugins_volume/#volume-plugin-protocol
注:官方虽然提供了一些卷插件,但是不够全面,可以根据官方提供的API接口自己开发和docker相集成。
卷插件没有和docker引擎相集成,即它是第三方外部的插件,本身并不属于docker引擎的管理范畴,需要我们手动管理。当docker客户端向docker引擎提交需要挂载卷的请求时,docker引擎会去扫描缺省路径中的卷插件,扫描之后通过卷插件来完成数据的挂接。
- Docker Plugin 是以Web Service的服务运行在每一台Docker Host上的,通过HTTP协议传输RPC风格的JSON数据完成通信。
- Plugin的启动和停止,并不归Docker管理,Docker Daemon依靠在缺省路径下查找Unix Socket文件,自动发现可用的插件。
- 当客户端与Daemon交互,使用插件创建数据卷时,Daemon会在后端找到插件对应的 socket 文件,建立连接并发起相应的API请求,最终结合Daemon自身的处理完成客户端的请求。
convoy卷插件
实验之前先把server1、server2上不用的容器、网络、数据卷全删掉。
- 支持三种运行方式:devicemapper、NFS、EBS。
- 以下实验使用nfs方式。
- 下载软件:https://github.com/rancher/convoy/releases/download/v0.5.0/convoy.tar.gz
- 在所有节点提前挂载NFS存储。
此插件再使用前需要在底层安装NFS文件系统。我们需要在两个节点上需要一个共同的NFS文件系统:
创建nfs文件系统目录:
在server2上不需要启nfs服务,只需要将server1上的/nfsshare 挂接到 server2上的/nfsshare上:
此时两个目录中的文件就同步了:
设置通过卷插件让docker引擎可以调用此文件系统:
解压插件包:
创建docker的缺省路径:
启动并打入后台:
启动后会在这个路径下自动创建 socket 文件:
这个文件就是刚刚我们写到/etc/docker/plugins中的文件的内容:
打入后台的程序就是我们手动启动的,docker引擎不会主动启动这个程序,它只会扫描缺省目录中的内容。
两边主机需要同步,在server2上:
查看现在有什么卷:
使用插件创建卷:
此时再进入/nfsshare 中会发现创建了一个目录,由于nfs同步,在server2中也建立了此文件,且两边都是同步的:
使用docker命令也能看见我们创建的卷:
同样的,我们也能用docker来创建convoy卷:
两种方式创建出来是一样的:
用这种方式就能在两个节点上进行数据同步。我们此时创建容器指定容器内目录挂接到刚刚创建卷上:
在/nfsshare/vol1 中会有容器内nginx的默认发布页:
我们对此默认发布页面进行更改:
此时我们假设这个容器被意外的销毁了(直接删除):
我们模拟此时该容器被集群迁移到了另外一个节点,我们以相同的命令在server2上创建相同的容器:
可以看到此时用户访问所看到的页面是不变的,因为两个节点间创建容器挂接的卷是相同的,数据是同步的。至此,我们就实现了跨节点的数据持久化(它仍然是基于底层nfs文件系统实现的)。
整套系统的回收删除:
若是删除步骤出了问题,可以将下面这个目录中的数据删掉,再重启docker就没问题了: