E591 Add support for maximum replicas per node without stack · docker/cli@f7f4d3b · GitHub
[go: up one dir, main page]

Skip to content

Commit f7f4d3b

Browse files
committed
Add support for maximum replicas per node without stack
Signed-off-by: Olli Janatuinen <olli.janatuinen@gmail.com>
1 parent db166da commit f7f4d3b

File tree

7 files changed

+83
-3
lines changed

7 files changed

+83
-3
lines changed

cli/command/service/formatter.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ Placement:
4949
{{- if .TaskPlacementPreferences }}
5050
Preferences: {{ .TaskPlacementPreferences }}
5151
{{- end }}
52+
{{- if .MaxReplicas }}
53+
Max Replicas Per Node: {{ .MaxReplicas }}
54+
{{- end }}
5255
{{- if .HasUpdateConfig }}
5356
UpdateConfig:
5457
Parallelism: {{ .UpdateParallelism }}
@@ -284,6 +287,13 @@ func (ctx *serviceInspectContext) TaskPlacementPreferences() []string {
284287
return strings
285288
}
286289

290+
func (ctx *serviceInspectContext) MaxReplicas() uint64 {
291+
if ctx.Service.Spec.TaskTemplate.Placement != nil {
292+
return ctx.Service.Spec.TaskTemplate.Placement.MaxReplicas
293+
}
294+
return 0
295+
}
296+
287297
func (ctx *serviceInspectContext) HasUpdateConfig() bool {
288298
return ctx.Service.Spec.UpdateConfig != nil
289299
}

cli/command/service/list.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,16 @@ func GetServicesStatus(services []swarm.Service, nodes []swarm.Node, tasks []swa
120120
for _, service := range services {
121121
info[service.ID] = ListInfo{}
122122
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
123-
info[service.ID] = ListInfo{
124-
Mode: "replicated",
125-
Replicas: fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas),
123+
if service.Spec.TaskTemplate.Placement != nil && service.Spec.TaskTemplate.Placement.MaxReplicas > 0 {
124+
info[service.ID] = ListInfo{
125+
Mode: "replicated",
126+
Replicas: fmt.Sprintf("%d/%d (max %d per node)", running[service.ID], *service.Spec.Mode.Replicated.Replicas, service.Spec.TaskTemplate.Placement.MaxReplicas),
127+
}
128+
} else {
129+
info[service.ID] = ListInfo{
130+
Mode: "replicated",
131+
Replicas: fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas),
132+
}
126133
}
127134
} else if service.Spec.Mode.Global != nil {
128135
info[service.ID] = ListInfo{

cli/command/service/opts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ type serviceOptions struct {
500500
restartPolicy restartPolicyOptions
501501
constraints opts.ListOpts
502502
placementPrefs placementPrefOpts
503+
maxReplicas uint64
503504
update updateOptions
504505
rollback updateOptions
505506
networks opts.NetworkOpt
@@ -541,6 +542,10 @@ func (options *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) {
541542
return serviceMode, errors.Errorf("replicas can only be used with replicated mode")
542543
}
543544

545+
if options.maxReplicas > 0 {
546+
return serviceMode, errors.New("replicas-max-per-node can only be used with replicated mode")
547+
}
548+
544549
serviceMode.Global = &swarm.GlobalService{}
545550
case "replicated":
546551
serviceMode.Replicated = &swarm.ReplicatedService{
@@ -645,6 +650,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
645650
Placement: &swarm.Placement{
646651
Constraints: options.constraints.GetAll(),
647652
Preferences: options.placementPrefs.prefs,
653+
MaxReplicas: options.maxReplicas,
648654
},
649655
LogDriver: options.logDriver.toLogDriver(),
650656
},
@@ -747,6 +753,8 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
747753

748754
flags.Var(&opts.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)"))
749755
flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
756+
flags.Uint64Var(&opts.maxReplicas, flagMaxReplicas, defaultFlagValues.getUint64(flagMaxReplicas), "Maximum number of tasks per node (default 0 = unlimited)")
757+
flags.SetAnnotation(flagMaxReplicas, "version", []string{"1.40"})
750758

751759
flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", flagDesc(flagRestartCondition, `Restart when condition is met ("none"|"on-failure"|"any")`))
752760
flags.Var(&opts.restartPolicy.delay, flagRestartDelay, flagDesc(flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)"))
@@ -853,6 +861,7 @@ const (
853861
flagLabelAdd = "label-add"
854862
flagLimitCPU = "limit-cpu"
855863
flagLimitMemory = "limit-memory"
864+
flagMaxReplicas = "replicas-max-per-node"
856865
flagMode = "mode"
857866
flagMount = "mount"
858867
flagMountRemove = "mount-rm"

cli/command/service/opts_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,12 @@ func TestToServiceUpdateRollback(t *testing.T) {
224224
assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig))
225225
assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig))
226226
}
227+
228+
func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
229+
opt := serviceOptions{
230+
mode: "global", B018
231+
maxReplicas: 1,
232+
}
233+
_, err := opt.ToServiceMode()
234+
assert.Error(t, err, "replicas-max-per-node can only be used with replicated mode")
235+
}

cli/command/service/update.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
387387
return err
388388
}
389389

390+
if anyChanged(flags, flagMaxReplicas) {
391+
updateUint64(flagMaxReplicas, &task.Placement.MaxReplicas)
392+
}
393+
390394
if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) {
391395
if spec.UpdateConfig == nil {
392396
spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update)

cli/command/service/update_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,3 +808,23 @@ func TestUpdateNetworks(t *testing.T) {
808808
assert.NilError(t, err)
809809
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id999"}}, svc.TaskTemplate.Networks))
810810
}
811+
812+
func TestUpdateMaxReplicas(t *testing.T) {
813+
ctx := context.Background()
814+
815+
svc := swarm.ServiceSpec{
816+
TaskTemplate: swarm.TaskSpec{
817+
ContainerSpec: &swarm.ContainerSpec{},
818+
Placement: &swarm.Placement{
819+
MaxReplicas: 1,
820+
},
821+
},
822+
}
823+
824+
flags := newUpdateCommand(nil).Flags()
825+
flags.Set(flagMaxReplicas, "2")
826+
err := updateService(ctx, nil, flags, &svc)
827+
assert.NilError(t, err)
828+
829+
assert.DeepEqual(t, svc.TaskTemplate.Placement, &swarm.Placement{MaxReplicas: uint64(2)})
830+
}

docs/reference/commandline/service_create.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Options:
6161
-q, --quiet Suppress progress output
6262
--read-only Mount the container's root filesystem as read only
6363
--replicas uint Number of tasks
64+
--replicas-max-per-node uint Maximum number of tasks per node (default 0 = unlimited)
6465
--reserve-cpu decimal Reserve CPUs
6566
--reserve-memory bytes Reserve Memory
6667
--restart-condition string Restart when condition is met ("none"|"on-failure"|"any") (default "any")
@@ -757,6 +758,26 @@ appends a new placement preference after all existing placement preferences.
757758
`--placement-pref-rm` removes an existing placement preference that matches the
758759
argument.
759760

761+
### Specify maximum replicas per node (--replicas-max-per-node)
762+
763+
Use the `--replicas-max-per-node` flag to set the maximum number of replica tasks that can run on a node.
764+
The following command creates a nginx service with 2 replica tasks but only one replica task per node.
765+
766+
One example where this can be useful is to balance tasks over a set of data centers together with `--placement-pref`
767+
and let `--replicas-max-per-node` setting make sure that replicas are not migrated to another datacenter during
768+
maintenance or datacenter failure.
769+
770+
The example below illustrates this:
771+
772+
```bash
773+
$ docker service create \
774+
--name nginx \
775+
--replicas 2 \
776+
--replicas-max-per-node 1 \
777+
--placement-pref 'spread=node.labels.datacenter' \
778+
nginx
779+
```
780+
760781
### Attach a service to an existing network (--network)
761782

762783
You can use overlay networks to connect one or more services within the swarm.

0 commit comments

Comments
 (0)
0