0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【コツコツAWS】AWS CDK - 静的サイトへのAPI追加 -

0
Posted at

AWS Cloud Development Kit (AWS CDK)を使いこなすため、勉強したことを残していきます。
今回はCloudFrontで配信する静的サイトにAWS LambdaとAmazon API Gatewayで機能を追加する実装を行います。
CloudFrontでの静的サイト配信に関してはこちらの記事を参照。

Lambda + API Gateway追加

以前に作成したCloudFrontでS3にある静的サイトを配信する構成に、簡単な出力をするLambda関数を作成して、API GatewayでLambda関数を実行する機能を追加します。Stackの実装は以下の通り。

Stack
from aws_cdk import (
    Stack,
    RemovalPolicy,
    CfnOutput,
    aws_s3 as s3,
    aws_s3_deployment as s3deploy,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
    aws_lambda as _lambda,
    aws_apigateway as apigw,
)
from constructs import Construct

class AddApiStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # S3 Bucket (private)
        bucket = s3.Bucket(
            self,
            "PrivateBucket",
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
            removal_policy=RemovalPolicy.DESTROY,
            auto_delete_objects=True,
        )

        # Lambda (API backend)
        used_lambda = _lambda.Function(
            self,
            "TestLambdaFunction",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="index.handler",
            code=_lambda.InlineCode(
                """
import json
import datetime

def handler(event, context):
    now = datetime.datetime.now(datetime.timezone.utc).astimezone()
    msg = f"あなたは{now.strftime('%Y-%m-%d %H:%M:%S %Z')}に、API経由でLambdaを使いました"
    return {
        "statusCode": 200,
        "headers": {
            "content-type": "application/json; charset=utf-8",
        },
        "body": json.dumps({"message": msg}, ensure_ascii=False),
    }
"""
            ),
        )

        # API Gateway (REST API)
        api = apigw.RestApi(
            self,
            "RestApi",
            endpoint_configuration=apigw.EndpointConfiguration(
                types=[apigw.EndpointType.REGIONAL]   # CloudFrontがあるのでREGIONAL
            ),
            deploy_options=apigw.StageOptions(
                stage_name="prod"
            ),
        )

        # /api/used (GET) 作成
        api_res = api.root.add_resource("api")
        used_res = api_res.add_resource("used")
        used_res.add_method(
            "GET",
            apigw.LambdaIntegration(used_lambda),
        )

        # CloudFront Distribution
        distribution = cloudfront.Distribution(
            self,
            "Distribution",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3BucketOrigin.with_origin_access_control(bucket),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            ),
            # API Gateway設定
            additional_behaviors={
                "api/*": cloudfront.BehaviorOptions(
                    origin=origins.RestApiOrigin(api),
                    allowed_methods=cloudfront.AllowedMethods.ALLOW_ALL,   # GET/POSTなどすべて許可
                    cache_policy=cloudfront.CachePolicy.CACHING_DISABLED,  # APIなのでキャッシュはOFF
                    origin_request_policy=cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,   # リクエスト情報
                    viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,   # HTTPはHTTPSに変換
                )
            },
            default_root_object="index.html",
        )

        # S3にアップロード
        s3deploy.BucketDeployment(
            self,
            "DeployWebsite",
            sources=[s3deploy.Source.asset("assets")],
            destination_bucket=bucket,
            distribution=distribution,
            distribution_paths=["/*"],
        )

        # Outputs
        CfnOutput(
            self,
            "CloudFrontURL",
            value=f"https://{distribution.domain_name}",
        )

REST APIの設定について

上記StackでのAPIの設定について詳細確認。
まず、EndpointConfigurationの部分について。

エンドポイントタイプとしては以下の種類があるようです。

タイプ 特徴
REGIONAL 特定Regionでの限定配信
EDGE Global配信 (内部でCloudFrontを設定)
PRIVATE VPC内限定

今回は実装部分でCloudFrontを使っているので、REGIONALを選択しました。

次にStageOptionsの部分について、
今回は簡単な検証なので作りこんでないですが、実際には環境ごとに設定を切り替える方が良さそうです。

環境 一般的な設定方針
dev (開発) デバッグ最優先, 全てのログを取る, 壊して問題なし
stg (検証) 本番環境再現, 動作部分のログだけ取る
prod (本番) コスト最適, ログはエラーだけ (不必要に情報を出さない)

切り替える場合は、cdk deploy -c env=prodみたいな形で環境の情報を渡して、Stack内かConfig用のディレクトリにある環境ごとの設定を読み込んで運用/管理。

設定例
CONFIG = {
    "dev": {
        "stage_name": "dev",
        "logging_level": apigw.MethodLoggingLevel.INFO,
        "data_trace_enabled": True,
        "metrics_enabled": True,
    },
    "stg": {
    ...省略
}

↑で設定したStageOptionsの中身は下表のもの、他にも色々あります。
(本番ならリクエスト数などを考慮すべき)
詳細は公式ドキュメント参照。

オプション名 内容
logging_level どこまでログを取るか, [OFF, ERROR, INFO]の3種
data_trace_enabled リクエストとレスポンスの中身をログに出すか
metrics_enabled CloudWatchメトリクスの有効/無効化

CloudFront Distributionの設定について

今回はcloudfront.Distributionの設定でAPIを叩けるようにしています。
api/*に関するURLは、S3のindex.htmlではなくREST API側にして、以下の設定をしています。

  • AllowedMethods.ALLOW_ALL: 各メソッドを許可
  • CachePolicy.CACHING_DISABLED: キャッシュ無効化
  • OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER: リクエスト情報保持
  • ViewerProtocolPolicy.REDIRECT_TO_HTTPS: HTTPをHTTPSに変換

【補足】
今回はadditional_behaviorsにAPIを追加する形を取っています。
URLを知っていればAPIを直接叩けることになるので、APIの内容によっては不適切です。
一方で、この機能を使うとパスごとでキャッシュ方針を変えたり、同一オリジンと見なすことができるため、CORS関連の面倒を減らすことができるようです。

Lambdaの別管理

上記Stackだと、Lambda関数の内容をインラインで入れることになるので少し不便です。
index.htmlのようにStackの外に出して、別で管理することにします。
以下のようなディレクトリの構成にします。

AddAPI/
├── app.py
├── add_api/
│   └── add_api_stack.py   ← これを編集
├── assets/
│   └── index.html
├── lambda/
│   └── app.py   ← ここにLambda関数を格納

lambda/app.pyの中身は上記のインラインで書いた内容をコピペ。
Stack側のLambdaの部分を以下のように修正します。

Lambda部分
        # Lambda (API backend)
        used_lambda = _lambda.Function(
            self,
            "TestLambdaFunction",
            runtime=_lambda.Runtime.PYTHON_3_12,
            code=_lambda.Code.from_asset("lambda"),
            handler="app.handler",
            )

これでStackからLanbda関数の部分を分離することができました。

改善点

目的である静的サイトからAPIを叩くことは達成しましたが、問題も残っています。
特にAPI GatewayをCloudFront経由に制限できていないのは要改善なので、認証関係の勉強と併せて直したいと思います。
一方で、additional_behaviorsを使うとCORSの面倒を減らせるとあるので、機能に合わせて使い分けた方が良さそう?
分からない点が多いので、別途まとめたいと思います。

まとめ

静的サイトにAPIを追加しました。

  • APIは構成によってエンドポイントタイプを選ぶ
  • 環境によってStageOptionsを切り替えられるようにする
  • CloudFront Distributionにadditional_behaviorsを追加すればパスごとの切り替えが可能 (同一オリジン扱いになる?)
  • Lambda関数はインラインではなく、別管理にした方が良さそう

改善点にも記載した通り、実際のサービスを考えると認証関係が必要になりそうなので、次は認証部分に関して勉強したいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?