mirror of https://github.com/docker/docs.git
Dependency Filter: co-schedule dependent containers on the same node.
Supported dependencies: - Shared volumes: `--volumes-from=dependency` - Links: `--link=dependency:alias` - Shared network stack: `--net=container:dependency` Fixes #251 Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
67e347fa1a
commit
97334b0193
4
flags.go
4
flags.go
|
|
@ -86,12 +86,12 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// hack for go vet
|
// hack for go vet
|
||||||
flFilterValue = cli.StringSlice([]string{"constraint", "affinity", "health", "port"})
|
flFilterValue = cli.StringSlice([]string{"constraint", "affinity", "health", "port", "dependency"})
|
||||||
DEFAULT_FILTER_NUMBER = len(flFilterValue)
|
DEFAULT_FILTER_NUMBER = len(flFilterValue)
|
||||||
|
|
||||||
flFilter = cli.StringSliceFlag{
|
flFilter = cli.StringSliceFlag{
|
||||||
Name: "filter, f",
|
Name: "filter, f",
|
||||||
Usage: "filter to use [constraint, affinity, health, port]",
|
Usage: "filter to use [constraint, affinity, health, port, dependency]",
|
||||||
Value: &flFilterValue,
|
Value: &flFilterValue,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,25 @@ $ docker run -d -p 80:80 nginx
|
||||||
2014/10/29 00:33:20 Error response from daemon: no resources available to schedule container
|
2014/10/29 00:33:20 Error response from daemon: no resources available to schedule container
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dependency Filter
|
||||||
|
|
||||||
|
This filter co-schedules dependent containers on the same node.
|
||||||
|
|
||||||
|
Currently, dependencies are declared as follows:
|
||||||
|
|
||||||
|
- Shared volumes: `--volumes-from=dependency`
|
||||||
|
- Links: `--link=dependency:alias`
|
||||||
|
- Shared network stack: `--net=container:dependency`
|
||||||
|
|
||||||
|
Swarm will attempt to co-locate the dependent container on the same node. If it
|
||||||
|
cannot be done (because the dependent container doesn't exist, or because the
|
||||||
|
node doesn't have enough resources), it will prevent the container creation.
|
||||||
|
|
||||||
|
The combination of multiple dependencies will be honored if possible. For
|
||||||
|
instance, `--volumes-from=A --net=container:B` will attempt to co-locate the
|
||||||
|
container on the same node as `A` and `B`. If those containers are running on
|
||||||
|
different nodes, Swarm will prevent you from scheduling the container.
|
||||||
|
|
||||||
## Health Filter
|
## Health Filter
|
||||||
|
|
||||||
This filter will prevent scheduling containers on unhealthy nodes.
|
This filter will prevent scheduling containers on unhealthy nodes.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/swarm/cluster"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DependencyFilter co-schedules dependent containers on the same node.
|
||||||
|
type DependencyFilter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DependencyFilter) Filter(config *dockerclient.ContainerConfig, nodes []*cluster.Node) ([]*cluster.Node, error) {
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract containers from links.
|
||||||
|
links := []string{}
|
||||||
|
for _, link := range config.HostConfig.Links {
|
||||||
|
links = append(links, strings.SplitN(link, ":", 2)[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if --net points to a container.
|
||||||
|
net := []string{}
|
||||||
|
if strings.HasPrefix(config.HostConfig.NetworkMode, "container:") {
|
||||||
|
net = append(net, strings.TrimPrefix(config.HostConfig.NetworkMode, "container:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates := []*cluster.Node{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if f.check(config.HostConfig.VolumesFrom, node) &&
|
||||||
|
f.check(links, node) &&
|
||||||
|
f.check(net, node) {
|
||||||
|
candidates = append(candidates, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
return nil, fmt.Errorf("Unable to find a node fulfilling all dependencies: %s", f.String(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a string representation of the dependencies found in the container config.
|
||||||
|
func (f *DependencyFilter) String(config *dockerclient.ContainerConfig) string {
|
||||||
|
dependencies := []string{}
|
||||||
|
for _, volume := range config.HostConfig.VolumesFrom {
|
||||||
|
dependencies = append(dependencies, fmt.Sprintf("--volumes-from=%s", volume))
|
||||||
|
}
|
||||||
|
for _, link := range config.HostConfig.Links {
|
||||||
|
dependencies = append(dependencies, fmt.Sprintf("--link=%s", link))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(config.HostConfig.NetworkMode, "container:") {
|
||||||
|
dependencies = append(dependencies, fmt.Sprintf("--net=%s", config.HostConfig.NetworkMode))
|
||||||
|
}
|
||||||
|
return strings.Join(dependencies, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the node contains all dependent containers.
|
||||||
|
func (f *DependencyFilter) check(dependencies []string, node *cluster.Node) bool {
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if node.Container(dependency) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/swarm/cluster"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDependencyFilterSimple(t *testing.T) {
|
||||||
|
var (
|
||||||
|
f = DependencyFilter{}
|
||||||
|
nodes = []*cluster.Node{
|
||||||
|
cluster.NewNode("node-1", 0),
|
||||||
|
cluster.NewNode("node-2", 0),
|
||||||
|
cluster.NewNode("node-3", 0),
|
||||||
|
}
|
||||||
|
result []*cluster.Node
|
||||||
|
err error
|
||||||
|
container *cluster.Container
|
||||||
|
config *dockerclient.ContainerConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||||
|
assert.NoError(t, nodes[0].AddContainer(container))
|
||||||
|
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||||
|
assert.NoError(t, nodes[1].AddContainer(container))
|
||||||
|
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||||
|
assert.NoError(t, nodes[2].AddContainer(container))
|
||||||
|
|
||||||
|
// No dependencies - make sure we don't filter anything out.
|
||||||
|
config = &dockerclient.ContainerConfig{}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, result, nodes)
|
||||||
|
|
||||||
|
// volumes-from.
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[0])
|
||||||
|
|
||||||
|
// link.
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
Links: []string{"c1:foobar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[1])
|
||||||
|
|
||||||
|
// net.
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
NetworkMode: "container:c2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[2])
|
||||||
|
|
||||||
|
// net not prefixed by "container:" should be ignored.
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, result, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependencyFilterMulti(t *testing.T) {
|
||||||
|
var (
|
||||||
|
f = DependencyFilter{}
|
||||||
|
nodes = []*cluster.Node{
|
||||||
|
cluster.NewNode("node-1", 0),
|
||||||
|
cluster.NewNode("node-2", 0),
|
||||||
|
cluster.NewNode("node-3", 0),
|
||||||
|
}
|
||||||
|
result []*cluster.Node
|
||||||
|
err error
|
||||||
|
container *cluster.Container
|
||||||
|
config *dockerclient.ContainerConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodes[0] has c0 and c1
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||||
|
assert.NoError(t, nodes[0].AddContainer(container))
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||||
|
assert.NoError(t, nodes[0].AddContainer(container))
|
||||||
|
|
||||||
|
// nodes[1] has c2
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||||
|
assert.NoError(t, nodes[1].AddContainer(container))
|
||||||
|
|
||||||
|
// nodes[2] has nothing
|
||||||
|
|
||||||
|
// Depend on c0 which is on nodes[0]
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[0])
|
||||||
|
|
||||||
|
// Depend on c1 which is on nodes[0]
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[0])
|
||||||
|
|
||||||
|
// Depend on c0 AND c1 which are both on nodes[0]
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0", "c1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[0])
|
||||||
|
|
||||||
|
// Depend on c0 AND c2 which are on different nodes.
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0", "c2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependencyFilterChaining(t *testing.T) {
|
||||||
|
var (
|
||||||
|
f = DependencyFilter{}
|
||||||
|
nodes = []*cluster.Node{
|
||||||
|
cluster.NewNode("node-1", 0),
|
||||||
|
cluster.NewNode("node-2", 0),
|
||||||
|
cluster.NewNode("node-3", 0),
|
||||||
|
}
|
||||||
|
result []*cluster.Node
|
||||||
|
err error
|
||||||
|
container *cluster.Container
|
||||||
|
config *dockerclient.ContainerConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodes[0] has c0 and c1
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||||
|
assert.NoError(t, nodes[0].AddContainer(container))
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||||
|
assert.NoError(t, nodes[0].AddContainer(container))
|
||||||
|
|
||||||
|
// nodes[1] has c2
|
||||||
|
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||||
|
assert.NoError(t, nodes[1].AddContainer(container))
|
||||||
|
|
||||||
|
// nodes[2] has nothing
|
||||||
|
|
||||||
|
// Different dependencies on c0 and c1
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0"},
|
||||||
|
Links: []string{"c1"},
|
||||||
|
NetworkMode: "container:c1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodes[0])
|
||||||
|
|
||||||
|
// Different dependencies on c0 and c2
|
||||||
|
config = &dockerclient.ContainerConfig{
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
VolumesFrom: []string{"c0"},
|
||||||
|
Links: []string{"c2"},
|
||||||
|
NetworkMode: "container:c1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err = f.Filter(config, nodes)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ func init() {
|
||||||
"health": &HealthFilter{},
|
"health": &HealthFilter{},
|
||||||
"constraint": &ConstraintFilter{},
|
"constraint": &ConstraintFilter{},
|
||||||
"port": &PortFilter{},
|
"port": &PortFilter{},
|
||||||
|
"dependency": &DependencyFilter{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue