Skip to content

Commit 9b0ccdb

Browse files
committed
add cloudfront cdn
1 parent e41a067 commit 9b0ccdb

3 files changed

Lines changed: 209 additions & 0 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
Geo-proximity, multi-region active-active delivery via CloudFront + Lambda@Edge.
3+
Based on: https://aws.amazon.com/blogs/networking-and-content-delivery/using-amazon-cloudfront-and-amazon-s3-to-build-multi-region-active-active-geo-proximity-applications/
4+
5+
On a cache miss, the origin-request Lambda@Edge function looks up the S3
6+
bucket replica closest to the executing edge region and rewrites the origin
7+
request to it.
8+
*/
9+
10+
locals {
11+
# Region -> regional S3 bucket domain, generated from the replicated buckets.
12+
region_bucket_domains = merge(
13+
{ for region, mod in module.s3_buckets : region => mod.bucket_regional_domain_name },
14+
{ "us-east-2" = module.us-east-2.bucket_regional_domain_name }
15+
)
16+
17+
# The authoritative bucket, used when an edge region has no mapping.
18+
default_bucket_domain = module.us-east-2.bucket_regional_domain_name
19+
default_bucket_region = "us-east-2"
20+
}
21+
22+
# --- Lambda@Edge origin router -----------------------------------------------
23+
24+
module "geo_router_role" {
25+
source = "terraform-aws-modules/iam/aws//modules/iam-role"
26+
version = "~> 6.6"
27+
name = "${var.prefix}geo-router"
28+
use_name_prefix = false
29+
trust_policy_permissions = {
30+
LambdaEdgeAssume = {
31+
actions = ["sts:AssumeRole"]
32+
principals = [{
33+
type = "Service"
34+
identifiers = [
35+
"lambda.amazonaws.com",
36+
"edgelambda.amazonaws.com",
37+
]
38+
}]
39+
}
40+
}
41+
policies = {
42+
AWSLambdaBasicExecutionRole = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
43+
}
44+
}
45+
46+
data "archive_file" "geo_router" {
47+
type = "zip"
48+
output_path = "${path.module}/.terraform/tmp/geo-router.zip"
49+
50+
source {
51+
filename = "index.py"
52+
content = <<-PYTHON
53+
# Generated by Terraform - do not edit by hand.
54+
# Routes CloudFront origin requests to the closest S3 bucket replica.
55+
REGIONS_MAPPING = ${jsonencode(local.region_bucket_domains)}
56+
DEFAULT_BUCKET = "${local.default_bucket_domain}"
57+
DEFAULT_REGION = "${local.default_bucket_region}"
58+
59+
60+
def lambda_handler(event, context):
61+
request = event['Records'][0]['cf']['request']
62+
63+
# Identify the edge region executing this replica of the function.
64+
lambda_region = context.invoked_function_arn.split(':')[3]
65+
66+
# Pick the closest bucket, falling back to the default bucket.
67+
if lambda_region in REGIONS_MAPPING:
68+
domain_name = REGIONS_MAPPING[lambda_region]
69+
bucket_region = lambda_region
70+
else:
71+
domain_name = DEFAULT_BUCKET
72+
bucket_region = DEFAULT_REGION
73+
74+
# Update the origin request object.
75+
request['origin']['s3']['domainName'] = domain_name
76+
request['origin']['s3']['region'] = bucket_region
77+
request['headers']['host'] = [{'key': 'host', 'value': domain_name}]
78+
return request
79+
PYTHON
80+
}
81+
}
82+
83+
resource "aws_lambda_function" "geo_router" {
84+
region = "us-east-1"
85+
function_name = "${var.prefix}geo-router"
86+
role = module.geo_router_role.arn
87+
runtime = "python3.14"
88+
handler = "index.lambda_handler"
89+
filename = data.archive_file.geo_router.output_path
90+
source_code_hash = data.archive_file.geo_router.output_base64sha256
91+
publish = true
92+
93+
# Origin-request functions are limited to 30s / 10240 MB, keep defaults modest
94+
timeout = 5
95+
memory_size = 128
96+
}
97+
98+
module "acm" {
99+
source = "terraform-aws-modules/acm/aws"
100+
version = "~> 6.3"
101+
region = "us-east-1"
102+
103+
domain_name = "cdn.registry.k8s.io"
104+
validation_method = "DNS"
105+
create_route53_records = false
106+
}
107+
108+
# --- CloudFront distribution --------------------------------------------------
109+
110+
# Redirect requests for the root path to the project repository.
111+
resource "aws_cloudfront_function" "redirect_root" {
112+
name = "${var.prefix}redirect-root"
113+
runtime = "cloudfront-js-2.0"
114+
comment = "Redirect / to the registry.k8s.io GitHub repository"
115+
publish = true
116+
code = <<-JS
117+
function handler(event) {
118+
if (event.request.uri === '/') {
119+
return {
120+
statusCode: 301,
121+
statusDescription: 'Moved Permanently',
122+
headers: {
123+
location: { value: 'https://github.com/kubernetes/registry.k8s.io' }
124+
}
125+
};
126+
}
127+
return event.request;
128+
}
129+
JS
130+
}
131+
132+
module "cdn" {
133+
source = "terraform-aws-modules/cloudfront/aws"
134+
version = "~> 6.7"
135+
136+
aliases = ["cdn.registry.k8s.io"]
137+
comment = "registry.k8s.io CDN"
138+
139+
# Strip internal S3 metadata headers from CDN responses.
140+
response_headers_policies = {
141+
drop_s3_headers = {
142+
name = "${var.prefix}drop-s3-headers"
143+
comment = "Strip internal S3 metadata headers from responses"
144+
remove_headers_config = {
145+
items = [
146+
{ header = "x-amz-meta-mtime" },
147+
{ header = "x-amz-meta-file-mtime" },
148+
{ header = "x-amz-replication-status" },
149+
{ header = "x-amz-server-side-encryption" },
150+
{ header = "x-amz-version-id" },
151+
]
152+
}
153+
}
154+
}
155+
156+
origin = {
157+
# S3 origin. On a cache miss the Lambda@Edge origin-request function
158+
# rewrites this origin to the bucket closest to the edge region.
159+
s3 = {
160+
domain_name = "prod-registry-k8s-io-us-east-2.s3.us-east-2.amazonaws.com"
161+
}
162+
}
163+
164+
default_cache_behavior = {
165+
target_origin_id = "s3"
166+
viewer_protocol_policy = "redirect-to-https"
167+
response_headers_policy_key = "drop_s3_headers"
168+
169+
allowed_methods = ["GET", "HEAD", "OPTIONS"]
170+
cached_methods = ["GET", "HEAD"]
171+
compress = true
172+
query_string = true
173+
174+
# Blobs are content-addressed (sha256) and immutable: cache aggressively
175+
# at the edge to avoid per-request origin revalidation (RefreshHit).
176+
min_ttl = 86400 # 1 day
177+
default_ttl = 2592000 # 30 days
178+
max_ttl = 31536000 # 1 year
179+
180+
# Redirect / to the project repository before hitting cache or origin.
181+
function_association = {
182+
viewer-request = {
183+
function_arn = aws_cloudfront_function.redirect_root.arn
184+
}
185+
}
186+
187+
# Geo-proximity routing: runs on cache miss, before the origin request.
188+
lambda_function_association = {
189+
origin-request = {
190+
lambda_arn = aws_lambda_function.geo_router.qualified_arn
191+
include_body = false
192+
}
193+
}
194+
}
195+
196+
viewer_certificate = {
197+
acm_certificate_arn = "arn:aws:acm:us-east-1:513428760722:certificate/05f38392-7830-4d6d-a3cb-bbf0f2bdce5f"
198+
ssl_support_method = "sni-only"
199+
}
200+
}
201+

infra/aws/terraform/registry.k8s.io/providers.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ terraform {
2828
source = "hashicorp/aws"
2929
version = "~> 6.22"
3030
}
31+
archive = {
32+
source = "hashicorp/archive"
33+
version = "~> 2.4"
34+
}
3135
}
3236
}
3337

infra/aws/terraform/registry.k8s.io/s3/output.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ limitations under the License.
1717
output "bucket_arn" {
1818
value = aws_s3_bucket.registry-k8s-io.arn
1919
}
20+
21+
output "bucket_regional_domain_name" {
22+
value = aws_s3_bucket.registry-k8s-io.bucket_regional_domain_name
23+
}

0 commit comments

Comments
 (0)