AWS and Docker

Docker has become an essential skill for developers and an integral part of DevOps practices in modern software development. The Docker container make application more efficiently and reliably accross different environments.

This article mainly to record down my on hand experience about deploying Docker on AWS.

image

To achieve this, I will need to:

  1. Create a AWS account and a custom role
  2. Create a S3 bucket to store Terraform state
  3. Create a Terraform Github repository and coding
  4. Run Terraform to create AWS EC2

Docker Deploy

Amazon Web Service (AWS) provide 6 months free plan for new user the free cost up to 200 USD.

I will apply the free plan and create Docker to AWS EC2.

AWS Authentication

Generate Access Key

Firstly, Create a new user, don’t use root user to operate AWS resource.

Setup the root user and role, assign trust relationships and role policy, make sure the new user can assume the new roles.

image

Secondly. login with new user, switch to role, and switch to IAM function so that we can create access key for terraform api call.

image

Select Command Line Interface (CLI) , click “Next” button

image

Provide description for the access key and then confirm.

image

Remember to store your key safely.

*Each IAM user can have a maximum of two active access keys at a time.

image

Install AWS CLI and Login

AWS CLI Installation Guide

Install AWS CLI on local machine, execute below command.

*For Mac, we can download pkg directly.

1
2
3
4
5
6
brew install awscli

# Health check result
(base) yihui.li@yihuilideMBP ~ % aws --version
aws-cli/2.32.16 Python/3.13.11 Darwin/22.6.0 exe/x86_64
(base) yihui.li@yihuilideMBP ~ %

Regarding authenticate to AWS via command line, we have 2 methods to configure the access.

The first method is to setup Linux variable.

From above result, we need to config AWS key pair and region

1
2
3
4
5
6
# Better to store it on ~/.bash_profile
export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="your_region"
export AWS_ACCOUNT_ID="your_account_id"
export AWS_ROLE_NAME="your_role_name"

To verify AWS config, run below command

1
aws sts assume-role --role-arn arn:aws:iam::$AWS_ACCOUNT_ID:role/$AWS_ROLE_NAME --role-session-name "CLI-Session"

image

By default, session duration is 1 hour, can use --duration-seconds to specify the vaule.

Session duration can’t exceed the Maximum session duration on AWS IAM role setting.

image

Or we can choose AWS config file to login.

Run command aws configure, it will create 2 files under ~/.aws/

~/.aws/credentials This file mainly to store Access Key ID and Secret Access Key

~/.aws/config This file will define each aws profile

After the setup done, can use aws configure list to list all profile setting.

image

Run below command to login.

If we have many profile defined on ~/.aws/config, can use --profile your-profile-name to specify the value.

1
2
3
export AWS_ACCOUNT_ID="your_account_id"
export AWS_ROLE_NAME="your_role_name"
aws sts assume-role --role-arn arn:aws:iam::$AWS_ACCOUNT_ID\:role/$AWS_ROLE_NAME --role-session-name "CLI-Session" --profile default

Next Step

After the login, it will return the SessionToken, now we should store it together with AWS key pair to the local variable. Because previously we only assume the role, it’s not contain any AWS resource policy, only the token represent the role permission actually.

If we don’t setup the AWS variable including the token, we can’t create any resources on AWS.

Here is the full command.

1
2
3
4
5
6
7
8
9
# AWS CLI assume role
export AWS_ACCOUNT_ID="your_account_id"
export AWS_ROLE_NAME="your_role_name"
aws sts assume-role --role-arn arn:aws:iam::$AWS_ACCOUNT_ID\:role/$AWS_ROLE_NAME --role-session-name "CLI-Session" --profile default > /tmp/aws_creds.json

# Setup the IAM credentials
export AWS_ACCESS_KEY_ID=$(cat /tmp/aws_creds.json | grep AccessKeyId | cut -d '"' -f 4)
export AWS_SECRET_ACCESS_KEY=$(cat /tmp/aws_creds.json | grep SecretAccessKey | cut -d '"' -f 4)
export AWS_SESSION_TOKEN=$(cat /tmp/aws_creds.json | grep SessionToken | cut -d '"' -f 4)

Example

Step 1 only assume role but not specify the IAM role credentials. It will permission deny error.

But step2 works fine after proivide the IAM role credentials.

image

One click auth script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# assume-role.sh

# This file should contain account_id, access key, role name
source ~/.bash_profile

CREDENTIALS_FILE="/tmp/aws_creds.json"

echo "Assuming $AWS_ROLE_NAME role..."
aws sts assume-role \
--role-arn "arn:aws:iam::$AWS_ACCOUN_ID\:role/$AWS_ROLE_NAME" \
--role-session-name "Terraform-$(date +%s)" \
--profile default > $CREDENTIALS_FILE

export AWS_ACCESS_KEY_ID=$(jq -r '.Credentials.AccessKeyId' $CREDENTIALS_FILE)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.Credentials.SecretAccessKey' $CREDENTIALS_FILE)
export AWS_SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' $CREDENTIALS_FILE)

# Health check
echo "Current identity:"
aws sts get-caller-identity

# Set timer for the session
echo "Credentials valid for 1 hour"

Result as below

image

Execute Terraform

Github Repository

Create terraform state bucket

Run step 1 to step 5, now we can find our own bucket. The bucket mainly to store Terraform state.

The bucket should include below feature now:

  • Enable version for each terraform change
  • Enable AES256 server side encryption
  • Only authenticated access can access the bucket

Health check result should as below

image

image

image

Run step 6 to create table on DynamoDB.

The table is to store the Terraform lock to prevent concurrent writes and ensures data consistency. Since DynamoDB is a AWS service, the locking mechanism should be good and reliable.

For cost saving, we can skip this part if needed. It will also need DynamoDB IAM access.

*We should also enable the deletion protection as well since it’s a important table, but I will skip this part.

image

Create EC2

Follow the step7, it wll initialize Terraform local repository, like download the AWS provider.

image

Run step 8 to create EC2 on AWS

This step will:

  • Create ssh key pair, store pem file on local and upload public key on AWS
  • Create EC2 with Amazon Linux 2 with docker installed
  • Assign inbound security group to the EC2
  • Allow AWS System Manager to manage EC2

Health check - Below AWS resource should be modified.

Terraform works fine, ssh connected.

image

Try to reboot the EC2 from console, the docker process should auto startup

image

Use System Manager to connect EC2

image

image

S3 bucket should be updated.

image

DynamoDB should also updated due to terraform state changed.

image

About EC2 key pair

During the testing, it will auto create the key pair and store it on local machine.

Actually the key pair is generated by terraform plugin tls_private_key.

But when we destroy the module as below, it will also delete the local file.

1
terraform destroy -target=module.ec2-docker -var-file="./aws-docker.tfvars"

To avoid this, we’d better use linux command to generate the key pair first, and then specify the key file path on PROD environment.

1
ssh-keygen -t rsa -b 4096 -m PEM -f ec2-docker-keypair

Troubleshoot

Handle terraform state lock

Sometimes terraform state file will be locked as below.

image

Solution:

Temporarily we can add -lock=false as below to skip the lock

1
terraform destroy -target=module.ec2-docker -var-file="./aws-docker.tfvars" --auto-approve -lock=false

For long term solution, we need to handle the S3 status.

Here I’m using S3 + DynamoDB, so I will need to handle the table record as well.

step1, try to init the terraform repository again

1
2
3
terraform init -reconfigure

terraform destroy -target=module.ec2-docker -var-file="./aws-docker.tfvars" --auto-approve

If it’s not working, try force unlock

1
2
3
4
5
terraform force-unlock aff280c9-91f6-971b-7d9a-4539ce90072f

# Optional for DynamoDB
aws dynamodb scan --table-name terraform-state-lock-table
aws dynamodb delete-item --table-name terraform-state-lock-table --key '{"LockID": {"S": "795359014551-terraform-state/tfstate/terraform.tfstate-md5"}}'

Result as below

image

IAM role simulation

During the terraform creating, we might need to check whether our role have enough permission for AWS action, then we can use below command to test.

1
2
3
4
5
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::$AWS_ACCOUN_ID\:user/$AWS_ROLE_NAME \
--action-names "ec2:DescribeImages" \
--resource-arns "*"

For example

image

Add 1 if else condition for the public key path

timestamp and hex for security group

0%