forked from LaconicNetwork/kompose
Add CPU limit, CPU Reservation and Memory Reservation
This adds support for CPU limit, CPU reservation as well as memory reservation. Specifically, when using the `deploy` key in Docker Compose.
This commit is contained in:
parent
3b2ef30afc
commit
af26b797a2
@ -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:""`
|
||||
@ -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:""`
|
||||
|
||||
@ -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
|
||||
@ -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{}) {
|
||||
serviceConfig.MemLimit = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Limits.MemoryBytes)
|
||||
}
|
||||
//Here we handle all Docker Compose Deploy keys
|
||||
|
||||
//Handling restart-policy
|
||||
// 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)
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@ -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" {
|
||||
|
||||
@ -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()
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
13
script/test/fixtures/v3/docker-compose-memcpu.yaml
vendored
Normal file
13
script/test/fixtures/v3/docker-compose-memcpu.yaml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
foo:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.01'
|
||||
memory: 50M
|
||||
reservations:
|
||||
cpus: '0.001'
|
||||
memory: 20M
|
||||
image: redis
|
||||
@ -155,7 +155,11 @@
|
||||
],
|
||||
"resources": {
|
||||
"limits": {
|
||||
"cpu": "1",
|
||||
"memory": "52428800"
|
||||
},
|
||||
"requests": {
|
||||
"memory": "20971520"
|
||||
}
|
||||
},
|
||||
"volumeMounts": [
|
||||
|
||||
77
script/test/fixtures/v3/output-memcpu-k8s.json
vendored
Normal file
77
script/test/fixtures/v3/output-memcpu-k8s.json
vendored
Normal file
@ -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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -155,7 +155,11 @@
|
||||
],
|
||||
"resources": {
|
||||
"limits": {
|
||||
"cpu": "1",
|
||||
"memory": "52428800"
|
||||
},
|
||||
"requests": {
|
||||
"memory": "20971520"
|
||||
}
|
||||
},
|
||||
"volumeMounts": [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user