Skip to content

Commit 51712a4

Browse files
authored
Migrate to a single server setup with ECS (#4164)
1 parent 8e9c997 commit 51712a4

33 files changed

+599
-670
lines changed

backend/pdm.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ dependencies = [
9494
"cryptography>=43.0.0",
9595
"openai>=1.52.0",
9696
"pydantic>=2.9.2",
97+
"gunicorn>=23.0.0",
9798
]
9899
name = "backend"
99100
version = "0.1.0"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terraform 1.9.8

infrastructure/applications/applications.tf

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@ locals {
1212

1313
module "pretix" {
1414
source = "./pretix"
15-
count = local.deploy_pretix ? 1 : 0
15+
count = 1
1616
ecs_arm_ami = local.ecs_arm_ami
17+
server_ip = module.cluster.server_ip
18+
cluster_id = module.cluster.cluster_id
19+
logs_group_name = module.cluster.logs_group_name
1720
}
1821

1922
module "pycon_backend" {
2023
source = "./pycon_backend"
2124
ecs_arm_ami = local.ecs_arm_ami
25+
cluster_id = module.cluster.cluster_id
26+
security_group_id = module.cluster.security_group_id
27+
server_ip = module.cluster.server_ip
28+
logs_group_name = module.cluster.logs_group_name
2229

2330
providers = {
2431
aws = aws
@@ -40,3 +47,17 @@ module "emails" {
4047
aws.us = aws.us
4148
}
4249
}
50+
51+
module "cluster" {
52+
source = "./cluster"
53+
ecs_arm_ami = local.ecs_arm_ami
54+
55+
providers = {
56+
aws = aws
57+
aws.us = aws.us
58+
}
59+
}
60+
61+
output "server_public_ip" {
62+
value = module.cluster.server_public_ip
63+
}

infrastructure/components/cloudfront/main.tf renamed to infrastructure/applications/cluster/cloudfront.tf

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
1+
locals {
2+
pycon_web_domain = local.is_prod ? "admin.pycon.it" : "${terraform.workspace}-admin.pycon.it"
3+
pretix_web_domain = local.is_prod ? "tickets.pycon.it" : "${terraform.workspace}-tickets.pycon.it"
4+
}
5+
6+
data "aws_cloudfront_origin_request_policy" "all_viewer" {
7+
name = "Managed-AllViewer"
8+
}
9+
110
data "aws_cloudfront_cache_policy" "caching_disabled" {
211
name = "Managed-CachingDisabled"
312
}
413

5-
data "aws_cloudfront_origin_request_policy" "all_viewer_except_host_header" {
6-
name = "Managed-AllViewerExceptHostHeader"
14+
data "aws_acm_certificate" "cert" {
15+
domain = "*.pycon.it"
16+
statuses = ["ISSUED"]
17+
provider = aws.us
718
}
819

920
resource "aws_cloudfront_distribution" "application" {
1021
enabled = true
1122
is_ipv6_enabled = true
12-
comment = "${terraform.workspace}-${var.application}"
23+
comment = "${terraform.workspace} server"
1324
wait_for_deployment = false
14-
aliases = [var.domain]
25+
aliases = [
26+
local.pycon_web_domain,
27+
local.pretix_web_domain
28+
]
1529

1630
origin {
17-
domain_name = var.origin_url
31+
domain_name = aws_eip.server.public_dns
1832
origin_id = "default"
1933

2034
custom_origin_config {
21-
origin_protocol_policy = "https-only"
35+
origin_protocol_policy = "http-only"
2236
http_port = "80"
2337
https_port = "443"
2438
origin_ssl_protocols = ["TLSv1"]
@@ -29,7 +43,7 @@ resource "aws_cloudfront_distribution" "application" {
2943
cloudfront_default_certificate = false
3044
minimum_protocol_version = "TLSv1"
3145
ssl_support_method = "sni-only"
32-
acm_certificate_arn = var.certificate_arn
46+
acm_certificate_arn = data.aws_acm_certificate.cert.arn
3347
}
3448

3549
default_cache_behavior {
@@ -38,16 +52,10 @@ resource "aws_cloudfront_distribution" "application" {
3852
target_origin_id = "default"
3953

4054
cache_policy_id = data.aws_cloudfront_cache_policy.caching_disabled.id
41-
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer_except_host_header.id
55+
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
4256

4357
viewer_protocol_policy = "redirect-to-https"
4458
compress = true
45-
46-
lambda_function_association {
47-
event_type = "viewer-request"
48-
lambda_arn = var.forward_host_header_lambda_arn
49-
include_body = false
50-
}
5159
}
5260

5361
restrictions {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
data "aws_route53_zone" "zone" {
2+
name = "pycon.it"
3+
}
4+
5+
resource "aws_route53_record" "web_pycon" {
6+
zone_id = data.aws_route53_zone.zone.zone_id
7+
name = local.pycon_web_domain
8+
type = "A"
9+
10+
alias {
11+
name = aws_cloudfront_distribution.application.domain_name
12+
zone_id = aws_cloudfront_distribution.application.hosted_zone_id
13+
evaluate_target_health = false
14+
}
15+
}
16+
17+
resource "aws_route53_record" "web_tickets" {
18+
zone_id = data.aws_route53_zone.zone.zone_id
19+
name = local.pretix_web_domain
20+
type = "A"
21+
22+
alias {
23+
name = aws_cloudfront_distribution.application.domain_name
24+
zone_id = aws_cloudfront_distribution.application.hosted_zone_id
25+
evaluate_target_health = false
26+
}
27+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
resource "aws_iam_instance_profile" "server" {
2+
name = "pythonit-${terraform.workspace}-server"
3+
role = aws_iam_role.server.name
4+
}
5+
6+
resource "aws_iam_role" "server" {
7+
name = "pythonit-${terraform.workspace}-server-role"
8+
assume_role_policy = data.aws_iam_policy_document.server_assume_role.json
9+
}
10+
11+
resource "aws_iam_role_policy" "server" {
12+
name = "pythonit-${terraform.workspace}-server-policy"
13+
role = aws_iam_role.server.id
14+
policy = data.aws_iam_policy_document.server_role_policy.json
15+
}
16+
17+
data "aws_iam_policy_document" "server_assume_role" {
18+
statement {
19+
effect = "Allow"
20+
21+
principals {
22+
type = "Service"
23+
identifiers = ["ec2.amazonaws.com", "ecs-tasks.amazonaws.com"]
24+
}
25+
26+
actions = ["sts:AssumeRole"]
27+
}
28+
}
29+
30+
data "aws_iam_policy_document" "server_role_policy" {
31+
statement {
32+
effect = "Allow"
33+
actions = [
34+
"iam:PassRole",
35+
"ses:*",
36+
"ecs:*",
37+
"ecr:*",
38+
"ec2:DescribeInstances",
39+
]
40+
resources = [
41+
"*"
42+
]
43+
}
44+
45+
statement {
46+
effect = "Allow"
47+
actions = ["cloudwatch:PutMetricData", "logs:*"]
48+
resources = ["*"]
49+
}
50+
51+
statement {
52+
effect = "Allow"
53+
actions = ["s3:*"]
54+
resources = [
55+
"arn:aws:s3:::${terraform.workspace}-pycon-backend-media",
56+
"arn:aws:s3:::${terraform.workspace}-pycon-backend-media/*",
57+
"arn:aws:s3:::${terraform.workspace}-pretix-media",
58+
"arn:aws:s3:::${terraform.workspace}-pretix-media/*",
59+
]
60+
}
61+
62+
statement {
63+
actions = [
64+
"sns:CreatePlatformEndpoint",
65+
"sns:Publish"
66+
]
67+
resources = ["*"]
68+
effect = "Allow"
69+
}
70+
71+
statement {
72+
actions = [
73+
"sqs:SendMessage",
74+
"sqs:DeleteMessage",
75+
"sqs:GetQueueAttributes",
76+
"sqs:ReceiveMessage",
77+
]
78+
resources = ["*"]
79+
effect = "Allow"
80+
}
81+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
resource "aws_ecs_task_definition" "traefik" {
2+
family = "pythonit-${terraform.workspace}-traefik"
3+
4+
container_definitions = jsonencode([
5+
{
6+
name = "traefik"
7+
image = "traefik:v3.1.2"
8+
memoryReservation = 200
9+
essential = true
10+
11+
environment = [
12+
{
13+
name = "TRAEFIK_PROVIDERS_ECS_CLUSTERS"
14+
value = aws_ecs_cluster.cluster.name
15+
},
16+
{
17+
name = "TRAEFIK_PROVIDERS_ECS_AUTODISCOVERCLUSTERS"
18+
value = "false",
19+
},
20+
{
21+
name = "TRAEFIK_PROVIDERS_ECS_EXPOSEDBYDEFAULT",
22+
value = "false",
23+
},
24+
{
25+
name = "TRAEFIK_ENTRYPOINTS_WEB_ADDRESS",
26+
value = ":80"
27+
},
28+
{
29+
name = "TRAEFIK_LOG_LEVEL",
30+
value = "DEBUG"
31+
}
32+
]
33+
34+
portMappings = [
35+
{
36+
containerPort = 80
37+
hostPort = 80
38+
},
39+
]
40+
41+
mountPoints = []
42+
systemControls = [
43+
{
44+
"namespace" : "net.core.somaxconn",
45+
"value" : "4096"
46+
}
47+
]
48+
49+
logConfiguration = {
50+
logDriver = "awslogs"
51+
options = {
52+
"awslogs-group" = aws_cloudwatch_log_group.cluster.name
53+
"awslogs-region" = "eu-central-1"
54+
"awslogs-stream-prefix" = "traefik"
55+
}
56+
}
57+
58+
healthCheck = {
59+
retries = 3
60+
command = [
61+
"CMD-SHELL",
62+
"echo 4"
63+
]
64+
timeout = 3
65+
interval = 10
66+
}
67+
68+
stopTimeout = 300
69+
}
70+
])
71+
72+
requires_compatibilities = []
73+
tags = {}
74+
}
75+
76+
resource "aws_ecs_service" "traefik" {
77+
name = "traefik"
78+
cluster = aws_ecs_cluster.cluster.id
79+
task_definition = aws_ecs_task_definition.traefik.arn
80+
desired_count = 1
81+
deployment_minimum_healthy_percent = 0
82+
deployment_maximum_percent = 100
83+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
resource "aws_cloudwatch_log_group" "cluster" {
2+
name = "/ecs/pythonit-${terraform.workspace}-cluster"
3+
retention_in_days = 3
4+
}
5+
6+
7+
output "logs_group_name" {
8+
value = aws_cloudwatch_log_group.cluster.name
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
locals {
2+
is_prod = terraform.workspace == "production"
3+
}
4+
5+
resource "aws_ecs_cluster" "cluster" {
6+
name = "pythonit-${terraform.workspace}"
7+
}
8+
9+
output "cluster_id" {
10+
value = aws_ecs_cluster.cluster.id
11+
}
12+
13+
resource "aws_ecs_account_setting_default" "trunking" {
14+
name = "awsvpcTrunking"
15+
value = "enabled"
16+
}

0 commit comments

Comments
 (0)