Add memory swap to swarm
Adds support for setting memory swap settings on Swarm services * Adds flags `memory-swap` and `memory-swappiness` to `docker service create` and `docker service update` commands. * Adds compose fields `memswap_limit` and `mem_swappiness` for `docker stack` commands. Signed-off-by: Drew Erny <derny@mirantis.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
committed by
Sebastiaan van Stijn
parent
d0c86d39ef
commit
71828f2792
@ -235,15 +235,17 @@ type resourceOptions struct {
|
||||
resCPU opts.NanoCPUs
|
||||
resMemBytes opts.MemBytes
|
||||
resGenericResources []string
|
||||
swapBytes opts.MemBytes
|
||||
memSwappiness int64
|
||||
}
|
||||
|
||||
func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) {
|
||||
func (r *resourceOptions) ToResourceRequirements(flags *pflag.FlagSet) (*swarm.ResourceRequirements, error) {
|
||||
generic, err := ParseGenericResources(r.resGenericResources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &swarm.ResourceRequirements{
|
||||
resreq := &swarm.ResourceRequirements{
|
||||
Limits: &swarm.Limit{
|
||||
NanoCPUs: r.limitCPU.Value(),
|
||||
MemoryBytes: r.limitMemBytes.Value(),
|
||||
@ -254,7 +256,20 @@ func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements,
|
||||
MemoryBytes: r.resMemBytes.Value(),
|
||||
GenericResources: generic,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SwapBytes and MemorySwappiness are *int64 (pointers), so we need to have
|
||||
// a variable we can take a pointer to. Additionally, we need to ensure
|
||||
// that these values are only set if they are set as options.
|
||||
if flags.Changed(flagSwapBytes) {
|
||||
swapBytes := r.swapBytes.Value()
|
||||
resreq.SwapBytes = &swapBytes
|
||||
}
|
||||
if flags.Changed(flagMemSwappiness) {
|
||||
resreq.MemorySwappiness = &r.memSwappiness
|
||||
}
|
||||
|
||||
return resreq, nil
|
||||
}
|
||||
|
||||
type restartPolicyOptions struct {
|
||||
@ -734,7 +749,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
return networks[i].Target < networks[j].Target
|
||||
})
|
||||
|
||||
resources, err := options.resources.ToResourceRequirements()
|
||||
resources, err := options.resources.ToResourceRequirements(flags)
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
@ -889,6 +904,10 @@ func addServiceFlags(flags *pflag.FlagSet, options *serviceOptions, defaultFlagV
|
||||
flags.Var(&options.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
|
||||
flags.Int64Var(&options.resources.limitPids, flagLimitPids, 0, "Limit maximum number of processes (default 0 = unlimited)")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.41"})
|
||||
flags.Var(&options.resources.swapBytes, flagSwapBytes, "Swap Bytes (-1 for unlimited)")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
|
||||
flags.Int64Var(&options.resources.memSwappiness, flagMemSwappiness, -1, "Tune memory swappiness (0-100), -1 to reset to default")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
|
||||
|
||||
flags.Var(&options.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)"))
|
||||
flags.Var(&options.replicas, flagReplicas, "Number of tasks")
|
||||
@ -1073,6 +1092,8 @@ const (
|
||||
flagUlimitAdd = "ulimit-add"
|
||||
flagUlimitRemove = "ulimit-rm"
|
||||
flagOomScoreAdj = "oom-score-adj"
|
||||
flagSwapBytes = "memory-swap"
|
||||
flagMemSwappiness = "memory-swappiness"
|
||||
)
|
||||
|
||||
func toNetipAddrSlice(ips []string) []netip.Addr {
|
||||
|
||||
@ -163,8 +163,10 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
flags := newCreateCommand(nil).Flags()
|
||||
|
||||
for _, opt := range incorrectOptions {
|
||||
_, err := opt.ToResourceRequirements()
|
||||
_, err := opt.ToResourceRequirements(flags)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
}
|
||||
|
||||
@ -178,12 +180,41 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, opt := range correctOptions {
|
||||
r, err := opt.ToResourceRequirements()
|
||||
r, err := opt.ToResourceRequirements(flags)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceOptionsToResourceRequirementsSwap(t *testing.T) {
|
||||
// first, check that no flag set means no field set in the return
|
||||
flags := newCreateCommand(nil).Flags()
|
||||
|
||||
// These should be the default values of the field.
|
||||
swapOptions := resourceOptions{
|
||||
swapBytes: 0,
|
||||
memSwappiness: -1,
|
||||
}
|
||||
|
||||
r, err := swapOptions.ToResourceRequirements(flags)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Nil(r.SwapBytes))
|
||||
assert.Check(t, is.Nil(r.MemorySwappiness))
|
||||
|
||||
// now set the flags and some values
|
||||
flags.Set(flagSwapBytes, "86000")
|
||||
flags.Set(flagMemSwappiness, "23")
|
||||
swapOptions.swapBytes = 86000
|
||||
swapOptions.memSwappiness = 23
|
||||
|
||||
r, err = swapOptions.ToResourceRequirements(flags)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, r.SwapBytes != nil)
|
||||
assert.Check(t, is.Equal(*(r.SwapBytes), int64(86000)))
|
||||
assert.Check(t, r.MemorySwappiness != nil)
|
||||
assert.Check(t, is.Equal(*(r.MemorySwappiness), int64(23)))
|
||||
}
|
||||
|
||||
func TestToServiceNetwork(t *testing.T) {
|
||||
nws := []network.Inspect{
|
||||
{
|
||||
|
||||
@ -560,6 +560,10 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
|
||||
GenericResources: generic,
|
||||
}
|
||||
}
|
||||
// These fields are themselves pointers -- we can simply assign, no need to
|
||||
// nil-check them. Nil is nil.
|
||||
resources.SwapBytes = source.MemswapLimit
|
||||
resources.MemorySwappiness = source.MemSwappiness
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
|
||||
@ -81,6 +81,9 @@ func TestConvertExtraHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConvertResourcesFull(t *testing.T) {
|
||||
// create some variables so we can get pointers
|
||||
memswap := int64(72090)
|
||||
swappiness := int64(27)
|
||||
source := composetypes.Resources{
|
||||
Limits: &composetypes.ResourceLimit{
|
||||
NanoCPUs: "0.003",
|
||||
@ -90,6 +93,8 @@ func TestConvertResourcesFull(t *testing.T) {
|
||||
NanoCPUs: "0.002",
|
||||
MemoryBytes: composetypes.UnitBytes(200000000),
|
||||
},
|
||||
MemswapLimit: &memswap,
|
||||
MemSwappiness: &swappiness,
|
||||
}
|
||||
resources, err := convertResources(source)
|
||||
assert.NilError(t, err)
|
||||
@ -103,6 +108,8 @@ func TestConvertResourcesFull(t *testing.T) {
|
||||
NanoCPUs: 2000000,
|
||||
MemoryBytes: 200000000,
|
||||
},
|
||||
SwapBytes: &memswap,
|
||||
MemorySwappiness: &swappiness,
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(expected, resources))
|
||||
}
|
||||
|
||||
@ -79,6 +79,8 @@ services:
|
||||
- discrete_resource_spec:
|
||||
kind: 'ssd'
|
||||
value: 1
|
||||
memswap_limit: 86000
|
||||
mem_swappiness: 27
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
|
||||
@ -108,6 +108,8 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
|
||||
},
|
||||
},
|
||||
},
|
||||
MemswapLimit: int64Ptr(86000),
|
||||
MemSwappiness: int64Ptr(27),
|
||||
},
|
||||
RestartPolicy: &types.RestartPolicy{
|
||||
Condition: "on-failure",
|
||||
|
||||
@ -970,6 +970,10 @@ func uint32Ptr(value uint32) *uint32 {
|
||||
return &value
|
||||
}
|
||||
|
||||
func int64Ptr(value int64) *int64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
func TestFullExample(t *testing.T) {
|
||||
skip.If(t, runtime.GOOS == "windows", "FIXME: substitutes platform-specific HOME-dirs and requires platform-specific golden files; see https://github.com/docker/cli/pull/4610")
|
||||
|
||||
|
||||
@ -181,7 +181,9 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"memswap_limit": 86000,
|
||||
"mem_swappiness": 27
|
||||
},
|
||||
"restart_policy": {
|
||||
"condition": "on-failure",
|
||||
|
||||
@ -73,6 +73,8 @@ services:
|
||||
- discrete_resource_spec:
|
||||
kind: ssd
|
||||
value: 1
|
||||
memswap_limit: 86000
|
||||
mem_swappiness: 27
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "config_schema_v3.13.json",
|
||||
"id": "config_schema_v3.14.json",
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"default": "3.13"
|
||||
"default": "3.14"
|
||||
},
|
||||
|
||||
"services": {
|
||||
@ -413,6 +413,12 @@
|
||||
"generic_resources": {"$ref": "#/definitions/generic_resources"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"memswap_limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mem_swappiness": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@ -307,8 +307,10 @@ type UpdateConfig struct {
|
||||
|
||||
// Resources the resource limits and reservations
|
||||
type Resources struct {
|
||||
Limits *ResourceLimit `yaml:",omitempty" json:"limits,omitempty"`
|
||||
Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"`
|
||||
Limits *ResourceLimit `yaml:",omitempty" json:"limits,omitempty"`
|
||||
Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"`
|
||||
MemswapLimit *int64 `mapstructure:"memswap_limit" yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"`
|
||||
MemSwappiness *int64 `mapstructure:"mem_swappiness" yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceLimit is a resource to be limited
|
||||
|
||||
@ -40,6 +40,8 @@ Create a new service
|
||||
| `--log-driver` | `string` | | Logging driver for service |
|
||||
| `--log-opt` | `list` | | Logging driver options |
|
||||
| `--max-concurrent` | `uint` | | Number of job tasks to run concurrently (default equal to --replicas) |
|
||||
| `--memory-swap` | `bytes` | `0` | Swap Bytes (-1 for unlimited) |
|
||||
| `--memory-swappiness` | `int64` | `-1` | Tune memory swappiness (0-100), -1 to reset to default |
|
||||
| `--mode` | `string` | `replicated` | Service mode (`replicated`, `global`, `replicated-job`, `global-job`) |
|
||||
| [`--mount`](#mount) | `mount` | | Attach a filesystem mount to the service |
|
||||
| `--name` | `string` | | Service name |
|
||||
|
||||
@ -53,6 +53,8 @@ Update a service
|
||||
| `--log-driver` | `string` | | Logging driver for service |
|
||||
| `--log-opt` | `list` | | Logging driver options |
|
||||
| `--max-concurrent` | `uint` | | Number of job tasks to run concurrently (default equal to --replicas) |
|
||||
| `--memory-swap` | `bytes` | `0` | Swap Bytes (-1 for unlimited) |
|
||||
| `--memory-swappiness` | `int64` | `-1` | Tune memory swappiness (0-100), -1 to reset to default |
|
||||
| [`--mount-add`](#mount-add) | `mount` | | Add or update a mount on a service |
|
||||
| `--mount-rm` | `list` | | Remove a mount by its target path |
|
||||
| [`--network-add`](#network-add) | `network` | | Add a network |
|
||||
|
||||
Reference in New Issue
Block a user