こしあん
2024-04-20

[Terraform]CloudFront+Lambda@EdgeでLambdaのみで静的ページをホストする


130{icon} {views}


CloudFront+Lambda@Edgeで割と簡単に静的ページをホストできるって前に読んだので、気になっていたパターン。ChatGPTに聞かせてTerraformで作って動かしてみた。

GPT Log

https://chat.openai.com/share/fb21f16b-164a-470c-abd4-10ea6f7cfd0a

人間が書いた記事はいっぱい出てくるのでぐぐって

ディレクトリ構成

- main.tf
- lambda_function.py

LambdaはPythonで定義。Lambda@EdgeはNode.jsとPythonが対応している。

ちなみに、Lambda@Edgeはus-east-1のリージョンである必要があるので、Terraformのproviderの取り回しに注意が必要。複数のリージョンをまたいだデプロイになるため、異なるリージョンをエイリアスで結べば良い。

コード

main.tf

provider "aws" {
  region  = "ap-northeast-1"
  profile = "develop"
}

provider "aws" {
  alias   = "east"       # 別のリージョン用のエイリアス
  region  = "us-east-1" # Lambda@Edgeはus-east-1である必要がある
  profile = "develop"
}

terraform {
  backend "local" {
    path = ".cache/terraform.tfstate"
  }
}

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "my_bucket" {
  bucket = "shikoan-homepage-bucket" # Enter your backet name
}

# Lambda setting
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_file = "lambda_function.py"
  output_path = ".cache/lambda_function.zip"
}

resource "aws_lambda_function" "edge_lambda" {
  provider = aws.east # deploy to us-east-1
  function_name = "LambdaAtEdgeExample"

  filename = data.archive_file.lambda_zip.output_path
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256

  handler = "lambda_function.handler"
  runtime = "python3.12"
  role    = aws_iam_role.lambda_exec.arn
  publish = true # Must publish
}

resource "aws_iam_role" "lambda_exec" {
  name = "lambda_exec_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      },
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "edgelambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_lambda_permission" "allow_cloudfront" {
  provider = aws.east
  statement_id  = "AllowExecutionFromCloudFront"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.edge_lambda.arn
  principal     = "edgelambda.amazonaws.com"
  source_arn    = "arn:aws:cloudfront::${data.aws_caller_identity.current.account_id}:distribution/${aws_cloudfront_distribution.example_distribution.id}"
}

# Cloud front
resource "aws_cloudfront_distribution" "example_distribution" {
  origin {
    domain_name = aws_s3_bucket.my_bucket.bucket_regional_domain_name
    origin_id   = "S3-myBucket"
  }

  enabled             = true
  is_ipv6_enabled     = true

  default_cache_behavior {
    target_origin_id = "S3-myBucket"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods = ["GET", "HEAD", "OPTIONS"]
    cached_methods = ["GET", "HEAD"]
    lambda_function_association {
      event_type   = "origin-request"
      lambda_arn   = "${aws_lambda_function.edge_lambda.arn}:${aws_lambda_function.edge_lambda.version}"
    }

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  price_class = "PriceClass_100"

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

output "cloudfront_distribution_url" {
  value = "https://${aws_cloudfront_distribution.example_distribution.domain_name}"
  description = "URL for the CloudFront distribution"
}

CloudFrontの設定は、以前のCloudFront+S3のケースを使いまわしたので割とガバガバかもしれない。

lambda_function.py

def handler(event, context):
    html_content = """
    <html>
    <head><title>Welcome</title></head>
    <body><h1>Hello from Lambda@Edge!</h1>
    <h2>I am shikoan.</h2></body>
    </html>
    """
    response = {
        'status': '200',
        'statusDescription': 'OK',
        'headers': {
            'content-type': [{
                'key': 'Content-Type',
                'value': 'text/html'
            }]
        },
        'body': html_content
    }
    return response

Lambdaの中にHTMLを書いていくスタイル。わかりやすい。

結果


まあうまくいく

ちなみにLambda(HTMLの内容)を変更してterraform applyしてもその内容がすぐには反映されないが、これはCloudFrontのキャッシュのせい。CloudFrontから明示的にキャッシュ削除を依頼する。

すると変更が反映される。

所感

  • CloudFrontのデプロイに少々時間がかかる(3分程度)が、単純な構成の割にはいろいろできそうなので楽しそう。
  • ちなみに普通のLambdaと異なり、CloudWatch Logsに書き出さないようで、AWSLambdaBasicExecutionRoleを付与してもログには見れなかった。Lambdaがどのタイミングで起動されたかは気になった。
  • terraform destroy時に、CloudFrontを削除してすぐだとLambda@Edgeを削除できない。これはLambda@Edgeの仕様により、他のリージョンに複製されるため。レプリカが削除されるまで数時間かかるらしい
  • Lambdaのパーミッションの部分に、リージョン変えたprovider指定しなくてハマった。


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です