EC2 capacity
Path-owned compute for ${NAME}-ec2. Confirm shared vars from Setup are still in this shell ($NAME, $VPC_ID, $PRIVATE_SUBNET_A, $EXEC_ROLE_ARN is not used here — the instance role is separate).
This page gives the EC2 path its compute. Fargate had none of this — on Fargate, AWS runs the host. Here you create an instance role, launch an ECS-optimized instance in a private subnet, and wait for it to register as capacity on the cluster.
Path variables
Section titled “Path variables”export PATH_NAME="${NAME}-ec2"export CLUSTER="$PATH_NAME"export GATEWAY="$PATH_NAME"echo "path ready: CLUSTER=$CLUSTER GATEWAY=$GATEWAY"path ready: CLUSTER=ecs-gw-lab-ec2 GATEWAY=ecs-gw-lab-ec2Instance role
Section titled “Instance role”ECS container instances need an IAM role trusted by ec2.amazonaws.com with AmazonEC2ContainerServiceforEC2Role.
aws iam create-role \ --role-name "${NAME}-ec2" \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }] }' \ --query 'Role.RoleName' --output textecs-gw-lab-ec2aws iam attach-role-policy \ --role-name "${NAME}-ec2" \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Roleecho "instance role policy attached"instance role policy attachedInstance profile
Section titled “Instance profile”aws iam create-instance-profile --instance-profile-name "${NAME}-ec2" \ --query 'InstanceProfile.InstanceProfileName' --output textecs-gw-lab-ec2aws iam add-role-to-instance-profile \ --instance-profile-name "${NAME}-ec2" \ --role-name "${NAME}-ec2"echo "role added to instance profile"role added to instance profileIAM is eventually consistent — wait before run-instances:
sleep 10echo "ready to launch"ready to launchInstance security group
Section titled “Instance security group”Private-subnet instance; no inbound rules required for this lab (tasks use their own ENIs).
EC2_SG_ID=$(aws ec2 create-security-group \ --group-name "${NAME}-ec2" \ --description "ECS container instances for ${NAME}-ec2" \ --vpc-id "$VPC_ID" \ --query GroupId --output text)export EC2_SG_IDecho "EC2_SG_ID set (sg-<id>)"EC2_SG_ID set (sg-<id>)Create the cluster first
Section titled “Create the cluster first”The ECS agent needs the cluster name at boot (ECS_CLUSTER in user data).
aws ecs create-cluster --cluster-name "$CLUSTER" \ --query 'cluster.{name:clusterName,status:status}'{ "name": "ecs-gw-lab-ec2", "status": "ACTIVE"}Resolve ECS-optimized AMI
Section titled “Resolve ECS-optimized AMI”AMI_ID=$(aws ssm get-parameters \ --names /aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id \ --query 'Parameters[0].Value' --output text)export AMI_IDecho "AMI_ID set (ami-<id>)"AMI_ID set (ami-<id>)User data
Section titled “User data”Pins the agent to this path’s cluster:
cat > /tmp/${PATH_NAME}-userdata.sh <<EOF#!/bin/bashecho ECS_CLUSTER=${CLUSTER} >> /etc/ecs/ecs.configEOFwc -l /tmp/${PATH_NAME}-userdata.sh2 /tmp/ecs-gw-lab-ec2-userdata.shLaunch instance
Section titled “Launch instance”Place the instance in a private subnet (no public IP). Outbound ECS agent and image traffic uses the NAT gateway. t3.medium leaves ENI headroom for awsvpc tasks.
INSTANCE_ID=$(aws ec2 run-instances \ --image-id "$AMI_ID" \ --instance-type t3.medium \ --iam-instance-profile "Name=${NAME}-ec2" \ --subnet-id "$PRIVATE_SUBNET_A" \ --security-group-ids "$EC2_SG_ID" \ --no-associate-public-ip-address \ --user-data "file:///tmp/${PATH_NAME}-userdata.sh" \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=${PATH_NAME}}]" \ --query 'Instances[0].InstanceId' --output text)export INSTANCE_IDecho "INSTANCE_ID set (i-<id>)"INSTANCE_ID set (i-<id>)aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \ --query 'Reservations[0].Instances[0].{state:State.Name,type:InstanceType,az:Placement.AvailabilityZone}'{ "state": "running", "type": "t3.medium", "az": "ap-southeast-6a"}Wait for registration
Section titled “Wait for registration”for i in $(seq 1 36); do COUNT=$(aws ecs list-container-instances --cluster "$CLUSTER" \ --query 'length(containerInstanceArns)' --output text) echo "container instances: $COUNT" [ "$COUNT" != "0" ] && break sleep 10donecontainer instances: 0container instances: 0container instances: 1Confirm without printing ARNs (they embed the account ID):
aws ecs list-container-instances --cluster "$CLUSTER" \ --query 'length(containerInstanceArns)' --output text1