| Cluster Volumes | |
| =============== | |
| Docker Cluster Volumes is a new feature which allows using CSI plugins to | |
| create cluster-aware volumes. | |
| The Container Storage Interface is a platform-agnostic API for storage | |
| providers to write storage plugins which are compatible with many container | |
| orchestrators. By leveraging the CSI, Docker Swarm can provide intelligent, | |
| cluster-aware access to volumes across many supported storage providers. | |
| ## Installing a CSI plugin | |
| Docker accesses CSI plugins through the Docker managed plugin system, using the | |
| `docker plugin` command. | |
| If a plugin is available for Docker, it can be installed through the `docker | |
| plugin install` command. Plugins may require configuration specific to the | |
| user's environment, they will ultimately be detected by and work automatically | |
| with Docker once enabled. | |
| Currently, there is no way to automatically deploy a Docker Plugin across all | |
| nodes in a cluster. Therefore, users must ensure the Docker Plugin is installed | |
| on all nodes in the cluster on which it is desired. | |
| The CSI plugin must be installed on all manager nodes. If a manager node does | |
| not have the CSI plugin installed, a leadership change to that manager nodes | |
| will make Swarm unable to use that driver. | |
| Docker Swarm worker nodes report their active plugins to the Docker Swarm | |
| managers, so it is not necessary to install a plugin on every worker node. The | |
| plugin only needs to be installed on those nodes need access to the volumes | |
| provided by that plugin. | |
| ### Multiple Instances of the Same Plugin | |
| In some cases, it may be desirable to run multiple instances of the same | |
| plugin. For example, there may be two different instances of some storage | |
| provider which each need a differently configured plugin. | |
| To run more than one instance of the same plugin, set the `--alias` option when | |
| installing the plugin. This will cause the plugin to take a local name | |
| different from its original name. | |
| Ensure that when using plugin name aliases, the plugin name alias is the same | |
| on every node. | |
| ## Creating a Docker CSI Plugin | |
| Most CSI plugins are shipped with configuration specific to Kubernetes. They | |
| are often provided in the form of Helm charts, and installation information may | |
| include Kubernetes-specific steps. Docker CSI Plugins use the same binaries as | |
| those for Kubernetes, but in a different environment and sometimes with | |
| different configuration. | |
| Before following this section, readers should ensure they are acquainted with | |
| the | |
| [Docker Engine managed plugin system](https://docs.docker.com/engine/extend/). | |
| Docker CSI plugins use this system to run. | |
| Docker Plugins consist of a root filesystem and a `config.json`. The root | |
| filesystem can generally be exported from whatever image is built for the | |
| plugin. The `config.json` specifies how the plugin is used. Several | |
| CSI-specific concerns, as well as some general but poorly-documented features, | |
| are outlined here. | |
| ### Basic Requirements | |
| Docker CSI plugins are identified with a special interface type. There are two | |
| related interfaces that CSI plugins can expose. | |
| * `docker.csicontroller/1.0` is used for CSI Controller plugins. | |
| * `docker.csinode/1.0` is used for CSI Node plugins. | |
| * Combined plugins should include both interfaces. | |
| Additionally, the interface field of the config.json includes a `socket` field. | |
| This can be set to any value, but the CSI plugin should have its `CSI_ENDPOINT` | |
| environment variable set appropriately. | |
| In the `config.json`, this should be set as such: | |
| ```json | |
| "interface": { | |
| "types": ["docker.csicontroller/1.0","docker.csinode/1.0"], | |
| "socket": "my-csi-plugin.sock" | |
| }, | |
| "env": [ | |
| { | |
| "name": "CSI_ENDPOINT", | |
| "value": "/run/docker/plugins/my-csi-plugin.sock" | |
| } | |
| ] | |
| ``` | |
| The CSI specification states that CSI plugins should have | |
| `CAP_SYS_ADMIN` privileges, so this should be set in the `config.json` as | |
| well: | |
| ```json | |
| "linux" : { | |
| "capabilities": ["CAP_SYS_ADMIN"] | |
| } | |
| ``` | |
| ### Propagated Mount | |
| In order for the plugin to expose volumes to Swarm, it must publish those | |
| volumes to a Propagated Mount location. This allows a mount to be itself | |
| mounted to a different location in the filesystem. The Docker Plugin system | |
| only allows one Propagated Mount, which is configured as a string representing | |
| the path in the plugin filesystem. | |
| When calling the CSI plugin, Docker Swarm specifies the publish target path, | |
| which is the path in the plugin filesystem that a volume should ultimately be | |
| used from. This is also the path that needs to be specified as the Propagated | |
| Mount for the plugin. This path is hard-coded to be `/data/published` in the | |
| plugin filesystem, and as such, the plugin configuration should list this as | |
| the Propagated Mount: | |
| ```json | |
| "propagatedMount": "/data/published" | |
| ``` | |
| ### Configurable Options | |
| Plugin configurations can specify configurable options for many fields. To | |
| expose a field as configurable, the object including that field should include | |
| a field `Settable`, which is an array of strings specifying the name of | |
| settable fields. | |
| For example, consider a plugin that supports a config file. | |
| ```json | |
| "mounts": [ | |
| { | |
| "name": "configfile", | |
| "description": "Config file mounted in from the host filesystem", | |
| "type": "bind", | |
| "destination": "/opt/my-csi-plugin/config.yaml", | |
| "source": "/etc/my-csi-plugin/config.yaml" | |
| } | |
| ] | |
| ``` | |
| This configuration would result in a file located on the host filesystem at | |
| `/etc/my-csi-plugin/config.yaml` being mounted into the plugin filesystem at | |
| `/opt/my-csi-plugin/config.yaml`. However, hard-specifying the source path of | |
| the configuration is undesirable. Instead, the plugin author can put the | |
| `Source` field in the Settable array: | |
| ```json | |
| "mounts": [ | |
| { | |
| "name": "configfile", | |
| "description": "Config file mounted in from the host filesystem", | |
| "type": "bind", | |
| "destination": "/opt/my-csi-plugin/config.yaml", | |
| "source": "", | |
| "settable": ["source"] | |
| } | |
| ] | |
| ``` | |
| When a field is exposed as settable, the user can configure that field when | |
| installing the plugin. | |
| ``` | |
| $ docker plugin install my-csi-plugin configfile.source="/srv/my-csi-plugin/config.yaml" | |
| ``` | |
| Or, alternatively, it can be set while the plugin is disabled: | |
| ``` | |
| $ docker plugin disable my-csi-plugin | |
| $ docker plugin set my-csi-plugin configfile.source="/var/lib/my-csi-plugin/config.yaml" | |
| $ docker plugin enable | |
| ``` | |
| ### Split-Component Plugins | |
| For split-component plugins, users can specify either the | |
| `docker.csicontroller/1.0` or `docker.csinode/1.0` plugin interfaces. Manager | |
| nodes should run plugin instances with the `docker.csicontroller/1.0` | |
| interface, and worker nodes the `docker.csinode/1.0` interface. | |
| Docker does support running two plugins with the same name, nor does it support | |
| specifying different drivers for the node and controller plugins. This means in | |
| a fully split plugin, Swarm will be unable to schedule volumes to manager | |
| nodes. | |
| If it is desired to run a split-component plugin such that the Volumes managed | |
| by that plugin are accessible to Tasks on the manager node, the user will need | |
| to build the plugin such that some proxy or multiplexer provides the illusion | |
| of combined components to the manager through one socket, and ensure the plugin | |
| reports both interface types. | |
| ## Using Cluster Volumes | |
| ### Create a Cluster Volume | |
| Creating a Cluster Volume is done with the same `docker volume` commands as any | |
| other Volume. To create a Cluster Volume, one needs to do both of things: | |
| * Specify a CSI-capable driver with the `--driver` or `-d` option. | |
| * Use any one of the cluster-specific `docker volume create` flags. | |
| For example, to create a Cluster Volume called `my-volume` with the | |
| `democratic-csi` Volume Driver, one might use this command: | |
| ```bash | |
| docker volume create \ | |
| --driver democratic-csi \ | |
| --type mount \ | |
| --sharing all \ | |
| --scope multi \ | |
| --limit-bytes 10G \ | |
| --required-bytes 1G \ | |
| my-volume | |
| ``` | |
| ### List Cluster Volumes | |
| Cluster Volumes will be listed along with other volumes when doing | |
| `docker volume ls`. However, if users want to see only Cluster Volumes, and | |
| with cluster-specific information, the flag `--cluster` can be specified: | |
| ``` | |
| $ docker volume ls --cluster | |
| VOLUME NAME GROUP DRIVER AVAILABILITY STATUS | |
| volume1 group1 driver1 active pending creation | |
| volume2 group1 driver1 pause created | |
| volume3 group2 driver2 active in use (1 node) | |
| volume4 group2 driver2 active in use (2 nodes) | |
| ``` | |
| ### Deploying a Service | |
| Cluster Volumes are only compatible with Docker Services, not plain Docker | |
| Containers. | |
| In Docker Services, a Cluster Volume is used the same way any other volume | |
| would be used. The `type` should be set to `cluster`. For example, to create a | |
| Service that uses `my-volume` created above, one would execute a command like: | |
| ```bash | |
| docker service create \ | |
| --name my-service \ | |
| --mount type=cluster,src=my-volume,dst=/srv/www \ | |
| nginx:alpine | |
| ``` | |
| When scheduling Services which use Cluster Volumes, Docker Swarm uses the | |
| volume's information and state to make decisions about Task placement. | |
| For example, the Service will be constrained to run only on nodes on which the | |
| volume is available. If the volume is configured with `scope=single`, meaning | |
| it can only be used on one node in the cluster at a time, then all Tasks for | |
| that Service will be scheduled to that same node. If that node changes for some | |
| reason, like a node failure, then the Tasks will be rescheduled to the new | |
| node automatically, without user input. | |
| If the Cluster Volume is accessible only on some set of nodes at the same time, | |
| and not the whole cluster, then Docker Swarm will only schedule the Service to | |
| those nodes as reported by the plugin. | |
| ### Using Volume Groups | |
| It is frequently desirable that a Service use any available volume out of an | |
| interchangeable set. To accomplish this in the most simple and straightforward | |
| manner possible, Cluster Volumes use the concept of a volume "Group". | |
| The Volume Group is a field, somewhat like a special label, which is used to | |
| instruct Swarm that a given volume is interchangeable with every other volume | |
| of the same Group. When creating a Cluster Volume, the Group can be specified | |
| by using the `--group` flag. | |
| To use a Cluster Volume by Group instead of by Name, the mount `src` option is | |
| prefixed with `group:`, followed by the group name. For example: | |
| ``` | |
| --mount type=cluster,src=group:my-group,dst=/srv/www | |
| ``` | |
| This instructs Docker Swarm that any Volume with the Group `my-group` can be | |
| used to satisfy the mounts. | |
| Volumes in a Group do not need to be identical, but they must be | |
| interchangeable. These caveats should be kept in mind when using Groups: | |
| * No Service ever gets the monopoly on a Cluster Volume. If several Services | |
| use the same Group, then the Cluster Volumes in that Group can be used with | |
| any of those Services at any time. Just because a particular Volume was used | |
| by a particular Service at one point does not mean it won't be used by a | |
| different Service later. | |
| * Volumes in a group can have different configurations, but all of those | |
| configurations must be compatible with the Service. For example, if some of | |
| the Volumes in a group have `sharing=readonly`, then the Service must be | |
| capable of using the volume in read-only mode. | |
| * Volumes in a Group are created statically ahead of time, not dynamically | |
| as-needed. This means that the user must ensure a sufficient number of | |
| Volumes belong to the desired Group to support the needs of the Service. | |
| ### Taking Cluster Volumes Offline | |
| For various reasons, users may wish to take a particular Cluster Volume | |
| offline, such that is not actively used by Services. To facilitate this, | |
| Cluster Volumes have an `availability` option similar to Docker Swarm nodes. | |
| Cluster Volume availability can be one of three states: | |
| * `active` - Default. Volume can be used as normal. | |
| * `pause` - The volume will not be used for new Services, but existing Tasks | |
| using the volume will not be stopped. | |
| * `drain` - The volume will not be used for new Services, and any running Tasks | |
| using the volume will be stopped and rescheduled. | |
| A Volume can only be removed from the cluster entirely if its availability is | |
| set to `drain`, and it has been fully unpublished from all nodes. | |
| ## Unsupported Features | |
| The CSI Spec allows for a large number of features which Cluster Volumes in | |
| this initial implementation do not support. Most notably, Cluster Volumes do | |
| not support snapshots, cloning, or volume expansion. |