-
Notifications
You must be signed in to change notification settings - Fork 4
Proposal: Docker plugins MVP #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b3c0768
b2d2757
fae773b
b5399a7
daa78b0
58b2efe
64cbb69
af5397e
7f2f234
88e4d36
18bafa3
1b9b606
505a94e
393f6d7
a1abec7
a82f88d
4cb9868
183166b
cb6d1a5
4188e32
41c7e16
6e80593
c355ce4
892735c
5b57ec7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
|
|
||
| Extension points currently supported are: | ||
|
|
||
| * `volume` - called when a volume is bind-mounted into a container at create or start time | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. As @progrium mentions below, most agents will require Perhaps a plugin should be a container + a
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 But I don't see this as a hard requirement for plugins.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should definitely not bind-mount the docker socket into the plugins. On Thu, Mar 12, 2015 at 4:20 AM, lukemarsden [email protected]
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 To be clear, the choice is:
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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My take on these:
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 |
||
|
|
||
| ``` | ||
| $ docker plugin load -e $ARGS clusterhq/flocker-plugin $@ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why should this have
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think having args at the end will be fine for the purpose of this document.
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** | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
|
|
||
| ``` | ||
| {HostPath: "/newpath", | ||
| ContainerID: "abcdef123"} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should match the --env/--volume semantics:
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. docker run --net=weave://network1 --net=other://network12 -v flocker://volume-foo:/usr/foo -v other://volume-bar:/usr/bar
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line also introduces the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do like the URI approach in ways, but extending syntax of Similarly with volumes, I am thinking that adding |
||
|
|
||
| **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? | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps "implements"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1