Skip to content

Commit f620349

Browse files
committed
Add systctl support for services
Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent a4a50de commit f620349

15 files changed

Lines changed: 229 additions & 4 deletions

File tree

cli/command/service/create.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
6060
flags.SetAnnotation(flagHost, "version", []string{"1.25"})
6161
flags.BoolVar(&opts.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
6262
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
63+
flags.Var(&opts.sysctls, flagSysCtl, "Sysctl options")
64+
flags.SetAnnotation(flagSysCtl, "version", []string{"1.40"})
6365

6466
flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources")
6567
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})

cli/command/service/formatter.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ ContainerSpec:
9696
{{- if .ContainerUser }}
9797
User: {{ .ContainerUser }}
9898
{{- end }}
99+
{{- if .ContainerSysCtls }}
100+
SysCtls:
101+
{{- range $k, $v := .ContainerSysCtls }}
102+
{{ $k }}{{if $v }}: {{ $v }}{{ end }}
103+
{{- end }}{{ end }}
99104
{{- if .ContainerMounts }}
100105
Mounts:
101106
{{- end }}
@@ -415,6 +420,14 @@ func (ctx *serviceInspectContext) ContainerMounts() []mounttypes.Mount {
415420
return ctx.Service.Spec.TaskTemplate.ContainerSpec.Mounts
416421
}
417422

423+
func (ctx *serviceInspectContext) ContainerSysCtls() map[string]string {
424+
return ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls
425+
}
426+
427+
func (ctx *serviceInspectContext) HasContainerSysCtls() bool {
428+
return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls) > 0
429+
}
430+
418431
func (ctx *serviceInspectContext) HasResources() bool {
419432
return ctx.Service.Spec.TaskTemplate.Resources != nil
420433
}

cli/command/service/opts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ type serviceOptions struct {
490490
dnsSearch opts.ListOpts
491491
dnsOption opts.ListOpts
492492
hosts opts.ListOpts
493+
sysctls opts.ListOpts
493494

494495
resources resourceOptions
495496
stopGrace opts.DurationOpt
@@ -531,6 +532,7 @@ func newServiceOptions() *serviceOptions {
531532
dnsOption: opts.NewListOpts(nil),
532533
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
533534
hosts: opts.NewListOpts(opts.ValidateExtraHost),
535+
sysctls: opts.NewListOpts(nil),
534536
}
535537
}
536538

@@ -643,6 +645,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
643645
StopGracePeriod: options.ToStopGracePeriod(flags),
644646
Healthcheck: healthConfig,
645647
Isolation: container.Isolation(options.isolation),
648+
Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()),
646649
},
647650
Networks: networks,
648651
Resources: resources,
@@ -890,6 +893,9 @@ const (
890893
flagRollbackOrder = "rollback-order"
891894
flagRollbackParallelism = "rollback-parallelism"
892895
flagInit = "init"
896+
flagSysCtl = "sysctl"
897+
flagSysCtlAdd = "sysctl-add"
898+
flagSysCtlRemove = "sysctl-rm"
893899
flagStopGracePeriod = "stop-grace-period"
894900
flagStopSignal = "stop-signal"
895901
flagTTY = "tty"

cli/command/service/opts_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,16 @@ func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
233233
_, err := opt.ToServiceMode()
234234
assert.Error(t, err, "replicas-max-per-node can only be used with replicated mode")
235235
}
236+
237+
func TestToServiceSysCtls(t *testing.T) {
238+
o := newServiceOptions()
239+
o.mode = "replicated"
240+
o.sysctls.Set("net.ipv4.ip_forward=1")
241+
o.sysctls.Set("kernel.shmmax=123456")
242+
243+
expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
244+
flags := newCreateCommand(nil).Flags()
245+
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
246+
assert.NilError(t, err)
247+
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
248+
}

cli/command/service/update.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
9696
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
9797
flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
9898
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
99+
flags.Var(&options.sysctls, flagSysCtlAdd, "Add or update a Sysctl option")
100+
flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
101+
flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
102+
flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
99103

100104
// Add needs parsing, Remove only needs the key
101105
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
@@ -328,6 +332,8 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
328332
return err
329333
}
330334

335+
updateSysCtls(flags, &task.ContainerSpec.Sysctls)
336+
331337
if anyChanged(flags, flagLimitCPU, flagLimitMemory) {
332338
taskResources().Limits = spec.TaskTemplate.Resources.Limits
333339
updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
@@ -661,6 +667,25 @@ func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
661667
}
662668
}
663669

670+
func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
671+
if *field != nil && flags.Changed(flagSysCtlRemove) {
672+
values := flags.Lookup(flagSysCtlRemove).Value.(*opts.ListOpts).GetAll()
673+
for key := range opts.ConvertKVStringsToMap(values) {
674+
delete(*field, key)
675+
}
676+
}
677+
if flags.Changed(flagSysCtlAdd) {
678+
if *field == nil {
679+
*field = map[string]string{}
680+
}
681+
682+
values := flags.Lookup(flagSysCtlAdd).Value.(*opts.ListOpts).GetAll()
683+
for key, value := range opts.ConvertKVStringsToMap(values) {
684+
(*field)[key] = value
685+
}
686+
}
687+
}
688+
664689
func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
665690
if flags.Changed(flagEnvAdd) {
666691
envSet := map[string]string{}

cli/command/service/update_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,100 @@ func TestUpdateMaxReplicas(t *testing.T) {
828828

829829
assert.DeepEqual(t, svc.TaskTemplate.Placement, &swarm.Placement{MaxReplicas: uint64(2)})
830830
}
831+
832+
func TestUpdateSysCtls(t *testing.T) {
833+
ctx := context.Background()
834+
835+
tests := []struct {
836+
name string
837+
spec map[string]string
838+
add []string
839+
rm []string
840+
expected map[string]string
841+
}{
842+
{
843+
name: "from scratch",
844+
add: []string{"sysctl.zet=value-99", "sysctl.alpha=value-1"},
845+
expected: map[string]string{"sysctl.zet": "value-99", "sysctl.alpha": "value-1"},
846+
},
847+
{
848+
name: "append new",
849+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
850+
add: []string{"new.sysctl=newvalue"},
851+
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
852+
},
853+
{
854+
name: "append duplicate is a no-op",
855+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
856+
add: []string{"sysctl.one=value-1"},
857+
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
858+
},
859+
{
860+
name: "remove and append existing is a no-op",
861+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
862+
add: []string{"sysctl.one=value-1"},
863+
rm: []string{"sysctl.one=value-1"},
864+
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
865+
},
866+
{
867+
name: "remove and append new should append",
868+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
869+
add: []string{"new.sysctl=newvalue"},
870+
rm: []string{"new.sysctl=newvalue"},
871+
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
872+
},
873+
{
874+
name: "update existing",
875+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
876+
add: []string{"sysctl.one=newvalue"},
877+
expected: map[string]string{"sysctl.one": "newvalue", "sysctl.two": "value-2"},
878+
},
879+
{
880+
name: "update existing twice",
881+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
882+
add: []string{"sysctl.one=newvalue", "sysctl.one=evennewervalue"},
883+
expected: map[string]string{"sysctl.one": "evennewervalue", "sysctl.two": "value-2"},
884+
},
885+
{
886+
name: "remove all",
887+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
888+
rm: []string{"sysctl.one=value-1", "sysctl.two=value-2"},
889+
expected: map[string]string{},
890+
},
891+
{
892+
name: "remove by key",
893+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
894+
rm: []string{"sysctl.one"},
895+
expected: map[string]string{"sysctl.two": "value-2"},
896+
},
897+
{
898+
name: "remove by key and different value",
899+
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
900+
rm: []string{"sysctl.one=anyvalueyoulike"},
901+
expected: map[string]string{"sysctl.two": "value-2"},
902+
},
903+
}
904+
905+
for _, tc := range tests {
906+
t.Run(tc.name, func(t *testing.T) {
907+
svc := swarm.ServiceSpec{
908+
TaskTemplate: swarm.TaskSpec{
909+
ContainerSpec: &swarm.ContainerSpec{Sysctls: tc.spec},
910+
},
911+
}
912+
flags := newUpdateCommand(nil).Flags()
913+
for _, v := range tc.add {
914+
assert.NilError(t, flags.Set(flagSysCtlAdd, v))
915+
}
916+
for _, v := range tc.rm {
917+
assert.NilError(t, flags.Set(flagSysCtlRemove, v))
918+
}
919+
err := updateService(ctx, &fakeClient{}, flags, &svc)
920+
assert.NilError(t, err)
921+
if !assert.Check(t, is.DeepEqual(svc.TaskTemplate.ContainerSpec.Sysctls, tc.expected)) {
922+
t.Logf("expected: %v", tc.expected)
923+
t.Logf("actual: %v", svc.TaskTemplate.ContainerSpec.Sysctls)
924+
}
925+
})
926+
}
927+
}

cli/compose/convert/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ func Service(
151151
Privileges: &privileges,
152152
Isolation: container.Isolation(service.Isolation),
153153
Init: service.Init,
154+
Sysctls: service.Sysctls,
154155
},
155156
LogDriver: logDriver,
156157
Resources: resources,

cli/compose/loader/full-example.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ services:
240240

241241
stop_signal: SIGUSR1
242242

243+
sysctls:
244+
net.core.somaxconn: 1024
245+
net.ipv4.tcp_syncookies: 0
246+
243247
# String or list
244248
# tmpfs: /run
245249
tmpfs:

cli/compose/loader/full-struct_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,12 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
346346
StdinOpen: true,
347347
StopSignal: "SIGUSR1",
348348
StopGracePeriod: durationPtr(20 * time.Second),
349-
Tmpfs: []string{"/run", "/tmp"},
350-
Tty: true,
349+
Sysctls: map[string]string{
350+
"net.core.somaxconn": "1024",
351+
"net.ipv4.tcp_syncookies": "0",
352+
},
353+
Tmpfs: []string{"/run", "/tmp"},
354+
Tty: true,
351355
Ulimits: map[string]*types.UlimitsConfig{
352356
"nproc": {
353357
Single: 65535,
@@ -756,6 +760,9 @@ services:
756760
stdin_open: true
757761
stop_grace_period: 20s
758762
stop_signal: SIGUSR1
763+
sysctls:
764+
net.core.somaxconn: "1024"
765+
net.ipv4.tcp_syncookies: "0"
759766
tmpfs:
760767
- /run
761768
- /tmp
@@ -1325,6 +1332,10 @@ func fullExampleJSON(workingDir string) string {
13251332
"stdin_open": true,
13261333
"stop_grace_period": "20s",
13271334
"stop_signal": "SIGUSR1",
1335+
"sysctls": {
1336+
"net.core.somaxconn": "1024",
1337+
"net.ipv4.tcp_syncookies": "0"
1338+
},
13281339
"tmpfs": [
13291340
"/run",
13301341
"/tmp"

cli/compose/loader/loader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
304304
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap,
305305
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
306306
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
307+
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
307308
reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true),
308309
reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false),
309310
reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false),

0 commit comments

Comments
 (0)