Skip to content

Commit de3da82

Browse files
committed
sort containers to optimize scale down
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent c79aabd commit de3da82

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

pkg/compose/convergence.go

+18
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
127127
}
128128

129129
sort.Slice(containers, func(i, j int) bool {
130+
// select obsolete containers first, so they get removed as we scale down
131+
if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete {
132+
// i is obsolete, so must be first in the list
133+
return true
134+
}
135+
if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete {
136+
// j is obsolete, so must be first in the list
137+
return false
138+
}
139+
140+
// For up-to-date containers, sort by container number to preserve low-values in container numbers
141+
ni, erri := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel])
142+
nj, errj := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel])
143+
if erri == nil && errj == nil {
144+
return ni < nj
145+
}
146+
147+
// If we don't get a container number (?) just sort by creation date
130148
return containers[i].Created < containers[j].Created
131149
})
132150
for i, container := range containers {

pkg/e2e/fixtures/scale/compose.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ services:
55
- db
66
db:
77
image: nginx:alpine
8+
environment:
9+
- MAYBE
810
front:
911
image: nginx:alpine
1012
deploy:

pkg/e2e/scale_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,78 @@ func TestScaleWithDepsCases(t *testing.T) {
9595
checkServiceContainer(t, res.Combined(), "scale-deps-tests-db", NO_STATE_TO_CHECK, 1)
9696
}
9797

98+
func TestScaleUpAndDownPreserveContainerNumber(t *testing.T) {
99+
const projectName = "scale-up-down-test"
100+
101+
c := NewCLI(t, WithEnv(
102+
"COMPOSE_PROJECT_NAME="+projectName))
103+
104+
reset := func() {
105+
c.RunDockerComposeCmd(t, "down", "--rmi", "all")
106+
}
107+
t.Cleanup(reset)
108+
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
109+
res.Assert(t, icmd.Success)
110+
111+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
112+
res.Assert(t, icmd.Success)
113+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
114+
115+
t.Log("scale down removes replica #2")
116+
res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db")
117+
res.Assert(t, icmd.Success)
118+
119+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
120+
res.Assert(t, icmd.Success)
121+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
122+
123+
t.Log("scale up restores replica #2")
124+
res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
125+
res.Assert(t, icmd.Success)
126+
127+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
128+
res.Assert(t, icmd.Success)
129+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
130+
}
131+
132+
func TestScaleDownRemovesObsolete(t *testing.T) {
133+
const projectName = "scale-down-obsolete-test"
134+
c := NewCLI(t, WithEnv(
135+
"COMPOSE_PROJECT_NAME="+projectName))
136+
137+
reset := func() {
138+
c.RunDockerComposeCmd(t, "down", "--rmi", "all")
139+
}
140+
t.Cleanup(reset)
141+
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "db")
142+
res.Assert(t, icmd.Success)
143+
144+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
145+
res.Assert(t, icmd.Success)
146+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
147+
148+
cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
149+
res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
150+
cmd.Env = append(cmd.Env, "MAYBE=value")
151+
})
152+
res.Assert(t, icmd.Success)
153+
154+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
155+
res.Assert(t, icmd.Success)
156+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
157+
158+
t.Log("scale down removes obsolete replica #1")
159+
cmd = c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db")
160+
res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
161+
cmd.Env = append(cmd.Env, "MAYBE=value")
162+
})
163+
res.Assert(t, icmd.Success)
164+
165+
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
166+
res.Assert(t, icmd.Success)
167+
assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
168+
}
169+
98170
func checkServiceContainer(t *testing.T, stdout, containerName, containerState string, count int) {
99171
found := 0
100172
lines := strings.Split(stdout, "\n")

0 commit comments

Comments
 (0)