feat: implement zswap support

Zswap allows to compress pages in memory before they hit the actual swap
device.

Both swap and zswap (or either one of these) can be enabled.

Fixes #10675

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2025-06-11 13:41:47 +04:00
parent 7f0300f108
commit c880835c80
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
33 changed files with 1579 additions and 33 deletions

View File

@ -243,3 +243,17 @@ message VolumeStatusSpec {
string parent_id = 19;
}
// ZswapStatusSpec is the spec for ZswapStatus resource.
message ZswapStatusSpec {
uint64 total_size_bytes = 1;
string total_size_human = 2;
uint64 stored_pages = 3;
uint64 pool_limit_hit = 4;
uint64 reject_reclaim_fail = 5;
uint64 reject_alloc_fail = 6;
uint64 reject_kmemcache_fail = 7;
uint64 reject_compress_fail = 8;
uint64 reject_compress_poor = 9;
uint64 written_back_pages = 10;
}

View File

@ -7,3 +7,9 @@ columns:
template: '{{ .MemorySwapHigh.HumanizeIBytes | printf "%8s" }}'
- name: SwapMax
template: '{{ .MemorySwapMax.HumanizeIBytes | printf "%8s" }}'
- name: ZswapCurrent
template: '{{ .MemoryZswapCurrent.HumanizeIBytes | printf "%8s" }}'
- name: ZswapMax
template: '{{ .MemoryZswapMax.HumanizeIBytes | printf "%8s" }}'
- name: ZswapWriteback
template: '{{ .MemoryZswapWriteback }}'

View File

@ -69,6 +69,13 @@ Talos now validates Kubernetes version in the image submitted in the machine con
Previously this check was performed only on upgrade, but now it is consistently applied to upgrade, initial provisioning, and machine configuration updates.
This implies that all image references should contain the tag, even if the image is pinned by digest.
"""
[notes.zswap]
title = "Zswap Support"
description = """\
Talos now supports zswap, a compressed cache for swap pages.
This feature can be enabled by using [ZswapConfig](https://www.talos.dev/v1.11/reference/configuration/block/zswapconfig/) document in the machine configuration.
"""
[make_deps]

View File

@ -21,3 +21,8 @@ machine:
extraConfig:
memorySwap:
swapBehavior: LimitedSwap
---
apiVersion: v1alpha1
kind: ZswapConfig
maxPoolPercent: 25
shrinkerEnabled: true

View File

@ -0,0 +1,115 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
import (
"context"
"fmt"
"strconv"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/optional"
"go.uber.org/zap"
configconfig "github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)
// ZswapConfigController provides zswap configuration based machine configuration.
type ZswapConfigController struct{}
// Name implements controller.Controller interface.
func (ctrl *ZswapConfigController) Name() string {
return "block.ZswapConfigController"
}
// Inputs implements controller.Controller interface.
func (ctrl *ZswapConfigController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: config.NamespaceName,
Type: config.MachineConfigType,
ID: optional.Some(config.ActiveID),
Kind: controller.InputWeak,
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *ZswapConfigController) Outputs() []controller.Output {
return []controller.Output{
{
Type: runtime.KernelParamSpecType,
Kind: controller.OutputShared,
},
}
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *ZswapConfigController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error {
for {
select {
case <-r.EventCh():
case <-ctx.Done():
return nil
}
// load config if present
cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.ActiveID)
if err != nil && !state.IsNotFoundError(err) {
return fmt.Errorf("error fetching machine configuration")
}
r.StartTrackingOutputs()
var zswapCfg configconfig.ZswapConfig
if cfg != nil {
zswapCfg = cfg.Config().ZswapConfig()
}
if zswapCfg != nil { // enabled
if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.enabled"),
func(p *runtime.KernelParamSpec) error {
p.TypedSpec().Value = "Y"
return nil
}); err != nil {
return fmt.Errorf("error setting zswap config: %w", err)
}
if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.max_pool_percent"),
func(p *runtime.KernelParamSpec) error {
p.TypedSpec().Value = strconv.Itoa(zswapCfg.MaxPoolPercent())
return nil
}); err != nil {
return fmt.Errorf("error setting zswap config: %w", err)
}
if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.shrinker_enabled"),
func(p *runtime.KernelParamSpec) error {
if zswapCfg.ShrinkerEnabled() {
p.TypedSpec().Value = "Y"
} else {
p.TypedSpec().Value = "N"
}
return nil
}); err != nil {
return fmt.Errorf("error setting zswap config: %w", err)
}
}
if err = safe.CleanupOutputs[*runtime.KernelParamSpec](ctx, r); err != nil {
return fmt.Errorf("error cleaning up volume configuration: %w", err)
}
}
}

View File

@ -0,0 +1,131 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/dustin/go-humanize"
"go.uber.org/zap"
machineruntime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)
// ZswapStatusController provides a view of active swap devices.
type ZswapStatusController struct {
V1Alpha1Mode machineruntime.Mode
}
// Name implements controller.Controller interface.
func (ctrl *ZswapStatusController) Name() string {
return "block.ZswapStatusController"
}
// Inputs implements controller.Controller interface.
func (ctrl *ZswapStatusController) Inputs() []controller.Input {
return []controller.Input{
{
// not really a dependency, but we refresh zswap status kernel param change
Namespace: runtime.NamespaceName,
Type: runtime.KernelParamStatusType,
Kind: controller.InputWeak,
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *ZswapStatusController) Outputs() []controller.Output {
return []controller.Output{
{
Type: block.ZswapStatusType,
Kind: controller.OutputExclusive,
},
}
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *ZswapStatusController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
// in container mode, no zswap applies
if ctrl.V1Alpha1Mode == machineruntime.ModeContainer {
return nil
}
// there is no way to watch for zswap status, so we are going to poll every minute
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
case <-ticker.C:
}
r.StartTrackingOutputs()
// try to read a single status file to see if zswap is enabled
if _, err := os.ReadFile("/sys/kernel/debug/zswap/pool_total_size"); err == nil {
if err = safe.WriterModify(ctx, r, block.NewZswapStatus(block.NamespaceName, block.ZswapStatusID),
func(zs *block.ZswapStatus) error {
for _, p := range []struct {
name string
out *uint64
}{
{"pool_total_size", &zs.TypedSpec().TotalSizeBytes},
{"stored_pages", &zs.TypedSpec().StoredPages},
{"pool_limit_hit", &zs.TypedSpec().PoolLimitHit},
{"reject_reclaim_fail", &zs.TypedSpec().RejectReclaimFail},
{"reject_alloc_fail", &zs.TypedSpec().RejectAllocFail},
{"reject_kmemcache_fail", &zs.TypedSpec().RejectKmemcacheFail},
{"reject_compress_fail", &zs.TypedSpec().RejectCompressFail},
{"reject_compress_poor", &zs.TypedSpec().RejectCompressPoor},
{"written_back_pages", &zs.TypedSpec().WrittenBackPages},
} {
if err := ctrl.readZswapParam(p.name, p.out); err != nil {
return err
}
}
zs.TypedSpec().TotalSizeHuman = humanize.IBytes(zs.TypedSpec().TotalSizeBytes)
return nil
},
); err != nil {
return fmt.Errorf("failed to create zswap status: %w", err)
}
}
if err := safe.CleanupOutputs[*block.ZswapStatus](ctx, r); err != nil {
return fmt.Errorf("failed to cleanup outputs: %w", err)
}
}
}
func (ctrl *ZswapStatusController) readZswapParam(name string, out *uint64) error {
content, err := os.ReadFile(filepath.Join("/sys/kernel/debug/zswap", name))
if err != nil {
return fmt.Errorf("failed to read zswap parameter %q: %w", name, err)
}
*out, err = strconv.ParseUint(string(bytes.TrimSpace(content)), 10, 64)
if err != nil {
return fmt.Errorf("failed to parse zswap parameter %q: %w", name, err)
}
return nil
}

View File

@ -110,6 +110,10 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
&block.VolumeManagerController{},
&block.ZswapConfigController{},
&block.ZswapStatusController{
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
&cluster.AffiliateMergeController{},
cluster.NewConfigController(),
&cluster.DiscoveryServiceController{},

View File

@ -112,6 +112,7 @@ func NewState() (*State, error) {
&block.VolumeMountRequest{},
&block.VolumeMountStatus{},
&block.VolumeStatus{},
&block.ZswapStatus{},
&cluster.Affiliate{},
&cluster.Config{},
&cluster.Identity{},

View File

@ -152,9 +152,10 @@ func CreateSystemCgroups(ctx context.Context, log *zap.Logger, rt runtime.Runtim
name: constants.CgroupApid,
resources: &cgroup2.Resources{
Memory: &cgroup2.Memory{
Min: pointer.To[int64](constants.CgroupApidReservedMemory),
Low: pointer.To[int64](constants.CgroupApidReservedMemory * 2),
Max: zeroIfRace(pointer.To[int64](constants.CgroupApidMaxMemory)),
Min: pointer.To[int64](constants.CgroupApidReservedMemory),
Low: pointer.To[int64](constants.CgroupApidReservedMemory * 2),
Max: zeroIfRace(pointer.To[int64](constants.CgroupApidMaxMemory)),
Swap: pointer.To[int64](0),
},
CPU: &cgroup2.CPU{
Weight: pointer.To[uint64](cgroup.MillicoresToCPUWeight(cgroup.MilliCores(constants.CgroupApidMillicores))),
@ -165,9 +166,10 @@ func CreateSystemCgroups(ctx context.Context, log *zap.Logger, rt runtime.Runtim
name: constants.CgroupTrustd,
resources: &cgroup2.Resources{
Memory: &cgroup2.Memory{
Min: pointer.To[int64](constants.CgroupTrustdReservedMemory),
Low: pointer.To[int64](constants.CgroupTrustdReservedMemory * 2),
Max: zeroIfRace(pointer.To[int64](constants.CgroupTrustdMaxMemory)),
Min: pointer.To[int64](constants.CgroupTrustdReservedMemory),
Low: pointer.To[int64](constants.CgroupTrustdReservedMemory * 2),
Max: zeroIfRace(pointer.To[int64](constants.CgroupTrustdMaxMemory)),
Swap: pointer.To[int64](0),
},
CPU: &cgroup2.CPU{
Weight: pointer.To[uint64](cgroup.MillicoresToCPUWeight(cgroup.MilliCores(constants.CgroupTrustdMillicores))),

View File

@ -755,6 +755,29 @@ func (suite *VolumesSuite) TestSwapOnOff() {
}))
}
// TestZswapStatus verifies that all zswap-enabled machines have zswap running.
func (suite *VolumesSuite) TestZswapStatus() {
for _, node := range suite.DiscoverNodeInternalIPs(suite.ctx) {
suite.Run(node, func() {
ctx := client.WithNode(suite.ctx, node)
cfg, err := suite.ReadConfigFromNode(ctx)
suite.Require().NoError(err)
if cfg.ZswapConfig() == nil {
suite.T().Skipf("skipping test, zswap is not enabled on node %s", node)
}
rtestutils.AssertResource(ctx, suite.T(), suite.Client.COSI,
block.ZswapStatusID,
func(vs *block.ZswapStatus, asrt *assert.Assertions) {
suite.T().Logf("zswap total size %s, stored pages %d", vs.TypedSpec().TotalSizeHuman, vs.TypedSpec().StoredPages)
},
)
})
}
}
func init() {
allSuites = append(allSuites, new(VolumesSuite))
}

View File

@ -134,6 +134,10 @@ type Node struct {
MemorySwapMax Value
MemorySwapPeak Value
MemoryZswapCurrent Value
MemoryZswapMax Value
MemoryZswapWriteback Value
PidsCurrent Value
PidsEvents FlatMap
PidsMax Value
@ -280,6 +284,12 @@ func (n *Node) Parse(filename string, r io.Reader) error {
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapMax, r)
case "memory.swap.peak":
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapPeak, r)
case "memory.zswap.current":
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapCurrent, r)
case "memory.zswap.max":
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapMax, r)
case "memory.zswap.writeback":
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapWriteback, r)
case "pids.current":
return parseSingleValue(ParseNewlineSeparatedValues, &n.PidsCurrent, r)
case "pids.events":

View File

@ -41,6 +41,7 @@ func PseudoSubMountPoints() Points {
NewPoint("securityfs", "/sys/kernel/security", "securityfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
NewPoint("tracefs", "/sys/kernel/tracing", "tracefs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV)),
NewPoint("configfs", "/sys/kernel/config", "configfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
NewPoint("debugfs", "/sys/kernel/debug", "debugfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
}
if _, err := os.Stat(constants.EFIVarsMountPoint); err == nil {

View File

@ -2025,6 +2025,123 @@ func (x *VolumeStatusSpec) GetParentId() string {
return ""
}
// ZswapStatusSpec is the spec for ZswapStatus resource.
type ZswapStatusSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
TotalSizeBytes uint64 `protobuf:"varint,1,opt,name=total_size_bytes,json=totalSizeBytes,proto3" json:"total_size_bytes,omitempty"`
TotalSizeHuman string `protobuf:"bytes,2,opt,name=total_size_human,json=totalSizeHuman,proto3" json:"total_size_human,omitempty"`
StoredPages uint64 `protobuf:"varint,3,opt,name=stored_pages,json=storedPages,proto3" json:"stored_pages,omitempty"`
PoolLimitHit uint64 `protobuf:"varint,4,opt,name=pool_limit_hit,json=poolLimitHit,proto3" json:"pool_limit_hit,omitempty"`
RejectReclaimFail uint64 `protobuf:"varint,5,opt,name=reject_reclaim_fail,json=rejectReclaimFail,proto3" json:"reject_reclaim_fail,omitempty"`
RejectAllocFail uint64 `protobuf:"varint,6,opt,name=reject_alloc_fail,json=rejectAllocFail,proto3" json:"reject_alloc_fail,omitempty"`
RejectKmemcacheFail uint64 `protobuf:"varint,7,opt,name=reject_kmemcache_fail,json=rejectKmemcacheFail,proto3" json:"reject_kmemcache_fail,omitempty"`
RejectCompressFail uint64 `protobuf:"varint,8,opt,name=reject_compress_fail,json=rejectCompressFail,proto3" json:"reject_compress_fail,omitempty"`
RejectCompressPoor uint64 `protobuf:"varint,9,opt,name=reject_compress_poor,json=rejectCompressPoor,proto3" json:"reject_compress_poor,omitempty"`
WrittenBackPages uint64 `protobuf:"varint,10,opt,name=written_back_pages,json=writtenBackPages,proto3" json:"written_back_pages,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ZswapStatusSpec) Reset() {
*x = ZswapStatusSpec{}
mi := &file_resource_definitions_block_block_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ZswapStatusSpec) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ZswapStatusSpec) ProtoMessage() {}
func (x *ZswapStatusSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_block_block_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ZswapStatusSpec.ProtoReflect.Descriptor instead.
func (*ZswapStatusSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{24}
}
func (x *ZswapStatusSpec) GetTotalSizeBytes() uint64 {
if x != nil {
return x.TotalSizeBytes
}
return 0
}
func (x *ZswapStatusSpec) GetTotalSizeHuman() string {
if x != nil {
return x.TotalSizeHuman
}
return ""
}
func (x *ZswapStatusSpec) GetStoredPages() uint64 {
if x != nil {
return x.StoredPages
}
return 0
}
func (x *ZswapStatusSpec) GetPoolLimitHit() uint64 {
if x != nil {
return x.PoolLimitHit
}
return 0
}
func (x *ZswapStatusSpec) GetRejectReclaimFail() uint64 {
if x != nil {
return x.RejectReclaimFail
}
return 0
}
func (x *ZswapStatusSpec) GetRejectAllocFail() uint64 {
if x != nil {
return x.RejectAllocFail
}
return 0
}
func (x *ZswapStatusSpec) GetRejectKmemcacheFail() uint64 {
if x != nil {
return x.RejectKmemcacheFail
}
return 0
}
func (x *ZswapStatusSpec) GetRejectCompressFail() uint64 {
if x != nil {
return x.RejectCompressFail
}
return 0
}
func (x *ZswapStatusSpec) GetRejectCompressPoor() uint64 {
if x != nil {
return x.RejectCompressPoor
}
return 0
}
func (x *ZswapStatusSpec) GetWrittenBackPages() uint64 {
if x != nil {
return x.WrittenBackPages
}
return 0
}
var File_resource_definitions_block_block_proto protoreflect.FileDescriptor
const file_resource_definitions_block_block_proto_rawDesc = "" +
@ -2225,7 +2342,19 @@ const file_resource_definitions_block_block_proto_rawDesc = "" +
"\x04type\x18\x10 \x01(\x0e21.talos.resource.definitions.enums.BlockVolumeTypeR\x04type\x12<\n" +
"\x1aconfigured_encryption_keys\x18\x11 \x03(\tR\x18configuredEncryptionKeys\x12\\\n" +
"\fsymlink_spec\x18\x12 \x01(\v29.talos.resource.definitions.block.SymlinkProvisioningSpecR\vsymlinkSpec\x12\x1b\n" +
"\tparent_id\x18\x13 \x01(\tR\bparentIdBt\n" +
"\tparent_id\x18\x13 \x01(\tR\bparentId\"\xd0\x03\n" +
"\x0fZswapStatusSpec\x12(\n" +
"\x10total_size_bytes\x18\x01 \x01(\x04R\x0etotalSizeBytes\x12(\n" +
"\x10total_size_human\x18\x02 \x01(\tR\x0etotalSizeHuman\x12!\n" +
"\fstored_pages\x18\x03 \x01(\x04R\vstoredPages\x12$\n" +
"\x0epool_limit_hit\x18\x04 \x01(\x04R\fpoolLimitHit\x12.\n" +
"\x13reject_reclaim_fail\x18\x05 \x01(\x04R\x11rejectReclaimFail\x12*\n" +
"\x11reject_alloc_fail\x18\x06 \x01(\x04R\x0frejectAllocFail\x122\n" +
"\x15reject_kmemcache_fail\x18\a \x01(\x04R\x13rejectKmemcacheFail\x120\n" +
"\x14reject_compress_fail\x18\b \x01(\x04R\x12rejectCompressFail\x120\n" +
"\x14reject_compress_poor\x18\t \x01(\x04R\x12rejectCompressPoor\x12,\n" +
"\x12written_back_pages\x18\n" +
" \x01(\x04R\x10writtenBackPagesBt\n" +
"(dev.talos.api.resource.definitions.blockZHgithub.com/siderolabs/talos/pkg/machinery/api/resource/definitions/blockb\x06proto3"
var (
@ -2240,7 +2369,7 @@ func file_resource_definitions_block_block_proto_rawDescGZIP() []byte {
return file_resource_definitions_block_block_proto_rawDescData
}
var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_resource_definitions_block_block_proto_goTypes = []any{
(*DeviceSpec)(nil), // 0: talos.resource.definitions.block.DeviceSpec
(*DiscoveredVolumeSpec)(nil), // 1: talos.resource.definitions.block.DiscoveredVolumeSpec
@ -2266,38 +2395,39 @@ var file_resource_definitions_block_block_proto_goTypes = []any{
(*VolumeMountRequestSpec)(nil), // 21: talos.resource.definitions.block.VolumeMountRequestSpec
(*VolumeMountStatusSpec)(nil), // 22: talos.resource.definitions.block.VolumeMountStatusSpec
(*VolumeStatusSpec)(nil), // 23: talos.resource.definitions.block.VolumeStatusSpec
(*v1alpha1.CheckedExpr)(nil), // 24: google.api.expr.v1alpha1.CheckedExpr
(enums.BlockEncryptionKeyType)(0), // 25: talos.resource.definitions.enums.BlockEncryptionKeyType
(enums.BlockEncryptionProviderType)(0), // 26: talos.resource.definitions.enums.BlockEncryptionProviderType
(enums.BlockFilesystemType)(0), // 27: talos.resource.definitions.enums.BlockFilesystemType
(enums.BlockVolumeType)(0), // 28: talos.resource.definitions.enums.BlockVolumeType
(enums.BlockVolumePhase)(0), // 29: talos.resource.definitions.enums.BlockVolumePhase
(*ZswapStatusSpec)(nil), // 24: talos.resource.definitions.block.ZswapStatusSpec
(*v1alpha1.CheckedExpr)(nil), // 25: google.api.expr.v1alpha1.CheckedExpr
(enums.BlockEncryptionKeyType)(0), // 26: talos.resource.definitions.enums.BlockEncryptionKeyType
(enums.BlockEncryptionProviderType)(0), // 27: talos.resource.definitions.enums.BlockEncryptionProviderType
(enums.BlockFilesystemType)(0), // 28: talos.resource.definitions.enums.BlockFilesystemType
(enums.BlockVolumeType)(0), // 29: talos.resource.definitions.enums.BlockVolumeType
(enums.BlockVolumePhase)(0), // 30: talos.resource.definitions.enums.BlockVolumePhase
}
var file_resource_definitions_block_block_proto_depIdxs = []int32{
24, // 0: talos.resource.definitions.block.DiskSelector.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
25, // 1: talos.resource.definitions.block.EncryptionKey.type:type_name -> talos.resource.definitions.enums.BlockEncryptionKeyType
26, // 2: talos.resource.definitions.block.EncryptionSpec.provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
25, // 0: talos.resource.definitions.block.DiskSelector.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
26, // 1: talos.resource.definitions.block.EncryptionKey.type:type_name -> talos.resource.definitions.enums.BlockEncryptionKeyType
27, // 2: talos.resource.definitions.block.EncryptionSpec.provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
6, // 3: talos.resource.definitions.block.EncryptionSpec.keys:type_name -> talos.resource.definitions.block.EncryptionKey
27, // 4: talos.resource.definitions.block.FilesystemSpec.type:type_name -> talos.resource.definitions.enums.BlockFilesystemType
24, // 5: talos.resource.definitions.block.LocatorSpec.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
28, // 4: talos.resource.definitions.block.FilesystemSpec.type:type_name -> talos.resource.definitions.enums.BlockFilesystemType
25, // 5: talos.resource.definitions.block.LocatorSpec.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
10, // 6: talos.resource.definitions.block.MountStatusSpec.spec:type_name -> talos.resource.definitions.block.MountRequestSpec
27, // 7: talos.resource.definitions.block.MountStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
26, // 8: talos.resource.definitions.block.MountStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
28, // 7: talos.resource.definitions.block.MountStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
27, // 8: talos.resource.definitions.block.MountStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
4, // 9: talos.resource.definitions.block.ProvisioningSpec.disk_selector:type_name -> talos.resource.definitions.block.DiskSelector
13, // 10: talos.resource.definitions.block.ProvisioningSpec.partition_spec:type_name -> talos.resource.definitions.block.PartitionSpec
8, // 11: talos.resource.definitions.block.ProvisioningSpec.filesystem_spec:type_name -> talos.resource.definitions.block.FilesystemSpec
28, // 12: talos.resource.definitions.block.VolumeConfigSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
29, // 12: talos.resource.definitions.block.VolumeConfigSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
14, // 13: talos.resource.definitions.block.VolumeConfigSpec.provisioning:type_name -> talos.resource.definitions.block.ProvisioningSpec
9, // 14: talos.resource.definitions.block.VolumeConfigSpec.locator:type_name -> talos.resource.definitions.block.LocatorSpec
11, // 15: talos.resource.definitions.block.VolumeConfigSpec.mount:type_name -> talos.resource.definitions.block.MountSpec
7, // 16: talos.resource.definitions.block.VolumeConfigSpec.encryption:type_name -> talos.resource.definitions.block.EncryptionSpec
16, // 17: talos.resource.definitions.block.VolumeConfigSpec.symlink:type_name -> talos.resource.definitions.block.SymlinkProvisioningSpec
29, // 18: talos.resource.definitions.block.VolumeStatusSpec.phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
29, // 19: talos.resource.definitions.block.VolumeStatusSpec.pre_fail_phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
27, // 20: talos.resource.definitions.block.VolumeStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
26, // 21: talos.resource.definitions.block.VolumeStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
30, // 18: talos.resource.definitions.block.VolumeStatusSpec.phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
30, // 19: talos.resource.definitions.block.VolumeStatusSpec.pre_fail_phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
28, // 20: talos.resource.definitions.block.VolumeStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
27, // 21: talos.resource.definitions.block.VolumeStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
11, // 22: talos.resource.definitions.block.VolumeStatusSpec.mount_spec:type_name -> talos.resource.definitions.block.MountSpec
28, // 23: talos.resource.definitions.block.VolumeStatusSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
29, // 23: talos.resource.definitions.block.VolumeStatusSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
16, // 24: talos.resource.definitions.block.VolumeStatusSpec.symlink_spec:type_name -> talos.resource.definitions.block.SymlinkProvisioningSpec
25, // [25:25] is the sub-list for method output_type
25, // [25:25] is the sub-list for method input_type
@ -2317,7 +2447,7 @@ func file_resource_definitions_block_block_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_resource_definitions_block_block_proto_rawDesc), len(file_resource_definitions_block_block_proto_rawDesc)),
NumEnums: 0,
NumMessages: 24,
NumMessages: 25,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -1859,6 +1859,91 @@ func (m *VolumeStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *ZswapStatusSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ZswapStatusSpec) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *ZswapStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.WrittenBackPages != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.WrittenBackPages))
i--
dAtA[i] = 0x50
}
if m.RejectCompressPoor != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectCompressPoor))
i--
dAtA[i] = 0x48
}
if m.RejectCompressFail != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectCompressFail))
i--
dAtA[i] = 0x40
}
if m.RejectKmemcacheFail != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectKmemcacheFail))
i--
dAtA[i] = 0x38
}
if m.RejectAllocFail != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectAllocFail))
i--
dAtA[i] = 0x30
}
if m.RejectReclaimFail != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectReclaimFail))
i--
dAtA[i] = 0x28
}
if m.PoolLimitHit != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.PoolLimitHit))
i--
dAtA[i] = 0x20
}
if m.StoredPages != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.StoredPages))
i--
dAtA[i] = 0x18
}
if len(m.TotalSizeHuman) > 0 {
i -= len(m.TotalSizeHuman)
copy(dAtA[i:], m.TotalSizeHuman)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.TotalSizeHuman)))
i--
dAtA[i] = 0x12
}
if m.TotalSizeBytes != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TotalSizeBytes))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *DeviceSpec) SizeVT() (n int) {
if m == nil {
return 0
@ -2631,6 +2716,47 @@ func (m *VolumeStatusSpec) SizeVT() (n int) {
return n
}
func (m *ZswapStatusSpec) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.TotalSizeBytes != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.TotalSizeBytes))
}
l = len(m.TotalSizeHuman)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.StoredPages != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.StoredPages))
}
if m.PoolLimitHit != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.PoolLimitHit))
}
if m.RejectReclaimFail != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectReclaimFail))
}
if m.RejectAllocFail != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectAllocFail))
}
if m.RejectKmemcacheFail != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectKmemcacheFail))
}
if m.RejectCompressFail != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectCompressFail))
}
if m.RejectCompressPoor != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectCompressPoor))
}
if m.WrittenBackPages != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.WrittenBackPages))
}
n += len(m.unknownFields)
return n
}
func (m *DeviceSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@ -7662,3 +7788,257 @@ func (m *VolumeStatusSpec) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func (m *ZswapStatusSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ZswapStatusSpec: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ZswapStatusSpec: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TotalSizeBytes", wireType)
}
m.TotalSizeBytes = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.TotalSizeBytes |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field TotalSizeHuman", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.TotalSizeHuman = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field StoredPages", wireType)
}
m.StoredPages = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.StoredPages |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field PoolLimitHit", wireType)
}
m.PoolLimitHit = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.PoolLimitHit |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RejectReclaimFail", wireType)
}
m.RejectReclaimFail = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RejectReclaimFail |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RejectAllocFail", wireType)
}
m.RejectAllocFail = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RejectAllocFail |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 7:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RejectKmemcacheFail", wireType)
}
m.RejectKmemcacheFail = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RejectKmemcacheFail |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 8:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RejectCompressFail", wireType)
}
m.RejectCompressFail = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RejectCompressFail |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 9:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RejectCompressPoor", wireType)
}
m.RejectCompressPoor = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RejectCompressPoor |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 10:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field WrittenBackPages", wireType)
}
m.WrittenBackPages = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.WrittenBackPages |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}

View File

@ -21,4 +21,5 @@ type Config interface { //nolint:interfacebloat
EthernetConfigs() []EthernetConfig
UserVolumeConfigs() []UserVolumeConfig
SwapVolumeConfigs() []SwapVolumeConfig
ZswapConfig() ZswapConfig
}

View File

@ -98,3 +98,10 @@ type SwapVolumeConfig interface {
Provisioning() VolumeProvisioningConfig
Encryption() EncryptionConfig
}
// ZswapConfig defines the interface to access zswap configuration.
type ZswapConfig interface {
ZswapConfigSignal()
MaxPoolPercent() int
ShrinkerEnabled() bool
}

View File

@ -230,6 +230,16 @@ func (container *Container) SwapVolumeConfigs() []config.SwapVolumeConfig {
return findMatchingDocs[config.SwapVolumeConfig](container.documents)
}
// ZswapConfig implements config.Config interface.
func (container *Container) ZswapConfig() config.ZswapConfig {
matching := findMatchingDocs[config.ZswapConfig](container.documents)
if len(matching) == 0 {
return nil
}
return matching[0]
}
// Bytes returns source YAML representation (if available) or does default encoding.
func (container *Container) Bytes() ([]byte, error) {
if !container.readonly {

View File

@ -370,6 +370,49 @@
],
"description": "VolumeConfig is a system volume configuration document.\\nNote: at the moment, only `EPHEMERAL` and `IMAGE-CACHE` system volumes are supported.\\n"
},
"block.ZswapConfigV1Alpha1": {
"properties": {
"apiVersion": {
"enum": [
"v1alpha1"
],
"title": "apiVersion",
"description": "apiVersion is the API version of the resource.\n",
"markdownDescription": "apiVersion is the API version of the resource.",
"x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
},
"kind": {
"enum": [
"ZswapConfig"
],
"title": "kind",
"description": "kind is the kind of the resource.\n",
"markdownDescription": "kind is the kind of the resource.",
"x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
},
"maxPoolPercent": {
"type": "integer",
"title": "maxPoolPercent",
"description": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\n",
"markdownDescription": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
"x-intellij-html-description": "\u003cp\u003eThe maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\u003c/p\u003e\n"
},
"shrinkerEnabled": {
"type": "boolean",
"title": "shrinkerEnabled",
"description": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\n",
"markdownDescription": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
"x-intellij-html-description": "\u003cp\u003eEnable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"apiVersion",
"kind"
],
"description": "ZswapConfig is a zswap (compressed memory) configuration document.\\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\\nwhen the system is under memory pressure.\\n"
},
"extensions.ConfigFile": {
"properties": {
"content": {
@ -4242,6 +4285,9 @@
{
"$ref": "#/$defs/block.VolumeConfigV1Alpha1"
},
{
"$ref": "#/$defs/block.ZswapConfigV1Alpha1"
},
{
"$ref": "#/$defs/extensions.ServiceConfigV1Alpha1"
},

View File

@ -5,6 +5,6 @@
// Package block provides block device and volume configuration documents.
package block
//go:generate docgen -output block_doc.go block.go encryption.go swap_volume_config.go user_volume_config.go volume_config.go
//go:generate docgen -output block_doc.go block.go encryption.go swap_volume_config.go user_volume_config.go volume_config.go zswap_config.go
//go:generate deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -type ZswapConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go .

View File

@ -462,6 +462,35 @@ func (DiskSelector) Doc() *encoder.Doc {
return doc
}
func (ZswapConfigV1Alpha1) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "ZswapConfig",
Comments: [3]string{"" /* encoder.HeadComment */, "ZswapConfig is a zswap (compressed memory) configuration document." /* encoder.LineComment */, "" /* encoder.FootComment */},
Description: "ZswapConfig is a zswap (compressed memory) configuration document.\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\nwhen the system is under memory pressure.\n",
Fields: []encoder.Doc{
{},
{
Name: "maxPoolPercent",
Type: "int",
Note: "",
Description: "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
Comments: [3]string{"" /* encoder.HeadComment */, "The maximum percent of memory that zswap can use." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "shrinkerEnabled",
Type: "bool",
Note: "",
Description: "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable the shrinker feature: kernel might move" /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
doc.AddExample("", exampleZswapConfigV1Alpha1())
return doc
}
// GetFileDoc returns documentation for the file block_doc.go.
func GetFileDoc() *encoder.FileDoc {
return &encoder.FileDoc{
@ -480,6 +509,7 @@ func GetFileDoc() *encoder.FileDoc {
VolumeConfigV1Alpha1{}.Doc(),
ProvisioningSpec{}.Doc(),
DiskSelector{}.Doc(),
ZswapConfigV1Alpha1{}.Doc(),
},
}
}

View File

@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Code generated by "deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
// Code generated by "deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -type ZswapConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package block
@ -143,3 +143,17 @@ func (o *VolumeConfigV1Alpha1) DeepCopy() *VolumeConfigV1Alpha1 {
}
return &cp
}
// DeepCopy generates a deep copy of *ZswapConfigV1Alpha1.
func (o *ZswapConfigV1Alpha1) DeepCopy() *ZswapConfigV1Alpha1 {
var cp ZswapConfigV1Alpha1 = *o
if o.MaxPoolPercentConfig != nil {
cp.MaxPoolPercentConfig = new(int)
*cp.MaxPoolPercentConfig = *o.MaxPoolPercentConfig
}
if o.ShrinkerEnabledConfig != nil {
cp.ShrinkerEnabledConfig = new(bool)
*cp.ShrinkerEnabledConfig = *o.ShrinkerEnabledConfig
}
return &cp
}

View File

@ -0,0 +1,4 @@
apiVersion: v1alpha1
kind: ZswapConfig
maxPoolPercent: 50
shrinkerEnabled: true

View File

@ -0,0 +1,2 @@
apiVersion: v1alpha1
kind: ZswapConfig

View File

@ -0,0 +1,127 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
//docgen:jsonschema
import (
"errors"
"fmt"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/internal/registry"
"github.com/siderolabs/talos/pkg/machinery/config/types/meta"
"github.com/siderolabs/talos/pkg/machinery/config/validation"
)
// ZswapConfigKind is a config document kind.
const ZswapConfigKind = "ZswapConfig"
func init() {
registry.Register(ZswapConfigKind, func(version string) config.Document {
switch version {
case "v1alpha1": //nolint:goconst
return &ZswapConfigV1Alpha1{}
default:
return nil
}
})
}
// Check interfaces.
var (
_ config.ZswapConfig = &ZswapConfigV1Alpha1{}
_ config.Validator = &ZswapConfigV1Alpha1{}
)
// ZswapConfigV1Alpha1 is a zswap (compressed memory) configuration document.
//
// description: |
// When zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.
// The compressed pages are stored in a memory pool, which is used to avoid writing to disk
// when the system is under memory pressure.
// examples:
// - value: exampleZswapConfigV1Alpha1()
// alias: ZswapConfig
// schemaRoot: true
// schemaMeta: v1alpha1/ZswapConfig
type ZswapConfigV1Alpha1 struct {
meta.Meta `yaml:",inline"`
// description: |
// The maximum percent of memory that zswap can use.
// This is a percentage of the total system memory.
// The value must be between 0 and 100.
MaxPoolPercentConfig *int `yaml:"maxPoolPercent,omitempty"`
// description: |
// Enable the shrinker feature: kernel might move
// cold pages from zswap to swap device to free up memory
// for other use cases.
ShrinkerEnabledConfig *bool `yaml:"shrinkerEnabled,omitempty"`
}
// NewZswapConfigV1Alpha1 creates a new zswap config document.
func NewZswapConfigV1Alpha1() *ZswapConfigV1Alpha1 {
return &ZswapConfigV1Alpha1{
Meta: meta.Meta{
MetaKind: ZswapConfigKind,
MetaAPIVersion: "v1alpha1",
},
}
}
func exampleZswapConfigV1Alpha1() *ZswapConfigV1Alpha1 {
cfg := NewZswapConfigV1Alpha1()
cfg.MaxPoolPercentConfig = pointer.To(25)
cfg.ShrinkerEnabledConfig = pointer.To(true)
return cfg
}
// Clone implements config.Document interface.
func (s *ZswapConfigV1Alpha1) Clone() config.Document {
return s.DeepCopy()
}
// Validate implements config.Validator interface.
//
//nolint:gocyclo
func (s *ZswapConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) {
var (
warnings []string
validationErrors error
)
if s.MaxPoolPercentConfig != nil {
if *s.MaxPoolPercentConfig < 0 || *s.MaxPoolPercentConfig > 100 {
validationErrors = errors.Join(validationErrors, fmt.Errorf("maxPoolPercent must be between 0 and 100"))
}
}
return warnings, validationErrors
}
// ZswapConfigSignal is a signal for zswap config.
func (s *ZswapConfigV1Alpha1) ZswapConfigSignal() {}
// MaxPoolPercent implements config.ZswapConfig interface.
func (s *ZswapConfigV1Alpha1) MaxPoolPercent() int {
if s.MaxPoolPercentConfig == nil {
return 20
}
return pointer.SafeDeref(s.MaxPoolPercentConfig)
}
// ShrinkerEnabled implements config.ZswapConfig interface.
func (s *ZswapConfigV1Alpha1) ShrinkerEnabled() bool {
if s.ShrinkerEnabledConfig == nil {
return false
}
return pointer.SafeDeref(s.ShrinkerEnabledConfig)
}

View File

@ -0,0 +1,141 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//nolint:dupl,goconst
package block_test
import (
"os"
"path/filepath"
"testing"
"github.com/siderolabs/go-pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/pkg/machinery/config/configloader"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/siderolabs/talos/pkg/machinery/config/types/block"
)
func TestZswapConfigMarshalUnmarshal(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
filename string
cfg func(t *testing.T) *block.ZswapConfigV1Alpha1
}{
{
name: "full config",
filename: "zswapconfig_full.yaml",
cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
c := block.NewZswapConfigV1Alpha1()
c.MaxPoolPercentConfig = pointer.To(50)
c.ShrinkerEnabledConfig = pointer.To(true)
return c
},
},
{
name: "min config",
filename: "zswapconfig_min.yaml",
cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
c := block.NewZswapConfigV1Alpha1()
return c
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg(t)
warnings, err := cfg.Validate(validationMode{})
require.NoError(t, err)
require.Empty(t, warnings)
marshaled, err := encoder.NewEncoder(cfg, encoder.WithComments(encoder.CommentsDisabled)).Encode()
require.NoError(t, err)
t.Log(string(marshaled))
expectedMarshaled, err := os.ReadFile(filepath.Join("testdata", test.filename))
require.NoError(t, err)
assert.Equal(t, string(expectedMarshaled), string(marshaled))
provider, err := configloader.NewFromBytes(expectedMarshaled)
require.NoError(t, err)
docs := provider.Documents()
require.Len(t, docs, 1)
assert.Equal(t, cfg, docs[0])
})
}
}
func TestZswapVolumeConfigValidate(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
cfg func(t *testing.T) *block.ZswapConfigV1Alpha1
expectedErrors string
}{
{
name: "minimal",
cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
c := block.NewZswapConfigV1Alpha1()
return c
},
},
{
name: "full",
cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
c := block.NewZswapConfigV1Alpha1()
c.MaxPoolPercentConfig = pointer.To(50)
c.ShrinkerEnabledConfig = pointer.To(true)
return c
},
},
{
name: "invalid percent",
cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
c := block.NewZswapConfigV1Alpha1()
c.MaxPoolPercentConfig = pointer.To(150)
return c
},
expectedErrors: "maxPoolPercent must be between 0 and 100",
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg(t)
_, err := cfg.Validate(validationMode{})
if test.expectedErrors == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
assert.EqualError(t, err, test.expectedErrors)
}
})
}
}

View File

@ -19,7 +19,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1"
)
//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -type ZswapStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate enumer -type=VolumeType,VolumePhase,FilesystemType,EncryptionKeyType,EncryptionProviderType -linecomment -text

View File

@ -40,6 +40,7 @@ func TestRegisterResource(t *testing.T) {
&block.VolumeMountRequest{},
&block.VolumeMountStatus{},
&block.VolumeStatus{},
&block.ZswapStatus{},
} {
assert.NoError(t, resourceRegistry.Register(ctx, resource))
}

View File

@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -type ZswapStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package block
@ -148,3 +148,9 @@ func (o VolumeStatusSpec) DeepCopy() VolumeStatusSpec {
}
return cp
}
// DeepCopy generates a deep copy of ZswapStatusSpec.
func (o ZswapStatusSpec) DeepCopy() ZswapStatusSpec {
var cp ZswapStatusSpec = o
return cp
}

View File

@ -0,0 +1,86 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package block
import (
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
"github.com/cosi-project/runtime/pkg/resource/typed"
"github.com/siderolabs/talos/pkg/machinery/proto"
)
// ZswapStatusType is type of ZswapStatus resource.
const ZswapStatusType = resource.Type("ZswapStatuses.block.talos.dev")
// ZswapStatus resource holds status of zwap subsystem.
type ZswapStatus = typed.Resource[ZswapStatusSpec, ZswapStatusExtension]
// ZswapStatusID is the ID of the singleton ZswapStatus resource.
const ZswapStatusID resource.ID = "zswap"
// ZswapStatusSpec is the spec for ZswapStatus resource.
//
//gotagsrewrite:gen
type ZswapStatusSpec struct {
TotalSizeBytes uint64 `yaml:"totalSize" protobuf:"1"`
TotalSizeHuman string `yaml:"totalSizeHuman" protobuf:"2"`
StoredPages uint64 `yaml:"storedPages" protobuf:"3"`
PoolLimitHit uint64 `yaml:"poolLimitHit" protobuf:"4"`
RejectReclaimFail uint64 `yaml:"rejectReclaimFail" protobuf:"5"`
RejectAllocFail uint64 `yaml:"rejectAllocFail" protobuf:"6"`
RejectKmemcacheFail uint64 `yaml:"rejectKmemcacheFail" protobuf:"7"`
RejectCompressFail uint64 `yaml:"rejectCompressFail" protobuf:"8"`
RejectCompressPoor uint64 `yaml:"rejectCompressPoor" protobuf:"9"`
WrittenBackPages uint64 `yaml:"writtenBackPages" protobuf:"10"`
}
// NewZswapStatus initializes a ZswapStatus resource.
func NewZswapStatus(namespace resource.Namespace, id resource.ID) *ZswapStatus {
return typed.NewResource[ZswapStatusSpec, ZswapStatusExtension](
resource.NewMetadata(namespace, ZswapStatusType, id, resource.VersionUndefined),
ZswapStatusSpec{},
)
}
// ZswapStatusExtension is auxiliary resource data for ZswapStatus.
type ZswapStatusExtension struct{}
// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
func (ZswapStatusExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
return meta.ResourceDefinitionSpec{
Type: ZswapStatusType,
Aliases: []resource.Type{},
DefaultNamespace: NamespaceName,
PrintColumns: []meta.PrintColumn{
{
Name: "Total Size",
JSONPath: "{.totalSizeHuman}",
},
{
Name: "Stored Pages",
JSONPath: "{.storedPages}",
},
{
Name: "Written Back",
JSONPath: "{.writtenBackPages}",
},
{
Name: "Pool Limit Hit",
JSONPath: "{.poolLimitHit}",
},
},
}
}
func init() {
proto.RegisterDefaultTypes()
err := protobuf.RegisterDynamic[ZswapStatusSpec](ZswapStatusType, &ZswapStatus{})
if err != nil {
panic(err)
}
}

View File

@ -51,6 +51,7 @@ description: Talos gRPC API reference.
- [VolumeMountRequestSpec](#talos.resource.definitions.block.VolumeMountRequestSpec)
- [VolumeMountStatusSpec](#talos.resource.definitions.block.VolumeMountStatusSpec)
- [VolumeStatusSpec](#talos.resource.definitions.block.VolumeStatusSpec)
- [ZswapStatusSpec](#talos.resource.definitions.block.ZswapStatusSpec)
- [resource/definitions/cluster/cluster.proto](#resource/definitions/cluster/cluster.proto)
- [AffiliateSpec](#talos.resource.definitions.cluster.AffiliateSpec)
@ -1302,6 +1303,30 @@ VolumeStatusSpec is the spec for VolumeStatus resource.
<a name="talos.resource.definitions.block.ZswapStatusSpec"></a>
### ZswapStatusSpec
ZswapStatusSpec is the spec for ZswapStatus resource.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| total_size_bytes | [uint64](#uint64) | | |
| total_size_human | [string](#string) | | |
| stored_pages | [uint64](#uint64) | | |
| pool_limit_hit | [uint64](#uint64) | | |
| reject_reclaim_fail | [uint64](#uint64) | | |
| reject_alloc_fail | [uint64](#uint64) | | |
| reject_kmemcache_fail | [uint64](#uint64) | | |
| reject_compress_fail | [uint64](#uint64) | | |
| reject_compress_poor | [uint64](#uint64) | | |
| written_back_pages | [uint64](#uint64) | | |
<!-- end messages -->
<!-- end enums -->

View File

@ -0,0 +1,37 @@
---
description: |
ZswapConfig is a zswap (compressed memory) configuration document.
When zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.
The compressed pages are stored in a memory pool, which is used to avoid writing to disk
when the system is under memory pressure.
title: ZswapConfig
---
<!-- markdownlint-disable -->
{{< highlight yaml >}}
apiVersion: v1alpha1
kind: ZswapConfig
maxPoolPercent: 25 # The maximum percent of memory that zswap can use.
shrinkerEnabled: true # Enable the shrinker feature: kernel might move
{{< /highlight >}}
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`maxPoolPercent` |int |The maximum percent of memory that zswap can use.<br>This is a percentage of the total system memory.<br>The value must be between 0 and 100. | |
|`shrinkerEnabled` |bool |Enable the shrinker feature: kernel might move<br>cold pages from zswap to swap device to free up memory<br>for other use cases. | |

View File

@ -370,6 +370,49 @@
],
"description": "VolumeConfig is a system volume configuration document.\\nNote: at the moment, only `EPHEMERAL` and `IMAGE-CACHE` system volumes are supported.\\n"
},
"block.ZswapConfigV1Alpha1": {
"properties": {
"apiVersion": {
"enum": [
"v1alpha1"
],
"title": "apiVersion",
"description": "apiVersion is the API version of the resource.\n",
"markdownDescription": "apiVersion is the API version of the resource.",
"x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
},
"kind": {
"enum": [
"ZswapConfig"
],
"title": "kind",
"description": "kind is the kind of the resource.\n",
"markdownDescription": "kind is the kind of the resource.",
"x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
},
"maxPoolPercent": {
"type": "integer",
"title": "maxPoolPercent",
"description": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\n",
"markdownDescription": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
"x-intellij-html-description": "\u003cp\u003eThe maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\u003c/p\u003e\n"
},
"shrinkerEnabled": {
"type": "boolean",
"title": "shrinkerEnabled",
"description": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\n",
"markdownDescription": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
"x-intellij-html-description": "\u003cp\u003eEnable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"apiVersion",
"kind"
],
"description": "ZswapConfig is a zswap (compressed memory) configuration document.\\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\\nwhen the system is under memory pressure.\\n"
},
"extensions.ConfigFile": {
"properties": {
"content": {
@ -4242,6 +4285,9 @@
{
"$ref": "#/$defs/block.VolumeConfigV1Alpha1"
},
{
"$ref": "#/$defs/block.ZswapConfigV1Alpha1"
},
{
"$ref": "#/$defs/extensions.ServiceConfigV1Alpha1"
},

View File

@ -0,0 +1,134 @@
---
title: "Swap"
description: "Guide on managing swap devices and zswap configuration in Talos Linux."
---
This guide provides an overview of the swap management features in Talos Linux.
## Overview
Swap devices are used to extend the available memory on a system by allowing the kernel to move inactive pages from RAM to disk.
Swap might be useful to free up memory when running memory-intensive workloads, but it can also lead to performance degradation if used excessively.
On other hand, moving inactive pages to swap can allow Linux to use more memory for buffers and caches, which can improve performance for workloads that benefit from caching.
Zswap is a compressed cache for swap pages that can help reduce the performance impact of swapping by keeping frequently accessed pages in memory.
Swap and zswap can be used together, but they can also be configured independently.
Swap and zswap are disabled by default in Talos, but can be enabled through the configuration.
## Swap Devices
Swap devices can be configured in the [Talos machine configuration]({{< relref "../../reference/configuration/block/swapvolumeconfig" >}}) similar to how [User Volumes]({{< relref "disk-management#" >}}) are configured.
As swap devices contain memory pages, it is recommended to enable disk encryption for swap devices to prevent sensitive data from being written to disk in plaintext.
It is also recommended to use a separate disk for swap devices to avoid performance degradation on the system disk and other workloads.
For example, to configure a swap device on a NVMe disk of size 4GiB, using static key for encryption, the following configuration patch can be used:
```yaml
apiVersion: v1alpha1
kind: SwapVolumeConfig
name: swap1
provisioning:
diskSelector:
match: 'disk.transport == "nvme"'
minSize: 4GiB
maxSize: 4GiB
encryption:
provider: luks2
keys:
- slot: 0
tpm: {}
- slot: 1
static:
passphrase: topsecret
```
Talos Linux will automatically provision the partition on the disk, label it as `s-swap1`, encrypt it using the provided key, and enable it as a swap device.
Current swap status can be checked using `talosctl get swap` command:
```shell
$ talosctl -n 172.20.0.5 get swap
NODE NAMESPACE TYPE ID VERSION DEVICE SIZE USED PRIORITY
172.20.0.5 runtime SwapStatus /dev/nvme0n2p2 1 /dev/nvme0n2p2 3.9 GiB 100 MiB -2
```
Removing a `SwapVolumeConfig` document will remove the swap device from the system, but the partition will remain on the disk.
To wipe the disk data, and make it allocatable again, use the following command (replace `nvme0n2p2` with the actual device name):
```bash
talosctl wipe disk nvme0n2p2 --drop-partition
```
## Zswap
Zswap is a compressed cache for swap pages that can help reduce the performance impact of swapping by keeping frequently accessed pages in memory.
Zswap can be enabled in the [Talos machine configuration]({{< relref "../../reference/configuration/block/zswapconfig" >}}):
```yaml
apiVersion: v1alpha1
kind: ZswapConfig
maxPoolPercent: 20
```
This configuration enables zswap with a maximum pool size of 20% of the total system memory.
To check the current zswap status, you can use the `talosctl get zswapstatus` command:
```shell
$ talosctl -n 172.20.0.5 get zswapstatus
NODE NAMESPACE TYPE ID VERSION TOTAL SIZE STORED PAGES WRITTEN BACK POOL LIMIT HIT
172.20.0.5 runtime ZswapStatus zswap 1 0 B 0 0 0
```
Removing a `ZswapConfig` document will disable zswap on the system.
## Kubernetes and Swap
Kubernetes by default [does not allow swap to be used by containers](https://kubernetes.io/blog/2025/03/25/swap-linux-improvements/), as it can lead to performance issues and unpredictable behavior.
At the very minimum, enable swap usage in the `kubelet` configuration with the following machine configuration patch:
```yaml
machine:
kubelet:
extraConfig:
memorySwap:
swapBehavior: LimitedSwap
```
Current swap and zwap usage and limits can be checked using the `talosctl cgroups` [command]({{< relref "../../advanced/cgroups-analysis" >}}):
```shell
$ talosctl cgroups --preset=swap
NAME SwapCurrent SwapPeak SwapHigh SwapMax ZswapCurrent ZswapMax ZswapWriteback
. unset unset unset unset unset unset 1
├──init 0 B 0 B max max 0 B max 1
├──kubepods 0 B 0 B max max 0 B max 1
│ ├──besteffort 0 B 0 B max max 0 B max 1
│ │ └──kube-system/kube-proxy-5gwvz 0 B 0 B max max 0 B max 1
│ │ ├──kube-proxy 0 B 0 B max 0 B 0 B max 1
│ │ └──sandbox 0 B 0 B max max 0 B max 1
│ └──burstable 0 B 0 B max max 0 B max 1
│ ├──kube-system/coredns-78d87fb69b-qd6xd 0 B 0 B max max 0 B max 1
│ │ ├──coredns 0 B 0 B max 0 B 0 B max 1
│ │ └──sandbox 0 B 0 B max max 0 B max 1
│ ├──kube-system/coredns-78d87fb69b-z8xj2 0 B 0 B max max 0 B max 1
│ │ ├──coredns 0 B 0 B max 0 B 0 B max 1
│ │ └──sandbox 0 B 0 B max max 0 B max 1
│ └──kube-system/kube-flannel-qqd8v 0 B 0 B max max 0 B max 1
│ ├──kube-flannel 0 B 0 B max 0 B 0 B max 1
│ └──sandbox 0 B 0 B max max 0 B max 1
├──podruntime 0 B 0 B max max 0 B max 1
│ ├──kubelet 0 B 0 B max max 0 B max 1
│ └──runtime 0 B 0 B max max 0 B max 1
└──system 0 B 0 B max max 0 B max 1
├──apid 0 B 0 B max 0 B 0 B max 1
├──dashboard 0 B 0 B max max 0 B max 1
├──runtime 0 B 0 B max max 0 B max 1
├──trustd 0 B 0 B max 0 B 0 B max 1
└──udevd 0 B 0 B max max 0 B max 1
```
If `SwapMax` is set to `0 B`, it means that swap is not enabled for this cgroup (container/pod).
Current swap and zswap usage can be seen in the `SwapCurrent` and `ZswapCurrent` columns, respectively.