Serverless-application-model: Should SAM support ECS Fargate?

Created on 24 Apr 2018  路  7Comments  路  Source: aws/serverless-application-model

SAM is currently designed to be used with Lambda for creating serverless applications.

But what about Fargate for ECS? This service allows you to run docker applications serverless-ly, but It requires an obscene amount of complicated boilerplate CFN to get even the simplest web application running.

I think it would be beneficial to have a virtual resource, e.g. AWS::Serverless::FargateApp that implicitly generates a service, task definition, and all associated public-facing load balancer resources if necessary. To keep things simple, a FargateApp will have only one container definition.

I can help contribute a spec if this idea is accepted.

priorit4-nice-to-have stagrequest-for-comments typfeature

Most helpful comment

@clareliguori The problem with CDK is that it brings a high overhead of re-learning and redesigning the DevOps mechanics, specially from a company that uses CodePipeline with CloudFormation as the deploy step. Switching from Containers (Source, CodeBuild, CloudFormation) to (Source, CodeBuild, CloudFormation) where CodeBuild used to build docker containers and now just aws cloudformation package instead is far easier than switching from CloudFormation to CDK.

The Serverless framework provides a simple, yet powerful abstraction on top of the extreme complexity of CloudFormation. Simplifying Fargate deployments could be an amazing improvement. Here's some of my proposals as requested by @brettstack (Note: I think this works well in combination of https://github.com/awslabs/serverless-application-model/issues/721#issuecomment-461555861)

FargateContainer:
  Type: AWS::Serverless::Fargate
  Properties:
    Role: !GetAtt ContainerRole.Arn
    Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-repository:{TagParameter}'
    Command: my-docker-command
    DesireCount: 1
    MemorySize: 1024
    CpuSize: 512
    Environment:
      Variables:
        MyVariable: 'MyValue'

This would provide the bare-minimum for a Fargate Container (as it works with Lambda). This part is the simplest one and would mostly abstract just the Task Definition.

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Environment:
            - Name: MyVariable
              Value: MyValue

          Name: Optionally available at FargateContainer.Name
          Essential: true
          Image: Available at FargateContainer.Image
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref 'AWS::Region'
              awslogs-stream-prefix: task

          Privileged: 'false'
      Cpu: Available at FargateContainer.CpuSize
      Memory: Available at FargateContainer.MemorySize
      Family: Optionally available at FargateContainer.Name or FargateContainer.Name?
      NetworkMode: awsvpc
      ExecutionRoleArn: Optionally available at FargateContainer.Role but fallback to !Sub 'arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole'
      TaskRoleArn: Optionally available at FargateContainer.Role but fallback to !Sub 'arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole'
      RequiresCompatibilities: [FARGATE]

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 30

I'd expect Global Environment variables to be applied to the Task Definition.


Things get more interesting when we bring the Events tag to Fargate. The first and most useful for me would be ALB.

    VpcConfig:
      SecurityGroupIds: [!ImportValue ContainerSecurityGroup]
      SubnetIds: !Split [',', !ImportValue PrivateSubnets]
    Events:
      WebContainer:
        Type: ALB
        Properties:
          LoadBalancerArn: !ImportValue LoadBalancer
          ListenerArn: !ImportValue Listener
          CertificateArn: !ImportValue Certificate
          Condition: [...]
          HealthCheckPath: /healthy.php

This would generate the following boilerplate:

  HttpsListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn:
            Ref: TargetGroup
      Conditions: {Available at Event Property}
      ListenerArn: {Available At Event Property}
      Priority: // This is a tricky one, see https://github.com/awslabs/serverless-application-model/issues/721#issuecomment-470358435

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 15
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      HealthCheckPath: {Available at Event Property}
      Matcher:
        HttpCode: '200'
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '30'
      TargetType: ip
      VpcId: {I guess this can be figured out by the Private Subnet?}

  Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: {Either make a new one or optionally get it from FargateContainer.Cluster}
      LaunchType: FARGATE
      DesiredCount: {FargateContainer.DesiredCount}
      LoadBalancers:
        - ContainerName: TaskDefinition.ContainerDefinitions.0.Name
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups: {FargateContainer.VpcConfig.SecurityGroupIds}
          Subnets: {FargateContainer.VpcConfig.SubnetIds}
      TaskDefinition: !Ref TaskDefinition

Obviously I'm biased and don't know what would be a sensible default for the health check configuration. But I believe AWS has the resources to analyze what would be a sensible default. I'd even be willing to just accept whatever AWS defines as default so I don't have to configure that amount of variables.

From all this, I feel there's one tricky metric: TaskDefinition.Properties.ContainerDefinitions.0.PortMappings. However, I think we might agree that worst-case scenario we'd have 1 more attribute at the AWS::Serverless::Fargate resource to define the port and it would cascade to Task Definition, Target Group and Service.


For Scheduled Task (provided someday Fargate finally provides Scheduled Tasks, per https://github.com/aws/containers-roadmap/issues/392), I'd expect the syntax to be somewhat like the following:

      Events:
        HourlyScheduling:
          Type: Schedule
          Properties:
            Schedule: cron(0 * * * ? *)

Which would create the following resource

  HourlySchedulingRule:
    Type: AWS::Events::Rule
    Properties:
      Description: {Optionally Available?}
      ScheduleExpression: cron(0 * * * ? *)
      State: ENABLED
      Targets:
        - Id: {The name defined for the generated resource TaskDefinition}
          EcsParameters:
            LaunchType: FARGATE
            PlatformVersion: LATEST
            TaskCount: 1
            TaskDefinitionArn: {Task Definition Resource}
            NetworkConfiguration:
              AwsvpcConfiguration:
                SecurityGroups:
                  - {Available Globally or in the AWS::Serverless::Fargate Resource}
                Subnets: {Available Globally or in the AWS::Serverless::Fargate Resource}
          Arn: [Create one or optionally take it from FargateContainer.Cluster]
          RoleArn: [Make a role with the permission to ecs:RunTask and iam:PassRole

This is my guess to what scheduling Fargate on CloudFormation would be based on AWS CLI documentation.

I'd also propose a syntax for running containers triggered by SQS, but I'm going to hold off on that for now to see what AWS / the community has to say about what I'm proposing so far first.

All 7 comments

This is an interesting idea, but I don't know if it will fit our immediate focus of making serverless application development experience super awesome. I will tag it as a feature request and keep the issue open to gather +1s and revisit later.

Any update on this? Would be really useful IMO. Configuring even a basic Fargate task in CloudFormation is incredibly daunting.

Use cases and suggestions on how SAM can simplify/abstract would be very helpful. Thanks.

Hi all, I'm from Fargate/ECS, and want to reiterate what Brett said: If you have some examples for how you'd like to see Fargate fit into SAM, I would definitely like to hear them!

So far on the Fargate team, we've focused on building AWS CDK applets to abstract Fargate applications in a declarative format (very similar to the FargateApp suggestion by @oharaandrew314 with service, task def, load balancer, etc all generated). See example here, and docs here. If you have feedback on the applets, we're eager to hear it on the CDK repo. But again, if you have suggestions specifically for Fargate-in-SAM, fire away!

I have written a CFN macro for fagate (https://github.com/taimos/cfn-macro-fargate) that could be a base for a discussion on how to add Fargate to SAM. I talked to @cmmeyer about this at re:Invent

@hoegertn Took a look through your macro. Seems like a very useful abstraction! Not sure we would support this as a native SAM resource type, but have you considered sharing the lambda function backing this macro in SAR? SAR doesn't allow the actual AWS::CloudFormation::Macro resource to be published, but you could share the macro function and then consumers could use nested apps to pull it into their template and then create the macro themselves.

@clareliguori The problem with CDK is that it brings a high overhead of re-learning and redesigning the DevOps mechanics, specially from a company that uses CodePipeline with CloudFormation as the deploy step. Switching from Containers (Source, CodeBuild, CloudFormation) to (Source, CodeBuild, CloudFormation) where CodeBuild used to build docker containers and now just aws cloudformation package instead is far easier than switching from CloudFormation to CDK.

The Serverless framework provides a simple, yet powerful abstraction on top of the extreme complexity of CloudFormation. Simplifying Fargate deployments could be an amazing improvement. Here's some of my proposals as requested by @brettstack (Note: I think this works well in combination of https://github.com/awslabs/serverless-application-model/issues/721#issuecomment-461555861)

FargateContainer:
  Type: AWS::Serverless::Fargate
  Properties:
    Role: !GetAtt ContainerRole.Arn
    Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-repository:{TagParameter}'
    Command: my-docker-command
    DesireCount: 1
    MemorySize: 1024
    CpuSize: 512
    Environment:
      Variables:
        MyVariable: 'MyValue'

This would provide the bare-minimum for a Fargate Container (as it works with Lambda). This part is the simplest one and would mostly abstract just the Task Definition.

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Environment:
            - Name: MyVariable
              Value: MyValue

          Name: Optionally available at FargateContainer.Name
          Essential: true
          Image: Available at FargateContainer.Image
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref 'AWS::Region'
              awslogs-stream-prefix: task

          Privileged: 'false'
      Cpu: Available at FargateContainer.CpuSize
      Memory: Available at FargateContainer.MemorySize
      Family: Optionally available at FargateContainer.Name or FargateContainer.Name?
      NetworkMode: awsvpc
      ExecutionRoleArn: Optionally available at FargateContainer.Role but fallback to !Sub 'arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole'
      TaskRoleArn: Optionally available at FargateContainer.Role but fallback to !Sub 'arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole'
      RequiresCompatibilities: [FARGATE]

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 30

I'd expect Global Environment variables to be applied to the Task Definition.


Things get more interesting when we bring the Events tag to Fargate. The first and most useful for me would be ALB.

    VpcConfig:
      SecurityGroupIds: [!ImportValue ContainerSecurityGroup]
      SubnetIds: !Split [',', !ImportValue PrivateSubnets]
    Events:
      WebContainer:
        Type: ALB
        Properties:
          LoadBalancerArn: !ImportValue LoadBalancer
          ListenerArn: !ImportValue Listener
          CertificateArn: !ImportValue Certificate
          Condition: [...]
          HealthCheckPath: /healthy.php

This would generate the following boilerplate:

  HttpsListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn:
            Ref: TargetGroup
      Conditions: {Available at Event Property}
      ListenerArn: {Available At Event Property}
      Priority: // This is a tricky one, see https://github.com/awslabs/serverless-application-model/issues/721#issuecomment-470358435

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 15
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      HealthCheckPath: {Available at Event Property}
      Matcher:
        HttpCode: '200'
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '30'
      TargetType: ip
      VpcId: {I guess this can be figured out by the Private Subnet?}

  Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: {Either make a new one or optionally get it from FargateContainer.Cluster}
      LaunchType: FARGATE
      DesiredCount: {FargateContainer.DesiredCount}
      LoadBalancers:
        - ContainerName: TaskDefinition.ContainerDefinitions.0.Name
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups: {FargateContainer.VpcConfig.SecurityGroupIds}
          Subnets: {FargateContainer.VpcConfig.SubnetIds}
      TaskDefinition: !Ref TaskDefinition

Obviously I'm biased and don't know what would be a sensible default for the health check configuration. But I believe AWS has the resources to analyze what would be a sensible default. I'd even be willing to just accept whatever AWS defines as default so I don't have to configure that amount of variables.

From all this, I feel there's one tricky metric: TaskDefinition.Properties.ContainerDefinitions.0.PortMappings. However, I think we might agree that worst-case scenario we'd have 1 more attribute at the AWS::Serverless::Fargate resource to define the port and it would cascade to Task Definition, Target Group and Service.


For Scheduled Task (provided someday Fargate finally provides Scheduled Tasks, per https://github.com/aws/containers-roadmap/issues/392), I'd expect the syntax to be somewhat like the following:

      Events:
        HourlyScheduling:
          Type: Schedule
          Properties:
            Schedule: cron(0 * * * ? *)

Which would create the following resource

  HourlySchedulingRule:
    Type: AWS::Events::Rule
    Properties:
      Description: {Optionally Available?}
      ScheduleExpression: cron(0 * * * ? *)
      State: ENABLED
      Targets:
        - Id: {The name defined for the generated resource TaskDefinition}
          EcsParameters:
            LaunchType: FARGATE
            PlatformVersion: LATEST
            TaskCount: 1
            TaskDefinitionArn: {Task Definition Resource}
            NetworkConfiguration:
              AwsvpcConfiguration:
                SecurityGroups:
                  - {Available Globally or in the AWS::Serverless::Fargate Resource}
                Subnets: {Available Globally or in the AWS::Serverless::Fargate Resource}
          Arn: [Create one or optionally take it from FargateContainer.Cluster]
          RoleArn: [Make a role with the permission to ecs:RunTask and iam:PassRole

This is my guess to what scheduling Fargate on CloudFormation would be based on AWS CLI documentation.

I'd also propose a syntax for running containers triggered by SQS, but I'm going to hold off on that for now to see what AWS / the community has to say about what I'm proposing so far first.

Was this page helpful?
0 / 5 - 0 ratings