Skip to content

Commit

Permalink
Add CPU limit, CPU Reservation and Memory Reservation
Browse files Browse the repository at this point in the history
This adds support for CPU limit, CPU reservation as well as memory
reservation.

Specifically, when using the `deploy` key in Docker Compose.
  • Loading branch information
cdrage committed Aug 2, 2017
1 parent 3b2ef30 commit f5ae6eb
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 41 deletions.
3 changes: 3 additions & 0 deletions pkg/kobject/kobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ type ServiceConfig struct {
CPUSet string `compose:"cpuset" bundle:""`
CPUShares int64 `compose:"cpu_shares" bundle:""`
CPUQuota int64 `compose:"cpu_quota" bundle:""`
CPULimit int64 `compose:"" bundle:""`
CPUReservation int64 `compose:"" bundle:""`
CapAdd []string `compose:"cap_add" bundle:""`
CapDrop []string `compose:"cap_drop" bundle:""`
Expose []string `compose:"expose" bundle:""`
Expand All @@ -92,6 +94,7 @@ type ServiceConfig struct {
Stdin bool `compose:"stdin_open" bundle:""`
Tty bool `compose:"tty" bundle:""`
MemLimit yaml.MemStringorInt `compose:"mem_limit" bundle:""`
MemReservation yaml.MemStringorInt `compose:"" bundle:""`
TmpFs []string `compose:"tmpfs" bundle:""`
Dockerfile string `compose:"dockerfile" bundle:""`
Replicas int `compose:"replicas" bundle:""`
Expand Down
50 changes: 37 additions & 13 deletions pkg/loader/compose/v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@ limitations under the License.
package compose

import (
libcomposeyaml "github.com/docker/libcompose/yaml"
"io/ioutil"
"strconv"
"strings"

libcomposeyaml "github.com/docker/libcompose/yaml"

"k8s.io/kubernetes/pkg/api"

"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/types"

"os"

log "github.com/Sirupsen/logrus"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/pkg/errors"
"os"
)

// converts os.Environ() ([]string) to map[string]string
Expand Down Expand Up @@ -191,25 +194,46 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
serviceConfig.Command = composeServiceConfig.Entrypoint
serviceConfig.Args = composeServiceConfig.Command

//Handling replicas
if composeServiceConfig.Deploy.Replicas != nil {
serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas)
}
//
// DEPLOY KEYS
//

// This is a bit messy since we use yaml.MemStringorInt
// TODO: Refactor yaml.MemStringorInt in kobject.go to int64
// Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing
if (composeServiceConfig.Deploy.Resources != types.Resources{}) {

// memory:
// TODO: Refactor yaml.MemStringorInt in kobject.go to int64
// Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing
serviceConfig.MemLimit = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Limits.MemoryBytes)
serviceConfig.MemReservation = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Reservations.MemoryBytes)

// cpu:
// convert to k8s format, for example: 0.5 = 500m
// See: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
// "The expression 0.1 is equivalent to the expression 100m, which can be read as “one hundred millicpu”."

cpuLimit, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Limits.NanoCPUs, 64)
if err != nil {
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to convert cpu limits resources value")
}
serviceConfig.CPULimit = int64(cpuLimit * 1000)

cpuReservation, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs, 64)
if err != nil {
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to convert cpu limits reservation value")
}
serviceConfig.CPUReservation = int64(cpuReservation * 1000)

}
//Here we handle all Docker Compose Deploy keys

//Handling restart-policy
// restart-policy:
if composeServiceConfig.Deploy.RestartPolicy != nil {
serviceConfig.Restart = composeServiceConfig.Deploy.RestartPolicy.Condition
}
// POOF. volumes_From is gone in v3. docker/cli will error out of volumes_from is added in v3
// serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom

// replicas:
if composeServiceConfig.Deploy.Replicas != nil {
serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas)
}

// TODO: Build is not yet supported, see:
// https://github.com/docker/cli/blob/master/cli/compose/types/types.go#L9
Expand Down
35 changes: 30 additions & 5 deletions pkg/transformer/kubernetes/k8sutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,14 +389,39 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
}

// Configure the resource limits
if service.MemLimit != 0 {
memoryResourceList := api.ResourceList{
api.ResourceMemory: *resource.NewQuantity(
int64(service.MemLimit), "RandomStringForFormat")}
template.Spec.Containers[0].Resources.Limits = memoryResourceList
if service.MemLimit != 0 || service.CPULimit != 0 {
resourceLimit := api.ResourceList{}

if service.MemLimit != 0 {
resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
}

if service.CPULimit != 0 {
resourceLimit[api.ResourceCPU] = *resource.NewQuantity(service.CPULimit, "RandomStringForFormat")
}

template.Spec.Containers[0].Resources.Limits = resourceLimit
}

// Configure the resource requests
if service.MemReservation != 0 || service.CPUReservation != 0 {
resourceRequests := api.ResourceList{}

if service.MemReservation != 0 {
resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
}

if service.CPUReservation != 0 {
resourceRequests[api.ResourceCPU] = *resource.NewQuantity(service.CPUReservation, "RandomStringForFormat")
}

template.Spec.Containers[0].Resources.Requests = resourceRequests
}

// Configure resource reservations

podSecurityContext := &api.PodSecurityContext{}

//set pid namespace mode
if service.Pid != "" {
if service.Pid == "host" {
Expand Down
104 changes: 81 additions & 23 deletions pkg/transformer/kubernetes/k8sutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,31 @@ func TestCreateService(t *testing.T) {
}

/*
Test the creation of a service with a memory limit
Test the creation of a service with a memory limit and reservation
*/
func TestCreateServiceWithMemLimit(t *testing.T) {

// An example service
service := kobject.ServiceConfig{
ContainerName: "name",
Image: "image",
Environment: []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}},
Port: []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolTCP}},
Command: []string{"cmd"},
WorkingDir: "dir",
Args: []string{"arg1", "arg2"},
VolList: []string{"/tmp/volume"},
Network: []string{"network1", "network2"}, // not supported
Labels: nil,
Annotations: map[string]string{"abc": "def"},
CPUQuota: 1, // not supported
CapAdd: []string{"cap_add"}, // not supported
CapDrop: []string{"cap_drop"}, // not supported
Expose: []string{"expose"}, // not supported
Privileged: true,
Restart: "always",
MemLimit: 1337,
ContainerName: "name",
Image: "image",
Environment: []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}},
Port: []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolTCP}},
Command: []string{"cmd"},
WorkingDir: "dir",
Args: []string{"arg1", "arg2"},
VolList: []string{"/tmp/volume"},
Network: []string{"network1", "network2"}, // not supported
Labels: nil,
Annotations: map[string]string{"abc": "def"},
CPUQuota: 1, // not supported
CapAdd: []string{"cap_add"}, // not supported
CapDrop: []string{"cap_drop"}, // not supported
Expose: []string{"expose"}, // not supported
Privileged: true,
Restart: "always",
MemLimit: 1337,
MemReservation: 1338,
}

// An example object generated via k8s runtime.Objects()
Expand All @@ -114,12 +115,69 @@ func TestCreateServiceWithMemLimit(t *testing.T) {
t.Error(errors.Wrap(err, "k.Transform failed"))
}

// Retrieve the deployment object and test that it matches the MemLimit value
// Retrieve the deployment object and test that it matches the mem value
for _, obj := range objects {
if deploy, ok := obj.(*extensions.Deployment); ok {
memTest, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64()
if memTest != 1337 {
t.Errorf("Expected 1337 for mem_limit check, got %v", memTest)
memLimit, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64()
if memLimit != 1337 {
t.Errorf("Expected 1337 for memory limit check, got %v", memLimit)
}
memReservation, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().AsInt64()
if memReservation != 1338 {
t.Errorf("Expected 1338 for memory reservation check, got %v", memReservation)
}
}
}
}

/*
Test the creation of a service with a cpu limit and reservation
*/
func TestCreateServiceWithCPULimit(t *testing.T) {

// An example service
service := kobject.ServiceConfig{
ContainerName: "name",
Image: "image",
Environment: []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}},
Port: []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolTCP}},
Command: []string{"cmd"},
WorkingDir: "dir",
Args: []string{"arg1", "arg2"},
VolList: []string{"/tmp/volume"},
Network: []string{"network1", "network2"}, // not supported
Labels: nil,
Annotations: map[string]string{"abc": "def"},
CPUQuota: 1, // not supported
CapAdd: []string{"cap_add"}, // not supported
CapDrop: []string{"cap_drop"}, // not supported
Expose: []string{"expose"}, // not supported
Privileged: true,
Restart: "always",
CPULimit: 10,
CPUReservation: 1,
}

// An example object generated via k8s runtime.Objects()
komposeObject := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{"app": service},
}
k := Kubernetes{}
objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3})
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}

// Retrieve the deployment object and test that it matches the cpu value
for _, obj := range objects {
if deploy, ok := obj.(*extensions.Deployment); ok {
cpuLimit, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().AsInt64()
if cpuLimit != 10 {
t.Errorf("Expected 10 for cpu limit check, got %v", cpuLimit)
}
cpuReservation, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().AsInt64()
if cpuReservation != 1 {
t.Errorf("Expected 1 for cpu reservation check, got %v", cpuReservation)
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions script/test/cmd/tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ cd $CURRENT_DIR

# Test V3 Support of Docker Compose

# Test support for cpu and memory limits + reservations
convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-memcpu.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-memcpu-k8s.json"

# Test volumes are passed correctly
convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-volumes.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-volumes-k8s.json"

Expand Down
13 changes: 13 additions & 0 deletions script/test/fixtures/v3/docker-compose-memcpu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "3"

services:
foo:
deploy:
resources:
limits:
cpus: '0.01'
memory: 50M
reservations:
cpus: '0.001'
memory: 20M
image: redis
4 changes: 4 additions & 0 deletions script/test/fixtures/v3/output-k8s-full-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@
],
"resources": {
"limits": {
"cpu": "1",
"memory": "52428800"
},
"requests": {
"memory": "20971520"
}
},
"volumeMounts": [
Expand Down
77 changes: 77 additions & 0 deletions script/test/fixtures/v3/output-memcpu-k8s.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "foo",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "foo"
}
},
"spec": {
"ports": [
{
"name": "headless",
"port": 55555,
"targetPort": 0
}
],
"selector": {
"io.kompose.service": "foo"
},
"clusterIP": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "foo",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "foo"
}
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "foo"
}
},
"spec": {
"containers": [
{
"name": "foo",
"image": "redis",
"resources": {
"limits": {
"cpu": "10",
"memory": "52428800"
},
"requests": {
"cpu": "1",
"memory": "20971520"
}
}
}
],
"restartPolicy": "Always"
}
},
"strategy": {}
},
"status": {}
}
]
}
4 changes: 4 additions & 0 deletions script/test/fixtures/v3/output-os-full-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@
],
"resources": {
"limits": {
"cpu": "1",
"memory": "52428800"
},
"requests": {
"memory": "20971520"
}
},
"volumeMounts": [
Expand Down

0 comments on commit f5ae6eb

Please sign in to comment.