Are you tired of maintaining your ECS cluster? Regular updates of the AMI, challenges with auto scaling, automated draining, and so on…

It’s time to migrate your ECS Services from EC2 to Fargate!

This is a checklist of things you have to consider and change when you want to migrate from an EC2 based service to Fargate. It assumes that you’re deploying your ECS services with CloudFormation.

Before the migration

You can make your ECS service compatible with Fargate without actual migrating it. This reduces the risk as the whole migration can be split into smaller steps.

1) Have the CPU settings defined on the container and task level

Fargate requires that CPU and memory are defined on a task level. The values depend on each other, so check this page to see what combinations are supported.

If you’re unsure how much CPU or memory you need, start with a bigger size. Watch your application in CloudWatch and adjust the size later on accordingly.

Be aware that CPU setting on task-level is a hard limit and does not allow any bursting (even on EC2). A deep dive into that topic can be found on the AWS Containers blog

TaskDefault:
  Type: 'AWS::ECS::TaskDefinition'
  Properties:
    Family: foobar
    Cpu: 1024
    Memory: 2048
    ContainerDefinitions:
      - Name: myApp
        Cpu: 1024
        Memory: 2048

2) Change network to awsvpc

Fargate works only with awsvpc network. If you don’t use it already, you’ve to change the NetworkMode to awsvpc in your task definition.

TaskDefault:
  Type: 'AWS::ECS::TaskDefinition'
  Properties:
    ... 
    ExecutionRoleArn: !GetAtt FargateExecutionRole.Arn
    NetworkMode: awsvpc

Your service needs to know which subnets can be used and if the service is public or not. One benefit of the awsvpc network is that it has it’s own SecurityGroup and doesn’t share it with the whole cluster. It will be created in the next step.

Service:
  Type: 'AWS::ECS::Service'
  Properties:
    ...
    NetworkConfiguration:
      AwsvpcConfiguration:
        AssignPublicIp: DISABLED
        SecurityGroups:
          - !Ref EcsServiceSecurityGroup
        Subnets:
          - !ImportValue 'vpc-PrivateSubnetA'
          - !ImportValue 'vpc-PrivateSubnetB'
          - !ImportValue 'vpc-PrivateSubnetC'

3) Allow traffic from your load balancer

Let’s assume there’s an Application LoadBalancer in front of our ECS Service. The SecurityGroup needs to allow ingres from the Application Load Balancer on the container port.

And that’s a bit different as before because with the default “bridge” network the SecurityGroup of the cluster instances had to allow the whole dynamic port range (usually 32768–61000).

EcsServiceSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: Allows ingress traffic from ALB
    VpcId: !ImportValue 'vpc-id'
    SecurityGroupIngress:
      - FromPort: !Ref ContainerPort
        ToPort: !Ref ContainerPort
        IpProtocol: tcp
        SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

4) Change the load balancer’s target type

The default target type of a TargetGroup is “instance”. With awsvpc network, this has to be changed to ip. Verify that the port of the TargetGroup is now the same as your container port.

  ALBTargetGroup:
    Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
    Properties:
      Port: !Ref ContainerPort
      TargetType: ip

5) Logging and Metrics

With the awsvpc network, your tasks are not able to talk to any other container or process on the same host. And if your service depends on any third party software to send logs or metrics (like DataDog or SumoLogic), you’ve to add these agents as so-called sidecars to your task definition.

TaskDefault:
  Type: 'AWS::ECS::TaskDefinition'
  Properties:
    Family: foobar
    Cpu: 1024
    Memory: 2048
    ContainerDefinitions:
      - Name: myApp
        Cpu: 896
        Memory: 1792
        DependsOn:
          - Condition: HEALTHY
            ContainerName: mySidecar
      - Name: mySidecar
        Essential: true
        Cpu: 128
        Memory: 256
        PortMappings:
          - ContainerPort: 1234
            Protocol: udp

6) Enforce compatibility

To verify that our changes are compatible with Fargate we can enforce it:

  TaskDefault:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      ... 
      RequiresCompatibilities:
        - EC2
        - FARGATE

7) Clean up of unnecessary service role

You might run into this error:

“You cannot define an IAM role for services that require a service-linked role”

In that case, you still have an IAM Role attached to your ECS service. Just remove it as it isn’t needed anymore. ECS uses a service-linked role that normally already exists in your account. It’s called “AWSServiceRoleForECS”.

If the role doesn’t exist check this page with further explanations.

8) Deploy

Now it’s time to deploy your changes. Your service still runs on EC2 and we just improved CPU/Memory settings and changed the network.

Migration

Now it’s time to migrate to Fargate. With all the preparations that we did the actual migration is quite easy. Just add the Fargate LaunchType and PlatformVersion to your ECS Service.

If you have any Placement Strategies remove them as they’re not needed in Fargate.

During deployment CloudFormation creates a new service, waits until all desired tasks are running and delete the old service afterward. Ensure that your application can run in parallel for a few minutes.

  Service:
    Type: 'AWS::ECS::Service'
    Properties:
      Cluster: 'default'
      LaunchType: FARGATE
      PlatformVersion: 1.3.0

Conclusion

The actual migration to Fargate is pretty easy. More effort is needed to make your ECS service compatible with Fargate. I hope this guide helps you with your migration. Let me know if anything is missing.