mirror of https://github.com/docker/cli.git
Add systctl support for services
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
a4a50de4b8
commit
f620349837
|
@ -60,6 +60,8 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||
flags.SetAnnotation(flagHost, "version", []string{"1.25"})
|
||||
flags.BoolVar(&opts.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
|
||||
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
|
||||
flags.Var(&opts.sysctls, flagSysCtl, "Sysctl options")
|
||||
flags.SetAnnotation(flagSysCtl, "version", []string{"1.40"})
|
||||
|
||||
flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
|
|
|
@ -96,6 +96,11 @@ ContainerSpec:
|
|||
{{- if .ContainerUser }}
|
||||
User: {{ .ContainerUser }}
|
||||
{{- end }}
|
||||
{{- if .ContainerSysCtls }}
|
||||
SysCtls:
|
||||
{{- range $k, $v := .ContainerSysCtls }}
|
||||
{{ $k }}{{if $v }}: {{ $v }}{{ end }}
|
||||
{{- end }}{{ end }}
|
||||
{{- if .ContainerMounts }}
|
||||
Mounts:
|
||||
{{- end }}
|
||||
|
@ -415,6 +420,14 @@ func (ctx *serviceInspectContext) ContainerMounts() []mounttypes.Mount {
|
|||
return ctx.Service.Spec.TaskTemplate.ContainerSpec.Mounts
|
||||
}
|
||||
|
||||
func (ctx *serviceInspectContext) ContainerSysCtls() map[string]string {
|
||||
return ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls
|
||||
}
|
||||
|
||||
func (ctx *serviceInspectContext) HasContainerSysCtls() bool {
|
||||
return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls) > 0
|
||||
}
|
||||
|
||||
func (ctx *serviceInspectContext) HasResources() bool {
|
||||
return ctx.Service.Spec.TaskTemplate.Resources != nil
|
||||
}
|
||||
|
|
|
@ -490,6 +490,7 @@ type serviceOptions struct {
|
|||
dnsSearch opts.ListOpts
|
||||
dnsOption opts.ListOpts
|
||||
hosts opts.ListOpts
|
||||
sysctls opts.ListOpts
|
||||
|
||||
resources resourceOptions
|
||||
stopGrace opts.DurationOpt
|
||||
|
@ -531,6 +532,7 @@ func newServiceOptions() *serviceOptions {
|
|||
dnsOption: opts.NewListOpts(nil),
|
||||
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
|
||||
hosts: opts.NewListOpts(opts.ValidateExtraHost),
|
||||
sysctls: opts.NewListOpts(nil),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -643,6 +645,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
|||
StopGracePeriod: options.ToStopGracePeriod(flags),
|
||||
Healthcheck: healthConfig,
|
||||
Isolation: container.Isolation(options.isolation),
|
||||
Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()),
|
||||
},
|
||||
Networks: networks,
|
||||
Resources: resources,
|
||||
|
@ -890,6 +893,9 @@ const (
|
|||
flagRollbackOrder = "rollback-order"
|
||||
flagRollbackParallelism = "rollback-parallelism"
|
||||
flagInit = "init"
|
||||
flagSysCtl = "sysctl"
|
||||
flagSysCtlAdd = "sysctl-add"
|
||||
flagSysCtlRemove = "sysctl-rm"
|
||||
flagStopGracePeriod = "stop-grace-period"
|
||||
flagStopSignal = "stop-signal"
|
||||
flagTTY = "tty"
|
||||
|
|
|
@ -233,3 +233,16 @@ func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
|
|||
_, err := opt.ToServiceMode()
|
||||
assert.Error(t, err, "replicas-max-per-node can only be used with replicated mode")
|
||||
}
|
||||
|
||||
func TestToServiceSysCtls(t *testing.T) {
|
||||
o := newServiceOptions()
|
||||
o.mode = "replicated"
|
||||
o.sysctls.Set("net.ipv4.ip_forward=1")
|
||||
o.sysctls.Set("kernel.shmmax=123456")
|
||||
|
||||
expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
|
||||
flags := newCreateCommand(nil).Flags()
|
||||
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
|
||||
}
|
||||
|
|
|
@ -96,6 +96,10 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
|||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
|
||||
flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
|
||||
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
|
||||
flags.Var(&options.sysctls, flagSysCtlAdd, "Add or update a Sysctl option")
|
||||
flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
|
||||
flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
|
||||
flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
|
||||
|
||||
// Add needs parsing, Remove only needs the key
|
||||
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
|
||||
|
@ -328,6 +332,8 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
|||
return err
|
||||
}
|
||||
|
||||
updateSysCtls(flags, &task.ContainerSpec.Sysctls)
|
||||
|
||||
if anyChanged(flags, flagLimitCPU, flagLimitMemory) {
|
||||
taskResources().Limits = spec.TaskTemplate.Resources.Limits
|
||||
updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
|
||||
|
@ -661,6 +667,25 @@ func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
|
|||
}
|
||||
}
|
||||
|
||||
func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
|
||||
if *field != nil && flags.Changed(flagSysCtlRemove) {
|
||||
values := flags.Lookup(flagSysCtlRemove).Value.(*opts.ListOpts).GetAll()
|
||||
for key := range opts.ConvertKVStringsToMap(values) {
|
||||
delete(*field, key)
|
||||
}
|
||||
}
|
||||
if flags.Changed(flagSysCtlAdd) {
|
||||
if *field == nil {
|
||||
*field = map[string]string{}
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagSysCtlAdd).Value.(*opts.ListOpts).GetAll()
|
||||
for key, value := range opts.ConvertKVStringsToMap(values) {
|
||||
(*field)[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
|
||||
if flags.Changed(flagEnvAdd) {
|
||||
envSet := map[string]string{}
|
||||
|
|
|
@ -828,3 +828,100 @@ func TestUpdateMaxReplicas(t *testing.T) {
|
|||
|
||||
assert.DeepEqual(t, svc.TaskTemplate.Placement, &swarm.Placement{MaxReplicas: uint64(2)})
|
||||
}
|
||||
|
||||
func TestUpdateSysCtls(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
spec map[string]string
|
||||
add []string
|
||||
rm []string
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
name: "from scratch",
|
||||
add: []string{"sysctl.zet=value-99", "sysctl.alpha=value-1"},
|
||||
expected: map[string]string{"sysctl.zet": "value-99", "sysctl.alpha": "value-1"},
|
||||
},
|
||||
{
|
||||
name: "append new",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"new.sysctl=newvalue"},
|
||||
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
|
||||
},
|
||||
{
|
||||
name: "append duplicate is a no-op",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"sysctl.one=value-1"},
|
||||
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
},
|
||||
{
|
||||
name: "remove and append existing is a no-op",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"sysctl.one=value-1"},
|
||||
rm: []string{"sysctl.one=value-1"},
|
||||
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
},
|
||||
{
|
||||
name: "remove and append new should append",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"new.sysctl=newvalue"},
|
||||
rm: []string{"new.sysctl=newvalue"},
|
||||
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
|
||||
},
|
||||
{
|
||||
name: "update existing",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"sysctl.one=newvalue"},
|
||||
expected: map[string]string{"sysctl.one": "newvalue", "sysctl.two": "value-2"},
|
||||
},
|
||||
{
|
||||
name: "update existing twice",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
add: []string{"sysctl.one=newvalue", "sysctl.one=evennewervalue"},
|
||||
expected: map[string]string{"sysctl.one": "evennewervalue", "sysctl.two": "value-2"},
|
||||
},
|
||||
{
|
||||
name: "remove all",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
rm: []string{"sysctl.one=value-1", "sysctl.two=value-2"},
|
||||
expected: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "remove by key",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
rm: []string{"sysctl.one"},
|
||||
expected: map[string]string{"sysctl.two": "value-2"},
|
||||
},
|
||||
{
|
||||
name: "remove by key and different value",
|
||||
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
||||
rm: []string{"sysctl.one=anyvalueyoulike"},
|
||||
expected: map[string]string{"sysctl.two": "value-2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svc := swarm.ServiceSpec{
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{Sysctls: tc.spec},
|
||||
},
|
||||
}
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
for _, v := range tc.add {
|
||||
assert.NilError(t, flags.Set(flagSysCtlAdd, v))
|
||||
}
|
||||
for _, v := range tc.rm {
|
||||
assert.NilError(t, flags.Set(flagSysCtlRemove, v))
|
||||
}
|
||||
err := updateService(ctx, &fakeClient{}, flags, &svc)
|
||||
assert.NilError(t, err)
|
||||
if !assert.Check(t, is.DeepEqual(svc.TaskTemplate.ContainerSpec.Sysctls, tc.expected)) {
|
||||
t.Logf("expected: %v", tc.expected)
|
||||
t.Logf("actual: %v", svc.TaskTemplate.ContainerSpec.Sysctls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,7 @@ func Service(
|
|||
Privileges: &privileges,
|
||||
Isolation: container.Isolation(service.Isolation),
|
||||
Init: service.Init,
|
||||
Sysctls: service.Sysctls,
|
||||
},
|
||||
LogDriver: logDriver,
|
||||
Resources: resources,
|
||||
|
|
|
@ -240,6 +240,10 @@ services:
|
|||
|
||||
stop_signal: SIGUSR1
|
||||
|
||||
sysctls:
|
||||
net.core.somaxconn: 1024
|
||||
net.ipv4.tcp_syncookies: 0
|
||||
|
||||
# String or list
|
||||
# tmpfs: /run
|
||||
tmpfs:
|
||||
|
|
|
@ -346,6 +346,10 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
|
|||
StdinOpen: true,
|
||||
StopSignal: "SIGUSR1",
|
||||
StopGracePeriod: durationPtr(20 * time.Second),
|
||||
Sysctls: map[string]string{
|
||||
"net.core.somaxconn": "1024",
|
||||
"net.ipv4.tcp_syncookies": "0",
|
||||
},
|
||||
Tmpfs: []string{"/run", "/tmp"},
|
||||
Tty: true,
|
||||
Ulimits: map[string]*types.UlimitsConfig{
|
||||
|
@ -756,6 +760,9 @@ services:
|
|||
stdin_open: true
|
||||
stop_grace_period: 20s
|
||||
stop_signal: SIGUSR1
|
||||
sysctls:
|
||||
net.core.somaxconn: "1024"
|
||||
net.ipv4.tcp_syncookies: "0"
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
|
@ -1325,6 +1332,10 @@ func fullExampleJSON(workingDir string) string {
|
|||
"stdin_open": true,
|
||||
"stop_grace_period": "20s",
|
||||
"stop_signal": "SIGUSR1",
|
||||
"sysctls": {
|
||||
"net.core.somaxconn": "1024",
|
||||
"net.ipv4.tcp_syncookies": "0"
|
||||
},
|
||||
"tmpfs": [
|
||||
"/run",
|
||||
"/tmp"
|
||||
|
|
|
@ -304,6 +304,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
|
|||
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap,
|
||||
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
|
||||
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
|
||||
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true),
|
||||
reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false),
|
||||
|
|
|
@ -1461,6 +1461,47 @@ services:
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadSysctls(t *testing.T) {
|
||||
config, err := loadYAML(`
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: busybox
|
||||
sysctls:
|
||||
- net.core.somaxconn=1024
|
||||
- net.ipv4.tcp_syncookies=0
|
||||
- testing.one.one=
|
||||
- testing.one.two
|
||||
`)
|
||||
assert.NilError(t, err)
|
||||
|
||||
expected := types.Mapping{
|
||||
"net.core.somaxconn": "1024",
|
||||
"net.ipv4.tcp_syncookies": "0",
|
||||
"testing.one.one": "",
|
||||
"testing.one.two": "",
|
||||
}
|
||||
|
||||
assert.Assert(t, is.Len(config.Services, 1))
|
||||
assert.Check(t, is.DeepEqual(expected, config.Services[0].Sysctls))
|
||||
|
||||
config, err = loadYAML(`
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: busybox
|
||||
sysctls:
|
||||
net.core.somaxconn: 1024
|
||||
net.ipv4.tcp_syncookies: 0
|
||||
testing.one.one: ""
|
||||
testing.one.two:
|
||||
`)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Assert(t, is.Len(config.Services, 1))
|
||||
assert.Check(t, is.DeepEqual(expected, config.Services[0].Sysctls))
|
||||
}
|
||||
|
||||
func TestTransform(t *testing.T) {
|
||||
var source = []interface{}{
|
||||
"80-82:8080-8082",
|
||||
|
|
|
@ -24,7 +24,6 @@ var UnsupportedProperties = []string{
|
|||
"restart",
|
||||
"security_opt",
|
||||
"shm_size",
|
||||
"sysctls",
|
||||
"ulimits",
|
||||
"userns_mode",
|
||||
}
|
||||
|
@ -200,7 +199,7 @@ type ServiceConfig struct {
|
|||
StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
|
||||
StopGracePeriod *Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
|
||||
StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
|
||||
Sysctls StringList `yaml:",omitempty" json:"sysctls,omitempty"`
|
||||
Sysctls Mapping `yaml:",omitempty" json:"sysctls,omitempty"`
|
||||
Tmpfs StringList `yaml:",omitempty" json:"tmpfs,omitempty"`
|
||||
Tty bool `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"`
|
||||
Ulimits map[string]*UlimitsConfig `yaml:",omitempty" json:"ulimits,omitempty"`
|
||||
|
@ -240,6 +239,12 @@ type StringOrNumberList []string
|
|||
// For the key without value (`key`), the mapped value is set to nil.
|
||||
type MappingWithEquals map[string]*string
|
||||
|
||||
// Mapping is a mapping type that can be converted from a list of
|
||||
// key[=value] strings.
|
||||
// For the key with an empty value (`key=`), or key without value (`key`), the
|
||||
// mapped value is set to an empty string `""`.
|
||||
type Mapping map[string]string
|
||||
|
||||
// Labels is a mapping type for labels
|
||||
type Labels map[string]string
|
||||
|
||||
|
|
|
@ -3696,6 +3696,7 @@ _docker_service_update_and_create() {
|
|||
--placement-pref
|
||||
--publish -p
|
||||
--secret
|
||||
--sysctl
|
||||
"
|
||||
|
||||
case "$prev" in
|
||||
|
@ -3746,6 +3747,8 @@ _docker_service_update_and_create() {
|
|||
--rollback
|
||||
--secret-add
|
||||
--secret-rm
|
||||
--sysctl-add
|
||||
--sysctl-rm
|
||||
"
|
||||
|
||||
boolean_options="$boolean_options
|
||||
|
|
|
@ -77,6 +77,7 @@ Options:
|
|||
--secret secret Specify secrets to expose to the service
|
||||
--stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h) (default 10s)
|
||||
--stop-signal string Signal to stop the container
|
||||
--sysctl list Sysctl options
|
||||
-t, --tty Allocate a pseudo-TTY
|
||||
--update-delay duration Delay between updates (ns|us|ms|s|m|h) (default 0s)
|
||||
--update-failure-action string Action on update failure ("pause"|"continue"|"rollback") (default "pause")
|
||||
|
|
|
@ -93,6 +93,8 @@ Options:
|
|||
--secret-rm list Remove a secret
|
||||
--stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h)
|
||||
--stop-signal string Signal to stop the container
|
||||
--sysctl-add list Add or update a Sysctl option
|
||||
--sysctl-rm list Remove a Sysctl option
|
||||
-t, --tty Allocate a pseudo-TTY
|
||||
--update-delay duration Delay between updates (ns|us|ms|s|m|h)
|
||||
--update-failure-action string Action on update failure ("pause"|"continue"|"rollback")
|
||||
|
|
Loading…
Reference in New Issue