Prerequisites #
Security Group #
Find Default VPC #
# Find default Subnet
aws ec2 describe-vpcs \
--filters Name=isDefault,Values=true \
--region us-east-1 \
--query "Vpcs[0].VpcId" --output text
# Shell output:
vpc-01c63f56a39efd950
List Security Groups #
# List security groups
aws ec2 describe-security-groups \
--filters Name=vpc-id,Values=vpc-01c63f56a39efd950 \
--region us-east-1 \
--query "SecurityGroups[?GroupName=='default'].[GroupId,GroupName]" --output table
# Shell output:
-------------------------------------
| DescribeSecurityGroups |
+-----------------------+-----------+
| sg-0de79f0cab5634038 | default |
+-----------------------+-----------+
Describe Default Security Group #
# List security group details
aws ec2 describe-security-groups \
--group-ids sg-0de79f0cab5634038 \
--region us-east-1
# Shell output:
{
"SecurityGroups": [
{
"GroupId": "sg-0de79f0cab5634038",
"IpPermissionsEgress": [
{
"IpProtocol": "-1",
"UserIdGroupPairs": [],
"IpRanges": [
{
"CidrIp": "0.0.0.0/0"
}
],
"Ipv6Ranges": [],
"PrefixListIds": []
}
],
"VpcId": "vpc-01c63f56a39efd950",
"SecurityGroupArn": "arn:aws:ec2:us-east-1:012345678912:security-group/sg-0de79f0cab5634038",
"OwnerId": "012345678912",
"GroupName": "default",
"Description": "default VPC security group",
"IpPermissions": [
{
"IpProtocol": "-1",
"UserIdGroupPairs": [
{
"UserId": "012345678912",
"GroupId": "sg-0de79f0cab5634038"
}
],
"IpRanges": [],
"Ipv6Ranges": [],
"PrefixListIds": []
}
]
}
]
}
Add Inbound Rule #
# Allow inbound traffic on port "80" / all sources
aws ec2 authorize-security-group-ingress \
--group-id sg-0de79f0cab5634038 \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0 \
--region us-east-1
Verify the Inbound Rule #
# Verify the inbound rule
aws ec2 describe-security-groups \
--group-ids sg-0de79f0cab5634038 \
--region us-east-1 \
--query "SecurityGroups[].IpPermissions[*].{Protocol:IpProtocol,From:FromPort,To:ToPort,Source:IpRanges[].CidrIp}" \
--output table
# Shell output:
------------------------------
| DescribeSecurityGroups |
+-------+-------------+------+
| From | Protocol | To |
+-------+-------------+------+
| 80 | tcp | 80 |
+-------+-------------+------+
|| Source ||
|+--------------------------+|
|| 0.0.0.0/0 ||
|+--------------------------+|
+------+------------+--------+
| From | Protocol | To |
+------+------------+--------+
| None| -1 | None |
+------+------------+--------+
List Default Subnets #
List the default subnets:
# List the default VPC in the "us-east-1" region
aws ec2 describe-vpcs --query "Vpcs[?IsDefault==\`true\`].VpcId" --output text --region us-east-1
# Shell output:
vpc-01c63f56a39efd950
# List default subnets
aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-01c63f56a39efd950 --query "Subnets[].SubnetId" --output text --region us-east-1
# Shell output:
subnet-0863f1a46bc384e59 subnet-0a68b63a736166ec5 subnet-018ce0a68d466f6b8 subnet-031bce85422909dd0 subnet-005aa1a09c535126c subnet-08029536779973d55
Create CloudWatch Log Group #
Create a log group in AWS CloudWatch logs:
-
A log group in AWS CloudWatch is a logical grouping of log streams.
-
Each ECS task or container generates logs, which are stored in individual log streams within the specified log group.
# Create a Cloudwatch log group
aws logs create-log-group \
--log-group-name /ecs/example-task \
--region us-east-1
IAM Role & Permissions #
ECS Task Execution Role #
- The
ecsTaskExecutionRole
is required by ECS to pull the container images from the ECR
# Create the role "ecsTaskExecutionRole"
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
Attach Policy #
Attach the managed AmazonECSTaskExecutionRolePolicy
policy to the role:
# Attach policy to role
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Verify the Role #
# List role details
aws iam get-role --role-name ecsTaskExecutionRole
# Shell output:
{
"Role": {
"Path": "/",
"RoleName": "ecsTaskExecutionRole",
"RoleId": "AROARCHUALIN46RDYSU3J",
"Arn": "arn:aws:iam::012345678912:role/ecsTaskExecutionRole",
"CreateDate": "2024-12-12T17:27:12+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600,
"RoleLastUsed": {}
}
}
AWS Elastic Container Registry (ECR) #
Create ECR Repository #
# Create a repository
aws ecr create-repository --repository-name example-application --region us-east-1
# Shell output:
{
"repository": {
"repositoryArn": "arn:aws:ecr:us-east-1:012345678912:repository/example-application",
"registryId": "012345678912",
"repositoryName": "example-application",
"repositoryUri": "012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application",
"createdAt": "2024-12-12T16:10:00.189000+00:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
Docker Login #
Login the new registry repository:
# Retrieve an authentication token and authenticate the Docker client
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application
Push Image to ECR #
Example Application #
- Dockerfile
# Use the official NGINX base image
FROM nginx:alpine
# Set working directory
WORKDIR /usr/share/nginx/html
# Remove default NGINX static files
RUN rm -rf ./*
# Copy your custom index.html file to the NGINX root directory
COPY index.html ./
# Expose port 80 for the web server
EXPOSE 80
# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
- index.html
<!DOCTYPE html>
<html>
<head>
<title>jklug.work</title>
</head>
<body>
<h1>Some HTML</h1>
<p>Example Application<br></p>
</body>
</html>
Build the Image #
# Build the image for the application
docker build -t example-application .
Tag the Image #
# Tag the local Docker image to associate it with the ECR repository
docker tag example-application:latest 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest
- Docker images deed a fully qualified name to push
Verify the image:
# List Docker images
docker images
# Shell output:
REPOSITORY TAG IMAGE ID CREATED SIZE
012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application latest 7044f8e660dc 32 seconds ago 11.8MB
example-application latest 7044f8e660dc 32 seconds ago 11.8MB
Push the Image to ECR #
# Push the image to ECR
docker push 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest
Verify / List Image in ECR #
# List container images in the ECR repository
aws ecr list-images --repository-name example-application --region us-east-1
# Shell output:
{
"imageIds": [
{
"imageDigest": "sha256:a3589b2487adbbbc6a4d03a9c2cd6c90227d5b67dab7a6925c508fbd3cae5763",
"imageTag": "latest"
}
]
}
ECS Cluster #
Create an ECS Cluster #
- Create a AWS ECS cluster with the default “Networking only” template, designed to use “AWS Fargate (serverless)”
# Create a AWS ECS cluster
aws ecs create-cluster --cluster-name example-cluster --region us-east-1 --no-cli-pager
# Shell output:
{
"cluster": {
"clusterArn": "arn:aws:ecs:us-east-1:012345678912:cluster/example-cluster",
"clusterName": "example-cluster",
"status": "ACTIVE",
"registeredContainerInstancesCount": 0,
"runningTasksCount": 0,
"pendingTasksCount": 0,
"activeServicesCount": 0,
"statistics": [],
"tags": [],
"settings": [
{
"name": "containerInsights",
"value": "disabled"
}
],
"capacityProviders": [],
"defaultCapacityProviderStrategy": []
}
}
Task Definition #
Create a Task Definition #
-
The task definition focuses on container configuration, like image, CPU, memory and ports.
-
Task definitions are not directly tied to a cluster when they are created, they are later referenced when deploying a task or service to a specific cluster.
# Create a configuration file for the task definition
vi task-definition.json
{
"family": "example-application-task",
"executionRoleArn": "arn:aws:iam::012345678912:role/ecsTaskExecutionRole",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "example-application",
"image": "012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest",
"memory": 512,
"cpu": 256,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/example-task",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "example"
}
}
}
],
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512"
}
# Register the task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json --region us-east-1 --no-cli-pager
Verify / List Task Definition #
# List ECS task definitions
aws ecs list-task-definitions --region us-east-1
# Shell output:
{
"taskDefinitionArns": [
"arn:aws:ecs:us-east-1:012345678912:task-definition/example-application-task:1"
]
}
Example Task #
Create a Task #
# Create the task
aws ecs run-task \
--cluster example-cluster \
--task-definition example-application-task \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-0863f1a46bc384e59],assignPublicIp=ENABLED}" \
--region us-east-1
Verify the Task #
# Verify the task
aws ecs list-tasks \
--cluster example-cluster \
--region us-east-1
# Shell output:
{
"taskArns": [
"arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11"
]
}
Describe the Task / List ENI #
# List task details
aws ecs describe-tasks \
--cluster example-cluster \
--tasks arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11 \
--region us-east-1 \
--no-cli-pager
Copy the network interface (ENI) ID of the task, it should look like this:
"name": "networkInterfaceId",
"value": "eni-08b4927774642caab"
Get Public IP of the ENI #
# List the network interface (ENI) ID of the task
aws ec2 describe-network-interfaces \
--network-interface-ids eni-08b4927774642caab \
--region us-east-1 \
--query "NetworkInterfaces[0].Association.PublicIp" \
--output text
# Shell output:
44.222.253.140
Curl Container via Public IP #
# Curl the container
curl 44.222.253.140:80
# Shell output:
<!DOCTYPE html>
<html>
<head>
<title>jklug.work</title>
</head>
<body>
<h1>Some HTML</h1>
<p>Example Application<br></p>
</body>
</html>
Delete the Task #
# Delete the Task
aws ecs stop-task \
--cluster example-cluster \
--task arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11 \
--region us-east-1
Cleanup #
Delete ECS Cluster #
aws ecs delete-cluster \
--cluster example-cluster \
--region us-east-1
Delete Task Definition #
List Task Definitions #
# List ECS task definitions
aws ecs list-task-definitions --region us-east-1
# Shell output:
{
"taskDefinitionArns": [
"arn:aws:ecs:us-east-1:012345678912:task-definition/example-application-task:1"
]
}
Deregister Task Definition #
# Deregister the task definition
aws ecs deregister-task-definition \
--task-definition example-application-task:1 \
--region us-east-1 \
--no-cli-pager
Delete ECR Repository #
# Delete the ECR repository
aws ecr delete-repository --repository-name example-application --region us-east-1 --force
Delete the CloudWatch Log Group #
# Delete the log group
aws logs delete-log-group \
--log-group-name /ecs/example-task \
--region us-east-1