はじめに
こんにちは。2021年度新入社員の碇川です。
本記事では、業務においてTerraformとSAMを使ってCloudFront + API Gateway + Lambdaの環境を作成した時の知見を紹介させていただきます。
私は主に@niftyトップページの運用を行っています。本記事の構成を作成した時は「ほかのAPIからデータを中継し、@niftyトップページに受け渡す」というAPIを作りました。
概要
Terraform
Terraformは、HashiCorpによって作成されたIaC (Infrastructure as Code)です。HashiCorp構成言語 (HCL)もしくはJSONを使用してAWSやGCPのリソースを管理することができます。
SAM
AWS サーバーレスアプリケーションモデル (SAM、Serverless Application Model) は、サーバーレスアプリケーション構築用のオープンソースフレームワークです。迅速に記述可能な構文で関数、API、データベース、イベントソースマッピングを表現できます。
どんなものを作るか
以下のような構成のものを作っていきます。
ディレクトリ構造は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
. ├── cloudformation.tf ├── cloudfront.tf ├── main.tf └── hello-sam ├── README.md ├── __init__.py ├── hello_world │ ├── __init__.py │ ├── app.py │ └── requirements.txt ├── template.yaml ├── events └── tests |
hello-samディレクトリの中はSAMから自動生成されるものになります。その中でも今回はtemplate.yamlを修正していきます。
このtemplate.yamlからSAMの機能を用いてCloudFormationのテンプレートファイルを生成します。
生成されたテンプレートファイルをTerraformを用いてデプロイします。
今回はTerraformではなくSAMを使って、LambdaとAPI Gatewayを作成します。
このような構成にする理由としては以下が挙げられます。
- Terraformはあくまでインフラリソース管理なのでソースコードは一緒に管理したくない
- TerraformでLambdaを管理する場合、zipでアーカイブ後デプロイする流れで、環境によってアーカイブする際に意図しない差分が発生する可能性がある
- CloudFrontには今回作成するAPIとは独立したAPIを紐づける可能性があり、API単体でのテストが容易であるため
以上の理由から部分的にSAMを使用することにします。
手順
1. Terraformのセットアップ
以下のようにterraformのバージョン指定や必要なproviderの設定を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 3.50.0" } } } provider "aws" { region = "ap-northeast-1" profile = "techblog" } |
設定できたら以下のコマンドでTerraformの初期化を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 3.50.0"... - Installing hashicorp/aws v3.50.0... - Installed hashicorp/aws v3.50.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. |
これでTerraformのセットアップは完了です。
2. SAMのセットアップ
次にSAMのセットアップを行います。SAMは対話形式でサンプルアプリケーションをセットアップできます。
今回は以下のように設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
$ sam init Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 What package type would you like to use? 1 - Zip (artifact is a zip uploaded to S3) 2 - Image (artifact is an image uploaded to an ECR image repository) Package type: 1 Which runtime would you like to use? 1 - nodejs14.x 2 - python3.9 3 - ruby2.7 4 - go1.x 5 - java11 6 - dotnetcore3.1 7 - nodejs12.x 8 - nodejs10.x 9 - python3.8 10 - python3.7 11 - python3.6 12 - python2.7 13 - ruby2.5 14 - java8.al2 15 - java8 16 - dotnetcore2.1 Runtime: 2 Project name [sam-app]: hello-sam Cloning from <https://github.com/aws/aws-sam-cli-app-templates> AWS quick start application templates: 1 - Hello World Example 2 - EventBridge Hello World 3 - EventBridge App from scratch (100+ Event Schemas) 4 - Step Functions Sample App (Stock Trader) 5 - Elastic File System Sample App Template selection: 1 ----------------------- Generating application: ----------------------- Name: hello-sam Runtime: python3.9 Dependency Manager: pip Application Template: hello-world Output Directory: . Next steps can be found in the README file at ./hello-sam/README.md |
この設定により、hello-app
ディレクトリが作成されます。
3. S3バケット作成
以下のコマンドを入力し、Lambdaのソースコードを保持しておくためのS3バケットを作成します。
バケット名は好きな名前で大丈夫です。XXXXXXX
を書き換えてください。
1 2 3 4 |
$ aws s3api create-bucket \ --acl private \ --bucket XXXXXXX \ --create-bucket-configuration LocationConstraint=ap-northeast-1 |
4. Lambdaのソースコードの用意
SAMから作られたファイルをそのまま用います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import json def lambda_handler(event, context): """ ~~~~ 色々コメント ~~~~ """ return { "statusCode": 200, "body": json.dumps({ "message": "hello world", }), } |
5. テンプレートの修正
こちらのテンプレートをもとにCloudFormationのテンプレートが生成されます。
template.yaml
を以下のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > hello-sam Sample SAM Template for hello-sam # More info about Globals: <https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst> Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: <https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction> Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.9 Events: HelloWorld: Type: Api # More info about API Event Source: <https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api> Properties: RestApiId: !Ref HelloWorldApi Path: /hello Method: get HelloWorldApi: Type: AWS::Serverless::Api Properties: Name: hello-world-api StageName: Prod OpenApiVersion: 3.0.3 Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # <https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api> HelloWorldApiDomain: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com" |
CloudFrontの設定をする際に作成するAPIのドメインが必要になってくるので、ドメインをOutputsとして出力しています。
今回はAPI Gatewayのリソース(HelloWorldApi
)を作成し、HelloWorldFunction
リソースに紐づけている形になっています。
API Gatewayのリソースを作成しない場合、「Stage」というステージが勝手に作成されてしまいますが、
上記のようにAPI Gatewayのリソースを紐づける形にすることで勝手に作成されることがなくなります。
これでSAMの準備はOKです。
6. CloudFormationをTerraformから利用できるようにする
SAMによって生成されるCloudFormationのスタックをTerraformから利用できるようにしていきます。
ここでのtemplate.yaml
はhello-sam/template.yaml
ではなく、後にSAMによって生成されるCloudFormationのテンプレートです。
1 2 3 4 5 6 7 8 9 10 11 12 |
resource "aws_cloudformation_stack" "hello-world-sam" { name = "hello-world-sam" template_body = file("template.yaml") # CloudFormationのテンプレートを指定 capabilities = ["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"] } data "aws_cloudformation_stack" "hello-world-sam" { name = aws_cloudformation_stack.hello-world-sam.name depends_on = [ aws_cloudformation_stack.hello-world-sam ] } |
7. CloudFrontの用意
次にCloudFrontの設定を行います。カスタムオリジンにAPI Gatewayを指定しています。
domain_name
にCloudFormationからのOutputsであるHelloWorldApiDomain
を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
resource "aws_cloudfront_distribution" "hello-world-cf" { origin { domain_name = data.aws_cloudformation_stack.hello-world-sam.outputs["HelloWorldApiDomain"] origin_id = "hello-world-sam-api-gateway" custom_origin_config { https_port = 443 http_port = 80 origin_protocol_policy = "https-only" origin_ssl_protocols = ["TLSv1.2"] } } enabled = true is_ipv6_enabled = true # キャッシュのデフォルト設定 default_cache_behavior { allowed_methods = ["GET", "HEAD"] # CloudFrontで許可するメソッド cached_methods = ["GET", "HEAD"] # キャッシュするメソッド target_origin_id = "hello-world-sam-api-gateway" # API Gatewayをオリジンとする forwarded_values { query_string = false # クエリ文字列は使用しない cookies { forward = "none" } } viewer_protocol_policy = "https-only" min_ttl = 0 default_ttl = 3600 max_ttl = 86400 } restrictions { # 地理的制限を設けて、日本にしか配信されないようにする geo_restriction { restriction_type = "whitelist" locations = [ "JP" ] } } viewer_certificate { cloudfront_default_certificate = true } } |
8. デプロイ
まずはSAMをビルドします。
1 2 |
$ cd hello-sam $ sam build |
作業ディレクトリはそのままで、以下のコマンドを実行します。 これはCloudFormationのテンプレートを生成し、指定したS3にLambdaのソースコードをアップロードしてくれます。
1 2 3 |
$ sam package --profile techblog \ --s3-bucket XXXXXXX \ --output-template-file ../template.yaml |
ディレクトリを一階層上に移動し、lsコマンドで見てみると生成されたCloudFormationのテンプレートファイルが確認できます。
1 2 3 |
$ cd .. $ ls cloudformation.tf cloudfront.tf hello-sam main.tf template.yaml |
テンプレートファイルが作成できたので以下のコマンドでデプロイします。
1 |
$ terraform apply |
途中で本当に実行していいか確認のメッセージが表示され入力を促されるので出力を確認し、yes
と入力しましょう。
しばらく待ち、Apply complete!と表示されたらデプロイ成功です。
以下は出力結果の例です。
9. 動作確認
AWSのコンソールへアクセスし、作成したCloudFrontのURLを確認します。
CloudFrontのリンクを以下に当てはめて、作成したAPIにリクエストを送信します。
1 |
$ curl [CloudFrontのリンク]/Prod/hello |
以下のような出力が返ってくれば成功です!
1 |
{"message": "hello world"} |
10. お片付け
以下のコマンドを入力することで今回作成した全てのリソースを削除することができます。
LambdaとAPI GatewayはCloudFormationによって作成されていますが、CloudFormation自身はTerraform管理下なのでLambdaとAPI Gatewayも削除されます。
1 |
terraform destroy |
おわりに
今回はTerraformとSAMを使ってCloudFront + API Gateway + Lambdaの環境を作りました。
AWSやTerraform、SAMといった技術は入社してから学んだ技術なのですが、アウトプットの機会を得られて、より知識が深まったと思います。
また、今回紹介した内容と同じような構成を実装する際に是非参考にしてみてください。