ECS Fargate

Elastic Container Service: Deploy Fargate and Load Balancer using AWS CDK and Python
Share this page:

If you’ve just landed here, we’re doing a “Become a Cloud Architect” Unicorn Workshop by building a Unicorn Pursuit Web App step by step, and you’re more then welcome to join!

About ECS - Fargate

ECS is Amazons managed Container Orchestration Platform. Yes, we are still using Docker Images, but most stuff that for example in Kubernetes we’d need to manage and operate - is managed for us.

Docker on AWS

There are two ways you can run a ECS cluster:

  • Fargate, which is cheaper, and comes as a manages service. IMHO - whenever you can, go for this one.
  • EC2, which gives you more flexibility, which means - you use it when you need GPUs or other hardware or software specific features not supported by Fargate.

Basic ECS Concepts you need to be familiar with:

  • Task (similar to a POD in Kubernetes): a basic unit of execution, with a single life-cycle management, handled by Load Balancer.
  • Task Definition: one of more containers, used to define app containers. This is where we define the requirements (CPU, RAM etc).
  • Cluster, as an IAM boundary: EC2 instances are under the hood, managed by AWS in case of Fargate, and by us in case of EC2.
  • Service: Maintain N running copies. Does exactly what SVC in K8S does. SVC is integrated with ELB.

Task Definition is where we define how the Task will work. It contains a list of up to 10 container definitions. The idea is to create a Definition for when we want to change or update the container. Basically:

  • When we need containers to run in the same Node, you use the same Task Definition.
  • When we don’t care where they will run - you use Task Definition per container.

When you launch a Fargate cluster, you need to define the CPU and Memory your task requires to run.

Code

Our idea is to run our Unicorn Pursuit Docker Container in Fargate. For this we will need to:

  • Containerize our App. Here is how to create a small Golang Dockerfile, and create a docker image.
  • TAG and PUSH our Docker Image to ECR (Amazons Elastic Container Repository, something like a Docker Hub on AWS).
  • Create an IAM Role, so that our Container could Read/Write data from S3, our DynamoDB databases, create users in Cognito etc.
  • Create Fargate in AWS CDK using Python, along with ALB (Application Load Balancer), that would later be used as Origin in CloudFront.

To understand what exactly we’re building, refer to the diagram below:

Fargate Diagram

If you’re not familiar with Fargate in Console, this 6 minute YouTube Video should be enough, before we dive into AWS CDK:

Apart from this, I’d highly recommend you to do a 3 hour Official AWS Fargate Lab. You’ll understand all the components in depth.

Deploy Fargate using AWS CDK and Python

Ok, so let’s start with CDK. We need to import 5 libraries that we’ll be using in this lesson:

from aws_cdk import (

...
  aws_ecs as ecs,
  aws_ecs_patterns as ecs_patterns,
  aws_ec2 as ec2,
  aws_ecr as ecr,
  aws_iam as iam
)

Don’t forget to install these using pip install aws-cdk.aws-X and update your requirements.txt, check out this post for details before you can import these to your Python AWS CDK Code.

Create IAM Role for Fargate in AWS CDK

IAM Permissions are split in 3 categories:

  • Cluster permissions: who can describe or launch new tasks.
  • App Permissions: Allows app containers to access AWS resources.
  • Housekeeping Permissions: Allows ECR image pull, ENI creation, CloudWatch logs pushing, etc.

For App Permissions, we first need to create a Service Role called fargate_role, which will be assumed by our ECS Task, and then add Policies per each Service we need, always following the least privilege principle.

fargate_role = iam.Role(
  self,
  "ecsTaskExecutionRole",
  role_name="ecsTaskExecutionRole",
  assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
  description="Service Role assumed by ECS Fargate (container)"
)

To grant S3 Read/Write from our www.unicornpursuit.com bucket, and our DynamoDB Tables, we need to add the policies. Check out the code below, we’re adding a Policy with Resources: our S3 bucket, and Actions: S3_all.

fargate_role.add_to_policy(statement=iam.PolicyStatement(
  resources=[bucket.bucket_arn],
  actions=["s3:*"]
))

Similar applies to DybamoDB, we’ll need to define both our DynamoDB tables as resorces, and we’ll also allow our Container to do a full set of DynamoDB actions:

fargate_role.add_to_policy(statement=iam.PolicyStatement(
  resources=[voting_ddb.attr_arn,users_ddb.table_arn],
  actions=["dynamodb:*"]
))

Since we will also be using SSM to retrieve sensitive parameters, and Cognito, we also need to add those policies:

fargate_role.add_to_policy(statement=iam.PolicyStatement(
    resources=["*"],
    actions=["ssm:*"]
))

fargate_role.add_to_policy(statement=iam.PolicyStatement(
    resources=[userpool.user_pool_arn],
    actions=["cognito-identity:*","cognito-idp:*","cognito-sync:*"]
))

ECS Networking

ECS Networking is probably the most complex part. We have 4 options:

  • Bridge: docker built-in
  • Host: ports directly mapped to ENI.
  • AWSVPC: Task has a dedicated ENI. Containers in the same host can use localhost for communication within the host.
  • None: no external connectivity

Every Fargate runs in AWSVPC mode, so each task gets a ENI in your VPC, with a Private IP, so it can communicate with other resources in your VPC (ELB, RDS, EC2). This way you can define Security Groups on your ENI.

This means that even though we’re not managing VMs, we do need to create a VPC and Subnets first, so that we can later create Fargate Service and a Load Balancer. Ok, so let’s role, we need a VPC, we’ll do 2 AZs for HA, we need a ECS Cluster, and a ECR Container Registry, where we’ll push our Docker Images:

vpc = ec2.Vpc(self, "UnicornVPC", max_azs=2)
cluster = ecs.Cluster(self, "UnicornCluster", vpc=vpc)
ecr.Repository(self, "unicorn", repository_name="unicorn")

Simple, right? Now the real deal - the Fargate Service, within which we need to define:

  • Application Load Balancer, exposing port 80 (http).
  • Task Image, including: container exposed port (8080), container image, and indicating that Task should assume the Service Role we defined, called fargate_role.
  • Securiy Groups, one allowing only HTTP towards ALB, and another one allowing only 8080 from ALB to our Fargate Container.
fargate_service = ecs_patterns.ApplicationLoadBalancedFargateService(self, "UnicornFargateService",
    cluster=cluster,
    cpu=512,
    desired_count=1,
    task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
        image=ecs.ContainerImage.from_registry(repo.repository_uri_for_tag()),
        container_port=8080,
        container_name="unicorn",
        execution_role=fargate_role,
    ),
    memory_limit_mib=1024,
    public_load_balancer=True
)

fargate_service.service.connections.security_groups[0].add_ingress_rule(
    peer = ec2.Peer.ipv4(vpc.vpc_cidr_block),
    connection = ec2.Port.tcp(8080),
    description="Allow http inbound from VPC"
)

In this example, we’re adding a new policy by giving the PullImage Action permission to our Fargate Service Role. You can always be more precise, by defining the resources to be the exact ECR Repository.

fargate_role.add_to_policy(statement=iam.PolicyStatement(
  resources=["*"],
  actions=["ecr:PullImage"]
))

That’s it. If we now go to EC2 - Load Balancers, and try to access the A Record, you’ll access your application!

Deep Dive

This chapter is not related to Unicorn Pursuit Fargate Implementation, but the users are advised to at least get acquainted with the concepts.

EKS - Elastic Kubernetes Service

There are 2 VPCs:

  • EKS VPC with Master Nodes and ELB
  • Customer VPC for Worker Nodes and containerized Apps (along with ENIs)

A recommended EKS Lab can be found here.

EKS Networking

Works with CNI plugin, integrated with VPC. Works a bit different:

  • A single ENI per Worker.
  • Each POD IP assigned as Public IP in ENI.
  • SVC type “LoadBalancer” uses the Classic LB integration.
  • ALB is used as Ingress Controller.

AWS Cloud Map

  • Used for micro service architectures, to discover and register all the internal and external services in the single architecture map.
  • Can be used with ECS or EKS.
  • You can register services that are not registered in Service Discovery automatically.

AWS App Mesh

It´s a control plane, that deploys sidecars to delegate “hard work”. Side-car is also envoy, like in Istio.

Istio Service Mesh

  • Mixer: Access control, quota checking, policy enforcement. Mixer keeps checking and getting reports if all Proxies are alive and well. Single API for syndicating, so Plugins for Monitoring, API management or Prometheus would go to Mixer.
  • Pilot: Delivering config to the Proxies (Envoy). As a User you interact with the Pilot, through CLI, Automatically, or CI/CD
  • Istio CA: Handles the certificates, to secure the communications.
  • Citadel: strong service-to-service and end-user authentication with built-in identity and credential management.

Amazon AppMesh

AppMesh has the same services:

  • Virtual Router
  • Virtual Node

Where to find more info




Last modified June 8, 2020: AWS Diagrams Added (c98aff0)