From 5cb462adeb1f51d9177bda8636e3f43ac600a552 Mon Sep 17 00:00:00 2001 From: Karim Radhouani Date: Sat, 6 Nov 2021 17:21:43 -0700 Subject: [PATCH 1/7] add the option to set nodes memory, CPUs and CPU sets --- clab/config.go | 3 ++- go.mod | 1 + go.sum | 1 + nodes/cvx/cvx.go | 10 +++++----- runtime/containerd/containerd.go | 15 ++++++++++++++- runtime/docker/docker.go | 18 +++++++++++++++++- runtime/ignite/iginite.go | 6 +++--- types/node_definition.go | 23 ++++++++++++++++------- types/topology.go | 31 ++++++++++++++++++++++--------- types/types.go | 4 +++- 10 files changed, 84 insertions(+), 28 deletions(-) diff --git a/clab/config.go b/clab/config.go index 539f14fd2..258026183 100644 --- a/clab/config.go +++ b/clab/config.go @@ -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 diff --git a/go.mod b/go.mod index ec40def92..f5de40dea 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 690f491be..a2839fac9 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/nodes/cvx/cvx.go b/nodes/cvx/cvx.go index 085a7e5cd..502236598 100644 --- a/nodes/cvx/cvx.go +++ b/nodes/cvx/cvx.go @@ -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" } } diff --git a/runtime/containerd/containerd.go b/runtime/containerd/containerd.go index efa282eba..94409c084 100644 --- a/runtime/containerd/containerd.go +++ b/runtime/containerd/containerd.go @@ -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" @@ -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)) } diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index 732152bd8..e39165810 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -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" @@ -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 { diff --git a/runtime/ignite/iginite.go b/runtime/ignite/iginite.go index 38804aaa0..eeb655aa8 100644 --- a/runtime/ignite/iginite.go +++ b/runtime/ignite/iginite.go @@ -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 } diff --git a/types/node_definition.go b/types/node_definition.go index 3b2456355..c0746f300 100644 --- a/types/node_definition.go +++ b/types/node_definition.go @@ -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"` + // + 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"` @@ -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 { diff --git a/types/topology.go b/types/topology.go index dc472f892..312c9f9a9 100644 --- a/types/topology.go +++ b/types/topology.go @@ -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 "" } diff --git a/types/types.go b/types/types.go index 35de6edd0..4ff9d908f 100644 --- a/types/types.go +++ b/types/types.go @@ -103,7 +103,9 @@ type NodeConfig struct { // Configured container runtime Runtime string // Resource requirements - CPU, RAM string + CPU float64 + CPUSet string + Memory string DeploymentStatus string // status that is set by containerlab to indicate deployment stage // Extras From ef5a8a6ba4acd355645547676b21dad5d1669720 Mon Sep 17 00:00:00 2001 From: Karim Radhouani Date: Sat, 6 Nov 2021 17:24:00 -0700 Subject: [PATCH 2/7] add the option to set nodes memory, CPUs and CPU sets --- clab/config.go | 3 ++- go.mod | 1 + go.sum | 1 + nodes/cvx/cvx.go | 10 +++++----- runtime/containerd/containerd.go | 15 ++++++++++++++- runtime/docker/docker.go | 18 +++++++++++++++++- runtime/ignite/iginite.go | 6 +++--- types/node_definition.go | 23 ++++++++++++++++------- types/topology.go | 31 ++++++++++++++++++++++--------- types/types.go | 4 +++- 10 files changed, 84 insertions(+), 28 deletions(-) diff --git a/clab/config.go b/clab/config.go index 539f14fd2..258026183 100644 --- a/clab/config.go +++ b/clab/config.go @@ -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 diff --git a/go.mod b/go.mod index ec40def92..f5de40dea 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 690f491be..a2839fac9 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/nodes/cvx/cvx.go b/nodes/cvx/cvx.go index 085a7e5cd..502236598 100644 --- a/nodes/cvx/cvx.go +++ b/nodes/cvx/cvx.go @@ -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" } } diff --git a/runtime/containerd/containerd.go b/runtime/containerd/containerd.go index efa282eba..94409c084 100644 --- a/runtime/containerd/containerd.go +++ b/runtime/containerd/containerd.go @@ -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" @@ -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)) } diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index 732152bd8..e39165810 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -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" @@ -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 { diff --git a/runtime/ignite/iginite.go b/runtime/ignite/iginite.go index 38804aaa0..eeb655aa8 100644 --- a/runtime/ignite/iginite.go +++ b/runtime/ignite/iginite.go @@ -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 } diff --git a/types/node_definition.go b/types/node_definition.go index 3b2456355..c0746f300 100644 --- a/types/node_definition.go +++ b/types/node_definition.go @@ -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"` + // + 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"` @@ -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 { diff --git a/types/topology.go b/types/topology.go index dc472f892..312c9f9a9 100644 --- a/types/topology.go +++ b/types/topology.go @@ -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 "" } diff --git a/types/types.go b/types/types.go index 35de6edd0..4ff9d908f 100644 --- a/types/types.go +++ b/types/types.go @@ -103,7 +103,9 @@ type NodeConfig struct { // Configured container runtime Runtime string // Resource requirements - CPU, RAM string + CPU float64 + CPUSet string + Memory string DeploymentStatus string // status that is set by containerlab to indicate deployment stage // Extras From 35efb7983acbe9a64dfee3e6f14c6c95507d85ba Mon Sep 17 00:00:00 2001 From: Karim Radhouani Date: Thu, 11 Nov 2021 13:26:11 -0800 Subject: [PATCH 3/7] add docs --- docs/manual/nodes.md | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index 040da2666..c84b08136 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -354,4 +354,56 @@ 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. \ No newline at end of file +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 in order to limit the amount of memory a node 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. + +Using the `cpu` parameter it is possible to limit the number of CPUs a containerlab node 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 in order 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/). \ No newline at end of file From c2fb5019d9bda0d552612269e722c7a6f7f81ca8 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Wed, 17 Nov 2021 08:58:54 +0100 Subject: [PATCH 4/7] comments --- docs/manual/nodes.md | 13 +++++++------ types/node_definition.go | 2 +- types/types.go | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index c84b08136..96c7477ca 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -359,9 +359,9 @@ The `exec` is particularly helpful to provide some startup configuration for lin ### 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. +A container can use too much of the host's memory, making the host OS unstable. -The `memory` parameter can be used in order to limit the amount of memory a node can use. +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. @@ -375,13 +375,14 @@ Supported memory suffixes (case insensitive): `b`, `kib`, `kb`, `mib`, `mb`, `gi ### cpu -By default, container runtimes do not impose any cpu resource constraints[^1]. +By default, container runtimes do not impose any CPU resource constraints[^1]. A container can use as much as the host's scheduler allows. -Using the `cpu` parameter it is possible to limit the number of CPUs a containerlab node can use. +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 will have access to at most 1.5 of the CPUs +# available in the host machine. my-node: image: alpine:3 kind: linux @@ -390,7 +391,7 @@ my-node: ### cpu-set -The `cpu-set` parameter can be used in order to limit the node CPU usage to specific cores of the host system. +The `cpu-set` parameter can be used to limit the node CPU usage to specific cores of the host system. Valid syntaxes: diff --git a/types/node_definition.go b/types/node_definition.go index c0746f300..8cbb7cad4 100644 --- a/types/node_definition.go +++ b/types/node_definition.go @@ -50,7 +50,7 @@ type NodeDefinition struct { Runtime string `yaml:"runtime,omitempty"` // Set node CPU (cgroup or hypervisor) 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"` diff --git a/types/types.go b/types/types.go index 4ff9d908f..71e8f71d0 100644 --- a/types/types.go +++ b/types/types.go @@ -103,9 +103,10 @@ type NodeConfig struct { // Configured container runtime Runtime string // Resource requirements - CPU float64 - CPUSet string - Memory string + CPU float64 + CPUSet string + Memory string + DeploymentStatus string // status that is set by containerlab to indicate deployment stage // Extras From a503da005b4db67fc6119b838af0d3b8269610c6 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Wed, 17 Nov 2021 10:13:45 +0100 Subject: [PATCH 5/7] added schema --- schemas/clab.schema.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/schemas/clab.schema.json b/schemas/clab.schema.json index 36878c10b..b378236dc 100644 --- a/schemas/clab.schema.json +++ b/schemas/clab.schema.json @@ -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": { @@ -367,4 +382,4 @@ "name", "topology" ] -} +} \ No newline at end of file From a6851d03882becf77240707527ce3bf6101abb13 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Wed, 17 Nov 2021 10:18:21 +0100 Subject: [PATCH 6/7] added mem/cpu integration tests --- tests/01-smoke/01-basic-flow.robot | 11 +++++++++++ tests/01-smoke/01-linux-nodes.clab.yml | 2 ++ 2 files changed, 13 insertions(+) diff --git a/tests/01-smoke/01-basic-flow.robot b/tests/01-smoke/01-basic-flow.robot index ec4b2554a..b4c91582e 100644 --- a/tests/01-smoke/01-basic-flow.robot +++ b/tests/01-smoke/01-basic-flow.robot @@ -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 diff --git a/tests/01-smoke/01-linux-nodes.clab.yml b/tests/01-smoke/01-linux-nodes.clab.yml index cd50d2b76..13fbfe5a0 100644 --- a/tests/01-smoke/01-linux-nodes.clab.yml +++ b/tests/01-smoke/01-linux-nodes.clab.yml @@ -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 From 67ed1158f75a5802f197f7e1b99ac26b2fc39d49 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Wed, 17 Nov 2021 10:39:25 +0100 Subject: [PATCH 7/7] added cpu/mem tests --- types/topology_test.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/types/topology_test.go b/types/topology_test.go index b865b101a..0768d86a4 100644 --- a/types/topology_test.go +++ b/types/topology_test.go @@ -14,13 +14,17 @@ var topologyTestSet = map[string]struct { input: &Topology{ Nodes: map[string]*NodeDefinition{ "node1": { - Kind: "srl", + Kind: "srl", + CPU: 1, + Memory: "1G", }, }, }, want: map[string]*NodeDefinition{ "node1": { - Kind: "srl", + Kind: "srl", + CPU: 1, + Memory: "1G", }, }, }, @@ -55,6 +59,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "v2", }, + CPU: 1, + Memory: "1G", }, }, Nodes: map[string]*NodeDefinition{ @@ -66,6 +72,7 @@ var topologyTestSet = map[string]struct { Labels: map[string]string{ "label2": "notv2", }, + Memory: "2G", }, }, }, @@ -99,6 +106,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "notv2", }, + CPU: 1, + Memory: "2G", }, }, }, @@ -107,6 +116,7 @@ var topologyTestSet = map[string]struct { Defaults: &NodeDefinition{ Kind: "srl", User: "user1", + CPU: 1, }, Kinds: map[string]*NodeDefinition{ "srl": { @@ -136,6 +146,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "v2", }, + CPU: 2, + Memory: "2G", }, }, Nodes: map[string]*NodeDefinition{ @@ -172,6 +184,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "v2", }, + CPU: 1, + Memory: "2G", }, }, }, @@ -205,6 +219,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "v2", }, + CPU: 1, + Memory: "1G", }, Nodes: map[string]*NodeDefinition{ "node1": {}, @@ -239,6 +255,8 @@ var topologyTestSet = map[string]struct { "label1": "v1", "label2": "v2", }, + CPU: 1, + Memory: "1G", }, }, },