From b3e142401c2b93cc401a1da7916378c1d8fe8564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:03:34 -0400 Subject: [PATCH 1/6] api: instance_memory_swap_bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- doc/api-extensions.md | 3 +++ internal/version/api.go | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index f16c3877f28..7bd290abff4 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2430,3 +2430,6 @@ Those integrations attach to network peers through some new fields: * `type` (`local` for current behavior, `remote` for integrations) * `target_integration` (reference to the integration) + +## `instance_memory_swap_bytes` +This extends `limits.memory.swap` to allow for a total limit in bytes. diff --git a/internal/version/api.go b/internal/version/api.go index 76075a80c31..7ae36ca0cd7 100644 --- a/internal/version/api.go +++ b/internal/version/api.go @@ -406,6 +406,7 @@ var APIExtensions = []string{ "numa_cpu_balanced", "image_restriction_nesting", "network_integrations", + "instance_memory_swap_bytes", } // APIExtensionsCount returns the number of available API extensions. From 23988aea4f5f5ef08549df83cc4f553e69146673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:03:49 -0400 Subject: [PATCH 2/6] internal/instance: Update validator for limits.memory.swap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- internal/instance/config.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/instance/config.go b/internal/instance/config.go index ccef496de35..892dc4394c5 100644 --- a/internal/instance/config.go +++ b/internal/instance/config.go @@ -506,14 +506,16 @@ var InstanceConfigKeysContainer = map[string]func(value string) error{ "limits.memory.enforce": validate.Optional(validate.IsOneOf("soft", "hard")), // gendoc:generate(entity=instance, group=resource-limits, key=limits.memory.swap) - // + // When set to `true` or `false`, it controls whether the container is likely to get some of + // its memory swapped by the kernel. Alternatively, it can be set to a bytes value which will + // then allow the container to make use of additional memory through swap. // --- - // type: bool + // type: string // defaultdesc: `true` // liveupdate: yes // condition: container - // shortdesc: Whether to encourage/discourage swapping less used pages for this instance - "limits.memory.swap": validate.Optional(validate.IsBool), + // shortdesc: Control swap usage by the instance + "limits.memory.swap": validate.Optional(validate.Or(validate.IsBool, validate.IsSize)), // gendoc:generate(entity=instance, group=resource-limits, key=limits.memory.swap.priority) // Specify an integer between 0 and 10. From 3dcc514d92674de228891473a0e3b9ead2a82690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:04:34 -0400 Subject: [PATCH 3/6] doc: Update configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- doc/config_options.txt | 8 +++++--- internal/server/metadata/configuration.json | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/config_options.txt b/doc/config_options.txt index b3d80ed12ae..a18c56b192b 100644 --- a/doc/config_options.txt +++ b/doc/config_options.txt @@ -439,9 +439,11 @@ If this option is set to `false`, regular system memory is used. :condition: "container" :defaultdesc: "`true`" :liveupdate: "yes" -:shortdesc: "Whether to encourage/discourage swapping less used pages for this instance" -:type: "bool" - +:shortdesc: "Control swap usage by the instance" +:type: "string" +When set to `true` or `false`, it controls whether the container is likely to get some of +its memory swapped by the kernel. Alternatively, it can be set to a bytes value which will +then allow the container to make use of additional memory through swap. ``` ```{config:option} limits.memory.swap.priority instance-resource-limits diff --git a/internal/server/metadata/configuration.json b/internal/server/metadata/configuration.json index b2a94b9447d..9ebe0ec2c18 100644 --- a/internal/server/metadata/configuration.json +++ b/internal/server/metadata/configuration.json @@ -453,9 +453,9 @@ "condition": "container", "defaultdesc": "`true`", "liveupdate": "yes", - "longdesc": "", - "shortdesc": "Whether to encourage/discourage swapping less used pages for this instance", - "type": "bool" + "longdesc": "When set to `true` or `false`, it controls whether the container is likely to get some of\nits memory swapped by the kernel. Alternatively, it can be set to a bytes value which will\nthen allow the container to make use of additional memory through swap.", + "shortdesc": "Control swap usage by the instance", + "type": "string" } }, { From bb2d60c5dba669b488f34c1944ef6a3a4b293c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:28:49 -0400 Subject: [PATCH 4/6] incusd/instance/lxc: Fix default swap allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allowing swap usage should not result in additional memory being made available to the instance. So limits.memory.swap should only control whether the instance is likely to get swapped through the swappiness control. Signed-off-by: Stéphane Graber --- .../server/instance/drivers/driver_lxc.go | 53 +++++-------------- 1 file changed, 13 insertions(+), 40 deletions(-) diff --git a/internal/server/instance/drivers/driver_lxc.go b/internal/server/instance/drivers/driver_lxc.go index 51d13c0b9db..6a26caa15a8 100644 --- a/internal/server/instance/drivers/driver_lxc.go +++ b/internal/server/instance/drivers/driver_lxc.go @@ -1155,25 +1155,13 @@ func (d *lxc) initLXC(config bool) (*liblxc.Container, error) { return nil, err } } else { - if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { - err = cg.SetMemoryLimit(valueInt) - if err != nil { - return nil, err - } + err = cg.SetMemoryLimit(valueInt) + if err != nil { + return nil, err + } - if util.IsFalse(memorySwap) { - err = cg.SetMemorySwapLimit(0) - if err != nil { - return nil, err - } - } else { - err = cg.SetMemorySwapLimit(valueInt) - if err != nil { - return nil, err - } - } - } else { - err = cg.SetMemoryLimit(valueInt) + if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { + err = cg.SetMemorySwapLimit(0) if err != nil { return nil, err } @@ -4624,28 +4612,14 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error { return err } } else { - if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { - err = cg.SetMemoryLimit(memoryInt) - if err != nil { - revertMemory() - return err - } + err = cg.SetMemoryLimit(memoryInt) + if err != nil { + revertMemory() + return err + } - if util.IsFalse(memorySwap) { - err = cg.SetMemorySwapLimit(0) - if err != nil { - revertMemory() - return err - } - } else { - err = cg.SetMemorySwapLimit(memoryInt) - if err != nil { - revertMemory() - return err - } - } - } else { - err = cg.SetMemoryLimit(memoryInt) + if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { + err = cg.SetMemorySwapLimit(0) if err != nil { revertMemory() return err @@ -4666,7 +4640,6 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error { // Configure the swappiness if key == "limits.memory.swap" || key == "limits.memory.swap.priority" { - memorySwap := d.expandedConfig["limits.memory.swap"] memorySwapPriority := d.expandedConfig["limits.memory.swap.priority"] if util.IsFalse(memorySwap) { err = cg.SetMemorySwappiness(0) From 7f72ac7a5afe1fd1e366dc360ba724fbbf7a627c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:36:22 -0400 Subject: [PATCH 5/6] incusd/project: Restrict access to limits.memory.swap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As limits.memory.swap can grant additional unaccounted memory, limit it to those who can use low-level LXC options. Signed-off-by: Stéphane Graber --- internal/server/project/permissions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/server/project/permissions.go b/internal/server/project/permissions.go index eb643e97ecf..4cc6cb2cc88 100644 --- a/internal/server/project/permissions.go +++ b/internal/server/project/permissions.go @@ -836,6 +836,7 @@ func isContainerLowLevelOptionForbidden(key string) bool { "boot.host_shutdown_action", "boot.host_shutdown_timeout", "linux.kernel_modules", + "limits.memory.swap", "raw.apparmor", "raw.idmap", "raw.lxc", From fc18bb7fbcdefc82ca18c729baa793bb5eb8368d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 28 Mar 2024 14:36:58 -0400 Subject: [PATCH 6/6] incusd/instance/lxc: Add support for extra swap in bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #541 Signed-off-by: Stéphane Graber --- .../server/instance/drivers/driver_lxc.go | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/internal/server/instance/drivers/driver_lxc.go b/internal/server/instance/drivers/driver_lxc.go index 6a26caa15a8..11b0532e3e2 100644 --- a/internal/server/instance/drivers/driver_lxc.go +++ b/internal/server/instance/drivers/driver_lxc.go @@ -1161,9 +1161,22 @@ func (d *lxc) initLXC(config bool) (*liblxc.Container, error) { } if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { - err = cg.SetMemorySwapLimit(0) - if err != nil { - return nil, err + if util.IsTrueOrEmpty(memorySwap) || util.IsFalse(memorySwap) { + err = cg.SetMemorySwapLimit(0) + if err != nil { + return nil, err + } + } else { + // Additional memory as swap. + swapInt, err := units.ParseByteSizeString(memorySwap) + if err != nil { + return nil, err + } + + err = cg.SetMemorySwapLimit(swapInt) + if err != nil { + return nil, err + } } } @@ -4619,10 +4632,25 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error { } if d.state.OS.CGInfo.Supports(cgroup.MemorySwap, cg) { - err = cg.SetMemorySwapLimit(0) - if err != nil { - revertMemory() - return err + if util.IsTrueOrEmpty(memorySwap) || util.IsFalse(memorySwap) { + err = cg.SetMemorySwapLimit(0) + if err != nil { + revertMemory() + return err + } + } else { + // Additional memory as swap. + swapInt, err := units.ParseByteSizeString(memorySwap) + if err != nil { + revertMemory() + return err + } + + err = cg.SetMemorySwapLimit(swapInt) + if err != nil { + revertMemory() + return err + } } }