Merge pull request #748 from docker/aci_resource_req

Aci resource reservation & limits
This commit is contained in:
Guillaume Tardif 2020-10-09 12:13:35 +02:00 committed by GitHub
commit 7fb976732f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 731 additions and 449 deletions

View File

@ -54,7 +54,7 @@ func ContainerToComposeProject(r containers.ContainerConfig) (types.Project, err
Environment: toComposeEnvs(r.Environment),
Deploy: &types.DeployConfig{
Resources: types.Resources{
Limits: &types.Resource{
Reservations: &types.Resource{
NanoCPUs: fmt.Sprintf("%f", r.CPULimit),
MemoryBytes: types.UnitBytes(r.MemLimit.Value()),
},

View File

@ -175,10 +175,6 @@ func getDNSSidecar(containers []containerinstance.Container) containerinstance.C
Image: to.StringPtr(dnsSidecarImage),
Command: &alpineCmd,
Resources: &containerinstance.ResourceRequirements{
Limits: &containerinstance.ResourceLimits{
MemoryInGB: to.Float64Ptr(0.1), // "The memory requirement should be in incrememts of 0.1 GB."
CPU: to.Float64Ptr(0.01), // "The CPU requirement should be in incrememts of 0.01."
},
Requests: &containerinstance.ResourceRequests{
MemoryInGB: to.Float64Ptr(0.1),
CPU: to.Float64Ptr(0.01),
@ -357,40 +353,75 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c
volumes = &allVolumes
}
memLimit := 1. // Default 1 Gb
var cpuLimit float64 = 1
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
if s.Deploy.Resources.Limits.MemoryBytes != 0 {
memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
}
if s.Deploy.Resources.Limits.NanoCPUs != "" {
cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
if err != nil {
return containerinstance.Container{}, err
}
}
resource, err := s.getResourceRequestsLimits()
if err != nil {
return containerinstance.Container{}, err
}
return containerinstance.Container{
Name: to.StringPtr(s.Name),
ContainerProperties: &containerinstance.ContainerProperties{
Image: to.StringPtr(s.Image),
Command: to.StringSlicePtr(s.Command),
EnvironmentVariables: getEnvVariables(s.Environment),
Resources: &containerinstance.ResourceRequirements{
Limits: &containerinstance.ResourceLimits{
MemoryInGB: to.Float64Ptr(memLimit),
CPU: to.Float64Ptr(cpuLimit),
},
Requests: &containerinstance.ResourceRequests{
MemoryInGB: to.Float64Ptr(memLimit), // TODO: use the memory requests here and not limits
CPU: to.Float64Ptr(cpuLimit), // TODO: use the cpu requests here and not limits
},
},
VolumeMounts: volumes,
Resources: resource,
VolumeMounts: volumes,
},
}, nil
}
func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.ResourceRequirements, error) {
memRequest := 1. // Default 1 Gb
var cpuRequest float64 = 1
var err error
hasMemoryRequest := func() bool {
return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.MemoryBytes != 0
}
hasCPURequest := func() bool {
return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
}
if hasMemoryRequest() {
memRequest = bytesToGb(s.Deploy.Resources.Reservations.MemoryBytes)
}
if hasCPURequest() {
cpuRequest, err = strconv.ParseFloat(s.Deploy.Resources.Reservations.NanoCPUs, 0)
if err != nil {
return nil, err
}
}
memLimit := memRequest
cpuLimit := cpuRequest
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
if s.Deploy.Resources.Limits.MemoryBytes != 0 {
memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
if !hasMemoryRequest() {
memRequest = memLimit
}
}
if s.Deploy.Resources.Limits.NanoCPUs != "" {
cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
if err != nil {
return nil, err
}
if !hasCPURequest() {
cpuRequest = cpuLimit
}
}
}
resources := containerinstance.ResourceRequirements{
Requests: &containerinstance.ResourceRequests{
MemoryInGB: to.Float64Ptr(memRequest),
CPU: to.Float64Ptr(cpuRequest),
},
Limits: &containerinstance.ResourceLimits{
MemoryInGB: to.Float64Ptr(memLimit),
CPU: to.Float64Ptr(cpuLimit),
},
}
return &resources, nil
}
func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.EnvironmentVariable {
result := []containerinstance.EnvironmentVariable{}
for key, value := range composeEnv {
@ -413,6 +444,10 @@ func bytesToGb(b types.UnitBytes) float64 {
return math.Round(f*100) / 100
}
func gbToBytes(memInBytes float64) uint64 {
return uint64(memInBytes * 1024 * 1024 * 1024)
}
// ContainerGroupToServiceStatus convert from an ACI container definition to service status
func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container, region string) compose.ServiceStatus {
var replicas = 1
@ -438,18 +473,27 @@ func fqdn(group containerinstance.ContainerGroup, region string) string {
// ContainerGroupToContainer composes a Container from an ACI container definition
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
memLimits := 0.
if cc.Resources != nil &&
cc.Resources.Limits != nil &&
cc.Resources.Limits.MemoryInGB != nil {
memLimits = *cc.Resources.Limits.MemoryInGB * 1024 * 1024 * 1024
}
memLimits := uint64(0)
memRequest := uint64(0)
cpuLimit := 0.
if cc.Resources != nil &&
cc.Resources.Limits != nil &&
cc.Resources.Limits.CPU != nil {
cpuLimit = *cc.Resources.Limits.CPU
cpuReservation := 0.
if cc.Resources != nil {
if cc.Resources.Limits != nil {
if cc.Resources.Limits.MemoryInGB != nil {
memLimits = gbToBytes(*cc.Resources.Limits.MemoryInGB)
}
if cc.Resources.Limits.CPU != nil {
cpuLimit = *cc.Resources.Limits.CPU
}
}
if cc.Resources.Requests != nil {
if cc.Resources.Requests.MemoryInGB != nil {
memRequest = gbToBytes(*cc.Resources.Requests.MemoryInGB)
}
if cc.Resources.Requests.CPU != nil {
cpuReservation = *cc.Resources.Requests.CPU
}
}
}
command := ""
@ -468,26 +512,30 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
}
}
var config *containers.RuntimeConfig = &containers.RuntimeConfig{FQDN: fqdn(cg, region)}
if envVars != nil {
config.Env = envVars
config := &containers.RuntimeConfig{
FQDN: fqdn(cg, region),
Env: envVars,
}
hostConfig := &containers.HostConfig{
CPULimit: cpuLimit,
CPUReservation: cpuReservation,
MemoryLimit: memLimits,
MemoryReservation: memRequest,
RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy),
}
c := containers.Container{
ID: containerID,
Status: status,
Image: to.String(cc.Image),
Command: command,
CPUTime: 0,
CPULimit: cpuLimit,
MemoryUsage: 0,
MemoryLimit: uint64(memLimits),
PidsCurrent: 0,
PidsLimit: 0,
Labels: nil,
Ports: ToPorts(cg.IPAddress, *cc.Ports),
Platform: platform,
RestartPolicyCondition: toContainerRestartPolicy(cg.RestartPolicy),
Config: config,
ID: containerID,
Status: status,
Image: to.String(cc.Image),
Command: command,
CPUTime: 0,
MemoryUsage: 0,
PidsCurrent: 0,
PidsLimit: 0,
Ports: ToPorts(cg.IPAddress, *cc.Ports),
Platform: platform,
Config: config,
HostConfig: hostConfig,
}
return c

View File

@ -83,6 +83,10 @@ func TestContainerGroupToContainer(t *testing.T) {
Resources: &containerinstance.ResourceRequirements{
Limits: &containerinstance.ResourceLimits{
CPU: to.Float64Ptr(3),
MemoryInGB: to.Float64Ptr(0.2),
},
Requests: &containerinstance.ResourceRequests{
CPU: to.Float64Ptr(2),
MemoryInGB: to.Float64Ptr(0.1),
},
},
@ -90,13 +94,11 @@ func TestContainerGroupToContainer(t *testing.T) {
}
var expectedContainer = containers.Container{
ID: "myContainerID",
Status: "Running",
Image: "sha256:666",
Command: "mycommand",
CPULimit: 3,
MemoryLimit: 107374182,
Platform: "Linux",
ID: "myContainerID",
Status: "Running",
Image: "sha256:666",
Command: "mycommand",
Platform: "Linux",
Ports: []containers.Port{{
HostPort: uint32(80),
ContainerPort: uint32(80),
@ -106,7 +108,13 @@ func TestContainerGroupToContainer(t *testing.T) {
Config: &containers.RuntimeConfig{
FQDN: "myapp.eastus.azurecontainer.io",
},
RestartPolicyCondition: "any",
HostConfig: &containers.HostConfig{
CPULimit: 3,
CPUReservation: 2,
MemoryLimit: gbToBytes(0.2),
MemoryReservation: gbToBytes(0.1),
RestartPolicy: "any",
},
}
container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer, "eastus")
@ -536,8 +544,9 @@ func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing
assert.Assert(t, group.IPAddress == nil)
}
func TestComposeContainerGroupToContainerResourceLimits(t *testing.T) {
_0_1Gb := 0.1 * 1024 * 1024 * 1024
var _0_1Gb = gbToBytes(0.1)
func TestComposeContainerGroupToContainerResourceRequests(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
@ -545,7 +554,7 @@ func TestComposeContainerGroupToContainerResourceLimits(t *testing.T) {
Image: "image1",
Deploy: &types.DeployConfig{
Resources: types.Resources{
Limits: &types.Resource{
Reservations: &types.Resource{
NanoCPUs: "0.1",
MemoryBytes: types.UnitBytes(_0_1Gb),
},
@ -558,12 +567,48 @@ func TestComposeContainerGroupToContainerResourceLimits(t *testing.T) {
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
request := *((*group.Containers)[0]).Resources.Requests
assert.Equal(t, *request.CPU, float64(0.1))
assert.Equal(t, *request.MemoryInGB, float64(0.1))
limits := *((*group.Containers)[0]).Resources.Limits
assert.Equal(t, *limits.CPU, float64(0.1))
assert.Equal(t, *limits.MemoryInGB, float64(0.1))
}
func TestComposeContainerGroupToContainerResourceLimitsDefaults(t *testing.T) {
func TestComposeContainerGroupToContainerResourceRequestsAndLimits(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
Deploy: &types.DeployConfig{
Resources: types.Resources{
Reservations: &types.Resource{
NanoCPUs: "0.1",
MemoryBytes: types.UnitBytes(_0_1Gb),
},
Limits: &types.Resource{
NanoCPUs: "0.3",
MemoryBytes: types.UnitBytes(2 * _0_1Gb),
},
},
},
},
},
}
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
request := *((*group.Containers)[0]).Resources.Requests
assert.Equal(t, *request.CPU, float64(0.1))
assert.Equal(t, *request.MemoryInGB, float64(0.1))
limits := *((*group.Containers)[0]).Resources.Limits
assert.Equal(t, *limits.CPU, float64(0.3))
assert.Equal(t, *limits.MemoryInGB, float64(0.2))
}
func TestComposeContainerGroupToContainerResourceLimitsOnly(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
@ -572,6 +617,35 @@ func TestComposeContainerGroupToContainerResourceLimitsDefaults(t *testing.T) {
Deploy: &types.DeployConfig{
Resources: types.Resources{
Limits: &types.Resource{
NanoCPUs: "0.3",
MemoryBytes: types.UnitBytes(2 * _0_1Gb),
},
},
},
},
},
}
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
request := *((*group.Containers)[0]).Resources.Requests
assert.Equal(t, *request.CPU, float64(0.3))
assert.Equal(t, *request.MemoryInGB, float64(0.2))
limits := *((*group.Containers)[0]).Resources.Limits
assert.Equal(t, *limits.CPU, float64(0.3))
assert.Equal(t, *limits.MemoryInGB, float64(0.2))
}
func TestComposeContainerGroupToContainerResourceRequestsDefaults(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
Deploy: &types.DeployConfig{
Resources: types.Resources{
Reservations: &types.Resource{
NanoCPUs: "",
MemoryBytes: 0,
},
@ -584,9 +658,9 @@ func TestComposeContainerGroupToContainerResourceLimitsDefaults(t *testing.T) {
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
limits := *((*group.Containers)[0]).Resources.Limits
assert.Equal(t, *limits.CPU, float64(1))
assert.Equal(t, *limits.MemoryInGB, float64(1))
request := *((*group.Containers)[0]).Resources.Requests
assert.Equal(t, *request.CPU, float64(1))
assert.Equal(t, *request.MemoryInGB, float64(1))
}
func TestComposeContainerGroupToContainerenvVar(t *testing.T) {

View File

@ -37,30 +37,37 @@ var RestartPolicyList = []string{RestartPolicyNone, RestartPolicyAny, RestartPol
// Container represents a created container
type Container struct {
ID string
Status string
Image string
Command string
CPUTime uint64
CPULimit float64
MemoryUsage uint64
MemoryLimit uint64
PidsCurrent uint64
PidsLimit uint64
Config *RuntimeConfig `json:",omitempty"`
Labels []string `json:",omitempty"`
Ports []Port `json:",omitempty"`
Platform string
RestartPolicyCondition string
ID string
Status string
Image string
Command string
CPUTime uint64
MemoryUsage uint64
PidsCurrent uint64
PidsLimit uint64
Config *RuntimeConfig `json:",omitempty"`
HostConfig *HostConfig `json:",omitempty"`
Ports []Port `json:",omitempty"`
Platform string
}
// RuntimeConfig config of a created container
type RuntimeConfig struct {
Env map[string]string `json:",omitempty"`
Labels []string `json:",omitempty"`
Env map[string]string `json:",omitempty"`
// FQDN is the fqdn to use
FQDN string `json:"fqdn,omitempty"`
}
// HostConfig config of the container host
type HostConfig struct {
RestartPolicy string
CPUReservation float64
CPULimit float64
MemoryReservation uint64
MemoryLimit uint64
}
// Port represents a published port of a container
type Port struct {
// HostPort is the port number on the host

View File

@ -4,11 +4,15 @@
"Image": "nginx",
"Command": "",
"CPUTime": 0,
"CPULimit": 0,
"MemoryUsage": 0,
"MemoryLimit": 0,
"PidsCurrent": 0,
"PidsLimit": 0,
"Platform": "Linux",
"RestartPolicyCondition": "none"
"HostConfig": {
"RestartPolicy": "none",
"CPUReservation": 0,
"CPULimit": 0,
"MemoryReservation": 0,
"MemoryLimit": 0
},
"Platform": "Linux"
}

View File

@ -71,7 +71,9 @@ func (cs *containerService) Inspect(ctx context.Context, id string) (containers.
ID: "id",
Image: "nginx",
Platform: "Linux",
RestartPolicyCondition: "none",
HostConfig: &containers.HostConfig{
RestartPolicy: "none",
},
}, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -45,14 +45,20 @@ message Container {
string command = 4;
uint64 cpu_time = 5;
uint64 memory_usage = 6;
uint64 memory_limit = 7;
uint64 pids_current = 8;
uint64 pids_limit = 9;
repeated string labels = 10;
repeated Port ports = 11;
uint64 cpu_limit = 12;
string platform = 13;
string restart_policy_condition = 14;
uint64 pids_current = 7;
uint64 pids_limit = 8;
repeated string labels = 9;
repeated Port ports = 10;
string platform = 11;
HostConfig host_config = 12;
}
message HostConfig {
uint64 memory_reservation = 1;
uint64 memory_limit = 2;
uint64 cpu_reservation = 3;
uint64 cpu_limit = 4;
string restart_policy = 5;
}
message InspectRequest {

View File

@ -122,20 +122,24 @@ func (p *proxy) Logs(request *containersv1.LogsRequest, stream containersv1.Cont
func toGrpcContainer(c containers.Container) *containersv1.Container {
return &containersv1.Container{
Id: c.ID,
Image: c.Image,
Status: c.Status,
Command: c.Command,
CpuTime: c.CPUTime,
MemoryUsage: c.MemoryUsage,
MemoryLimit: c.MemoryLimit,
Platform: c.Platform,
PidsCurrent: c.PidsCurrent,
PidsLimit: c.PidsLimit,
Labels: c.Labels,
Ports: portsToGrpc(c.Ports),
CpuLimit: uint64(c.CPULimit),
RestartPolicyCondition: c.RestartPolicyCondition,
Id: c.ID,
Image: c.Image,
Status: c.Status,
Command: c.Command,
CpuTime: c.CPUTime,
MemoryUsage: c.MemoryUsage,
Platform: c.Platform,
PidsCurrent: c.PidsCurrent,
PidsLimit: c.PidsLimit,
Labels: c.Config.Labels,
Ports: portsToGrpc(c.Ports),
HostConfig: &containersv1.HostConfig{
MemoryReservation: c.HostConfig.MemoryReservation,
MemoryLimit: c.HostConfig.MemoryLimit,
CpuReservation: uint64(c.HostConfig.CPUReservation),
CpuLimit: uint64(c.HostConfig.CPULimit),
RestartPolicy: c.HostConfig.RestartPolicy,
},
}
}

View File

@ -249,8 +249,9 @@ func TestRunVolume(t *testing.T) {
containerInspect, err := ParseContainerInspect(res.Stdout())
assert.NilError(t, err)
assert.Equal(t, containerInspect.Platform, "Linux")
assert.Equal(t, containerInspect.CPULimit, 1.0)
assert.Equal(t, containerInspect.RestartPolicyCondition, containers.RestartPolicyNone)
assert.Equal(t, containerInspect.HostConfig.CPULimit, 1.0)
assert.Equal(t, containerInspect.HostConfig.CPUReservation, 1.0)
assert.Equal(t, containerInspect.HostConfig.RestartPolicy, containers.RestartPolicyNone)
assert.Assert(t, is.Len(containerInspect.Ports, 1))
hostIP = containerInspect.Ports[0].HostIP
@ -388,16 +389,18 @@ func TestContainerRunAttached(t *testing.T) {
}
return poll.Continue("waiting for container to be running, current inspect result: \n%s", res.Combined())
}
poll.WaitOn(t, checkRunning, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second))
poll.WaitOn(t, checkRunning, poll.WithDelay(5*time.Second), poll.WithTimeout(90*time.Second))
inspectRes := c.RunDockerCmd("inspect", container)
containerInspect, err := ParseContainerInspect(inspectRes.Stdout())
assert.NilError(t, err)
assert.Equal(t, containerInspect.Platform, "Linux")
assert.Equal(t, containerInspect.CPULimit, 0.1)
assert.Equal(t, containerInspect.MemoryLimit, uint64(107374182))
assert.Equal(t, containerInspect.RestartPolicyCondition, containers.RestartPolicyOnFailure)
assert.Equal(t, containerInspect.HostConfig.CPULimit, 0.1)
assert.Equal(t, containerInspect.HostConfig.MemoryLimit, uint64(107374182))
assert.Equal(t, containerInspect.HostConfig.CPUReservation, 0.1)
assert.Equal(t, containerInspect.HostConfig.MemoryReservation, uint64(107374182))
assert.Equal(t, containerInspect.HostConfig.RestartPolicy, containers.RestartPolicyOnFailure)
assert.Assert(t, is.Len(containerInspect.Ports, 1))
port := containerInspect.Ports[0]
@ -480,6 +483,39 @@ func overwriteFileStorageAccount(t *testing.T, absComposefileName string, storag
assert.NilError(t, err)
}
func TestUpResources(t *testing.T) {
const (
composeProjectName = "testresources"
serverContainer = composeProjectName + "_web"
wordsContainer = composeProjectName + "_words"
)
c := NewParallelE2eCLI(t, binDir)
setupTestResourceGroup(t, c)
t.Run("compose up", func(t *testing.T) {
c.RunDockerCmd("compose", "up", "-f", "../composefiles/aci-demo/aci_demo_port_resources.yaml", "--project-name", composeProjectName)
res := c.RunDockerCmd("inspect", serverContainer)
webInspect, err := ParseContainerInspect(res.Stdout())
assert.NilError(t, err)
assert.Equal(t, webInspect.HostConfig.CPULimit, 0.7)
assert.Equal(t, webInspect.HostConfig.MemoryLimit, uint64(1073741824))
assert.Equal(t, webInspect.HostConfig.CPUReservation, 0.5)
assert.Equal(t, webInspect.HostConfig.MemoryReservation, uint64(536870912))
res = c.RunDockerCmd("inspect", wordsContainer)
wordsInspect, err := ParseContainerInspect(res.Stdout())
assert.NilError(t, err)
assert.Equal(t, wordsInspect.HostConfig.CPULimit, 0.5)
assert.Equal(t, wordsInspect.HostConfig.MemoryLimit, uint64(751619276))
assert.Equal(t, wordsInspect.HostConfig.CPUReservation, 0.5)
assert.Equal(t, wordsInspect.HostConfig.MemoryReservation, uint64(751619276))
})
}
func TestUpUpdate(t *testing.T) {
const (
composeProjectName = "acidemo"

View File

@ -1,14 +0,0 @@
services:
db:
build: db
image: gtardif/sentences-db
words:
build: words
image: gtardif/sentences-api
web:
build: web
image: gtardif/sentences-web
ports:
- "80:80"

View File

@ -0,0 +1,24 @@
services:
db:
image: gtardif/sentences-db
words:
image: gtardif/sentences-api
deploy:
resources:
reservations:
cpus: '0.5'
memory: 0.7G
web:
image: gtardif/sentences-web
ports:
- "80:80"
deploy:
resources:
limits:
cpus: '0.7'
memory: 1G
reservations:
cpus: '0.5'
memory: 0.5G

View File

@ -4,11 +4,15 @@
"Image": "nginx",
"Command": "",
"CPUTime": 0,
"CPULimit": 0,
"MemoryUsage": 0,
"MemoryLimit": 0,
"PidsCurrent": 0,
"PidsLimit": 0,
"Platform": "Linux",
"RestartPolicyCondition": "none"
"HostConfig": {
"RestartPolicy": "none",
"CPUReservation": 0,
"CPULimit": 0,
"MemoryReservation": 0,
"MemoryLimit": 0
},
"Platform": "Linux"
}