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:""`
|
CPUSet string `compose:"cpuset" bundle:""`
|
||||||
CPUShares int64 `compose:"cpu_shares" bundle:""`
|
CPUShares int64 `compose:"cpu_shares" bundle:""`
|
||||||
CPUQuota int64 `compose:"cpu_quota" bundle:""`
|
CPUQuota int64 `compose:"cpu_quota" bundle:""`
|
||||||
|
CPULimit int64 `compose:"" bundle:""`
|
||||||
|
CPUReservation int64 `compose:"" bundle:""`
|
||||||
CapAdd []string `compose:"cap_add" bundle:""`
|
CapAdd []string `compose:"cap_add" bundle:""`
|
||||||
CapDrop []string `compose:"cap_drop" bundle:""`
|
CapDrop []string `compose:"cap_drop" bundle:""`
|
||||||
Expose []string `compose:"expose" bundle:""`
|
Expose []string `compose:"expose" bundle:""`
|
||||||
@ -92,6 +94,7 @@ type ServiceConfig struct {
|
|||||||
Stdin bool `compose:"stdin_open" bundle:""`
|
Stdin bool `compose:"stdin_open" bundle:""`
|
||||||
Tty bool `compose:"tty" bundle:""`
|
Tty bool `compose:"tty" bundle:""`
|
||||||
MemLimit yaml.MemStringorInt `compose:"mem_limit" bundle:""`
|
MemLimit yaml.MemStringorInt `compose:"mem_limit" bundle:""`
|
||||||
|
MemReservation yaml.MemStringorInt `compose:"" bundle:""`
|
||||||
TmpFs []string `compose:"tmpfs" bundle:""`
|
TmpFs []string `compose:"tmpfs" bundle:""`
|
||||||
Dockerfile string `compose:"dockerfile" bundle:""`
|
Dockerfile string `compose:"dockerfile" bundle:""`
|
||||||
Replicas int `compose:"replicas" bundle:""`
|
Replicas int `compose:"replicas" bundle:""`
|
||||||
|
|||||||
@ -17,19 +17,22 @@ limitations under the License.
|
|||||||
package compose
|
package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
libcomposeyaml "github.com/docker/libcompose/yaml"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
libcomposeyaml "github.com/docker/libcompose/yaml"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
"github.com/docker/cli/cli/compose/types"
|
"github.com/docker/cli/cli/compose/types"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/kubernetes/kompose/pkg/kobject"
|
"github.com/kubernetes/kompose/pkg/kobject"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// converts os.Environ() ([]string) to map[string]string
|
// converts os.Environ() ([]string) to map[string]string
|
||||||
@ -191,25 +194,46 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
|
|||||||
serviceConfig.Command = composeServiceConfig.Entrypoint
|
serviceConfig.Command = composeServiceConfig.Entrypoint
|
||||||
serviceConfig.Args = composeServiceConfig.Command
|
serviceConfig.Args = composeServiceConfig.Command
|
||||||
|
|
||||||
//Handling replicas
|
//
|
||||||
if composeServiceConfig.Deploy.Replicas != nil {
|
// Deploy keys
|
||||||
serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas)
|
//
|
||||||
}
|
|
||||||
|
|
||||||
// This is a bit messy since we use yaml.MemStringorInt
|
if (composeServiceConfig.Deploy.Resources != types.Resources{}) {
|
||||||
|
|
||||||
|
// memory:
|
||||||
// TODO: Refactor yaml.MemStringorInt in kobject.go to int64
|
// TODO: Refactor yaml.MemStringorInt in kobject.go to int64
|
||||||
// Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing
|
// 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)
|
serviceConfig.MemLimit = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Limits.MemoryBytes)
|
||||||
}
|
serviceConfig.MemReservation = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Reservations.MemoryBytes)
|
||||||
//Here we handle all Docker Compose Deploy keys
|
|
||||||
|
|
||||||
//Handling restart-policy
|
// 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 {
|
if composeServiceConfig.Deploy.RestartPolicy != nil {
|
||||||
serviceConfig.Restart = composeServiceConfig.Deploy.RestartPolicy.Condition
|
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:
|
// TODO: Build is not yet supported, see:
|
||||||
// https://github.com/docker/cli/blob/master/cli/compose/types/types.go#L9
|
// 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
|
// Configure the resource limits
|
||||||
|
if service.MemLimit != 0 || service.CPULimit != 0 {
|
||||||
|
resourceLimit := api.ResourceList{}
|
||||||
|
|
||||||
if service.MemLimit != 0 {
|
if service.MemLimit != 0 {
|
||||||
memoryResourceList := api.ResourceList{
|
resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
|
||||||
api.ResourceMemory: *resource.NewQuantity(
|
|
||||||
int64(service.MemLimit), "RandomStringForFormat")}
|
|
||||||
template.Spec.Containers[0].Resources.Limits = memoryResourceList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}
|
podSecurityContext := &api.PodSecurityContext{}
|
||||||
|
|
||||||
//set pid namespace mode
|
//set pid namespace mode
|
||||||
if service.Pid != "" {
|
if service.Pid != "" {
|
||||||
if service.Pid == "host" {
|
if service.Pid == "host" {
|
||||||
|
|||||||
@ -78,7 +78,7 @@ 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) {
|
func TestCreateServiceWithMemLimit(t *testing.T) {
|
||||||
|
|
||||||
@ -102,6 +102,7 @@ func TestCreateServiceWithMemLimit(t *testing.T) {
|
|||||||
Privileged: true,
|
Privileged: true,
|
||||||
Restart: "always",
|
Restart: "always",
|
||||||
MemLimit: 1337,
|
MemLimit: 1337,
|
||||||
|
MemReservation: 1338,
|
||||||
}
|
}
|
||||||
|
|
||||||
// An example object generated via k8s runtime.Objects()
|
// 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"))
|
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 {
|
for _, obj := range objects {
|
||||||
if deploy, ok := obj.(*extensions.Deployment); ok {
|
if deploy, ok := obj.(*extensions.Deployment); ok {
|
||||||
memTest, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64()
|
memLimit, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64()
|
||||||
if memTest != 1337 {
|
if memLimit != 1337 {
|
||||||
t.Errorf("Expected 1337 for mem_limit check, got %v", memTest)
|
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 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
|
# 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"
|
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": {
|
"resources": {
|
||||||
"limits": {
|
"limits": {
|
||||||
|
"cpu": "1",
|
||||||
"memory": "52428800"
|
"memory": "52428800"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"memory": "20971520"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"volumeMounts": [
|
"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": {
|
"resources": {
|
||||||
"limits": {
|
"limits": {
|
||||||
|
"cpu": "1",
|
||||||
"memory": "52428800"
|
"memory": "52428800"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"memory": "20971520"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"volumeMounts": [
|
"volumeMounts": [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user