-
Notifications
You must be signed in to change notification settings - Fork 618
/
Copy pathtask_linux.go
223 lines (196 loc) · 7.8 KB
/
task_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// +build linux
// Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package task
import (
"path/filepath"
"time"
apicontainerstatus "github.com/aws/amazon-ecs-agent/agent/api/container/status"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/taskresource"
"github.com/aws/amazon-ecs-agent/agent/taskresource/cgroup"
resourcestatus "github.com/aws/amazon-ecs-agent/agent/taskresource/status"
resourcetype "github.com/aws/amazon-ecs-agent/agent/taskresource/types"
"github.com/cihub/seelog"
docker "github.com/fsouza/go-dockerclient"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
const (
//memorySwappinessDefault is the expected default value for this platform. This is used in task_windows.go
//and is maintained here for unix default. Also used for testing
memorySwappinessDefault = 0
defaultCPUPeriod = 100 * time.Millisecond // 100ms
// With a 100ms CPU period, we can express 0.01 vCPU to 10 vCPUs
maxTaskVCPULimit = 10
// Reference: http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
minimumCPUShare = 2
minimumCPUPercent = 0
bytesPerMegabyte = 1024 * 1024
)
// PlatformFields consists of fields specific to Linux for a task
type PlatformFields struct{}
func (task *Task) adjustForPlatform(cfg *config.Config) {
task.lock.Lock()
defer task.lock.Unlock()
task.MemoryCPULimitsEnabled = cfg.TaskCPUMemLimit.Enabled()
}
func (task *Task) initializeCgroupResourceSpec(cgroupPath string, resourceFields *taskresource.ResourceFields) error {
cgroupRoot, err := task.BuildCgroupRoot()
if err != nil {
return errors.Wrapf(err, "cgroup resource: unable to determine cgroup root for task")
}
resSpec, err := task.BuildLinuxResourceSpec()
if err != nil {
return errors.Wrapf(err, "cgroup resource: unable to build resource spec for task")
}
cgroupResource := cgroup.NewCgroupResource(task.Arn, resourceFields.Control,
resourceFields.IOUtil, cgroupRoot, cgroupPath, resSpec)
task.AddResource(resourcetype.CgroupKey, cgroupResource)
for _, container := range task.Containers {
container.BuildResourceDependency(cgroupResource.GetName(),
resourcestatus.ResourceStatus(cgroup.CgroupCreated),
apicontainerstatus.ContainerPulled)
}
return nil
}
func getCanonicalPath(path string) string { return path }
// BuildCgroupRoot helps build the task cgroup prefix
// Example: /ecs/task-id
func (task *Task) BuildCgroupRoot() (string, error) {
taskID, err := task.GetID()
if err != nil {
return "", errors.Wrapf(err, "task build cgroup root: unable to get task-id from task ARN: %s", task.Arn)
}
return filepath.Join(config.DefaultTaskCgroupPrefix, taskID), nil
}
// BuildLinuxResourceSpec returns a linuxResources object for the task cgroup
func (task *Task) BuildLinuxResourceSpec() (specs.LinuxResources, error) {
linuxResourceSpec := specs.LinuxResources{}
// If task level CPU limits are requested, set CPU quota + CPU period
// Else set CPU shares
if task.CPU > 0 {
linuxCPUSpec, err := task.buildExplicitLinuxCPUSpec()
if err != nil {
return specs.LinuxResources{}, err
}
linuxResourceSpec.CPU = &linuxCPUSpec
} else {
linuxCPUSpec := task.buildImplicitLinuxCPUSpec()
linuxResourceSpec.CPU = &linuxCPUSpec
}
// Validate and build task memory spec
// NOTE: task memory specifications are optional
if task.Memory > 0 {
linuxMemorySpec, err := task.buildLinuxMemorySpec()
if err != nil {
return specs.LinuxResources{}, err
}
linuxResourceSpec.Memory = &linuxMemorySpec
}
return linuxResourceSpec, nil
}
// buildExplicitLinuxCPUSpec builds CPU spec when task CPU limits are
// explicitly requested
func (task *Task) buildExplicitLinuxCPUSpec() (specs.LinuxCPU, error) {
if task.CPU > maxTaskVCPULimit {
return specs.LinuxCPU{},
errors.Errorf("task CPU spec builder: unsupported CPU limits, requested=%f, max-supported=%d",
task.CPU, maxTaskVCPULimit)
}
taskCPUPeriod := uint64(defaultCPUPeriod / time.Microsecond)
taskCPUQuota := int64(task.CPU * float64(taskCPUPeriod))
// TODO: DefaultCPUPeriod only permits 10VCPUs.
// Adaptive calculation of CPUPeriod required for further support
// (samuelkarp) The largest available EC2 instance in terms of CPU count is a x1.32xlarge,
// with 128 vCPUs. If we assume a fixed evaluation period of 100ms (100000us),
// we'd need a quota of 12800000us, which is longer than the maximum of 1000000.
// For 128 vCPUs, we'd probably need something like a 1ms (1000us - the minimum)
// evaluation period, an 128000us quota in order to stay within the min/max limits.
return specs.LinuxCPU{
Quota: &taskCPUQuota,
Period: &taskCPUPeriod,
}, nil
}
// buildImplicitLinuxCPUSpec builds the implicit task CPU spec when
// task CPU and memory limit feature is enabled
func (task *Task) buildImplicitLinuxCPUSpec() specs.LinuxCPU {
// If task level CPU limits are missing,
// aggregate container CPU shares when present
var taskCPUShares uint64
for _, container := range task.Containers {
if container.CPU > 0 {
taskCPUShares += uint64(container.CPU)
}
}
// If there are are no CPU limits at task or container level,
// default task CPU shares
if taskCPUShares == 0 {
// Set default CPU shares
taskCPUShares = minimumCPUShare
}
return specs.LinuxCPU{
Shares: &taskCPUShares,
}
}
// buildLinuxMemorySpec validates and builds the task memory spec
func (task *Task) buildLinuxMemorySpec() (specs.LinuxMemory, error) {
// If task memory limit is not present, cgroup parent memory is not set
// If task memory limit is set, ensure that no container
// of this task has a greater request
for _, container := range task.Containers {
containerMemoryLimit := int64(container.Memory)
if containerMemoryLimit > task.Memory {
return specs.LinuxMemory{},
errors.Errorf("task memory spec builder: container memory limit(%d) greater than task memory limit(%d)",
containerMemoryLimit, task.Memory)
}
}
// Kernel expects memory to be expressed in bytes
memoryBytes := task.Memory * bytesPerMegabyte
return specs.LinuxMemory{
Limit: &memoryBytes,
}, nil
}
// platformHostConfigOverride to override platform specific feature sets
func (task *Task) platformHostConfigOverride(hostConfig *docker.HostConfig) error {
// Override cgroup parent
return task.overrideCgroupParent(hostConfig)
}
// overrideCgroupParent updates hostconfig with cgroup parent when task cgroups
// are enabled
func (task *Task) overrideCgroupParent(hostConfig *docker.HostConfig) error {
task.lock.RLock()
defer task.lock.RUnlock()
if task.MemoryCPULimitsEnabled {
cgroupRoot, err := task.BuildCgroupRoot()
if err != nil {
return errors.Wrapf(err, "task cgroup override: unable to obtain cgroup root for task: %s", task.Arn)
}
hostConfig.CgroupParent = cgroupRoot
}
return nil
}
// dockerCPUShares converts containerCPU shares if needed as per the logic stated below:
// Docker silently converts 0 to 1024 CPU shares, which is probably not what we
// want. Instead, we convert 0 to 2 to be closer to expected behavior. The
// reason for 2 over 1 is that 1 is an invalid value (Linux's choice, not Docker's).
func (task *Task) dockerCPUShares(containerCPU uint) int64 {
if containerCPU <= 1 {
seelog.Debugf(
"Converting CPU shares to allowed minimum of 2 for task arn: [%s] and cpu shares: %d",
task.Arn, containerCPU)
return 2
}
return int64(containerCPU)
}