Skip to content

Commit 1348d34

Browse files
author
Kyler Middleton
committed
init
0 parents  commit 1348d34

File tree

4 files changed

+451
-0
lines changed

4 files changed

+451
-0
lines changed

main.tf

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
provider "aws" {
2+
region = "us-east-1"
3+
}
4+
5+
# Lookup secret information, we'll use this later to extract the KMS CMK ARN so we can grant permissions to it
6+
data "aws_secretsmanager_secret" "kms_cmk_arn" {
7+
arn = "kms_cmk_arn" # We look up the secret via ARN, and find the KMS CMK's ARN, referenced below
8+
}
9+
10+
module "Ue1TiGitHubBuilders" {
11+
source = "./modules/ecs_on_fargate"
12+
ecs_name = "ResourceGroupName" # Name to use for customizing resources, permits deploying this module multiple times with different names
13+
image_ecr_url = "url_of_ECR" # URL of the container repo where image is stored
14+
15+
task_environment_variables = [ # List of maps of environment variables to pass to container when it's spun up
16+
{ name : "ENV1", value : "env_value1" }, # Remember these are clear-text in the console and via CLI
17+
{ name : "ENV2", value : "env_value2" }
18+
]
19+
task_secret_environment_variables = [ #Use this secret block for secrets, passkeys, etc.
20+
{ name : "SECRET", valueFrom : "secrets_manager_secret_arn" } # Note we're using 'valueFrom' here, which accepts a secrets manager ARN rather than plain-text secret
21+
]
22+
23+
execution_iam_access = {
24+
secrets = [
25+
"secrets_manager_secret_arn" # ARN of secret to grant access to
26+
]
27+
kms_cmk = [
28+
data.aws_secretsmanager_secret.kms_cmk_arn.kms_key_id # For secret encrypted with CMK, find CMK ARN and grant access
29+
]
30+
s3_buckets = [
31+
"s3_bucket_arn" # S3 bucket ARN to grant access to
32+
]
33+
}
34+
35+
task_role_arn = "arn_of_task_role" # This role is used by the container that's launched
36+
37+
service_subnets = [ # A list of subnets to put the fargate and container into
38+
var.subnet1_id,
39+
var.subnet2_id,
40+
]
41+
service_sg = [ # A list of SGs to assign to the container
42+
var.sg_id,
43+
]
44+
}

modules/ecs_on_fargate/data.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
data "aws_region" "current_region" {} # Find region, e.g. us-east-1
2+
data "aws_caller_identity" "current_account" {} # Find account ID, e.g. 198451936645
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# Cloudwatch to store logs
2+
resource "aws_cloudwatch_log_group" "CloudWatchLogGroup" {
3+
name = "${var.ecs_name}LogGroup"
4+
5+
tags = {
6+
Terraform = "true"
7+
Name = "${var.ecs_name}LogGroup"
8+
}
9+
}
10+
11+
# Create new IAM role for execution policy to use
12+
resource "aws_iam_role" "ExecutionRole" {
13+
name = "${var.ecs_name}ExecutionRole"
14+
assume_role_policy = jsonencode({
15+
Version = "2012-10-17"
16+
Statement = [
17+
{
18+
Action = "sts:AssumeRole"
19+
Effect = "Allow"
20+
Sid = ""
21+
Principal = {
22+
Service = "ecs-tasks.amazonaws.com"
23+
}
24+
},
25+
]
26+
})
27+
28+
tags = {
29+
Name = "${var.ecs_name}ExecutionRole"
30+
Terraform = "true"
31+
}
32+
}
33+
34+
# Link to AWS-managed policy - AmazonECSTaskExecutionRolePolicy
35+
resource "aws_iam_role_policy_attachment" "ExecutionRole_to_ecsTaskExecutionRole" {
36+
role = aws_iam_role.ExecutionRole.name
37+
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
38+
}
39+
40+
# Construct IAM policies
41+
locals {
42+
# Find all secret ARNs and output as a list
43+
execution_iam_secrets = try(
44+
flatten([
45+
for permission_type, permission_targets in var.execution_iam_access : [
46+
for secret in permission_targets : "${secret}*"
47+
]
48+
if permission_type == "secrets"
49+
]),
50+
# If nothing provided, default to empty set
51+
[],
52+
)
53+
54+
# Final all S3 bucket ARNs and output as list
55+
execution_iam_s3_buckets = try(
56+
flatten([
57+
for permission_type, permission_targets in var.execution_iam_access : permission_targets if permission_type == "s3_buckets"
58+
]),
59+
# If nothing provided, default to empty set
60+
[],
61+
)
62+
63+
# Final all S3 bucket ARNs and output as list for object access
64+
execution_iam_s3_buckets_object_access = try(
65+
flatten(
66+
[
67+
for buckets in local.execution_iam_s3_buckets : "${buckets}/*"
68+
]
69+
),
70+
# If nothing provided, default to empty set
71+
[],
72+
)
73+
74+
# Find all KMS CMK ARNs passed to module and output as a list
75+
execution_iam_kms_cmk = try(
76+
flatten([
77+
for permission_type, permission_targets in var.execution_iam_access : [
78+
for kms_cmk in permission_targets : kms_cmk
79+
]
80+
if permission_type == "kms_cmk"
81+
]),
82+
# If nothing provided, default to empty set
83+
[],
84+
)
85+
}
86+
87+
# Construct the secrets policy
88+
data "aws_iam_policy_document" "ecs_secrets_access" {
89+
count = local.execution_iam_secrets == [] ? 0 : 1
90+
statement {
91+
sid = "${var.ecs_name}EcsSecretAccess"
92+
#effect = "Allow"
93+
resources = local.execution_iam_secrets
94+
actions = [
95+
"secretsmanager:GetSecretValue",
96+
]
97+
}
98+
}
99+
100+
# Build role policy using data, link to role
101+
resource "aws_iam_role_policy" "ecs_secrets_access_role_policy" {
102+
count = local.execution_iam_secrets == [] ? 0 : 1
103+
name = "${var.ecs_name}EcsSecretExecutionRolePolicy"
104+
role = aws_iam_role.ExecutionRole.id
105+
policy = data.aws_iam_policy_document.ecs_secrets_access[0].json
106+
}
107+
108+
# Construct the S3 bucket list policy
109+
data "aws_iam_policy_document" "s3_bucket_list_access" {
110+
count = local.execution_iam_s3_buckets == [] ? 0 : 1
111+
statement {
112+
sid = "S3ListBucketAccess"
113+
effect = "Allow"
114+
resources = local.execution_iam_s3_buckets
115+
actions = [
116+
"s3:ListBucket",
117+
]
118+
}
119+
}
120+
121+
# Build role policy using data, link to role
122+
resource "aws_iam_role_policy" "ecs_s3_bucket_list_access_role_policy" {
123+
count = local.execution_iam_s3_buckets == [] ? 0 : 1
124+
name = "${var.ecs_name}EcsS3BucketListExecutionRolePolicy"
125+
role = aws_iam_role.ExecutionRole.id
126+
policy = data.aws_iam_policy_document.s3_bucket_list_access[0].json
127+
}
128+
129+
# Construct the S3 bucket object policy
130+
data "aws_iam_policy_document" "s3_bucket_object_access" {
131+
count = local.execution_iam_s3_buckets_object_access == [] ? 0 : 1
132+
statement {
133+
sid = "S3BucketObjectAccess"
134+
effect = "Allow"
135+
resources = local.execution_iam_s3_buckets_object_access
136+
actions = [
137+
"s3:PutObject",
138+
"s3:GetObject",
139+
"s3:DeleteObject"
140+
]
141+
}
142+
}
143+
144+
# Build role policy using data, link to role
145+
resource "aws_iam_role_policy" "ecs_s3_bucket_object_access_role_policy" {
146+
count = local.execution_iam_s3_buckets_object_access == [] ? 0 : 1
147+
name = "${var.ecs_name}EcsS3BucketObjectAccessExecutionRolePolicy"
148+
role = aws_iam_role.ExecutionRole.id
149+
policy = data.aws_iam_policy_document.s3_bucket_object_access[0].json
150+
}
151+
152+
# Construct the S3 bucket object policy
153+
data "aws_iam_policy_document" "kms_cmk_access" {
154+
count = local.execution_iam_kms_cmk == [] ? 0 : 1
155+
statement {
156+
sid = "KmsCmkAccess"
157+
effect = "Allow"
158+
resources = local.execution_iam_kms_cmk
159+
actions = [
160+
"kms:Decrypt"
161+
]
162+
}
163+
}
164+
165+
# Build role policy using data, link to role
166+
resource "aws_iam_role_policy" "ecs_kms_cmk_access_role_policy" {
167+
count = local.execution_iam_kms_cmk == [] ? 0 : 1
168+
name = "${var.ecs_name}EcsKmsCmkAccessExecutionRolePolicy"
169+
role = aws_iam_role.ExecutionRole.id
170+
policy = data.aws_iam_policy_document.kms_cmk_access[0].json
171+
}
172+
173+
# Task definition
174+
# Will be relaunched by service frequently
175+
resource "aws_ecs_task_definition" "ecs_task_definition" {
176+
family = "${var.ecs_name}"
177+
execution_role_arn = aws_iam_role.ExecutionRole.arn
178+
task_role_arn = var.task_role_arn == null ? null : var.task_role_arn
179+
network_mode = "awsvpc"
180+
requires_compatibilities = ["FARGATE"]
181+
# Fargate cpu/mem must match available options: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
182+
cpu = var.fargate_cpu
183+
memory = var.fargate_mem
184+
container_definitions = jsonencode(
185+
[
186+
{
187+
name = "${var.ecs_name}"
188+
image = "${var.image_ecr_url}:${var.image_tag}"
189+
cpu = "${var.container_cpu}"
190+
memory = "${var.container_mem}"
191+
essential = true
192+
environment = var.task_environment_variables == [] ? null : var.task_environment_variables
193+
secrets = var.task_secret_environment_variables == [] ? null : var.task_secret_environment_variables
194+
logConfiguration : {
195+
logDriver : "awslogs",
196+
options : {
197+
awslogs-group : "${var.ecs_name}LogGroup",
198+
awslogs-region : "${data.aws_region.current_region.name}",
199+
awslogs-stream-prefix : "${var.ecs_name}"
200+
}
201+
}
202+
}
203+
]
204+
)
205+
206+
tags = {
207+
Name = "${var.ecs_name}"
208+
}
209+
}
210+
211+
# Cluster is compute that service will run on
212+
resource "aws_ecs_cluster" "fargate_cluster" {
213+
name = "${var.ecs_name}Cluster"
214+
capacity_providers = [
215+
"FARGATE"
216+
]
217+
default_capacity_provider_strategy {
218+
capacity_provider = "FARGATE"
219+
}
220+
}
221+
222+
# Service definition, auto heals if task shuts down
223+
resource "aws_ecs_service" "ecs_service" {
224+
name = "${var.ecs_name}Service"
225+
cluster = aws_ecs_cluster.fargate_cluster.id
226+
task_definition = aws_ecs_task_definition.ecs_task_definition.arn
227+
desired_count = var.autoscale_task_weekday_scale_down
228+
launch_type = "FARGATE"
229+
platform_version = "LATEST"
230+
network_configuration {
231+
subnets = var.service_subnets
232+
security_groups = var.service_sg
233+
}
234+
235+
# Ignored desired count changes live, permitting schedulers to update this value without terraform reverting
236+
lifecycle {
237+
ignore_changes = [desired_count]
238+
}
239+
}
240+
241+
# Create autoscaling target linked to ECS
242+
resource "aws_appautoscaling_target" "ServiceAutoScalingTarget" {
243+
count = var.enable_scaling ? 1 : 0
244+
min_capacity = var.autoscale_task_weekday_scale_down
245+
max_capacity = var.autoscale_task_weekday_scale_up
246+
resource_id = "service/${aws_ecs_cluster.fargate_cluster.name}/${aws_ecs_service.ecs_service.name}" # service/(clusterName)/(serviceName)
247+
scalable_dimension = "ecs:service:DesiredCount"
248+
service_namespace = "ecs"
249+
250+
lifecycle {
251+
ignore_changes = [
252+
min_capacity,
253+
max_capacity,
254+
]
255+
}
256+
}
257+
258+
# Scale up weekdays at beginning of day
259+
resource "aws_appautoscaling_scheduled_action" "WeekdayScaleUp" {
260+
count = var.enable_scaling ? 1 : 0
261+
name = "${var.ecs_name}ScaleUp"
262+
service_namespace = aws_appautoscaling_target.ServiceAutoScalingTarget[0].service_namespace
263+
resource_id = aws_appautoscaling_target.ServiceAutoScalingTarget[0].resource_id
264+
scalable_dimension = aws_appautoscaling_target.ServiceAutoScalingTarget[0].scalable_dimension
265+
schedule = "cron(0 5 ? * MON-FRI *)" #Every weekday at 5 a.m. PST
266+
timezone = "America/Los_Angeles"
267+
268+
scalable_target_action {
269+
min_capacity = var.autoscale_task_weekday_scale_up
270+
max_capacity = var.autoscale_task_weekday_scale_up
271+
}
272+
}
273+
274+
# Scale down weekdays at end of day
275+
resource "aws_appautoscaling_scheduled_action" "WeekdayScaleDown" {
276+
count = var.enable_scaling ? 1 : 0
277+
name = "${var.ecs_name}ScaleDown"
278+
service_namespace = aws_appautoscaling_target.ServiceAutoScalingTarget[0].service_namespace
279+
resource_id = aws_appautoscaling_target.ServiceAutoScalingTarget[0].resource_id
280+
scalable_dimension = aws_appautoscaling_target.ServiceAutoScalingTarget[0].scalable_dimension
281+
schedule = "cron(0 20 ? * MON-FRI *)" #Every weekday at 8 p.m. PST
282+
timezone = "America/Los_Angeles"
283+
284+
scalable_target_action {
285+
min_capacity = var.autoscale_task_weekday_scale_down
286+
max_capacity = var.autoscale_task_weekday_scale_down
287+
}
288+
}
289+
290+
# Scale to 0 to refresh fleet
291+
resource "aws_appautoscaling_scheduled_action" "Refresh" {
292+
count = var.enable_scaling ? 1 : 0
293+
name = "${var.ecs_name}Refresh"
294+
service_namespace = aws_appautoscaling_target.ServiceAutoScalingTarget[0].service_namespace
295+
resource_id = aws_appautoscaling_target.ServiceAutoScalingTarget[0].resource_id
296+
scalable_dimension = aws_appautoscaling_target.ServiceAutoScalingTarget[0].scalable_dimension
297+
schedule = "cron(0 0 * * ? *)" #Every day at midnight PST
298+
timezone = "America/Los_Angeles"
299+
300+
scalable_target_action {
301+
min_capacity = 0
302+
max_capacity = 0
303+
}
304+
}
305+
306+
# Scale down to minimum after rebuild
307+
resource "aws_appautoscaling_scheduled_action" "RefreshBackUp" {
308+
count = var.enable_scaling ? 1 : 0
309+
name = "${var.ecs_name}RefreshBackUp"
310+
service_namespace = aws_appautoscaling_target.ServiceAutoScalingTarget[0].service_namespace
311+
resource_id = aws_appautoscaling_target.ServiceAutoScalingTarget[0].resource_id
312+
scalable_dimension = aws_appautoscaling_target.ServiceAutoScalingTarget[0].scalable_dimension
313+
schedule = "cron(5 0 * * ? *)" #Every day at 12:05a PST
314+
timezone = "America/Los_Angeles"
315+
316+
scalable_target_action {
317+
min_capacity = var.autoscale_task_weekday_scale_down
318+
max_capacity = var.autoscale_task_weekday_scale_down
319+
}
320+
}

0 commit comments

Comments
 (0)