From 5256f015c4fab57cdc3d8327c33e489b47f96f58 Mon Sep 17 00:00:00 2001 From: Michael Wittig Date: Tue, 28 Feb 2017 15:22:47 +0100 Subject: [PATCH] moved task definition into image --- ecs/README.md | 29 +--- ecs/container-definitions.json | 22 --- ecs/service-cluster-alb.yaml | 45 +++--- ecs/service-dedicated-alb.yaml | 45 +++--- .../awscftemplates/ecs/TestECSService.java | 141 +++++++----------- 5 files changed, 114 insertions(+), 168 deletions(-) delete mode 100644 ecs/container-definitions.json diff --git a/ecs/README.md b/ecs/README.md index bde038187..8b1a21dd1 100644 --- a/ecs/README.md +++ b/ecs/README.md @@ -32,32 +32,11 @@ This template describes a fault tolerant and scalable ECS cluster on AWS. The cl ## ECS service This template describes a fault tolerant and scalable ECS service on AWS. The service scales based on CPU utilization. -### Creating an ECS task definition -Before you can start with the ECS service, you need to create a task definition. The task definition references your Docker image from Docker Hub or ECR. +> The image needs to expose port 80 or the `AWS::ECS::TaskDefinition` needs to be adjusted! -In the [container-definitions.json](./container-definitions.json) file, replace: -* `$Image` with your published Docker image (e.g. `nginx:1.11.5` or `123456789012.dkr.ecr.us-east-1.amazonaws.com/demo:1.0.0`) -* `$AWSRegion` with the region your ECS cluster runs in (e.g. `eu-west-1`) -* `$ClusterLogGroup` with the `LogGroup` output from the `ecs-cluster` stack (e.g. via the CLI `aws cloudformation describe-stacks --stack-name $ClusterName --query "Stacks[0].Outputs[?OutputKey=='LogGroup'].OutputValue" --output text`) -* `$ServiceName` with the name of the service (e.g. `demo`) - -Other options can be found in the AWS docs: http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html - -The following CLI command creates a task definition and outputs the unique ARN that you will need later when you create the service: - -``` -aws ecs register-task-definition --family $ServiceName --network-mode bridge --container-definitions file://container-definitions.json --query "taskDefinition.taskDefinitionArn" --output text -``` - -#### Updating an ECS task definition - -If you want to update your task definition because you want to deploy a new version of your image, just re run the `aws ecs register-task-definition` command from above. This will create a new task definition because you can not change them. Take a note of the new ARN that the command returns. - - -### Choosing a service template flavour -We provide two service templates. -The first one (`service-cluster-alb.yaml`) uses the cluster's load balancer and path based routing. If you want to run multiple services on the same cluster they all will use the same domain name but start with different paths (e.g. `https://yourdomain.com/service1/` and `https://yourdomain.com/service2/`). -The second one (`service-dedicated-alb.yaml`) includes a dedicated load balancer (ALB). You can then use a separate domain name for each service. +We provide two service templates: +* `service-cluster-alb.yaml` uses the cluster's load balancer and path based routing. If you want to run multiple services on the same cluster they all will use the same domain name but start with different paths (e.g. `https://yourdomain.com/service1/` and `https://yourdomain.com/service2/`). +* `service-dedicated-alb.yaml` includes a dedicated load balancer (ALB). You can then use a separate domain name for each service. ### Using the cluster's load balancer and path based routing This template describes a fault tolerant and scalable ECS service that uses the cluster's load balancer and path based routing. diff --git a/ecs/container-definitions.json b/ecs/container-definitions.json deleted file mode 100644 index 1a3d673d3..000000000 --- a/ecs/container-definitions.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "main", - "image": "$Image", - "memory": 128, - "portMappings": [ - { - "containerPort": 80, - "protocol": "tcp" - } - ], - "essential": true, - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-region": "$AWSRegion", - "awslogs-group": "$ClusterLogGroup", - "awslogs-stream-prefix": "$ServiceName" - } - } - } -] diff --git a/ecs/service-cluster-alb.yaml b/ecs/service-cluster-alb.yaml index 01443ae88..0fab6b3d5 100644 --- a/ecs/service-cluster-alb.yaml +++ b/ecs/service-cluster-alb.yaml @@ -17,12 +17,10 @@ Metadata: - Label: default: 'Task Parameters' Parameters: - - TaskDefinitionArn + - Image - DesiredCount - MaxCapacity - MinCapacity - - ContainerPort - - ContainerName Parameters: ParentClusterStack: Description: 'Stack name of parent Cluster stack based on ecs/cluster.yaml template.' @@ -47,8 +45,8 @@ Parameters: AllowedValues: - true - false - TaskDefinitionArn: - Description: 'The ARN of the task definition (including the revision number) that you want to run on the cluster, such as arn:aws:ecs:us-east-1:123456789012:task-definition/mytask:3.' + Image: + Description: 'The image to use for a container, which is passed directly to the Docker daemon. You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag).' Type: String DesiredCount: Description: 'The number of simultaneous tasks, that you want to run on the cluster.' @@ -68,21 +66,30 @@ Parameters: Default: 2 ConstraintDescription: 'Must be >= 1' MinValue: 1 - ContainerPort: - Description: 'The port number on the container to direct load balancer traffic to. Your container instances must allow ingress traffic on this port. The container definition must match with this value.' - Type: Number - Default: 80 - ConstraintDescription: 'Must be in the range [0-65535]' - MinValue: 0 - MaxValue: 65535 - ContainerName: - Description: 'The name of a container to use with the load balancer. The container definition must match with this value.' - Type: String - Default: main Mappings: {} Conditions: HasLoadBalancerHttps: !Equals [!Ref LoadBalancerHttps, 'true'] Resources: + TaskDefinition: + Type: 'AWS::ECS::TaskDefinition' + Properties: + Family: !Ref 'AWS::StackName' + NetworkMode: bridge + ContainerDefinitions: + - Name: main # if you change this, you also must change the AWS::ECS::Service + Image: !Ref Image + Memory: 128 + PortMappings: + - ContainerPort: 80 # if you change this, you also must change the AWS::ECS::Service + Protocol: tcp + Essential: true + LogConfiguration: + LogDriver: awslogs + Options: + 'awslogs-region': !Ref 'AWS::Region' + 'awslogs-group': + 'Fn::ImportValue': !Sub '${ParentClusterStack}-LogGroup' + 'awslogs-stream-prefix': !Ref 'AWS::StackName' LoadBalancerTargetGroup: Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' Properties: @@ -147,11 +154,11 @@ Resources: MinimumHealthyPercent: 50 DesiredCount: !Ref DesiredCount LoadBalancers: - - ContainerName: !Ref ContainerName - ContainerPort: !Ref ContainerPort + - ContainerName: main + ContainerPort: 80 TargetGroupArn: !Ref LoadBalancerTargetGroup Role: !GetAtt 'ServiceRole.Arn' - TaskDefinition: !Ref TaskDefinitionArn + TaskDefinition: !Ref TaskDefinition ScalableTargetRole: # based on http://docs.aws.amazon.com/AmazonECS/latest/developerguide/autoscale_IAM_role.html Type: 'AWS::IAM::Role' Properties: diff --git a/ecs/service-dedicated-alb.yaml b/ecs/service-dedicated-alb.yaml index 5255ec97e..575f24952 100644 --- a/ecs/service-dedicated-alb.yaml +++ b/ecs/service-dedicated-alb.yaml @@ -18,12 +18,10 @@ Metadata: - Label: default: 'Task Parameters' Parameters: - - TaskDefinitionArn + - Image - DesiredCount - MaxCapacity - MinCapacity - - ContainerPort - - ContainerName Parameters: ParentVPCStack: Description: 'Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.' @@ -46,8 +44,8 @@ Parameters: Description: 'Optional Amazon Resource Name (ARN) of the certificate to associate with the load balancer.' Type: String Default: '' - TaskDefinitionArn: - Description: 'The ARN of the task definition (including the revision number) that you want to run on the cluster, such as arn:aws:ecs:us-east-1:123456789012:task-definition/mytask:3.' + Image: + Description: 'The image to use for a container, which is passed directly to the Docker daemon. You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag).' Type: String DesiredCount: Description: 'The number of simultaneous tasks, which you specify by using the TaskDefinition property, that you want to run on the cluster.' @@ -67,17 +65,6 @@ Parameters: Default: 2 ConstraintDescription: 'Must be >= 1' MinValue: 1 - ContainerPort: - Description: 'The port number on the container to direct load balancer traffic to. Your container instances must allow ingress traffic on this port. The container definition must match with this value.' - Type: Number - Default: 80 - ConstraintDescription: 'Must be in the range [0-65535]' - MinValue: 0 - MaxValue: 65535 - ContainerName: - Description: 'The name of a container to use with the load balancer. The container definition must match with this value.' - Type: String - Default: main Mappings: {} Conditions: HasAuthProxySecurityGroup: !Not [!Equals [!Ref ParentAuthProxyStack, '']] @@ -87,6 +74,26 @@ Conditions: HasAuthProxySecurityGroupAndLoadBalancerCertificateArn: !And [!Condition HasAuthProxySecurityGroup, !Condition HasLoadBalancerCertificateArn] HasNotAuthProxySecurityGroupAndLoadBalancerCertificateArn: !And [!Condition HasNotAuthProxySecurityGroup, !Condition HasLoadBalancerCertificateArn] Resources: + TaskDefinition: + Type: 'AWS::ECS::TaskDefinition' + Properties: + Family: !Ref 'AWS::StackName' + NetworkMode: bridge + ContainerDefinitions: + - Name: main # if you change this, you also must change the AWS::ECS::Service + Image: !Ref Image + Memory: 128 + PortMappings: + - ContainerPort: 80 # if you change this, you also must change the AWS::ECS::Service + Protocol: tcp + Essential: true + LogConfiguration: + LogDriver: awslogs + Options: + 'awslogs-region': !Ref 'AWS::Region' + 'awslogs-group': + 'Fn::ImportValue': !Sub '${ParentClusterStack}-LogGroup' + 'awslogs-stream-prefix': !Ref 'AWS::StackName' ALBSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: @@ -211,11 +218,11 @@ Resources: MinimumHealthyPercent: 50 DesiredCount: !Ref DesiredCount LoadBalancers: - - ContainerName: !Ref ContainerName - ContainerPort: !Ref ContainerPort + - ContainerName: main + ContainerPort: 80 TargetGroupArn: !Ref DefaultTargetGroup Role: !GetAtt 'ServiceRole.Arn' - TaskDefinition: !Ref TaskDefinitionArn + TaskDefinition: !Ref TaskDefinition ScalableTargetRole: # based on http://docs.aws.amazon.com/AmazonECS/latest/developerguide/autoscale_IAM_role.html Type: 'AWS::IAM::Role' Properties: diff --git a/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java index 6d40d7f8a..166c69d82 100644 --- a/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java +++ b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java @@ -15,21 +15,6 @@ public class TestECSService extends ACloudFormationTest { - private final AmazonECS ecs = AmazonECSClientBuilder.standard().withCredentials(this.credentialsProvider).build(); - - private String createTaskDefinition(final String family) { - final PortMapping pm = new PortMapping().withContainerPort(80).withProtocol("tcp"); - final ContainerDefinition cd = new ContainerDefinition().withName("main").withImage("nginx:1.11.5").withMemory(128).withPortMappings(pm).withEssential(true); - final RegisterTaskDefinitionResult res = this.ecs.registerTaskDefinition(new RegisterTaskDefinitionRequest().withFamily(family).withNetworkMode("bridge").withContainerDefinitions(cd)); - return res.getTaskDefinition().getTaskDefinitionArn(); - } - - private void deleteTaskDefinition(final String taskDefinitionArn) { - if (Config.get(Config.Key.DELETION_POLICY).equals("delete")) { - this.ecs.deregisterTaskDefinition(new DeregisterTaskDefinitionRequest().withTaskDefinition(taskDefinitionArn)); - } - } - @Test public void testClusterAlb() { final String vpcStackName = "vpc-2azs-" + this.random8String(); @@ -37,52 +22,47 @@ public void testClusterAlb() { final String stackName = "ecs-service-" + this.random8String(); final String classB = "10"; final String keyName = "key-" + this.random8String(); - final String taskDefinitionArn = this.createTaskDefinition(stackName); try { + this.createKey(keyName); try { - this.createKey(keyName); + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); try { - this.createStack(vpcStackName, - "vpc/vpc-2azs.yaml", - new Parameter().withParameterKey("ClassB").withParameterValue(classB) + this.createStack(clusterStackName, + "ecs/cluster.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) ); + final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); try { - this.createStack(clusterStackName, - "ecs/cluster.yaml", - new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), - new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + this.createStack(stackName, + "ecs/service-cluster-alb.yaml", + new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), + new Parameter().withParameterKey("Image").withParameterValue("nginx:1.11.5") ); - final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); - try { - this.createStack(stackName, - "ecs/service-cluster-alb.yaml", - new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), - new Parameter().withParameterKey("TaskDefinitionArn").withParameterValue(taskDefinitionArn) - ); - final String url = this.getStackOutputValue(stackName, "URL"); - final Callable callable = () -> { - final HttpResponse response = WS.url(url).timeout(10000).get(); - // check HTTP response code - if (WS.getStatus(response) != 404) { - throw new RuntimeException("404 expected, but saw " + WS.getStatus(response)); - } - return true; - }; - Assert.assertTrue(this.retry(callable)); - } finally { - this.deleteStack(stackName); - } + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 404) { + throw new RuntimeException("404 expected, but saw " + WS.getStatus(response)); + } + return true; + }; + Assert.assertTrue(this.retry(callable)); } finally { - this.deleteStack(clusterStackName); + this.deleteStack(stackName); } } finally { - this.deleteStack(vpcStackName); + this.deleteStack(clusterStackName); } } finally { - this.deleteKey(keyName); + this.deleteStack(vpcStackName); } } finally { - this.deleteTaskDefinition(taskDefinitionArn); + this.deleteKey(keyName); } } @@ -93,55 +73,50 @@ public void testDedicatedAlb() { final String stackName = "ecs-service-" + this.random8String(); final String classB = "10"; final String keyName = "key-" + this.random8String(); - final String taskDefinitionArn = this.createTaskDefinition(stackName); try { + this.createKey(keyName); try { - this.createKey(keyName); + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); try { - this.createStack(vpcStackName, - "vpc/vpc-2azs.yaml", - new Parameter().withParameterKey("ClassB").withParameterValue(classB) + this.createStack(clusterStackName, + "ecs/cluster.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) ); + final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); try { - this.createStack(clusterStackName, - "ecs/cluster.yaml", + this.createStack(stackName, + "ecs/service-dedicated-alb.yaml", new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), - new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), + new Parameter().withParameterKey("Image").withParameterValue("nginx:1.11.5") ); - final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); - try { - this.createStack(stackName, - "ecs/service-dedicated-alb.yaml", - new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), - new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), - new Parameter().withParameterKey("TaskDefinitionArn").withParameterValue(taskDefinitionArn) - ); - final String url = this.getStackOutputValue(stackName, "URL"); - final Callable callable = () -> { - final HttpResponse response = WS.url(url).timeout(10000).get(); - // check HTTP response code - if (WS.getStatus(response) != 200) { - throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); - } - return WS.getResponseAsString(response); - }; - final String response = this.retry(callable); - // check if nginx page appears - Assert.assertTrue(response.contains("Welcome to nginx!")); - } finally { - this.deleteStack(stackName); - } + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 200) { + throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if nginx page appears + Assert.assertTrue(response.contains("Welcome to nginx!")); } finally { - this.deleteStack(clusterStackName); + this.deleteStack(stackName); } } finally { - this.deleteStack(vpcStackName); + this.deleteStack(clusterStackName); } } finally { - this.deleteKey(keyName); + this.deleteStack(vpcStackName); } } finally { - this.deleteTaskDefinition(taskDefinitionArn); + this.deleteKey(keyName); } }