Skip to content

Commit

Permalink
Latest updates, but note
Browse files Browse the repository at this point in the history
- allow_public_ip must be set to true otherwise ECS cannot pull images from ECR, Docker Hub, or anywhere
- Apparently my Solr container has never worked! Need to debug this
- Committing this work-in-progress so people better at AWS and Terraform can provide feedback—I feel lost
  • Loading branch information
nutjob4life committed Jan 9, 2025
1 parent 99958e5 commit 803ec37
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 73 deletions.
17 changes: 16 additions & 1 deletion terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ For me, I used `KEYNAME` = `SKAWS` and the `ed25519` key, so:
aws ec2 import-key-pair --key-name SKAWS --public-key-material fileb://~/.ssh/id_ed25519_aws.pub


## Env vars
## Variables

For personal running (or MCP running), set env vars (bash, fish, ksh, etc.):

Expand All @@ -32,6 +32,8 @@ Or for csh, tcsh, etc.:
setenv AWS_ACCESS_KEY
setenv AWS_SECRET_ACCESS_KEY

Also update `terraform.tfvars`.


## Platform Mismatch Issue

Expand All @@ -41,3 +43,16 @@ If you get the arm64 not supported for template, try:
m1-terraform-provider-helper activate
m1-terraform-provider-helper install hashicorp/template -v 2.2.0
terraform init


## Outputs

Running "by hand" produces outputs like the following:
```
public_dns = "ec2-54-213-219-46.us-west-2.compute.amazonaws.com"
public_ip = "54.213.219.46"
rds_endpoint = "terraform-20250108191909482800000004.ch0oioye4p04.us-west-2.rds.amazonaws.com"
rds_port = 3306
solr_service_dns = "solr.drupalservices.local"
```
These need to become the "inputs" to services "launched by" Terraform so they can contact each other.
1 change: 0 additions & 1 deletion terraform/aws.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ data "aws_subnets" "default" {
values = [data.aws_vpc.default.id]
}
}

203 changes: 145 additions & 58 deletions terraform/drupal.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,77 @@
#
# The real one will use an ECS with the Apache+Drupal image built via GitHub Actions.

# Create an IAM role for the EC2 instance (for demo purposes? or to
# prove we can do it?)
resource "aws_iam_role" "ec2_role" {
name = "apache-server-role"
assume_role_policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow"
}
]
locals {
drupal_version = "latest"
}
EOF


# ECS Execution Role
#
# Allows pulling from ECR, sending logs to CloudWatch, etc.
resource "aws_iam_role" "ecs_execution_role" {
name = "ecs-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs_task_execution_assume_role.json
}

# Attach a simple policy to the role (CloudWatch read example, optional)
resource "aws_iam_role_policy_attachment" "cloudwatch_attach" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess"
data "aws_iam_policy_document" "ecs_task_execution_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}

# Create an instance profile to associate the IAM role with the EC2 instance
resource "aws_iam_instance_profile" "ec2_instance_profile" {
name = "apache-server-instance-profile"
role = aws_iam_role.ec2_role.name
# Attach the AWS-managed policy that allows ECS tasks to pull images and push logs
resource "aws_iam_role_policy_attachment" "ecs_execution_attachment" {
role = aws_iam_role.ecs_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# Security group allowing HTTP (and optional SSH)
resource "aws_security_group" "apache_sg" {
name = "apache_sg"
description = "Allow HTTP inbound traffic"
# Security group for ALB sitting in front of Drupal
resource "aws_security_group" "portal_alb_sg" {
name = "portal-alb-sg"
description = "Security group for the ALB"
vpc_id = data.aws_vpc.default.id

ingress {
description = "HTTP from anywhere"
description = "Inbound HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "portal-alb-sg"
}
}


# Security group for Drupal tasks
resource "aws_security_group" "drupal_tasks_sg" {
name = "drupal-tasks-sg"
description = "Security group for Drupal ECS tasks"
vpc_id = data.aws_vpc.default.id

ingress {
description = "Inbound from the ALB on port 80"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.portal_alb_sg.id]
}

# If SSH is allowed
dynamic "ingress" {
for_each = var.allow_ssh ? [1] : []
Expand All @@ -71,46 +96,108 @@ resource "aws_security_group" "apache_sg" {
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "drupal-tasks-sg"
}
}

# Simple user data to install Apache and create an index.html
locals {
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo "<html><h1>Hey world, 👋 🌎</h1></html>" > /var/www/html/index.html
EOF
# ALB (Application Load Balancer)
resource "aws_lb" "portal_alb" {
name = "portal-alb"
load_balancer_type = "application"
security_groups = [aws_security_group.portal_alb_sg.id]
subnets = data.aws_subnets.default.ids

tags = {
Name = "portal-alb"
}
}

# Launch EC2 instance
resource "aws_instance" "apache_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.drupal_instance_type
key_name = var.key_name
user_data = local.user_data
resource "aws_lb_target_group" "portal_tg" {
name = "portal-tg"
port = 80
protocol = "HTTP"
vpc_id = data.aws_vpc.default.id
target_type = "ip" # ECS Fargate tasks require 'ip' target type

health_check {
path = "/"
protocol = "HTTP"
}

iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name
tags = {
Name = "portal-tg"
}
}

# Just pick the first subnet for simplicity
subnet_id = data.aws_subnets.default.ids[0]
resource "aws_lb_listener" "portal_http_listener" {
load_balancer_arn = aws_lb.portal_alb.arn
port = 80
protocol = "HTTP"

vpc_security_group_ids = [aws_security_group.apache_sg.id]
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.portal_tg.arn
}

tags = {
Name = "ApacheServer"
Name = "portal-http-listener"
}
}

# Data source for the latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
# ECS Cluster
resource "aws_ecs_cluster" "portal_cluster" {
name = "drupal-portal-cluster"
}

# ECS Task Definition
resource "aws_ecs_task_definition" "portal_task" {
family = "portal-task"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256 # 0.25 vCPU
memory = 512 # 0.5 GB RAM
execution_role_arn = aws_iam_role.ecs_execution_role.arn

filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
container_definitions = <<-EOF
[
{
"name": "portal",
"image": "${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/drupal-portal:${local.drupal_version}",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080
}
]
}
]
EOF
}

# ECS Service behind the ALB
resource "aws_ecs_service" "portal_service" {
name = "portal-service"
cluster = aws_ecs_cluster.portal_cluster.id
task_definition = aws_ecs_task_definition.portal_task.arn
desired_count = 1
launch_type = "FARGATE"

network_configuration {
subnets = data.aws_subnets.default.ids
security_groups = [aws_security_group.drupal_tasks_sg.id]
assign_public_ip = true # This MUST be true or else images can't be pulled from ECR, Docker Hub, or ANYWHERE
}

load_balancer {
target_group_arn = aws_lb_target_group.portal_tg.arn
container_name = "portal"
container_port = 8080
}
depends_on = [
aws_lb_listener.portal_http_listener
]
}

20 changes: 10 additions & 10 deletions terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
output "public_ip" {
description = "Public IP address of the Apache server"
value = aws_instance.apache_server.public_ip
}

output "public_dns" {
description = "Public DNS name of the Apache server"
value = aws_instance.apache_server.public_dns
}

# Outputs
# =======
#
# For debugging mostly

output "rds_endpoint" {
description = "Endpoint of the RDS instance—internal to VPC only (informative)"
value = var.enable_rds && length(aws_db_instance.mysql_db) > 0 ? aws_db_instance.mysql_db[0].address : null
}


output "rds_port" {
description = "Port of the RDS instance—internal to VPC only (informative)"
value = var.enable_rds && length(aws_db_instance.mysql_db) > 0 ? aws_db_instance.mysql_db[0].port : null
}

output "portal_alb_dns_name" {
description = "Public DNS name of the ALB"
value = aws_lb.portal_alb.dns_name
}
2 changes: 1 addition & 1 deletion terraform/rds.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ resource "aws_security_group" "rds_sg" {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.apache_sg.id]
security_groups = [aws_security_group.drupal_tasks_sg.id]
}

egress {
Expand Down
4 changes: 2 additions & 2 deletions terraform/solr.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ resource "aws_security_group" "solr_sg" {
from_port = local.solr_port
to_port = local.solr_port
protocol = "tcp"
security_groups = [aws_security_group.apache_sg.id]
security_groups = [aws_security_group.drupal_tasks_sg.id]
}

egress {
Expand Down Expand Up @@ -122,7 +122,7 @@ resource "aws_ecs_service" "solr_service" {

network_configuration {
subnets = data.aws_subnets.default.ids
assign_public_ip = false
assign_public_ip = true # This MUST be true or else images can't be pulled from ECR, Docker Hub, or ANYWHERE
security_groups = [aws_security_group.solr_sg.id]
}

Expand Down

0 comments on commit 803ec37

Please sign in to comment.