Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add the option to set nodes memory, CPUs and CPU sets #679

Merged
merged 8 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clab/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx
Kernel: c.Config.Topology.GetNodeKernel(nodeName),
Runtime: c.Config.Topology.GetNodeRuntime(nodeName),
CPU: c.Config.Topology.GetNodeCPU(nodeName),
RAM: c.Config.Topology.GetNodeRAM(nodeName),
CPUSet: c.Config.Topology.GetNodeCPUSet(nodeName),
Memory: c.Config.Topology.GetNodeMemory(nodeName),
StartupDelay: c.Config.Topology.GetNodeStartupDelay(nodeName),

// Extras
Expand Down
55 changes: 54 additions & 1 deletion docs/manual/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,57 @@ my-node:
- bash /myscript.sh
```

The `exec` is particularly helpful to provide some startup configuration for linux nodes such as IP addressing and routing instructions.
The `exec` is particularly helpful to provide some startup configuration for linux nodes such as IP addressing and routing instructions.

### memory

By default, container runtimes do not impose any memory resource constraints[^1].
A container can use too much of the host's memory, making the host OS unstable.

The `memory` parameter can be used to limit the amount of memory a node/container can use.

```yaml
# my-node will have access to at most 1Gb of memory.
my-node:
image: alpine:3
kind: linux
memory: 1Gb
```

Supported memory suffixes (case insensitive): `b`, `kib`, `kb`, `mib`, `mb`, `gib`, `gb`.

### cpu

By default, container runtimes do not impose any CPU resource constraints[^1].
A container can use as much as the host's scheduler allows.

The `cpu` parameter can be used to limit the number of CPUs a node/container can use.

```yaml
# my-node will have access to at most 1.5 of the CPUs
# available in the host machine.
my-node:
image: alpine:3
kind: linux
cpu: 1.5
```

### cpu-set

The `cpu-set` parameter can be used to limit the node CPU usage to specific cores of the host system.

Valid syntaxes:

- `0-3`: Cores 0, 1, 2 and 3
- `0,3`: Cores 0 and 3
- `0-1,4-5`: Cores 0, 1, 4 and 5

```yaml
# my-node will have access to CPU cores 0, 1, 4 and 5.
my-node:
image: alpine:3
kind: linux
cpu-set: 0-1,4-5
```

[^1]: [docker runtime resources constraints](https://docs.docker.com/config/containers/resource_constraints/).
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/docker/docker v20.10.6+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/dustin/go-humanize v1.0.0
github.com/google/go-cmp v0.5.6
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.2.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNE
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
Expand Down
10 changes: 5 additions & 5 deletions nodes/cvx/cvx.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ func (c *cvx) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error {
return fmt.Errorf("failed to parse OCI image ref %q: %s", cfg.Image, err)
}

// if RAM is not statically set, apply the defaults
if cfg.RAM == "" {
ram, ok := memoryReqs[ociRef.Ref().Tag()]
cfg.RAM = ram
// if Memory is not statically set, apply the defaults
if cfg.Memory == "" {
mem, ok := memoryReqs[ociRef.Ref().Tag()]
cfg.Memory = mem

// by default setting the limit to 768MB
if !ok {
cfg.RAM = "768MB"
cfg.Memory = "768MB"
}
}

Expand Down
15 changes: 14 additions & 1 deletion runtime/containerd/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/docker/go-units"
"github.com/dustin/go-humanize"
"github.com/google/shlex"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
Expand Down Expand Up @@ -201,7 +202,19 @@ func (c *ContainerdRuntime) CreateContainer(ctx context.Context, node *types.Nod
if node.User != "" {
opts = append(opts, oci.WithUser(node.User))
}

if node.Memory != "" {
mem, err := humanize.ParseBytes(node.Memory)
if err != nil {
return nil, err
}
opts = append(opts, oci.WithMemoryLimit(mem))
}
if node.CPU != 0 {
opts = append(opts, oci.WithCPUCFS(int64(node.CPU*100000), 100000))
}
if node.CPUSet != "" {
opts = append(opts, oci.WithCPUs(node.CPUSet))
}
if len(mounts) > 0 {
opts = append(opts, oci.WithMounts(mounts))
}
Expand Down
18 changes: 17 additions & 1 deletion runtime/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/docker/docker/api/types/network"
dockerC "github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/dustin/go-humanize"
"github.com/google/shlex"
log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/runtime"
Expand Down Expand Up @@ -276,7 +277,22 @@ func (c *DockerRuntime) CreateContainer(ctx context.Context, node *types.NodeCon
NetworkMode: container.NetworkMode(c.Mgmt.Network),
ExtraHosts: node.ExtraHosts, // add static /etc/hosts entries
}

var resources container.Resources
if node.Memory != "" {
mem, err := humanize.ParseBytes(node.Memory)
if err != nil {
return nil, err
}
resources.Memory = int64(mem)
}
if node.CPU != 0 {
resources.CPUQuota = int64(node.CPU * 100000)
resources.CPUPeriod = 100000
}
if node.CPUSet != "" {
resources.CpusetCpus = node.CPUSet
}
containerHostConfig.Resources = resources
containerNetworkingConfig := &network.NetworkingConfig{}

switch node.NetworkMode {
Expand Down
6 changes: 3 additions & 3 deletions runtime/ignite/iginite.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ func (c *IgniteRuntime) CreateContainer(ctx context.Context, node *types.NodeCon
vm := c.baseVM.DeepCopy()

// updating the node RAM if it's set
if node.RAM != "" {
ram, err := meta.NewSizeFromString(node.RAM)
if node.Memory != "" {
ram, err := meta.NewSizeFromString(node.Memory)
if err != nil {
return nil, fmt.Errorf("failed to parse %q as memory value: %s", node.RAM, err)
return nil, fmt.Errorf("failed to parse %q as memory value: %s", node.Memory, err)
}
vm.Spec.Memory = ram
}
Expand Down
17 changes: 16 additions & 1 deletion schemas/clab.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@
"enum": [
"host"
]
},
"cpu": {
"type": "integer",
"description": "number of vcpu to allocate for this node/container",
"markdownDescription": "Allowed [CPU](https://containerlab.srlinux.dev/manual/nodes/#cpu) usage by the node/container"
},
"memory": {
"type": "string",
"description": "memory limit for this node/container",
"markdownDescription": "Allowed [Memory](https://containerlab.srlinux.dev/manual/nodes/#memory) usage by the node/container"
},
"cpu-set": {
"type": "string",
"description": "CPU cores to use by this node/container",
"markdownDescription": "[CPU cores](https://containerlab.srlinux.dev/manual/nodes/#cpu-set) to be used by the node/container"
}
},
"if": {
Expand Down Expand Up @@ -367,4 +382,4 @@
"name",
"topology"
]
}
}
11 changes: 11 additions & 0 deletions tests/01-smoke/01-basic-flow.robot
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ Verify Hosts entries exist
Should Be Equal As Integers ${rc} 0
Should Contain ${output} 6

Verify Mem and CPU limits are set
[Documentation] Checking if cpu and memory limits set for a node has been reflected in the host config
Skip If '${runtime}' != 'docker'
${rc} ${output} = Run And Return Rc And Output
... docker inspect clab-${lab-name}-l1 -f '{{.HostConfig.Memory}} {{.HostConfig.CpuQuota}}'
Log ${output}
# cpu=1.5
Should Contain ${output} 150000
# memory=1G
Should Contain ${output} 1000000000

Destroy ${lab-name} lab
${rc} ${output} = Run And Return Rc And Output
... sudo containerlab --runtime ${runtime} destroy -t ${CURDIR}/01-linux-nodes.clab.yml --cleanup
Expand Down
2 changes: 2 additions & 0 deletions tests/01-smoke/01-linux-nodes.clab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ topology:
exec:
- echo this_is_an_exec_test
- cat /etc/os-release
cpu: 1.5
memory: 1G
l2:
kind: linux
image: nginx:stable-alpine
Expand Down
23 changes: 16 additions & 7 deletions types/node_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ type NodeDefinition struct {
// Override container runtime
Runtime string `yaml:"runtime,omitempty"`
// Set node CPU (cgroup or hypervisor)
CPU string `yaml:"cpu,omitempty"`
// Set node RAM (cgroup or hypervisor)
RAM string `yaml:"ram,omitempty"`
CPU float64 `yaml:"cpu,omitempty"`
// Set node CPUs to use
CPUSet string `yaml:"cpu-set,omitempty"`
// Set node Memory (cgroup or hypervisor)
Memory string `yaml:"memory,omitempty"`

// Extra options, may be kind specific
Extras *Extras `yaml:"extras,omitempty"`
Expand Down Expand Up @@ -226,18 +228,25 @@ func (n *NodeDefinition) GetNodeRuntime() string {
return n.Runtime
}

func (n *NodeDefinition) GetNodeCPU() string {
func (n *NodeDefinition) GetNodeCPU() float64 {
if n == nil {
return ""
return 0
}
return n.CPU
}

func (n *NodeDefinition) GetNodeRAM() string {
func (n *NodeDefinition) GetNodeCPUSet() string {
if n == nil {
return ""
}
return n.CPUSet
}

func (n *NodeDefinition) GetNodeMemory() string {
if n == nil {
return ""
}
return n.RAM
return n.Memory
}

func (n *NodeDefinition) GetExec() []string {
Expand Down
31 changes: 22 additions & 9 deletions types/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,28 +374,41 @@ func (t *Topology) GetNodeRuntime(name string) string {
return ""
}

func (t *Topology) GetNodeCPU(name string) string {
func (t *Topology) GetNodeCPU(name string) float64 {
if ndef, ok := t.Nodes[name]; ok {
if ndef.GetNodeCPU() != "" {
if ndef.GetNodeCPU() != 0 {
return ndef.GetNodeCPU()
}
if t.GetKind(t.GetNodeKind(name)).GetNodeCPU() != "" {
if t.GetKind(t.GetNodeKind(name)).GetNodeCPU() != 0 {
return t.GetKind(t.GetNodeKind(name)).GetNodeCPU()
}
return t.GetDefaults().GetNodeCPU()
}
return 0
}

func (t *Topology) GetNodeCPUSet(name string) string {
if ndef, ok := t.Nodes[name]; ok {
if ndef.GetNodeCPUSet() != "" {
return ndef.GetNodeCPUSet()
}
if t.GetKind(t.GetNodeKind(name)).GetNodeCPUSet() != "" {
return t.GetKind(t.GetNodeKind(name)).GetNodeCPUSet()
}
return t.GetDefaults().GetNodeCPUSet()
}
return ""
}

func (t *Topology) GetNodeRAM(name string) string {
func (t *Topology) GetNodeMemory(name string) string {
if ndef, ok := t.Nodes[name]; ok {
if ndef.GetNodeRAM() != "" {
return ndef.GetNodeRAM()
if ndef.GetNodeMemory() != "" {
return ndef.GetNodeMemory()
}
if t.GetKind(t.GetNodeKind(name)).GetNodeRAM() != "" {
return t.GetKind(t.GetNodeKind(name)).GetNodeRAM()
if t.GetKind(t.GetNodeKind(name)).GetNodeMemory() != "" {
return t.GetKind(t.GetNodeKind(name)).GetNodeMemory()
}
return t.GetDefaults().GetNodeRAM()
return t.GetDefaults().GetNodeMemory()
}
return ""
}
Expand Down
Loading