最近、TerraformでAWSサーバーレスなサービスのインフラ構築をコード化する機会があったので理解を深めるためにプライベートでも作ってみました。
AWSサーバーレスなサービスの全体像
今回、Terraformで構築をコード化する対象の環境です。SPA(Single Page Application)な構成としました。
大きくWebの部分とAPIの部分、その他の部分に分かれています。
Webの部分:
- ユーザからアクセスを受ける CloudFront
- SPAのHTMLを配置する S3 bucket
APIの部分:
- SPAのHTMLからアクセスを受ける CloudFront
- APIの機能を提供する Lambda Function
- CloudFront と Lambda Function を橋渡しする API Gateway
その他の部分:
- 認証のための Cognito
- セキュリティ対策のための WAF
- データ永続化のための Dynamo DB
インフラ構築の流れ
セットアップ用のCodeBuild
まず起点となるセットアップ用のCodeBuildです。
最初にTerraformを使って下記を構築します。
- サービスのStaging環境を構築するCodeBuild
- サービスのProduction環境を構築するCodeBuild
- 上記のCodeBuildを順次実行するCodePipeline
そして、最後に構築したCodePipelineを実行してサービスのStaging環境とProduction環境を構築します。
Staging環境の構築後には承認アクションが必要なようにしました。Staging環境の確認で問題が見つかった場合は承認を却下して以降の構築を停止することもできます。
サービスの環境を構築するCodeBuild
次はサービスのStaging環境を構築するCodeBuildおよびProduction環境を構築するCodeBuildです。こちらもTerraformを使います。
まず、他のリソースに依存しない、WebやAPIでは無い部分の下記を構築します。
- Dynamo DB
- Cognito
- WAF
次にWebの下記を構築します。ここで得られたCloudFront(Web)のURLは後続で構築するAPIのCORSに設定します。
- S3 bucket(Web)
- CloudFront(Web)
そして、API部分の構築・デプロイを行うCodeBuildを構築・実行します。これで下記が構築・デプロイされます。下記の構築・デプロイには Serverless Framework を使います。ここで得られたAPI Gatewayのドメイン名は後続で構築するCloudFront(API)の紐付け先に設定します。
次にAPIのCloudFrontを構築します。ここで得られたCloudFront(API)のURLは後続で構築するWebのHTMLにAPIの呼び出し先URLとして設定します。
- CloudFront(API)
最後にWeb部分のデプロイを行うCodeBuildを構築および実行します。これで下記がデプロイされます。ここは Angular を使います。
- HTML(Web)
Production環境の場合はこのタイミングで下記を構築します。これらもサービスのCodePipelineと同様にStaging環境の構築後には承認アクションがあります。
- APIのStaging環境を構築・デプロイするCodeBuildとProduction環境を構築・デプロイするCodeBuildを順次実行するCodePipeline
- WebのStaging環境にデプロイするCodeBuildとProduction環境にデプロイするCodeBuildを順次実行するCodePipeline
Staging環境の構築とProduction環境の構築を結ぶCodePipeline
サービス全体およびAPIの部分、Webの部分はそれぞれにCodePipelineが構築されます。
それぞれCodeCommitのmasterブランチに変化があると実行されます。
APIの部分とWebの部分のCodePipelineがあることでAPIの部分だけ、Webの部分だけでのデプロイが行えます。サービス全体のCodePipelineはAPIの部分とWebの部分も構築・デプロイを行います。
実装内容
コードをGitHubにアップしました。
aws-sls-spa-sample-api と aws-sls-spa-sample-web は aws-sls-spa-sample-terraform でインフラ構築するための必要な最小限の実装しかありません。
使っているツールのバージョンは下記です。
- Terraform: 0.11.7
- Serverless Framework: 1.30.0
- Angular: 6.0.8
インフラ部分:aws-sls-spa-sample-terraform
大まかなディレクトリ構成です。
. ├── bin ... シェルなど ├── buildspec_setup.yml ... セットアップ用のCodeBuild向け ├── buildspec_staging.yml ... サービスのStaging構築CodeBuild向け ├── buildspec_production.yml ... サービスのProduction構築CodeBuildCodeBuild向け ├── environments ... 構築フローに沿ったTerraformの定義 │ │── setup ... セットアップ用 │ └── service ... サービス用 │ ├── base ... その他の部分 │ │ ├── pre │ │ └── after_api │ ├── api ... API部分 │ │ ├── staging │ │ └── production │ │── web ... Web部分 │ │ ├── staging │ │ └── production │ └── pipeline ... API部分/Web部分のCodePipeline └── modules ... 各リソースのTerraformの定義 ├── cloudfront ├── cloudwatch_event ├── codebuild ├── codepipeline ├── cognito ├── dynamodb ├── iam ├── s3 └── waf
CodeBuildが buildspec_xxxx.yml
を呼び出して構築をしていきます。
それぞれのTerraformの定義は リファレンス を参照しつつ基本的に consoleで作成したリソースのterraform import
を実施した結果から作りました。
例えばaws_cognito_user_poolならこんな感じです。
$ cat terraform.tfvars access_key = "XXXXXXXXX" secret_key = "XXXXXXXXX" region = "ap-northeast-1" # 空の定義を用意する $ cat main.tf variable "access_key" {} variable "secret_key" {} variable "region" {} provider "aws" { access_key = "${var.access_key}" secret_key = "${var.secret_key}" region = "${var.region}" } resource "aws_cognito_user_pool" "hoge" { } $ terraform init # 空で定義したリソースをimportする $ terraform import aws_cognito_user_pool.hoge ap-northeast-1_XXXXXX # tfstateに定義がimportされるので参照して定義を作っていく... $ cat terraform.tfstate
API部分:aws-sls-spa-sample-api
ほぼ Serverless Framework の aws-nodejs-typescript
をテンプレートにして作成した素のままの状態です。
$ serverless create --template aws-nodejs-typescript
これに aws-sls-spa-sample-terraform で必要な buildspec.yml
の追加と serverless.yml
の修正を行いました。
Web部分:aws-sls-spa-sample-web
こちらもほぼ Angular で ng new
した素のままの状態です。
$ ng new aws-sls-spa-sample-web --style=scss --routing
これに aws-sls-spa-sample-terraform で必要な buildspec.yml
の追加と angular.json
の修正を行いました。
使い方
セットアップ用のCodeBuildを手動で構築・実行してサービスのインフラ構築を行うまでの手順です。リソース名は例のため実際に実施するときは変える必要があるので注意が必要です。
1. GitHubのリポジトリをCodeCommitに持ってくる
CodeCommitにリポジトリを作成します。
# spa infra repository... $ aws codecommit create-repository --repository-name foobar-sample-spa-infra # spa api repository... $ aws codecommit create-repository --repository-name foobar-sample-spa-api # spa web repository... $ aws codecommit create-repository --repository-name foobar-sample-spa-web
作成したCodeCommitのリポジトリにGitHubのリポジトリをpushする。
# spa infra repository... $ git clone -b v0.0.0 https://github.com/nihemak/aws-sls-spa-sample-terraform.git sample-spa-infra $ cd sample-spa-infra $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-infra $ cd .. # spa api repository... $ git clone -b v0.0.0 https://github.com/nihemak/aws-sls-spa-sample-api.git sample-spa-api $ cd sample-spa-api $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-api $ cd .. # spa web repository... $ git clone -b v0.0.0 https://github.com/nihemak/aws-sls-spa-sample-web.git sample-spa-web $ cd sample-spa-web $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-web $ cd ..
2. セットアップ用のCodeBuildを動かすために必要なリソースを作る
CodePipelineの承認アクションで使うSNSのトピックを作成する。
$ aws sns create-topic --name foobar-sample-spa-approval-topic $ aws sns subscribe --topic-arn arn:aws:sns:ap-northeast-1:<account-id>:foobar-sample-spa-approval-topic \ --protocol email \ --notification-endpoint <your email> # and confirm... $ aws sns confirm-subscription --topic-arn arn:aws:sns:ap-northeast-1:<account-id>:foobar-sample-spa-approval-topic \ --token <token value>
Terraformの状態管理ファイルを保存するS3のバケットを作成する。S3のバケット名は全世界でユニークにする必要があるので注意が必要です。
$ aws s3 mb s3://foobar-sample-spa-terraform-state --region ap-northeast-1
CodeBuildを動かすために必要なIAMロールを作成する。 TF_VAR_service_name
に指定した文字列が作成されるリソースのプレフィックスになるので他と被らない、長すぎない文字列を使うように注意が必要です。
$ cat Trust-Policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } $ aws iam create-role --role-name foobar-sample-spa-setup-codebuild \ --assume-role-policy-document file://Trust-Policy.json $ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \ --role-name foobar-sample-spa-setup-codebuild
セットアップ用のCodeBuildを作成する。
$ cat Source.json { "type": "CODECOMMIT", "location": "https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-infra", "buildspec": "buildspec_setup.yml" } $ cat Artifacts.json { "type": "NO_ARTIFACTS" } $ cat Environment.json { "type": "LINUX_CONTAINER", "image": "aws/codebuild/ubuntu-base:14.04", "computeType": "BUILD_GENERAL1_SMALL", "environmentVariables": [ { "name": "TF_VAR_service_name", "value": "foobar-sample-spa", "type": "PLAINTEXT" }, { "name": "TF_VAR_approval_sns_topic_arn", "value": "arn:aws:sns:ap-northeast-1:<account-id>:foobar-sample-spa-approval-topic", "type": "PLAINTEXT" }, { "name": "TF_VAR_s3_bucket_terraform_state_id", "value": "foobar-sample-spa-terraform-state", "type": "PLAINTEXT" }, { "name": "TF_VAR_codecommit_infra_repository", "value": "foobar-sample-spa-infra", "type": "PLAINTEXT" }, { "name": "TF_VAR_codecommit_api_repository", "value": "foobar-sample-spa-api", "type": "PLAINTEXT" }, { "name": "TF_VAR_codecommit_web_repository", "value": "foobar-sample-spa-web", "type": "PLAINTEXT" } ] } $ aws codebuild create-project --name foobar-sample-spa-setup \ --source file://Source.json \ --artifacts file://Artifacts.json \ --environment file://Environment.json \ --service-role arn:aws:iam::<account-id>:role/foobar-sample-spa-setup-codebuild
3. セットアップ用のCodeBuildを実行する
セットアップ用のCodeBuildを実行すると環境が順次、作成されていきます。リソースはそれぞれお金がかかるので注意が必要です。
$ aws codebuild start-build --project-name foobar-sample-spa-setup
WebのエンドポイントはCloudFrontのconsoleから探せます。
httpsでのみアクセスできます。反映まで少しかかるのでアクセスできるようになるまで少し待つ必要があります。
中身がないのでAngularのデフォルトページです。
もしここでアクセスした時に307リダイレクトが起きたらだいぶ待つ必要があるみたいです...(何度か遭遇しました https://stackoverflow.com/questions/38706424/aws-cloudfront-returns-http-307-when-origin-is-s3-bucket
まとめ
今回はTerraformでAWSサーバーレスなサービスのインフラ構築をするサンプルを作ってみました。心残りは下記です。