みなさんこんにちは!

オルトプラスではにぎやか担当のサーバーサイドエンジニア兼、少しずつAWSの知識が増え始めた「らん」と申します。どうぞよろしくお願いします! 今回はインフラエンジニアとして新米の私がCloudFrontを手動で構築して、Terraformでコード化した流れをまとめてみました。 まだ本番適用までは至っていませんが、何かの参考になれば幸いです。

◇プロジェクト概要◇

有料動画の視聴機能や、ユーザー自身が動画を投稿できる仕組み、さらにはコミュニティ的な要素もあるようなサービスの開発を行ってました。 その中で、動画の再生において署名付きURLやHLS配信が必要になり、CloudFrontやS3を中心とした構成を構築しました。 当初はWeb検索しながらCloudFrontを手動構築しましたが、将来を見据えたのと社内でTerraformによるIaC化を推進していることもありTerraform化しました。

◇手動でCloudFrontを構築したときの話◇

要件としては、S3バケットにあるHLS動画をCloudFront経由で配信しつつ、署名付きURLによるアクセス制御を行うというものでした。 その時の開発環境はTerraformを使用して構築したのですが、一部Terraform化されていない諸々が追加されており、この状態でTerraformで追加するの怖いなぁという思いがあったので、マネジメントコンソールから作成しました。 そもそもCloudFrontってなんぞ?から始めたのと設定項目の単語の意味も良くわかっていなかったので人に頼ったりAIに頼ったりしながら設定しました。 執筆時点で愛用しているAIはChatGPT君です。

◇Terraform化に向けてやったこと◇

Terraform化では、CloudFrontの構成をaws cloudfront get-distribution-config --id <DistributionID> のコマンドを使って取得し、そのJSONをもとに .tf ファイルを手動で書き起こすという方法を取りました。 現状、cache_policy_id や origin_request_policy_id については、一旦固定値にしてあります。 今後はTerraform側で aws_cloudfront_cache_policy リソースを定義して、IDを動的に参照できるよう改善していきたいと考えています。 まずは「コードとして再現できること」を優先しつつ、少しずつTerraformらしい管理に寄せていく、というスタンスで進めました。 何事も臨機応変です。 参考までに、実際のCloudFrontの設定JSONの抜粋(マスク&編集済み)を掲載します。

{
    "ETag": "XXXXXXXXXXX",
    "DistributionConfig": {
        "CallerReference": "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
        "Aliases": {
            "Quantity": 1,
            "Items": [
                "example.jp"
            ]
        },
        "DefaultRootObject": "",
        "Origins": {
            "Quantity": 1,
            "Items": [
                {
                    "Id": "example.s3.ap-northeast-1.amazonaws.com",
                    "DomainName": "example.s3.ap-northeast-1.amazonaws.com",
                    "OriginPath": "",
                    "CustomHeaders": {
                        "Quantity": 0
                    },
                    "S3OriginConfig": {
                        "OriginAccessIdentity": "origin-access-identity/cloudfront/XXXXXXXXXX"
                    },
                    "ConnectionAttempts": 3,
                    "ConnectionTimeout": 10,
                    "OriginShield": {
                        "Enabled": false
                    }
                }
            ]
        },
        "OriginGroups": {
            "Quantity": 0
        },
        "DefaultCacheBehavior": {
            "TargetOriginId": "example.s3.ap-northeast-1.amazonaws.com",
            "TrustedSigners": {
                "Enabled": false,
                "Quantity": 0
            },
            "TrustedKeyGroups": {
                "Enabled": true,
                "Quantity": 1,
                "Items": [
                    "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
                ]
            },
            "ViewerProtocolPolicy": "redirect-to-https",
            "AllowedMethods": {
                "Quantity": 3,
                "Items": [
                    "HEAD",
                    "GET",
                    "OPTIONS"
                ],
                "CachedMethods": {
                    "Quantity": 2,
                    "Items": [
                        "HEAD",
                        "GET"
                    ]
                }
            },
            "SmoothStreaming": false,
            "Compress": true,
            "LambdaFunctionAssociations": {
                "Quantity": 0
            },
            "FunctionAssociations": {
                "Quantity": 0
            },
            "FieldLevelEncryptionId": "",
            "CachePolicyId": "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
            "ResponseHeadersPolicyId": "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
        },
        "CacheBehaviors": {
            "Quantity": 0
        },
        "CustomErrorResponses": {
            "Quantity": 0
        },
        "Comment": "",
        "Logging": {
            "Enabled": false,
            "IncludeCookies": false,
            "Bucket": "",
            "Prefix": ""
        },
        "PriceClass": "PriceClass_All",
        "Enabled": true,
        "ViewerCertificate": {
            "CloudFrontDefaultCertificate": false,
            "ACMCertificateArn": "arn:aws:acm:us-east-1:XXXXXXXXXXX:certificate/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
            "SSLSupportMethod": "sni-only",
            "MinimumProtocolVersion": "TLSv1.2_2021",
            "Certificate": "arn:aws:acm:us-east-1:XXXXXXXXXXX:certificate/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
            "CertificateSource": "acm"
        },
        "Restrictions": {
            "GeoRestriction": {
                "RestrictionType": "none",
                "Quantity": 0
            }
        },
        "WebACLId": "",
        "HttpVersion": "http2and3",
        "IsIPV6Enabled": true
    }
}

◇苦労したこと◇

CloudFrontとはなんぞや?から始めたため、各設定の意味や影響範囲を把握しきれない部分が多々ありました。 そのため、.tfファイルに書き起こした一部のオプションは後で調査したり社内のベテランインフラエンジニアさんに聞こうと判断し、コメントアウトの状態で残しているものもあります。 完璧に作り切ってからでないとIaC化しちゃいけない、ということはないと思っているので、まずは書けるところから書く・後で直す前提で進める、という方針で取り組んでいました。 とりあえず手を動かして動くものを用意したい気持ちです。

◇最後に◇

手動で作ったCloudFrontのIaC化は、思った以上に構成が複雑で、時間もかかりました。ですが、「再現性のあるインフラ」という観点ではやはりTerraform化しておく意味は大きいと感じています。本番適用まではされていませんが、同じようにCloudFrontのTerraform化を検討している方の参考になれば幸いです。 最後まで読んでいただきありがとうございました。

◇参考になったもの◇

アセット 5@4x.png