Skip to main content

CloudFront Website Architecture

CloudFront 是 AWS 的內容分發網絡(CDN)服務,可以將網站內容緩存到全球各地的邊緣位置,從而提高網站的性能和可用性。

本網站也是通過CloudFront來進行分發的,下面是部署過程。

部署資源

S3 Bucket

  • aws_s3_bucket: This resource provides functionality for managing S3 general purpose buckets in an AWS Partition.
  • aws_s3_bucket_versioning: This resource provides a resource for controlling versioning on an S3 bucket.
  • aws_iam_policy_document: This data provides a resource for generating an IAM policy document.
  • aws_s3_bucket_policy: This resource provides a resource for managing S3 bucket policies.

為什麼需要policy

  • 因為S3 bucket可以保持私有,避免內容被直接訪問。
  • 緩存控制,強制流量通過CloudFront,從而獲得更好的性能和安全性。
  • 成本,直接訪問S3比CloudFront昂貴,且CloudFront具有HTTPS、geo-restriction等功能。

CloudFront Distribution

  • aws_cloudfront_origin_access_control: Manages an AWS CloudFront Origin Access Control, which is used by CloudFront Distributions with an Amazon S3 bucket as the origin.

  • aws_cloudfront_distribution: Creates an Amazon CloudFront web distribution.

為什麼使用cloudfront oac

  • S3 bucket預設是私有的,但是CloudFront需要讀取裡面的檔案,OAC的作用是讓CloudFront能夠以自己的身分(而不是公開的URL)去向S3發送帶有AWS SigV4簽名的請求,這樣S3 bucket可以設定Bucket policy只允許來自特定CloudFront Distribution的請求

ACM

  • aws_acm_certificate: The ACM certificate resource allows requesting and management of certificates from the Amazon Certificate Manager.

CloudFlare

  • Domain Name

S3詳細配置

data "aws_iam_policy_document" "s3_iam" {
statement {
sid = "AllowCloudFrontServicePrincipalReadWrite"
effect = "Allow"

principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}

actions = [
"s3:GetObject",
"s3:PutObject"
]

resources = [
"${aws_s3_bucket.qsy.arn}/*"
]

condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [var.cloudfront_arn]
}
}
}

這是Terraform提供的一個data source,並不是resource,作用是通過HCL語法產生AWS IAM Policy的JSON格式字符串,最後通過.json屬性輸出,再給實際需要policy的資源,例如aws_s3_bucket_policy。

例如:

resource "aws_s3_bucket_policy" "s3_policy" {
bucket = aws_s3_bucket.qsy.id
policy = data.aws_iam_policy_document.s3_iam.json
}

比直接寫jsonencode()有更好的易讀性,有語法癌症,可組成多個statement。

statement block 一條權限規則

  • sid: Statement ID,自取名稱,識別用途,可省略
  • effect: 只能是Allow或者Deny

principals bloc ——誰有權限

  • type: 主題類型,可以有"AWS" "Service" "Federated"
  • identifiers: AWS服務,如cloudfront.amazonaws.com

actions ——允許的操作

  • "s3:GetObject": 允許讀取S3對象
  • "s3:PutObject": 允許寫入S3對象
  • "s3:*": 允許所有S3操作
  • "*": 允許所有操作

resources ——作用對象

  • ${aws_s3_bucket.qsy.arn}/* : 允許對指定bucket下的所有對象進行操作
  • arn:aws:s3:::mybucket : 允許對指定bucket本身進行操作
  • arn:aws:s3:::mybucket/* : 允許對指定bucket下的特定對象進行操作

condition block ——條件限制

  • test: 條件測試類型,如StringEquals、IpAddress等
  • variable: 條件變量,如AWS:SourceArn、AWS:SourceIp
  • values: 條件值,如var.cloudfront_arn

CloudFront 詳細配置

aws_cloudfront_origin_access_control

resource "aws_cloudfront_origin_access_control" "control" {
name = "${var.project}-cloudfront-origin-access-control"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}

origin_access_control_origin_type

  • 指定為"s3",來源類型,目前只支持S3

signing_behavior

  • 對請求加簽名的行為,"always"表示始終對請求加簽名,"never"表示從不對請求加簽名,"no-override"表示如果來源已經有簽名則使用來源的簽名,否則根據CloudFront分配的OAC進行簽名。

signing_protocol

  • AWS標準簽名協議,目前唯一選項

aws_cloudfront_distribution

esource "aws_cloudfront_distribution" "qsy_distribution" {
origin {
domain_name = var.regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.control.id
origin_id = "qsy"
}

enabled = true
is_ipv6_enabled = true
comment = "some comment"
default_root_object = "index.html"

aliases = ["qingshiyuu.com", "www.qingshiyuu.com"]

default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "qsy"

forwarded_values {
query_string = false

cookies {
forward = "none"
}
}

viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}


ordered_cache_behavior {
path_pattern = "/content/immutable/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "qsy"

forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}

min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}

# Cache behavior with precedence 1
ordered_cache_behavior {
path_pattern = "/content/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "qsy"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}

min_ttl = 0
default_ttl = 3600
max_ttl = 86400
compress = true
viewer_protocol_policy = "redirect-to-https"
}

price_class = "PriceClass_200"

restrictions {
geo_restriction {
restriction_type = "none"
}
}

tags = {
Environment = "production"
}

viewer_certificate {
acm_certificate_arn = var.acm_certificate_arn
ssl_support_method = "sni-only"
}

}

domain_name

  • S3 bucket的regional domain(例如xxx.s3.us-east-1.amazonaws.com)

origin_access_control_id

  • 綁定上面的OAC,讓CloudFront能夠以自己的身分去訪問私有S3 bucket

origin_id

  • 來源的別稱,在cache behavior中用target_origin_id引用

enabled

  • 是否啟用此distribution

is_ipv6_enabled

  • 是否啟用IPv6支持

comment

  • 分配的註釋,沒有特定功能

default_root_object

  • 當訪問根URL時返回的默認對象,這裡是index.html

aliases

  • 自定義域名,需配合ACM使用

price_class

  • 決定使用哪些Edge Location, PriceClass_200包含北美、歐洲以及亞洲部分節點,比All便宜

default_cache_behavior

  • allowed_methods: CloudFront接受並轉發給origin的HTTP方法
  • cached_methods: 實際會被快取的方法(通常是GET/HEAD)
  • target_origin_id: 指定此cache behavior對應的origin,這裡是上面定義的"qsy"
  • forwarded_values: false=不把query string傳給origin,也不納入快取key
  • cookies.forward: "none"=不轉發cookie,提升效率
  • viewer_protocol_policy: "allow-all"=允許HTTP和HTTPS訪問,"redirect-to-https"=將HTTP請求重定向到HTTPS,"https-only"=只允許HTTPS訪問
  • min_ttl/default_ttl/max_ttl: 分別是最小、默認和最大快取時間,單位是秒

ordered_cahce_behavior

按照宣告順序比對路徑,比default優先

restrictions.geo_restriction

  • restriction_type: "none"=不限制,"blacklist"=禁止訪問,"whitelist"=只允許訪問

viewer_certificate

  • acm_certificate_arn: ACM證書的ARN,必須要在us-east-1申請,以支持自定域名HTTPS
  • ssl_support_method: "sni-only"=使用SNI,節省成本,另一個"vip"需要額外付費

ACM 配置

由於我是在CloudFlare註冊的域名,所以部署過程中需要手動操作+自動化操作

第一步

ACM要驗證我真的擁有qingshiyuu.com域名,流程是

  • 在AWS Console手動申請qingshiyuu.com的ACM證書,必須在us-east-1區域申請,因為CloudFront只認us-east-1的證書
  • AWS ACM給我一條CNAME記錄,格式類似: _abc123.qingshiyuu.com → _xyz789.acm-validations.aws
  • 在我的CloudFlare DNS管理界面添加這條CNAME記錄,完成域名所有權驗證
  • AWS偵測到CNAME後,憑證變成ISSUED

第二步

data "aws_acm_certificate" "qsy_cert" {
domain = "qingshiyuu.com"
statuses = ["ISSUED"]
}
  • 在Terraform中使用data source aws_acm_certificate來查詢已經發行的ACM證書。
  • 通過output輸出到 cloudfront module中的cloudfront_distribution的viewer_certificate.acm_certificate_arn屬性,讓CloudFront使用這個證書來支持HTTPS。
  • CloudFront會給我一個distribution domain,例如d1234abcd.cloudfront.net。我需要在CloudFlare中加入一條CNAME記錄,讓qingshiyuu.com指向d1234abcd.cloudfront.net,這樣訪問qingshiyuu.com就會通過CloudFront分發內容了。⋯⋯

參考文檔