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 1, 2017
1 parent 0ed4bdc commit 4b90ec0
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 36 deletions.
2 changes: 1 addition & 1 deletion pkg/kobject/kobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type ServiceConfig struct {
CPUSet string `compose:"cpuset" bundle:""`
CPUShares int64 `compose:"cpu_shares" bundle:""`
CPUQuota int64 `compose:"cpu_quota" bundle:""`
CPULimit float64 `compose:"" bundle""`
CPULimit int64 `compose:"" bundle""`
CPUReservation int64 `compose:"" bundle""`
CapAdd []string `compose:"cap_add" bundle:""`
CapDrop []string `compose:"cap_drop" bundle:""`
Expand Down
12 changes: 10 additions & 2 deletions pkg/loader/compose/v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,25 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
// 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)

serviceConfig.CPULimit = cpuLimit
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)

//serviceConfig.CPUReservation = int64(composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs)
}

// restart-policy:
Expand Down
28 changes: 20 additions & 8 deletions pkg/transformer/kubernetes/k8sutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,26 +390,38 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic

// Configure the resource limits
if service.MemLimit != 0 || service.CPULimit != 0 {
resourceList := api.ResourceList{}
resourceLimit := api.ResourceList{}

// TODO CPU + Memory
if service.MemLimit != 0 {
resourceList[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
}

/* As per Kubernetes docs:
The spec.containers[].resources.limits.cpu is converted to its millicore value, multiplied by 100000, and then divided by 1000. This number is used as the value of the --cpu-quota flag in the docker run command. The [--cpu-period] flag is set to 100000, which represents the default 100ms period for measuring quota usage. The kubelet enforces cpu limits if it is started with the [--cpu-cfs-quota] flag set to true. As of Kubernetes version 1.2, this flag defaults to true.
*/
if service.CPULimit != 0 {
resourceList[api.ResourceCPU] = *resource.NewQuantity(int64((service.CPULimit*100000)/1000), "RandomStringForFormat")
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")
}

template.Spec.Containers[0].Resources.Limits = resourceList
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
4 changes: 2 additions & 2 deletions script/test/fixtures/v3/docker-compose-memcpu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ services:
deploy:
resources:
limits:
cpus: '0.001'
cpus: '0.01'
memory: 50M
reservations:
cpus: '0.0001'
cpus: '0.001'
memory: 20M
image: redis
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": {}
}
]
}

0 comments on commit 4b90ec0

Please sign in to comment.