So far, we have established that all the plugins that we have installed have actually nothing to do with Docker directly, so what does a plugin do?
Docker describes a plugin as:
"Docker plugins are out-of-process extensions which add capabilities to the Docker Engine."
This is exactly what we have seen when installing third-party tools, they all run alongside Docker as separate daemons.
Let's assume that we are going to be creating a volume plugin called mobyfs
for the remainder of this chapter. The mobyfs plugin is a fictional service which is written in Go and it runs as a daemon.
Typically, a plugin will be installed on the same host as the Docker binary. We can register our mobyfs plugin with Docker by creating the following files in either /run/docker/plugins
if it's a Unix socket file, or /etc/docker/plugins
or /usr/lib/docker/plugins
if it is one of the other two files:
mobyfs.sock
mobyfs.spec
mobyfs.json
Plugins that use a Unix socket file must run on the same hosts as your Docker installation. Ones which use either a .spec
or .json
file can run on external hosts if your daemon supports TCP connections.
If you were using a .spec file, your file would just contain a single URL to either a TCP host and port or local socket file. Any of the following three examples are valid:
tcp://192.168.1.1:8080 tcp://localhost:8080 unix:///other.sock
If you wanted to use a .json
file, it must look something similar to the following code:
{ "Name": "mobyfs", "Addr": "https:// 192.168.1.1:8080", "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", } }
The TLSConfig
section of the JSON file is optional; however, if you are running your service on host other than your Docker host, I would recommend using HTTPS for communication between Docker and your plugin.
Ideally, your plugin service should be started before Docker. If you are running a host, which has systemd
installed, this can be achieved by using a systemd
service file similar to the following one, which should be called mobyfs.service
:
[Unit] Description= mobyfs Before=docker.service [Service] EnvironmentFile=/etc/mobyfs/mobyfs.env ExecStart=/usr/bin/mobyfs start -p 8080 ExecReload=/bin/kill -HUP $MAINPID KillMode=process [Install] WantedBy=docker.service
This will ensure that your plugin service is always started before the main Docker service.
If you are hosting your Plugin service on an external host, you may have to restart Docker for Docker to start communicating with your plugin service.
It is possible to package your plugin inside a container. To get around Docker having to be started before the plugin service, each activation request will retry several times over 30 seconds.
This will give the container enough time to start and to run the plugin service run though any bootstrapping processes before binding itself to a port on the container.
Now that the plugin service has started, and we need to let Docker know where it should send requests to if the plugin service is called. According to our example, service is a volume plugin and we should run something similar to the following command:
docker run -ti -v volumename:/data --volume-driver=mobyfs russmckendrick/base bash
This will mount the volumename
volume, which we have already configured in our plugin service to /data
in a container, which runs my base container image and attaches us to a shell.
When the mobyfs volume driver is called, Docker will search through the three plugin directories that we covered in the Discovery section. By default, Docker will always look for a socket file, then either a .spec
or .json
file. The plugin name must match the filename in front of the file extension. If it is doesn't, the plugin will not be recognized by Docker.
Once the plugin has been called, the Docker daemon will make a post request using RPC-style JSON over HTTP to the plugin service using either the socket file or the URL defined in the .spec
or .json
file.
This means that your plugin service must implement an HTTP server and bind itself to the socket or port that you defined in the Discovery section.
The first request that is made by Docker will be to /Plugin.Activate
. Your plugin service must respond to one of three responses. As mobyfs is a volume plugin, the response would be as follows:
{ "Implements": ["VolumeDriver"] }
If it was a network driver, then the response our plugin service should give would be as follows:
{ "Implements": ["NetworkDriver"] }
The final response of plugin service is as shown in the following code:
{ "Implements": ["authz"] }
Any other responses will be rejected and the activation will fail. Now that Docker has activated the plugin, it will continue to make post requests to the plugin service depending on the response it got when calling /Plugin.Activate
.