Skip to content

Commit

Permalink
#15 control swap and memory reservation
Browse files Browse the repository at this point in the history
  • Loading branch information
towe75 committed Dec 27, 2019
1 parent af9338f commit 85d899b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 29 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Contributions are welcome, of course.
* Container log is forwarded to [Nomad logger](https://www.nomadproject.io/docs/commands/alloc/logs.html)
* utilize podmans --init feature
* set username or UID used for the specified command within the container (podman --user option).
* fine tune memory usage: standard [nomad memory resource](https://www.nomadproject.io/docs/job-specification/resources.html#memory) plus additional driver specific swap, swappiness and reservation parameters, OOM handling


## Building The Driver from source
Expand Down Expand Up @@ -152,6 +153,34 @@ config {
```

* **memory_reservation** - Memory soft limit (nit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))

After setting memory reservation, when the system detects memory contention or low memory, containers are forced to restrict their consumption to their reservation. So you should always set the value below --memory, otherwise the hard limit will take precedence. By default, memory reservation will be the same as memory limit.

```
config {
memory_reservation = "100m"
}
```

* **memory_swap** - A limit value equal to memory plus swap. The swap LIMIT should always be larger than the [memory value](https://www.nomadproject.io/docs/job-specification/resources.html#memory).

Unit can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes). If you don't specify a unit, b is used. Set LIMIT to -1 to enable unlimited swap.

```
config {
memory_swap = "180m"
}
```

* **memory_swappiness** - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.

```
config {
memory_swap = "180m"
}
```

## Example job

```
Expand Down
38 changes: 22 additions & 16 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ var (
// taskConfigSpec is the hcl specification for the driver config section of
// a task within a job. It is returned in the TaskConfigSchema RPC
taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{
"image": hclspec.NewAttr("image", "string", true),
"command": hclspec.NewAttr("command", "string", false),
"hostname": hclspec.NewAttr("hostname", "string", false),
"init": hclspec.NewAttr("init", "bool", false),
"init_path": hclspec.NewAttr("init_path", "string", false),
"args": hclspec.NewAttr("args", "list(string)", false),
"volumes": hclspec.NewAttr("volumes", "list(string)", false),
"port_map": hclspec.NewAttr("port_map", "list(map(number))", false),
"image": hclspec.NewAttr("image", "string", true),
"command": hclspec.NewAttr("command", "string", false),
"hostname": hclspec.NewAttr("hostname", "string", false),
"init": hclspec.NewAttr("init", "bool", false),
"init_path": hclspec.NewAttr("init_path", "string", false),
"args": hclspec.NewAttr("args", "list(string)", false),
"volumes": hclspec.NewAttr("volumes", "list(string)", false),
"port_map": hclspec.NewAttr("port_map", "list(map(number))", false),
"memory_reservation": hclspec.NewAttr("memory_reservation", "string", false),
"memory_swap": hclspec.NewAttr("memory_swap", "string", false),
"memory_swappiness": hclspec.NewAttr("memory_swappiness", "number", false),
})
)

Expand All @@ -78,12 +81,15 @@ type PluginConfig struct {

// TaskConfig is the driver configuration of a task within a job
type TaskConfig struct {
Image string `codec:"image"`
Command string `codec:"command"`
Args []string `codec:"args"`
Volumes []string `codec:"volumes"`
Hostname string `codec:"hostname"`
Init bool `codec:"init"`
InitPath string `codec:"init_path"`
PortMap hclutils.MapStrInt `codec:"port_map"`
Image string `codec:"image"`
Command string `codec:"command"`
Args []string `codec:"args"`
Volumes []string `codec:"volumes"`
Hostname string `codec:"hostname"`
Init bool `codec:"init"`
InitPath string `codec:"init_path"`
PortMap hclutils.MapStrInt `codec:"port_map"`
MemoryReservation string `codec:"memory_reservation"`
MemorySwap string `codec:"memory_swap"`
MemorySwappiness int64 `codec:"memory_swappiness"`
}
31 changes: 19 additions & 12 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,19 +321,26 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
}
}

swap := memoryLimit
if driverConfig.MemorySwap != "" {
swap = driverConfig.MemorySwap
}

createOpts := iopodman.Create{
Args: allArgs,
Env: &allEnv,
Name: &containerName,
Volume: &allVolumes,
Memory: &memoryLimit,
MemorySwap: &memoryLimit,
CpuShares: &cpuShares,
LogOpt: &logOpts,
Hostname: &driverConfig.Hostname,
Init: &driverConfig.Init,
InitPath: &driverConfig.InitPath,
User: &cfg.User,
Args: allArgs,
Env: &allEnv,
Name: &containerName,
Volume: &allVolumes,
Memory: &memoryLimit,
CpuShares: &cpuShares,
LogOpt: &logOpts,
Hostname: &driverConfig.Hostname,
Init: &driverConfig.Init,
InitPath: &driverConfig.InitPath,
User: &cfg.User,
MemoryReservation: &driverConfig.MemoryReservation,
MemorySwap: &swap,
MemorySwappiness: &driverConfig.MemorySwappiness,
}

// Setup port mapping and exposed ports
Expand Down
58 changes: 57 additions & 1 deletion driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ func TestPodmanDriver_OOM(t *testing.T) {
taskCfg := newTaskConfig("", []string{
// Incrementally creates a bigger and bigger variable.
"sh",
"-c",
"-c",
"x=a; while true; do eval x='$x$x'; done",
})
// enable --init
Expand Down Expand Up @@ -726,6 +726,62 @@ func TestPodmanDriver_User(t *testing.T) {

}

// test memory/swap options
func TestPodmanDriver_Swap(t *testing.T) {
if !tu.IsCI() {
t.Parallel()
}

taskCfg := newTaskConfig("", busyboxLongRunningCmd)
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "swap",
AllocID: uuid.Generate(),
Resources: createBasicResources(),
}
// limit memory to 50MB
task.Resources.NomadResources.Memory.MemoryMB = 50
// but reserve 40MB
taskCfg.MemoryReservation = "40m"
// and allow mem+swap of 100MB (= 50 MB Swap)
taskCfg.MemorySwap = "100m"
// set a swappiness of 60
taskCfg.MemorySwappiness = 60
require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))

containerName := BuildContainerName(task)

d := podmanDriverHarness(t, nil)
cleanup := d.MkAllocDir(task, true)
defer cleanup()

_, _, err := d.StartTask(task)
require.NoError(t, err)

defer d.DestroyTask(task.ID, true)

// Attempt to wait
waitCh, err := d.WaitTask(context.Background(), task.ID)
require.NoError(t, err)

select {
case <-waitCh:
t.Fatalf("wait channel should not have received an exit result")
case <-time.After(time.Duration(tu.TestMultiplier()*1) * time.Second):
}
// inspect container to learn about the actual podman limits
inspectJSON := inspectContainer(t, containerName)
var inspectData iopodman.InspectContainerData

require.NoError(t, json.Unmarshal([]byte(inspectJSON), &inspectData))
// see if the configured values are set correctly
require.Equal(t, int64(52428800), inspectData.HostConfig.Memory)
require.Equal(t, int64(41943040), inspectData.HostConfig.MemoryReservation)
require.Equal(t, int64(104857600), inspectData.HostConfig.MemorySwap)
require.Equal(t, int64(60), inspectData.HostConfig.MemorySwappiness)

}

// read a tasks logfile into a string, fail on error
func readLogfile(t *testing.T, task *drivers.TaskConfig) string {
logfile := filepath.Join(filepath.Dir(task.StdoutPath), fmt.Sprintf("%s.stdout.0", task.Name))
Expand Down
15 changes: 15 additions & 0 deletions iopodman/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,19 @@ type InspectContainerHostConfig struct {
// and represents the container port. A single container port may be
// bound to multiple host ports (on different IPs).
PortBindings map[string][]InspectHostPort `json:"PortBindings"`
// Memory indicates the memory resources allocated to the container.
// This is the limit (in bytes) of RAM the container may use.
Memory int64 `json:"Memory"`
// MemoryReservation is the reservation (soft limit) of memory available
// to the container. Soft limits are warnings only and can be exceeded.
MemoryReservation int64 `json:"MemoryReservation"`
// MemorySwap is the total limit for all memory available to the
// container, including swap. 0 indicates that there is no limit to the
// amount of memory available.
MemorySwap int64 `json:"MemorySwap"`
// MemorySwappiness is the willingness of the kernel to page container
// memory to swap. It is an integer from 0 to 100, with low numbers
// being more likely to be put into swap.
// -1, the default, will not set swappiness and use the system defaults.
MemorySwappiness int64 `json:"MemorySwappiness"`
}

0 comments on commit 85d899b

Please sign in to comment.