chore: Add README

This commit is contained in:
Alexander Hosking 2023-09-07 01:00:13 -04:00
parent f4e9dcd5e8
commit dfcef80c41
2 changed files with 100 additions and 86 deletions

19
air-tech/README.md Normal file
View File

@ -0,0 +1,19 @@
# Air-Tech
## Definition of Done
- [x] This infrastructure is a production quality infrastructure.
- [x] The coding exercise is to run this environment in AWS.
- [ ] We are looking for an infrastructure-as-code (IaC) solution using Pulumi.
- [x] You are free to architect the infrastructure as you see fit. The final goal is to run both services (Web and API) on AWS. We are leaving all the details to you.
- [x] Be mindful of security best practices. The API is not a public API, and it is only accessed by Web UI.
- [x] Only the web UI should be accessible through the internet (port 80)
- [ ] Tag all the environment resources for auditing purposes.
## Diagram
![Infra Diagram](infra_diagram.png)
## What's missing?
Tags are missing, the solution does not run to a viable url because of permissions issues between ECS and ECR. When these are tweaked outside of Pulumi, the service works as expected.

View File

@ -1,19 +1,17 @@
import pulumi import pulumi
from pulumi import Config, Output, export from pulumi import Config
import pulumi_aws as aws import pulumi_aws as aws
from pulumi_aws import acm, alb, ecs, ec2, lb, iam, ecr from pulumi_aws import acm, ecs, ec2, lb, iam, ecr
import pulumi_awsx as awsx import pulumi_awsx as awsx
import json
config = Config() config = Config()
# container_port = config.get_int("containerPort", 80)
cpu = config.get_int("cpu", 512) cpu = config.get_int("cpu", 512)
memory = config.get_int("memory", 128) memory = config.get_int("memory", 128)
# Create a VPC
vpc = ec2.Vpc("production", cidr_block="10.0.0.0/16") vpc = ec2.Vpc("production", cidr_block="10.0.0.0/16")
# Create a Gateway
ig = ec2.InternetGateway("internet-gateway", vpc_id=vpc.id) ig = ec2.InternetGateway("internet-gateway", vpc_id=vpc.id)
cert = acm.Certificate( cert = acm.Certificate(
@ -21,14 +19,12 @@ cert = acm.Certificate(
domain_name="air-tech.ahosking.com", domain_name="air-tech.ahosking.com",
validation_method="DNS", validation_method="DNS",
) )
# Public Subnet
public_subnet = ec2.Subnet( public_subnet = ec2.Subnet(
"public-subnet", "public-subnet",
vpc_id=vpc.id, vpc_id=vpc.id,
cidr_block="10.0.1.0/26", cidr_block="10.0.1.0/26",
availability_zone="us-east-2a", availability_zone="us-east-2a",
# Allow the Internet Gateway to handle public traffic
map_public_ip_on_launch=True map_public_ip_on_launch=True
) )
public_subnet2 = ec2.Subnet( public_subnet2 = ec2.Subnet(
@ -36,17 +32,15 @@ public_subnet2 = ec2.Subnet(
vpc_id=vpc.id, vpc_id=vpc.id,
cidr_block="10.0.2.0/26", cidr_block="10.0.2.0/26",
availability_zone="us-east-2b", availability_zone="us-east-2b",
# Allow the Internet Gateway to handle public traffic
map_public_ip_on_launch=True map_public_ip_on_launch=True
) )
# Associate the Gateway to a route route_table = ec2.RouteTable("route-table", vpc_id=vpc.id)
route_table = ec2.RouteTable('route-table', vpc_id=vpc.id) route = ec2.Route(
route = ec2.Route('route', route_table_id=route_table.id, "route", route_table_id=route_table.id,
destination_cidr_block='0.0.0.0/0', gateway_id=ig.id) destination_cidr_block="0.0.0.0/0", gateway_id=ig.id)
# Private Subnet
private_subnet = ec2.Subnet( private_subnet = ec2.Subnet(
"private-subnet", "private-subnet",
vpc_id=vpc.id, vpc_id=vpc.id,
@ -63,37 +57,36 @@ private_subnet2 = ec2.Subnet(
) )
repo = ecr.Repository("repo") repo = ecr.Repository("repo")
# Create an IAM role # ecs_task_execution_role = iam.Role("ecsExecutionRole",
ecs_task_execution_role = iam.Role("ecsExecutionRole", # path="/",
path="/", # assume_role_policy=iam.get_policy_document(statements=[{
assume_role_policy=iam.get_policy_document(statements=[{ # "actions": ["sts:AssumeRole"],
"actions": ["sts:AssumeRole"], # "principals": {
"principals": { # "type": "Service",
"type": "Service", # "identifiers": ["ecs-tasks.amazonaws.com"]
"identifiers": ["ecs-tasks.amazonaws.com"] # }
} # }
} # ]).json,
]).json, # )
)
# Attach a policy to the execution role that will allow it to pull images from ECR
iam.RolePolicy("accessECRImages", # iam.RolePolicy("accessECRImages",
role=ecs_task_execution_role.id, # role=ecs_task_execution_role.id,
policy={ # policy={
"Version": "2012-10-17", # "Version": "2012-10-17",
"Statement": [ # "Statement": [
{ # {
"Effect": "Allow", # "Effect": "Allow",
"Action": [ # "Action": [
"ecr:GetDownloadUrlForLayer", # "ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage", # "ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability", # "ecr:BatchCheckLayerAvailability",
], # ],
"Resource": repo.repository_arn # "Resource": repo.repository_arn
} # }
] # ]
} # }
) # )
image_web = awsx.ecr.Image( image_web = awsx.ecr.Image(
@ -118,7 +111,8 @@ security_group = aws.ec2.SecurityGroup(
cidr_blocks=["0.0.0.0/0"], cidr_blocks=["0.0.0.0/0"],
ipv6_cidr_blocks=["::/0"], ipv6_cidr_blocks=["::/0"],
)]) )])
# Create an ECS Cluster
cluster = ecs.Cluster("cluster") cluster = ecs.Cluster("cluster")
pulumi.export("certificateArn", cert.arn) pulumi.export("certificateArn", cert.arn)
@ -129,50 +123,52 @@ loadbalancer = lb.LoadBalancer(
subnets=[public_subnet.id, public_subnet2.id]) subnets=[public_subnet.id, public_subnet2.id])
target_group = lb.TargetGroup('target-group', target_group = lb.TargetGroup(
port=5000, "target-group",
protocol="HTTP", port=5000,
target_type="ip", protocol="HTTP",
vpc_id=vpc.id) target_type="ip",
vpc_id=vpc.id)
# Create a listener for the ALB at port 80 listener = lb.Listener(
listener = lb.Listener('listener', "listener",
load_balancer_arn=loadbalancer.arn, load_balancer_arn=loadbalancer.arn,
port=80, port=80,
default_actions=[{ default_actions=[{
'type': "forward", "type": "forward",
'target_group_arn': target_group.arn # Forward to our target group "target_group_arn": target_group.arn
}]) }])
web_sg = ec2.SecurityGroup('web-sg', web_sg = ec2.SecurityGroup(
vpc_id=vpc.id, "web-sg",
ingress=[ vpc_id=vpc.id,
{ ingress=[
'protocol': 'tcp', {
'from_port': 80, "protocol": "tcp",
'to_port': 80, "from_port": 80,
'cidr_blocks': ['0.0.0.0/0'], "to_port": 80,
} "cidr_blocks": ["0.0.0.0/0"],
], }
egress=[ ],
{ egress=[
'protocol': '-1', {
'from_port': 0, "protocol": "-1",
'to_port': 0, "from_port": 0,
'cidr_blocks': ['0.0.0.0/0'], "to_port": 0,
} "cidr_blocks": ["0.0.0.0/0"],
] }
) ]
)
api = awsx.ecs.FargateService( api = awsx.ecs.FargateService(
"api", "api",
cluster=cluster.arn, cluster=cluster.arn,
network_configuration={ network_configuration={
'assignPublicIp': "false", "assignPublicIp": "false",
'subnets': [private_subnet.id], "subnets": [private_subnet.id],
'securityGroups': [web_sg.id] "securityGroups": [web_sg.id]
}, },
task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs(
container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( container=awsx.ecs.TaskDefinitionContainerDefinitionArgs(
@ -193,11 +189,10 @@ service = awsx.ecs.FargateService(
"web", "web",
cluster=cluster.arn, cluster=cluster.arn,
network_configuration={ network_configuration={
'assignPublicIp': "true", "assignPublicIp": "true",
'subnets': [public_subnet.id], "subnets": [public_subnet.id],
'securityGroups': [web_sg.id] "securityGroups": [web_sg.id]
}, },
# assign_public_ip=True,
task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs(
container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( container=awsx.ecs.TaskDefinitionContainerDefinitionArgs(
name="theOne", name="theOne",