Skip to content

emrahsifoglu/ha-nginx-cluster-iac

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

30 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Deploying a HA Nginx cluster in Docker using IaC

Diagram


Prerequisites


Project Setup

Create IAM User on AWS Console

For the purpose of the project, the AdministratorAccess managed policy is acceptable.

Set Up AWS CLI Credentials

You need the credentials that contain your aws_access_key_id and your aws_secret_access_key.

It is highly recommended to check service costs before choosing a region like eu-north-1.

# Set up your AWS CLI with your access keys and default region
aws configure
# Verify your AWS credentials by showing the account and user details
aws sts get-caller-identity
# List all IAM policies attached to a specific user
aws iam list-attached-user-policies --user-name <user-name>

Create Budget

Do not forget to set the email address in ./config/notifications.json.

# Get your account id
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws budgets create-budget \
  --account-id $ACCOUNT_ID \
  --budget file://config/budget.json \
  --notifications-with-subscribers file://config/notifications.json \
  --region us-north-1

Create Backend

After running the command below, you can copy ./config/configuration.s3.tfbackend.example to ./config/configuration.s3.tfbackend then update its content.

Create S3 Bucket

aws s3api create-bucket --bucket <unique-tf-state-bucket-name> \
  --region <region> \
  --create-bucket-configuration LocationConstraint=<region>

Expected output

{
    "Location": "http://<unique-tf-state-bucket-name>.s3.amazonaws.com/"
}

Create DynamoDB Table

aws dynamodb create-table --table-name <tf-state-lock-table> \
  --region <region> \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST 

Expected output(partially):

{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "LockID",
                "AttributeType": "S"
            }
        ],
        "TableName": "<tf-state-lock-table>",
        "KeySchema": [
            {
                "AttributeName": "LockID",
                "KeyType": "HASH"
            }
        ],
        "TableStatus": "CREATING",
        ...
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST"
        },
        "DeletionProtectionEnabled": false
    }
}
# You can check the status after 10-30 second
aws dynamodb describe-table --table-name sif-state-lock-table --region eu-north-1 --query 'Table.TableStatus'

Expected output: is "ACTIVE"

Initializes a Working Directory

# With backend configuration
terraform init -backend-config=config/configuration.s3.tfbackend

Expected output:

Successfully configured the backend "s3"! Terraform will automatically
Terraform has been successfully initialized!

Generate self-signed certificate

The command below generates a self-signed certificate used in a non-production environment. Therefore the domain name in the Common Name (CN) field does not have to match a real domain.

openssl req -x509 -newkey rsa:2048 -nodes -keyout private.key -out certificate.crt \
  -days 365 \
  -subj "/CN=your-alb-name.eu-north-1.elb.amazonaws.com"

Provision the Infrastructure

terraform plan -var-file="terraform.tfvars" -out=plan
terraform apply "plan"

How to Deploy to ESC Cluster

Manually Authenticate

Be aware that access-token will get expired after sometime.

aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com

Build the Docker Image

Build the Docker image for the Linux arm64 platform and this is a must.

docker buildx build --platform linux/arm64 -t ha-nginx-app . 

Tag the Image for the ECR Repository

docker tag ha-nginx-app <account-id>.dkr.ecr.<region>.amazonaws.com<registry>:latest

Push the Image to ECR Repository

docker push <account-id>.dkr.ecr.<region>.amazonaws.com<registry>:latest

Deploy the Service

After image is pushed, you can force new deployment on ECS Service.

aws ecs update-service \
  --cluster <cluster-name> \
  --service <service-name> \
  --force-new-deployment

Build GitHub Workflow

Make sure that trust-policy.sh is executable: chmod +x ./scripts/trust-policy.sh

# Create trust-policy.json for the role
./scripts/trust-policy.sh <account-id> <github-username> <github-repo-name>

Create a Role for GitHub Actions

aws iam create-role \                      
  --role-name GitHubActionsRole \                  
  --assume-role-policy-document file://config/trust-policy.json
# Attach ECR policy for image operations
aws iam attach-role-policy \
    --role-name GitHubActionsRole \
    --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
# Attach ECS policy for deployment
aws iam attach-role-policy \
    --role-name GitHubActionsRole \
    --policy-arn arn:aws:iam::aws:policy/AmazonECS_FullAccess

Create an OpenID Connect Provider

THUMBPRINT=$(echo | openssl s_client -servername token.actions.githubusercontent.com \
  -connect token.actions.githubusercontent.com:443 2>/dev/null \
  | openssl x509 -fingerprint -noout \
  | sed 's/://g' | awk -F= '{print tolower($2)}')
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list $THUMBPRINT

Expected output:

{
    "OpenIDConnectProviderArn": "arn:aws:iam::<account-id>:oidc-provider/token.actions.githubusercontent.com"
}

Set Secrets for GitHub Actions

The table below lists all secrets required for the workflow in GitHub.

Name Last updated
AWS_ACCOUNT_ID Unique identifier for the AWS account
AWS_REGION 1 The AWS region where resources are deployed (e.g., eu-north-1)
AWS_ROLE_ARN IAM Role ARN assumed by the service for resource access
CLUSTER_NAME The name of the target ECS or EKS cluster
ECR_REPOSITORY The name of the Amazon ECR container image repository
SERVICE_NAME The name of the target ECS service within the cluster

Usage

Assuming that there is only the Application Load Balancer.

# Get the DNS name of the Application Load Balancer
ALB_DNS=$( aws elbv2 describe-load-balancers --query 'LoadBalancers[*].[DNSName]' --output text)
# Check the health endpoint
curl --location "http://${ALB_DNS}/health"
curl -k --location "https://${ALB_DNS}/health"

Expected output: is healthy

# Check the phrase endpoint
curl --location "http://${ALB_DNS}/phrase"
curl -k --location "https://${ALB_DNS}/phrase"

Expected output: is OK


πŸ“š Resources

🧱 Terraform & Infrastructure as Code

🐳 AWS ECS / Fargate / ECR

🌐 NGINX, EC2 & Load Balancing

βš™οΈ Troubleshooting & AWS Networking

πŸŽ₯ Video Tutorials

About

Deploying a highly available Nginx cluster in Docker using IaC

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published