mirror of https://github.com/docker/compose.git
Introduce x-aws-autoscale to support CPU based autoscaling
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
3685cbbf8d
commit
757b9bb221
|
@ -8,38 +8,49 @@ This document describes the mapping between compose application model and AWS co
|
||||||
This diagram shows compose model and on same line AWS components that get created as equivalent resources
|
This diagram shows compose model and on same line AWS components that get created as equivalent resources
|
||||||
|
|
||||||
```
|
```
|
||||||
+----------+ +-------------+ +-------------------+
|
+----------+ +-------------+ +-------------------+
|
||||||
| Project | | Cluster | | LoadBalancer |
|
| Project | . . . . . . . . . . . . . . | Cluster | . . . . . . . | LoadBalancer |
|
||||||
+-+--------+ +-------------+ +-------------------+
|
+-+--------+ +-------------+ +-------------------+
|
||||||
|
|
|
|
||||||
| +----------+ +-------------+ +----------------+ +-------------------+
|
| +----------+ +-------------++-------------------+ +-------------------+
|
||||||
+----+ Service | | Service | | TaskDefinition | | TargetGroup |
|
+----+ Service | . . . . . . . . . . | Service || TaskDefinition | | TargetGroup |
|
||||||
| +--+-------+ +-------------+ +----------------+ +-------------------+
|
| +--+-------+ +-------------++-------------------+-+ +-------------------+
|
||||||
| | +----------------+
|
| | | TaskRole |
|
||||||
| | x-aws-role, x-aws-policies | TaskRole |
|
| | +-------------------+-+
|
||||||
| | +----------------+
|
| | x-aws-role, x-aws-policies . . . . . . . . | TaskExecutionRole |
|
||||||
| | +---------+ +-------------+ +-------------------+
|
| | +-------------------+
|
||||||
| +--+ Ports | | IngressRule | | Listener |
|
| | +---------+
|
||||||
| | +---------+ +-------------+ +-------------------+
|
| +--+ Deploy |
|
||||||
|
| | +---------+ +-------------------+
|
||||||
|
| | x-aws-autoscale . . . . . . | ScalableTarget |
|
||||||
|
| | +-------------------+---+
|
||||||
|
| | | ScalingPolicy |
|
||||||
|
| | +-------------------+-+
|
||||||
|
| | | AutoScalingRole |
|
||||||
|
| | +-------------------+
|
||||||
| |
|
| |
|
||||||
|
| | +---------+ +-------------+ +-------------------+
|
||||||
|
| +--+ Ports | . . . . . . . | IngressRule +-----+ | Listener |
|
||||||
|
| | +---------+ +-------------+ | +-------------------+
|
||||||
|
| | |
|
||||||
| | +---------+ +---------------+ +------------------+
|
| | +---------+ +---------------+ +------------------+
|
||||||
| +--+ Secrets | | InitContainer | |TaskExecutionRole |
|
| +--+ Secrets | . . . . . . . | InitContainer | |TaskExecutionRole |
|
||||||
| | +---------+ +---------------+ +------------+-----+
|
| | +---------+ +---------------+ +------------+-----+
|
||||||
| | |
|
| | | |
|
||||||
| | +---------+ |
|
| | +---------+ | |
|
||||||
| +--+ Volumes | |
|
| +--+ Volumes | | |
|
||||||
| | +---------+ |
|
| | +---------+ | |
|
||||||
| | |
|
| | | |
|
||||||
| | +---------------+ | +------------------------------------------+
|
| | +---------------+ | | +-------------------+
|
||||||
| +--+ DeviceRequest | | | CapacityProvider || AutoscalingGroup |
|
| +--+ DeviceRequest | . . . . . . . . . . . . . . . | . . . . | . . . | CapacityProvider |
|
||||||
| +---------------+ | +------------------------------------------+
|
| +---------------+ | | +-------------------+--------+
|
||||||
| | | LaunchConfiguration |
|
| | | | AutoscalingGroup |
|
||||||
| +------------+ +---------------+ | +---------------------+
|
| +------------+ +---------------+ | | +---------------------+
|
||||||
+---+ Networks | | SecurityGroup | |
|
+---+ Networks | . . . . . . . . . | SecurityGroup +---+ | | LaunchConfiguration |
|
||||||
| +------------+ +---------------+ |
|
| +------------+ +---------------+ | +---------------------+
|
||||||
| |
|
| |
|
||||||
| +------------+ +---------------+ |
|
| +------------+ +---------------+ |
|
||||||
+---+ Secret | | Secret +--------------+
|
+---+ Secret | . . . . . . . . . | Secret +--------------+
|
||||||
+------------+ +---------------+
|
+------------+ +---------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -63,6 +74,6 @@ A `TaskExecutionRole` is also created per service, and is updated to grant acces
|
||||||
Services using a GPU (`DeviceRequest`) get the `Cluster` extended with an EC2 `CapacityProvider`, using an `AutoscalingGroup` to manage
|
Services using a GPU (`DeviceRequest`) get the `Cluster` extended with an EC2 `CapacityProvider`, using an `AutoscalingGroup` to manage
|
||||||
EC2 resources allocation based on a `LaunchConfiguration`. The latter uses ECS recommended AMI and machine type for GPU.
|
EC2 resources allocation based on a `LaunchConfiguration`. The latter uses ECS recommended AMI and machine type for GPU.
|
||||||
|
|
||||||
|
Service to declare `deploy.x-aws-autoscaling` get a `ScalingPolicy` created targeting specified the configured CPU usage metric
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License 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 ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
applicationautoscaling2 "github.com/aws/aws-sdk-go/service/applicationautoscaling"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/iam"
|
||||||
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *ecsAPIService) createAutoscalingPolicy(project *types.Project, resources awsResources, template *cloudformation.Template, service types.ServiceConfig) {
|
||||||
|
if service.Deploy == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v, ok := service.Deploy.Extensions[extensionAutoScaling]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
role := fmt.Sprintf("%sAutoScalingRole", normalizeResourceName(service.Name))
|
||||||
|
template.Resources[role] = &iam.Role{
|
||||||
|
AssumeRolePolicyDocument: ausocalingAssumeRolePolicyDocument,
|
||||||
|
Path: "/",
|
||||||
|
Policies: []iam.Role_Policy{
|
||||||
|
{
|
||||||
|
PolicyDocument: &PolicyDocument{
|
||||||
|
Statement: []PolicyStatement{
|
||||||
|
{
|
||||||
|
Effect: "Allow",
|
||||||
|
Action: []string{
|
||||||
|
actionAutoScaling,
|
||||||
|
actionDescribeService,
|
||||||
|
actionUpdateService,
|
||||||
|
actionGetMetrics,
|
||||||
|
},
|
||||||
|
Resource: []string{cloudformation.Ref(serviceResourceName(service.Name))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PolicyName: "service-autoscaling",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tags: serviceTags(project, service),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why isn't this just the service ARN ?????
|
||||||
|
resourceID := cloudformation.Join("/", []string{"service", resources.cluster, cloudformation.GetAtt(serviceResourceName(service.Name), "Name")})
|
||||||
|
|
||||||
|
target := fmt.Sprintf("%sScalableTarget", normalizeResourceName(service.Name))
|
||||||
|
template.Resources[target] = &applicationautoscaling.ScalableTarget{
|
||||||
|
MaxCapacity: 10,
|
||||||
|
MinCapacity: 0,
|
||||||
|
ResourceId: resourceID,
|
||||||
|
RoleARN: cloudformation.GetAtt(role, "Arn"),
|
||||||
|
ScalableDimension: applicationautoscaling2.ScalableDimensionEcsServiceDesiredCount,
|
||||||
|
ServiceNamespace: applicationautoscaling2.ServiceNamespaceEcs,
|
||||||
|
AWSCloudFormationDependsOn: []string{serviceResourceName(service.Name)},
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := fmt.Sprintf("%sScalingPolicy", normalizeResourceName(service.Name))
|
||||||
|
template.Resources[policy] = &applicationautoscaling.ScalingPolicy{
|
||||||
|
PolicyType: "TargetTrackingScaling",
|
||||||
|
PolicyName: policy,
|
||||||
|
ScalingTargetId: cloudformation.Ref(target),
|
||||||
|
StepScalingPolicyConfiguration: nil,
|
||||||
|
TargetTrackingScalingPolicyConfiguration: &applicationautoscaling.ScalingPolicy_TargetTrackingScalingPolicyConfiguration{
|
||||||
|
PredefinedMetricSpecification: &applicationautoscaling.ScalingPolicy_PredefinedMetricSpecification{
|
||||||
|
PredefinedMetricType: applicationautoscaling2.MetricTypeEcsserviceAverageCpuutilization,
|
||||||
|
},
|
||||||
|
ScaleOutCooldown: 60,
|
||||||
|
ScaleInCooldown: 60,
|
||||||
|
TargetValue: float64(v.(int)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License 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 ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
autoscaling "github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAutoScaling(t *testing.T) {
|
||||||
|
template := convertYaml(t, `
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
image: hello_world
|
||||||
|
deploy:
|
||||||
|
x-aws-autoscaling: 75
|
||||||
|
`)
|
||||||
|
target := template.Resources["FooScalableTarget"].(*autoscaling.ScalableTarget)
|
||||||
|
assert.Check(t, target != nil)
|
||||||
|
policy := template.Resources["FooScalingPolicy"].(*autoscaling.ScalingPolicy)
|
||||||
|
if policy == nil || policy.TargetTrackingScalingPolicyConfiguration == nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert.Check(t, policy.TargetTrackingScalingPolicyConfiguration.TargetValue == float64(75))
|
||||||
|
}
|
|
@ -91,7 +91,7 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources)
|
||||||
|
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
taskExecutionRole := b.createTaskExecutionRole(project, service, template)
|
taskExecutionRole := b.createTaskExecutionRole(project, service, template)
|
||||||
taskRole := b.createTaskRole(service, template)
|
taskRole := b.createTaskRole(project, service, template)
|
||||||
|
|
||||||
definition, err := b.createTaskExecution(project, service)
|
definition, err := b.createTaskExecution(project, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -183,6 +183,8 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources)
|
||||||
Tags: serviceTags(project, service),
|
Tags: serviceTags(project, service),
|
||||||
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.createAutoscalingPolicy(project, resources, template, service)
|
||||||
}
|
}
|
||||||
return template, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
@ -363,11 +365,12 @@ func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service
|
||||||
ecsTaskExecutionPolicy,
|
ecsTaskExecutionPolicy,
|
||||||
ecrReadOnlyPolicy,
|
ecrReadOnlyPolicy,
|
||||||
},
|
},
|
||||||
|
Tags: serviceTags(project, service),
|
||||||
}
|
}
|
||||||
return taskExecutionRole
|
return taskExecutionRole
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
|
func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
|
||||||
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
|
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
|
||||||
rolePolicies := []iam.Role_Policy{}
|
rolePolicies := []iam.Role_Policy{}
|
||||||
if roles, ok := service.Extensions[extensionRole]; ok {
|
if roles, ok := service.Extensions[extensionRole]; ok {
|
||||||
|
@ -388,6 +391,7 @@ func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cl
|
||||||
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
||||||
Policies: rolePolicies,
|
Policies: rolePolicies,
|
||||||
ManagedPolicyArns: managedPolicies,
|
ManagedPolicyArns: managedPolicies,
|
||||||
|
Tags: serviceTags(project, service),
|
||||||
}
|
}
|
||||||
return taskRole
|
return taskRole
|
||||||
}
|
}
|
||||||
|
|
48
ecs/iam.go
48
ecs/iam.go
|
@ -21,35 +21,35 @@ const (
|
||||||
ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
||||||
ecsEC2InstanceRole = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
|
ecsEC2InstanceRole = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
|
||||||
|
|
||||||
actionGetSecretValue = "secretsmanager:GetSecretValue"
|
actionGetSecretValue = "secretsmanager:GetSecretValue"
|
||||||
actionGetParameters = "ssm:GetParameters"
|
actionGetParameters = "ssm:GetParameters"
|
||||||
actionDecrypt = "kms:Decrypt"
|
actionDecrypt = "kms:Decrypt"
|
||||||
|
actionAutoScaling = "application-autoscaling:*"
|
||||||
|
actionGetMetrics = "cloudwatch:GetMetricStatistics"
|
||||||
|
actionDescribeService = "ecs:DescribeServices"
|
||||||
|
actionUpdateService = "ecs:UpdateService"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ecsTaskAssumeRolePolicyDocument = PolicyDocument{
|
var (
|
||||||
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
ecsTaskAssumeRolePolicyDocument = policyDocument("ecs-tasks.amazonaws.com")
|
||||||
Statement: []PolicyStatement{
|
ec2InstanceAssumeRolePolicyDocument = policyDocument("ec2.amazonaws.com")
|
||||||
{
|
ausocalingAssumeRolePolicyDocument = policyDocument("application-autoscaling.amazonaws.com")
|
||||||
Effect: "Allow",
|
)
|
||||||
Principal: PolicyPrincipal{
|
|
||||||
Service: "ecs-tasks.amazonaws.com",
|
|
||||||
},
|
|
||||||
Action: []string{"sts:AssumeRole"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var ec2InstanceAssumeRolePolicyDocument = PolicyDocument{
|
func policyDocument(service string) PolicyDocument {
|
||||||
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
return PolicyDocument{
|
||||||
Statement: []PolicyStatement{
|
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
||||||
{
|
Statement: []PolicyStatement{
|
||||||
Effect: "Allow",
|
{
|
||||||
Principal: PolicyPrincipal{
|
Effect: "Allow",
|
||||||
Service: "ec2.amazonaws.com",
|
Principal: PolicyPrincipal{
|
||||||
|
Service: service,
|
||||||
|
},
|
||||||
|
Action: []string{"sts:AssumeRole"},
|
||||||
},
|
},
|
||||||
Action: []string{"sts:AssumeRole"},
|
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PolicyDocument describes an IAM policy document
|
// PolicyDocument describes an IAM policy document
|
||||||
|
|
|
@ -301,6 +301,16 @@
|
||||||
"ManagedPolicyArns": [
|
"ManagedPolicyArns": [
|
||||||
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
||||||
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
||||||
|
],
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "com.docker.compose.project",
|
||||||
|
"Value": "TestSimpleConvert"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "com.docker.compose.service",
|
||||||
|
"Value": "simple"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Type": "AWS::IAM::Role"
|
"Type": "AWS::IAM::Role"
|
||||||
|
|
1
ecs/x.go
1
ecs/x.go
|
@ -29,4 +29,5 @@ const (
|
||||||
extensionRetention = "x-aws-logs_retention"
|
extensionRetention = "x-aws-logs_retention"
|
||||||
extensionRole = "x-aws-role"
|
extensionRole = "x-aws-role"
|
||||||
extensionManagedPolicies = "x-aws-policies"
|
extensionManagedPolicies = "x-aws-policies"
|
||||||
|
extensionAutoScaling = "x-aws-autoscaling"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue