Skip to content

Commit 7cab283

Browse files
feat: add DisableMemorySwappiness flag for Podman compatibility (#264)
Add `--disable-memory-swappiness` flag to set container MemorySwappiness to nil, addressing Podman compatibility with crun and cgroupv2, as proposed by containrrr#2072. Changes include: - Implemented flag logic to set MemorySwappiness to nil - Added flag parsing and validation - Updated CLI command configuration - Added unit tests for flag functionality - Documented flag in arguments
1 parent 732dead commit 7cab283

File tree

7 files changed

+141
-10
lines changed

7 files changed

+141
-10
lines changed

cmd/root.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ func preRun(cmd *cobra.Command, _ []string) {
252252
reviveStopped, _ := flagsSet.GetBool("revive-stopped")
253253
removeVolumes, _ := flagsSet.GetBool("remove-volumes")
254254
warnOnHeadPullFailed, _ := flagsSet.GetString("warn-on-head-failure")
255+
disableMemorySwappiness, _ := flagsSet.GetBool("disable-memory-swappiness")
255256

256257
// Warn about potential redundancy in flag combinations that could result in no action.
257258
if monitorOnly && noPull {
@@ -263,11 +264,12 @@ func preRun(cmd *cobra.Command, _ []string) {
263264

264265
// Initialize the Docker client with options reflecting the desired container handling behavior.
265266
client = container.NewClient(container.ClientOptions{
266-
IncludeStopped: includeStopped,
267-
ReviveStopped: reviveStopped,
268-
RemoveVolumes: removeVolumes,
269-
IncludeRestarting: includeRestarting,
270-
WarnOnHeadFailed: container.WarningStrategy(warnOnHeadPullFailed),
267+
IncludeStopped: includeStopped,
268+
ReviveStopped: reviveStopped,
269+
RemoveVolumes: removeVolumes,
270+
IncludeRestarting: includeRestarting,
271+
DisableMemorySwappiness: disableMemorySwappiness,
272+
WarnOnHeadFailed: container.WarningStrategy(warnOnHeadPullFailed),
271273
})
272274

273275
// Set up the notification system with types specified via flags (e.g., email, Slack).

docs/arguments.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,15 @@ Environment Variable: WATCHTOWER_PORCELAIN
510510
Possible values: v1
511511
Default: -
512512
```
513+
514+
## Compatibility with podman (Disable memory swappiness)
515+
516+
Disable memory swappiness. By default, podman sets the memory-swappiness value to 0 when no memory-swappiness is defined
517+
When this flag is specified, watchtower will set the memory-swappiness to nil, fixing a compatibility issue with podman running with crun and cgroupv2
518+
519+
```text
520+
Argument: --disable-memory-swappiness
521+
Environment Variable: WATCHTOWER_DISABLE_MEMORY_SWAPPINESS
522+
Type: Boolean
523+
Default: false
524+
```

internal/flags/flags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
260260
"",
261261
envBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),
262262
"Label applied to containers take precedence over arguments")
263+
264+
flags.BoolP(
265+
"disable-memory-swappiness",
266+
"",
267+
envBool("WATCHTOWER_DISABLE_MEMORY_SWAPPINESS"),
268+
"Label used for setting memory swappiness as nil when recreating the container, used for compatibility with podman",
269+
)
263270
}
264271

265272
// RegisterNotificationFlags adds notification flags to the root command.
@@ -526,6 +533,7 @@ func SetDefaults() {
526533
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
527534
viper.SetDefault("WATCHTOWER_LOG_LEVEL", "info")
528535
viper.SetDefault("WATCHTOWER_LOG_FORMAT", "auto")
536+
viper.SetDefault("WATCHTOWER_DISABLE_MEMORY_SWAPPINESS", false)
529537
}
530538

531539
// EnvConfig sets Docker environment variables from flags.

internal/flags/flags_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,3 +626,17 @@ func testGetSecretsFromFiles(t *testing.T, flagName string, expected string, arg
626626

627627
assert.Equal(t, expected, value)
628628
}
629+
630+
func TestDisableMemorySwappinessFlag(t *testing.T) {
631+
cmd := new(cobra.Command)
632+
633+
SetDefaults()
634+
RegisterSystemFlags(cmd)
635+
636+
err := cmd.ParseFlags([]string{"--disable-memory-swappiness"})
637+
require.NoError(t, err)
638+
639+
disableMemorySwappiness, err := cmd.PersistentFlags().GetBool("disable-memory-swappiness")
640+
require.NoError(t, err)
641+
assert.True(t, disableMemorySwappiness, "disable-memory-swappiness flag should be true")
642+
}

pkg/container/client.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ type client struct {
8585
//
8686
// It controls container management and warning behaviors.
8787
type ClientOptions struct {
88-
RemoveVolumes bool
89-
IncludeStopped bool
90-
ReviveStopped bool
91-
IncludeRestarting bool
92-
WarnOnHeadFailed WarningStrategy
88+
RemoveVolumes bool
89+
IncludeStopped bool
90+
ReviveStopped bool
91+
IncludeRestarting bool
92+
DisableMemorySwappiness bool
93+
WarnOnHeadFailed WarningStrategy
9394
}
9495

9596
// NewClient initializes a new Client instance for Docker API interactions.
@@ -223,6 +224,7 @@ func (c client) StartContainer(container types.Container) (types.ContainerID, er
223224
c.ReviveStopped,
224225
clientVersion,
225226
flags.DockerAPIMinVersion,
227+
c.DisableMemorySwappiness,
226228
)
227229
if err != nil {
228230
logrus.WithFields(fields).WithError(err).Debug("Failed to start new container")

pkg/container/container_target.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
// - reviveStopped: Whether to start stopped containers.
2525
// - clientVersion: API version of the client.
2626
// - minSupportedVersion: Minimum API version for full features.
27+
// - disableMemorySwappiness: Whether to disable memory swappiness for Podman compatibility.
2728
//
2829
// Returns:
2930
// - types.ContainerID: ID of the new container.
@@ -35,6 +36,7 @@ func StartTargetContainer(
3536
reviveStopped bool,
3637
clientVersion string,
3738
minSupportedVersion string,
39+
disableMemorySwappiness bool,
3840
) (types.ContainerID, error) {
3941
ctx := context.Background()
4042
clog := logrus.WithFields(logrus.Fields{
@@ -46,6 +48,13 @@ func StartTargetContainer(
4648
config := sourceContainer.GetCreateConfig()
4749
hostConfig := sourceContainer.GetCreateHostConfig()
4850

51+
// Set MemorySwappiness to nil for Podman compatibility if flag is enabled.
52+
if disableMemorySwappiness {
53+
hostConfig.MemorySwappiness = nil
54+
55+
clog.Debug("Disabled memory swappiness for Podman compatibility")
56+
}
57+
4958
// Log network details for debugging.
5059
isHostNetwork := sourceContainer.ContainerInfo().HostConfig.NetworkMode.IsHost()
5160
debugLogMacAddress(

pkg/container/container_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/sirupsen/logrus"
1010

1111
dockerContainerType "github.com/docker/docker/api/types/container"
12+
dockerImageType "github.com/docker/docker/api/types/image"
1213
dockerNetworkType "github.com/docker/docker/api/types/network"
1314
dockerNat "github.com/docker/go-connections/nat"
1415

@@ -79,6 +80,7 @@ var _ = ginkgo.Describe("the container", func() {
7980
},
8081
)
8182
})
83+
8284
ginkgo.Describe("GetCreateConfig", func() {
8385
ginkgo.When("container healthcheck config is equal to image config", func() {
8486
ginkgo.It("should return empty healthcheck values", func() {
@@ -174,6 +176,7 @@ var _ = ginkgo.Describe("the container", func() {
174176
})
175177
})
176178
})
179+
177180
ginkgo.When("asked for metadata", func() {
178181
var container *Container
179182
ginkgo.BeforeEach(func() {
@@ -635,4 +638,85 @@ var _ = ginkgo.Describe("the container", func() {
635638
})
636639
})
637640
})
641+
642+
ginkgo.Describe("DisableMemorySwappiness Configuration", func() {
643+
var mockContainer *Container
644+
var defaultMemorySwappiness int64 = 60
645+
containerName := "test-container"
646+
containerID := "test-container-id"
647+
648+
WithMemorySwappiness := func(swappiness int64) MockContainerUpdate {
649+
return func(c *dockerContainerType.InspectResponse, _ *dockerImageType.InspectResponse) {
650+
if c.HostConfig == nil {
651+
c.HostConfig = &dockerContainerType.HostConfig{}
652+
}
653+
c.HostConfig.MemorySwappiness = &swappiness
654+
}
655+
}
656+
657+
ginkgo.BeforeEach(func() {
658+
mockContainer = MockContainer(WithMemorySwappiness(defaultMemorySwappiness))
659+
inspectResponse := dockerContainerType.InspectResponse{
660+
ContainerJSONBase: &dockerContainerType.ContainerJSONBase{
661+
ID: containerID,
662+
Name: "/" + containerName,
663+
HostConfig: mockContainer.GetCreateHostConfig(),
664+
State: &dockerContainerType.State{Running: true},
665+
},
666+
Config: &dockerContainerType.Config{},
667+
}
668+
mockContainer.containerInfo = &inspectResponse
669+
})
670+
671+
ginkgo.When("DisableMemorySwappiness is true", func() {
672+
ginkgo.It("sets MemorySwappiness to nil and logs debug message", func() {
673+
logOutput := &bytes.Buffer{}
674+
logrus.SetOutput(logOutput)
675+
logrus.SetLevel(logrus.DebugLevel)
676+
677+
clog := logrus.WithFields(logrus.Fields{
678+
"container": mockContainer.Name(),
679+
"id": mockContainer.ID().ShortID(),
680+
})
681+
hostConfig := mockContainer.GetCreateHostConfig()
682+
disableMemorySwappiness := true
683+
684+
if disableMemorySwappiness {
685+
hostConfig.MemorySwappiness = nil
686+
clog.Debug("Disabled memory swappiness for Podman compatibility")
687+
}
688+
689+
gomega.Expect(hostConfig.MemorySwappiness).To(gomega.BeNil(),
690+
"MemorySwappiness should be nil when DisableMemorySwappiness is true")
691+
gomega.Expect(logOutput.String()).To(gomega.ContainSubstring(
692+
"Disabled memory swappiness for Podman compatibility"))
693+
})
694+
})
695+
696+
ginkgo.When("DisableMemorySwappiness is false", func() {
697+
ginkgo.It("preserves MemorySwappiness and does not log debug message", func() {
698+
logOutput := &bytes.Buffer{}
699+
logrus.SetOutput(logOutput)
700+
logrus.SetLevel(logrus.DebugLevel)
701+
702+
clog := logrus.WithFields(logrus.Fields{
703+
"container": mockContainer.Name(),
704+
"id": mockContainer.ID().ShortID(),
705+
})
706+
hostConfig := mockContainer.GetCreateHostConfig()
707+
disableMemorySwappiness := false
708+
709+
if disableMemorySwappiness {
710+
hostConfig.MemorySwappiness = nil
711+
clog.Debug("Disabled memory swappiness for Podman compatibility")
712+
}
713+
714+
gomega.Expect(hostConfig.MemorySwappiness).
715+
To(gomega.Equal(&defaultMemorySwappiness),
716+
"MemorySwappiness should remain unchanged when DisableMemorySwappiness is false")
717+
gomega.Expect(logOutput.String()).NotTo(gomega.ContainSubstring(
718+
"Disabled memory swappiness for Podman compatibility"))
719+
})
720+
})
721+
})
638722
})

0 commit comments

Comments
 (0)