mirror of https://github.com/docker/docs.git
Filter for containerslots-label
Signed-off-by: michael.freund <michael.freund@shopgate.com>
This commit is contained in:
parent
e8cb44fe81
commit
0ccc958b50
|
@ -25,6 +25,7 @@ Each filter has a name that identifies it. The node filters are:
|
||||||
|
|
||||||
* `constraint`
|
* `constraint`
|
||||||
* `health`
|
* `health`
|
||||||
|
* `containerslots`
|
||||||
|
|
||||||
The container configuration filters are:
|
The container configuration filters are:
|
||||||
|
|
||||||
|
@ -48,6 +49,8 @@ $ swarm manage --filter=health --filter=dependency
|
||||||
|
|
||||||
When creating a container or building an image, you use a `constraint` or
|
When creating a container or building an image, you use a `constraint` or
|
||||||
`health` filter to select a subset of nodes to consider for scheduling.
|
`health` filter to select a subset of nodes to consider for scheduling.
|
||||||
|
If there are nodes in the swarm that have a label with key as `containerslots`
|
||||||
|
and a number-value, Swarm will not launch more containers than the given number.
|
||||||
|
|
||||||
### Use a constraint filter
|
### Use a constraint filter
|
||||||
|
|
||||||
|
@ -175,6 +178,16 @@ The node `health` filter prevents the scheduler form running containers
|
||||||
on unhealthy nodes. A node is considered unhealthy if the node is down or it
|
on unhealthy nodes. A node is considered unhealthy if the node is down or it
|
||||||
can't communicate with the cluster store.
|
can't communicate with the cluster store.
|
||||||
|
|
||||||
|
### Use the containerslots filter
|
||||||
|
|
||||||
|
You may give your Docker nodes the containerslots label
|
||||||
|
```bash
|
||||||
|
$ docker daemon --label containerslots=3
|
||||||
|
```
|
||||||
|
Swarm will prevent running more than three containers at this node, if
|
||||||
|
all nodes are "full", an error is thrown. If the value is not castable
|
||||||
|
to an integer number or is not present, there will be no limit.
|
||||||
|
|
||||||
## Container filters
|
## Container filters
|
||||||
|
|
||||||
When creating a container, you can use three types of container filters:
|
When creating a container, you can use three types of container filters:
|
||||||
|
|
|
@ -30,6 +30,7 @@ func init() {
|
||||||
filters = []Filter{
|
filters = []Filter{
|
||||||
&HealthFilter{},
|
&HealthFilter{},
|
||||||
&PortFilter{},
|
&PortFilter{},
|
||||||
|
&SlotsFilter{},
|
||||||
&DependencyFilter{},
|
&DependencyFilter{},
|
||||||
&AffinityFilter{},
|
&AffinityFilter{},
|
||||||
&ConstraintFilter{},
|
&ConstraintFilter{},
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/docker/swarm/cluster"
|
||||||
|
"github.com/docker/swarm/scheduler/node"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoNodeWithFreeSlotsAvailable is exported
|
||||||
|
ErrNoNodeWithFreeSlotsAvailable = errors.New("No node with enough open slots available in the cluster")
|
||||||
|
)
|
||||||
|
|
||||||
|
//SlotsFilter only schedules containers with open slots.
|
||||||
|
type SlotsFilter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the filter
|
||||||
|
func (f *SlotsFilter) Name() string {
|
||||||
|
return "containerslots"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter is exported
|
||||||
|
func (f *SlotsFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node, _ bool) ([]*node.Node, error) {
|
||||||
|
result := []*node.Node{}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
|
||||||
|
if slotsString, ok := node.Labels["containerslots"]; ok {
|
||||||
|
slots, err := strconv.Atoi(slotsString) //if err => cannot cast to int, so ignore the label
|
||||||
|
if err != nil || len(node.Containers) < slots {
|
||||||
|
result = append(result, node)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//no limit if label is missing
|
||||||
|
result = append(result, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil, ErrNoNodeWithFreeSlotsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilters returns just the info that this node failed, because there where no free slots
|
||||||
|
func (f *SlotsFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) {
|
||||||
|
return []string{"free slots"}, nil
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/engine-api/types"
|
||||||
|
"github.com/docker/swarm/cluster"
|
||||||
|
"github.com/docker/swarm/scheduler/node"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var labelsWithSlots = make(map[string]string)
|
||||||
|
var labelsWithoutSlots = make(map[string]string)
|
||||||
|
var labelsWithStringSlot = make(map[string]string)
|
||||||
|
|
||||||
|
func testFixturesAllFreeNode() []*node.Node {
|
||||||
|
return []*node.Node{
|
||||||
|
{
|
||||||
|
ID: "node-0-id",
|
||||||
|
Name: "node-0-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "node-1-id",
|
||||||
|
Name: "node-1-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFixturesPartlyFreeNode() []*node.Node {
|
||||||
|
return []*node.Node{
|
||||||
|
{
|
||||||
|
ID: "node-0-id",
|
||||||
|
Name: "node-0-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "node-1-id",
|
||||||
|
Name: "node-1-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFixturesAllNoLabelNode() []*node.Node {
|
||||||
|
return []*node.Node{
|
||||||
|
{
|
||||||
|
ID: "node-0-id",
|
||||||
|
Name: "node-0-name",
|
||||||
|
Labels: labelsWithoutSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ID: "node-1-id",
|
||||||
|
Name: "node-1-name",
|
||||||
|
Labels: labelsWithoutSlots,
|
||||||
|
Containers: []*cluster.Container{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFixturesNoFreeNode() []*node.Node {
|
||||||
|
return []*node.Node{
|
||||||
|
{
|
||||||
|
ID: "node-0-id",
|
||||||
|
Name: "node-0-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ID: "node-1-id",
|
||||||
|
Name: "node-1-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFixturesNoFreeNodeButStringLabel() []*node.Node {
|
||||||
|
return []*node.Node{
|
||||||
|
{
|
||||||
|
ID: "node-0-id",
|
||||||
|
Name: "node-0-name",
|
||||||
|
Labels: labelsWithSlots,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ID: "node-1-id",
|
||||||
|
Name: "node-1-name",
|
||||||
|
Labels: labelsWithStringSlot,
|
||||||
|
Containers: []*cluster.Container{
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
{Container: types.Container{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlotsFilter(t *testing.T) {
|
||||||
|
|
||||||
|
labelsWithSlots["containerslots"] = "3"
|
||||||
|
labelsWithStringSlot["containerslots"] = "foo"
|
||||||
|
|
||||||
|
var (
|
||||||
|
f = SlotsFilter{}
|
||||||
|
nodesAllFree = testFixturesAllFreeNode()
|
||||||
|
nodesPartlyFree = testFixturesPartlyFreeNode()
|
||||||
|
nodesAllNoLabel = testFixturesAllNoLabelNode()
|
||||||
|
nodesNoFree = testFixturesNoFreeNode()
|
||||||
|
nodesNoFreeButStringLabel = testFixturesNoFreeNodeButStringLabel()
|
||||||
|
result []*node.Node
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllFree, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, result, nodesAllFree)
|
||||||
|
|
||||||
|
result, err = f.Filter(&cluster.ContainerConfig{}, nodesPartlyFree, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodesPartlyFree[1])
|
||||||
|
|
||||||
|
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllNoLabel, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, result, nodesAllNoLabel)
|
||||||
|
|
||||||
|
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoFree, true)
|
||||||
|
assert.Equal(t, err, ErrNoNodeWithFreeSlotsAvailable)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoFreeButStringLabel, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, result[0], nodesNoFreeButStringLabel[1])
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ function teardown() {
|
||||||
run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build
|
run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
[[ "${lines[1]}" == *"Unable to find a node that satisfies the following conditions"* ]]
|
[[ "${lines[1]}" == *"Unable to find a node that satisfies the following conditions"* ]]
|
||||||
[[ "${lines[2]}" == *"[node==node-9]"* ]]
|
[[ "${lines[3]}" == *"[node==node-9]"* ]]
|
||||||
|
|
||||||
run docker_swarm images -q
|
run docker_swarm images -q
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
swarm_manage_cleanup
|
||||||
|
stop_docker
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "containerslots filter" {
|
||||||
|
start_docker_with_busybox 2 --label containerslots=2
|
||||||
|
swarm_manage
|
||||||
|
|
||||||
|
# Use busybox to save image pulling time for integration test.
|
||||||
|
# Running the first 4 containers, it should be fine.
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# When trying to start the 5th one, it should be error finding a node with free slots.
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
[[ "${lines[0]}" == *"Unable to find a node that satisfies the following conditions"* ]]
|
||||||
|
[[ "${lines[1]}" == *"free slots"* ]]
|
||||||
|
|
||||||
|
# And the number of running containers should be still 4.
|
||||||
|
run docker_swarm ps
|
||||||
|
[ "${#lines[@]}" -eq 5 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "containerslots without existing label" {
|
||||||
|
start_docker_with_busybox 2
|
||||||
|
swarm_manage
|
||||||
|
|
||||||
|
# Use busybox to save image pulling time for integration test.
|
||||||
|
# Running more than 5 containers, it should be fine.
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# And the number of running containers should be 5.
|
||||||
|
run docker_swarm ps
|
||||||
|
[ "${#lines[@]}" -eq 6 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "containerslots with invalid label" {
|
||||||
|
start_docker_with_busybox 2 --label containerslots="foo"
|
||||||
|
swarm_manage
|
||||||
|
|
||||||
|
# Use busybox to save image pulling time for integration test.
|
||||||
|
# Running more than 5 containers, it should be fine.
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_swarm run -d -t busybox sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# And the number of running containers should be 5.
|
||||||
|
run docker_swarm ps
|
||||||
|
[ "${#lines[@]}" -eq 6 ]
|
||||||
|
}
|
Loading…
Reference in New Issue