Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/sources/reference/plugin-mvp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Brian Goff and Luke Marsden paired on an MVP of the plugin handshake interface.

# pre-MVP design

We ended up with the following design:

```
$ docker run --plugin \
-v /var/run/docker.sock:/var/run/docker.sock \
clusterhq/flocker-plugin
```

Note the new `--plugin` argument.

Internally this translates into:

```
$ docker run -d \
-v /var/lib/docker/containers/<container_id>/plugin/:/var/run/docker/ \
-v /var/run/docker.sock:/var/run/docker.sock \
clusterhq/flocker-plugin
```

Docker blocks on waiting for a successful handshake with the plugin on `/var/run/docker/plugin.sock` with an HTTP `POST /v1/handshake` with an empty body.

# Plugin API

## `POST /v1/handshake`

**Request:** POST with empty body

**Response:** `application/json` as follows:

```
{
InterestedIn: ["volume"],
Name: "flocker",
Author: "Luke Marsden <[email protected]>",
Org: "ClusterHQ, Inc.",
Website: "https://clusterhq.com/",
}
```

`PluginName` is a human readable short string identifying the plugin.

`InterestedIn` is a list of extension points.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps "implements"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


Extension points currently supported are:

* `volume` - called when a volume is bind-mounted into a container at create or start time
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we can replace "when" with "after" ? just to be clear about when it is called.


Planned extension points include:

* `api` - pre-hooks and post-hooks on the Docker remote API (powerstrip-style)
* `network` - called when ???

## `POST /v1/volume/volumes`

The simplest of plugin types, `volume` provides a single request-response type on `POST /v1/volume/volumes` (`POST ~= create`):

**Request**

```
{HostPath: "/path",
ContainerID: "abcdef123"}
```

**Response**

```
{HostPath: "/newpath",
ContainerID: "abcdef123"}
```

# Caveats

NB: plugin loading order is undefined, so plugins should not depend on eachother.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can people assume they are called in the order in which they are registered?


# Versioning strategy

When we bump `/v1/handshake` to `/v2/handshake` etc, we can have Docker start by trying to do the highest numbered handshake it can, and then iterate backwards through supported handshake versions until it finds one which matches (ie does not give a 404).

---

TODO

* sort out plugins from normal containers and load plugins first so that containers that want to use services (e.g. portable volumes) from containers get to consume those services from other plugins.
159 changes: 159 additions & 0 deletions docs/sources/userguide/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
Docker Plugins MVP
==================

Plugins provide a mechanism for hooking into the Docker engine to extend its behavior.

HTTP over a local UNIX socket is used to communicate between Docker and its plugins.

Plugins are distributed as containers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think packaging plugins as containers is great - it saves a lot of work from the plugin authors side.
However, The question I have is about how they are distributed. Today the Docker registry doesn't contain any metadata about a container and what permissions it requires. This metadata is important from a users standpoint (a parallel would be the Google Play Store for Android) so they can make a decision of whether or not to use a plugin.

As @progrium mentions below, most agents will require --privileged or perhaps a combination of --cap-add and --cap-drop.

Perhaps a plugin should be a container + a docker-compose.yml

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is something we can work towards... basically having the image declare the permissions it needs to be able to run and error out if those perms weren't granted (ie by docker plugin load --privileged).

But I don't see this as a hard requirement for plugins.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Caveat here is that if we bind-mount the docker socket, then any plugin can elevate its own privileges. This needs more thought but not yet for an extensions MVP.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely not bind-mount the docker socket into the plugins.
Once people start writing plugins which assume this, it will be impossible
to change the design without breaking them.

On Thu, Mar 12, 2015 at 4:20 AM, lukemarsden [email protected]
wrote:

In docs/sources/userguide/plugins.md
#3 (comment):

@@ -0,0 +1,157 @@
+Docker Plugins MVP
+==================
+
+Plugins provide a mechanism for hooking into the Docker engine to extend its behavior.
+
+HTTP over a local UNIX socket is used to communicate between Docker and its plugins.
+
+Plugins are distributed as containers.

Agree. Caveat here is that if we bind-mount the docker socket, then any
plugin can elevate its own privileges. This needs more thought but not yet
for an extensions MVP.


Reply to this email directly or view it on GitHub
https://github.com/cpuguy83/docker/pull/3/files#r26293764.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with not automatically doing it for all plugins, it would be nice to request one (not using -v /var/run/docker.sock:/var/run/docker.sock) from the UI.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this @shykes. Bind-mounting (which is how we'd propose to expose the Docker API) is already arguably part of the docker permissions interface (let the container mess with this subtree on the host filesystem). Does that mean that the plugin loader interface should have -v?

To be clear, the choice is:

  • generic -v support for plugin loader.
  • some sort of specific --allow-docker-api flag to the plugin loader which special-cases bind-mounting in the docker sock.

I think @progrium preferred the latter. I'm cool with either. If you strongly prefer the former @shykes I'm fine with that.

One data point: the prototypical containerized flocker-zfs-agent I'm working on currently has 5 bind-mounts. BTW, I was imagining that the flocker extension would start the flocker-zfs-agent with its requisite bind-mounts via the docker api when it started... but we really do need the extensions to be able to "do stuff". I don't think we have time to define an interface with granular permissions. And in fact arguably the interface with granular permissions should be a later version of the Docker API!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a point of protocol, I think @progrium is working on the loader spec this/next week. It may be more productive to let him finish that and then do a review pass over it than to pre-empt his proposed design, which I'm sure will handle the use cases we have in mind. ;)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for having some way for a plugin to speak to the main docker server - it is vital for various reasons - I'm not overly concerned with how though (-v or --allow-docker-api)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take on these:

  • I'd rather not expose docker run arguments to docker plugin for two reasons
    • As soon as we start adding some of them, we'll end up adding all of them.
    • I like that we have a dedicated docker plugin top-level command that hides the fact that it's actually pulling an image and running a container: I believe that it makes perfect sense for an MVP (as we reuse many of the infrastructure in place), but I don't think we'd like to lock ourselves in that as the only possible execution model.
  • Mounting the daemon socket should not be the default, although I agree it should be made possible through some command-line flag that makes it obvious you're doing something "privileged", such as --allow-full-daemon-access.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I like the idea of a container for plugins on the daemon-side, I'm wondering about CLI plugins. I don't think they can use the same mechanism. I wonder if it makes sense to have cli-plugins be executables that use the same API defined in there? Meaning, via some cli-specific registration mechanism (perhaps they're listed in the cli config file), the cli can start the exe's and then talk to them via HTTP using the same protocol defined here. So while we can't get consistency around distribution, we can be consistent on the API/protocol.


This is targeted at Docker 1.7.

# Usage

Plugins can be loaded using a special `docker plugin load` command, as follows:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing general usefulness of hidden/system/agent containers and that they would pretty much all operate with the same semantics of plugins, maybe we can call this subcommand agent? To then decouple this (also so it can be a standalone PR) from plugins, we can make the plugin handshake protocol optional with a flag. Another flag would provide the Docker socket as discussed.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is an agent?

On Friday, March 6, 2015, Jeff Lindsay [email protected] wrote:

In docs/sources/userguide/plugins.md
#3 (comment):

@@ -0,0 +1,157 @@
+Docker Plugins MVP
+==================
+
+Plugins provide a mechanism for hooking into the Docker engine to extend its behavior.
+
+HTTP over a local UNIX socket is used to communicate between Docker and its plugins.
+
+Plugins are distributed as containers.
+
+This is targeted at Docker 1.7.
+
+# Usage
+
+Plugins can be loaded using a special docker plugin load command, as follows:

After discussing general usefulness of hidden/system/agent containers and
that they would pretty much all operate with the same semantics of plugins,
maybe we can call this subcommand agent? To then decouple this (also so
it can be a standalone PR) from plugins, we can make the plugin handshake
protocol optional with a flag. Another flag would provide the Docker socket
as discussed.


Reply to this email directly or view it on GitHub
https://github.com/cpuguy83/docker/pull/3/files#r25986848.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is missing context. There is a very common pattern in deployments of "system" containers or "agents", whether custom or from a third party that do various things for the system, but are not part of the main compute payload of a host. This happens in every deployment we've worked on.

Many examples are candidates for "plugins" but not all of them necessarily need to hook into Docker extension points. They just need to 1) always run on Docker boot, 2) likely have a default restart policy, 3) not show in main docker ps. Their management semantics match plugins but are not always plugins. For example, Docker Swarm is an agent.

Another example of this pattern shows up in RancherOS, how they've separated system containers from user containers. Our terminology is "agent" as almost all examples of this are already described as agents.

Other common properties of agents are their use of --privileged and/or the Docker socket, hence this being a reasonable place to make that easy.


```
$ docker plugin load -e $ARGS clusterhq/flocker-plugin $@

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why should this have -e $ARGS and $@? I suppose these are placeholders for the launch configuration... The line is a little confusing, I think if it just said docker plugin load clusterhq/flocker-plugin, it'd totally fine. You can hint about configuration though, which if you want can be through arguments. Although, keeping configuration out of the scope for now would be good, you could suggest that configuration would be part of the image if you like.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The -e $ARGS was for environment variables to pass to the plugin, and $@ just to say that you can pass some arguments. Should we just pick one of these? Or have neither?

Having config as part of the plugin image doesn't make sense because what we care about here is config provided by the cluster admin (the person typing docker plugin load not the plugin author).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just pick one of these? Or have neither?

I think having args at the end will be fine for the purpose of this document.

Having config as part of the plugin image doesn't make sense...

Well, the idea was to reduce the scope of this proposal, there is all sorts of things one can dream up about config, but it's the kind of thing that is always easy to over-engineer. For example, this can easily get everyone into bikeshedding a distributed config store or syntax format, and that's best to avoid if progress is to be made on introducing the primitives, into which this is already getting deep enough.

[... fetching...]
Plugin flocker:v0.1 loaded and registered with extension points:
* volumes
Plugin flocker activated.
```

This is really just syntactic sugar for the following:

```
$ docker run -d -e $ARGS \
-v /var/lib/docker/containers/<container_id>/plugin/:/var/run/docker/ \
-v /var/run/docker.sock:/var/run/docker.sock \
clusterhq/flocker-plugin $@
```

(The docker socket should only be mounted if the plugin is started with `--privileged`.)

Loading a plugin forces it to always be loaded when Docker restarts (and Docker doesn't respond to API requests until it completes loading all its plugins).

Docker then waits for the plugin to start listening on the socket (it polls the socket until it gets a successful response to an HTTP query to `/v1/handshake` on the socket, which returns with just a list of subsystems the plugin is interested in - response defined below).
According to the type of plugin which is negotiated in the handshake, Docker registers the plugin to send it HTTP requests on certain events.

Plugins should name themselves in the response, which should be in this format:

```
{
InterestedIn: ["volume"],
PluginName: "flocker"
}
```

Every event should be sent to every plugin registered for that subsystem for now.
Later the handshake can include more granular negotiation over which events it wants to receive.

Other `plugin` subcommands can include `list`, `status`, `upgrade`, and `unload`.

# Types of plugin

Each plugin type defines a straightforward (MVP) protocol.

## Volume

We will start with this first because it is has the tiniest interface.

The simplest of plugin types, `volume` provides a single request-response type on `POST /v1/volumes` (`POST ~= create`):

**Request**

```
{HostPath: "/path",
ContainerID: "abcdef123"}
```

**Response**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get some text around what the values in the response mean? It would seem to imply that the plugin can change the hostpath and container id - but that's doesn't seem right. Also, above I was assuming this is called after the volume is mounted, but this response (if these values are modifiable by the plug-in) would seem to imply its called before its mounted. For each plug-point we need to be clear about the "before" vs "after" nature of each.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to consider responses that stop the current action. For example, does the mount stop if the plug-in fails? Do we allow the plug-in to stop the action at all? What happens if it fails? Do we roll things back?
My initial guess is that a failed plug-in should stop the flow - otherwise users could get unexpected results - or worse, very bad results if one of the plug-ins is an auth-type check. And therefore, if possible, I think things should rollback.


```
{HostPath: "/newpath",
ContainerID: "abcdef123"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the ContainerID here -- can the plugin change it? What would that mean?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's there so that the plugin can look up (or record) the container info if it needs to.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean in the reply

```

In the initial version, if the plugin responds with a 404 then the response is ignored.

See reference implementation:

* [Docker volumes extension mechanism](https://github.com/cpuguy83/docker/compare/ddb366ee9a07e3feab766cc712c9683ad0c3c309)
* [Flocker extension](https://github.com/clusterhq/powerstrip-flocker/compare/docker-volume-extension)

**Demo**

Despite the name, this demo does not use powerstrip!

```
$ git clone https://github.com/clusterhq/powerstrip-flocker
$ cd powerstrip-flocker/vagrant-aws
$ git checkout docker-volume-extension
$ vagrant up --provider=aws
$ vagrant ssh node1
node1$ docker run -v /flocker/test:/data ubuntu sh -c "echo fish > /data/file.txt"
node1$ exit
$ vagrant ssh node2
node2$ docker run -v /flocker/test:/data ubuntu cat /data/file.txt
fish
```

In future, we will want to extend this to notify volume extensions when containers start and stop, so that the underlying volumes mechanism can hold and release *leases* on volumes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an extension has access to the docker socket - it can listen to the event stream to know if a container has stopped. Is it better to leave this up to the extension? The same issue would be true of networking extensions that want to release an IP.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think that's OK for the plugin to implement in its own way.


### Volumes CLI

We may want to extend some (as-yet non-existent) docker core volumes CLI in due course, so you can do something like the following:

```
$ docker volume create --driver flocker \
--metadata size=50G,tier=ssd,replication=global nicevol

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should match the --env/--volume semantics:

--metadata size=50G --metadata tier=ssd --metadata replication=global

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or more really the forthcoming label API

$ docker run -v /flocker/nicevol:/data dockerfile/postgresql
```

This would result in similar API requests being made.

Other `docker volume` commands which could make sense are e.g. `docker volume snapshot` and `docker volume rm` which will need corresponding extension API message types to be defined.

## API

Extensions to the Docker remote API (extension type `api`) can be achieved via a [Powerstrip-like protocol](https://github.com/clusterhq/powerstrip#pre-hook-adapter-endpoints-receive-posts-like-this).
The corresponding limitations will initially apply to the types of requests which can be processed, but this is acceptable for an MVP.

## Network

Network extensions exist to implement the following UX:

```
$ docker network create greatnet

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is greatnet the name of the plugin, or the name of the network?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the network, I guess. This is where my view of what @shykes has in mind for the networking UX gets pretty hazy.

$ docker run --net=greatnet --ip=1.2.3.4 dockerfile/postgresql
```

(and of course: `$ docker run --net=greatnet --ip=1.2.3.4 -v //nicevol:/data dockerfile/postgresql` should also be possible, when both networking and storage plugins are loaded)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm wondering whether it would be useful for a plugin to register a custom uri scheme.
So you could chain plugins like so...

docker run --net=weave://network1 --net=other://network12 -v flocker://volume-foo:/usr/foo -v other://volume-bar:/usr/bar

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for the URI scheme in a later revision.

I've been toying with /flocker/volume-foo, //volume-foo, flocker://volume-foo and I like flocker://volume-foo the best. The reason I hadn't proposed that yet though is that it would require a change to the parsing logic because colons already have a meaning in volume specs. /flocker/volume-foo certainly isn't final, but it will get us over the hurdle of getting an extensions MVP out.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we've talked about this in the past and it the response was basically this was overloading the -v... that said I think it totally makes sense.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line also introduces the --ip argument, which rises another question - how would user know that this argument is handled by the extension? One way of solving this problem is to add extend --net="..." syntax, but that is gonna get hairy soon. Perhaps plugins could introduce a namespaced argument, e.g. --awesome-ip= in this specific case would work a bit better.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the URI approach in ways, but extending syntax of --net is not a great idea, I would rather deprecate that flag and introduce something like this:

docker run --weave-net=network1 --other-net=network12 --other-param=foo

Similarly with volumes, I am thinking that adding --flocker-volume would appear much clearer to the user.


**Implementation note:** Whether `docker network` calls the Docker client binary and Docker daemon, or whether the first class networks functionality is implemented by separate binary *should be transparent to the plugin*.

Similar request-response pairs as for volumes can be defined for network `create`, `add-container`, etc.


# Semantics

This area needs fleshing out.

* Should chaining be possible?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@progrium suggest that this behaviour should depend on the extension point.

* What happens when you load several of the same type of extension?
* What happens when a hook or pre-hook fails?
What about post-hooks?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post hooks are important - IMHO they should certainly be included somehow in the workflow. I think about it as pre-hook=setup and post-hook=react where the react step is "do something now that container is running"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the API extension discussion then. When we get to that bit, we can start with what we learned from powerstrip, write down the desired formal semantics and iterate on that.

Do we need special cleanup hooks?

# Possible future functionality

* While waiting for the plugin to initialize, Docker should not allow any API requests to succeed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this prevent plugins from doing any work through the Docker API? For instance, when a weave plugin was loaded, we'd want it to start a weave router container, maybe a weaveDNS container, and so on.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point - a plugin might itself need to start other containers. Perhaps we can say "While waiting for the plugin to initialize, Docker should not allow any API requests that are not from plugins to succeed". This is also another good use-case for plugins in general being able to talk to the docker deamon

This is to avoid startup race where e.g. `create` requests might not get passed through the plugins.
* Plugins could use a protocol other than HTTP.
* Loading a plugin marks the plugin's container as hidden from `docker ps`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an example of the more generic "agent" pattern discussed by @progrium above?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think we agreed to go ahead and start with this (it's up to @progrium how he implements the loader 😄).


# Areas for discussion

* Should the plugin endpoints all be a single endpoint, or should they define e.g. `/v1/networks`, `/v1/volumes` etc?
Certainly for the `api` type plugins, `POST`ing them all to a single endpoint made sense for Powerstrip.