AWS ProtonでTerraformがサポートされました

2022.03.25

いわさです。

AWS ProtonはCloudFormationをバンドルしたProtonテンプレートを使うことで、デプロイされる環境を標準化するためのサービスです。
従来までIaCテンプレートにはCloudFormationを利用することが出来ていましたが、今回のアップデートでTerraformを使う機能がGAとなりました。

早速試してみましたので少し紹介したいと思います。
CloudFormationがTerraformに変わっただけ、という感じではないので注意が必要です。

なお、Proton関係なしにCloudFormationとTerraformがどう違うかという点についてはこの記事では触れません。

Protonテンプレートのバンドル方法はほとんどCloudFormationと同じ

まず、Protonテンプレートについてですが、こちらについてはCloudFormationと大きく違いません。
CloudFormationの場合はIaCファイルは1つまでなのですが、Terraformでは複数のIaCファイルを含めることが出来ます。

入力パラメータの参照方法は、CloudFormationではJinjaを使用していますが、Terraformでは使用していません。通常どおり変数を参照すれば良いです。
ProtonがProtonコンソールで受け付けたパラメータを吸収してTerraformの変数ファイルを生成してくれます。

Terraformでも、以下のGitHub連携機能を使ってテンプレート同期を行うことが可能です。

以下がテンプレートバンドルに使用したコードです。

infrastructure/manifest.yaml

infrastructure:
  templates:
    - file: "*"
      rendering_engine: hcl
      template_language: terraform

infrastructure/environment.tf

# VPC
resource "aws_vpc" "tf-hoge-vpc" {
  cidr_block           = var.environment.inputs.vpc_cidr
  enable_dns_hostnames = true
}

# Subnet
resource "aws_subnet" "tf-hoge-subnet" {
  vpc_id     = aws_vpc.tf-hoge-vpc.id
  cidr_block = var.environment.inputs.subnet_cidr
}

infrastructure/config.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }

  backend "s3" {}
}

# Configure the AWS Provider
provider "aws" {}

variable "aws_region" {
  type = string
}

schema/schema.yaml

schema:                            # required
  format:                          # required
    openapi: "3.0.0"               # required
  # required              defined by administrator
  environment_input_type: "EnvironmentOutput"
  types:                           # required
    # defined by administrator
    EnvironmentOutput:
      type: object
      description: "Outputs of the environment"
      properties:
        vpc_cidr:                   # parameter
          type: string
          description: "This CIDR range for your VPC"
          default: 10.0.0.0/16
          pattern: ([0-9]{1,3}\.){3}[0-9]{1,3}($|/(16|24))
        subnet_cidr:                   # parameter
          type: string
          description: "This CIDR range for your VPC"
          default: 10.0.0.0/24
          pattern: ([0-9]{1,3}\.){3}[0-9]{1,3}($|/(24))

プロビジョニングの概念がCloudFormationとかなり違う

Protonテンプレートから作成された環境を実際にプロビジョニングしていくのですが、ここがCloudFormationとTerraformでは大きくことなります。

CloudFormatiomの場合はAWSマネージド型プロビジョニングと呼ばれ、ProtonがProtonテンプレート内のCloudFormationテンプレートをデプロイします。この方法はAWS(Proton)のみでほぼ完結しています。


https://docs.aws.amazon.com/proton/latest/adminguide/ag-works-prov-methods.html より引用

Terraformの場合は自己管理型プロビジョニングと呼ばれ、Protonはプロビジョニング時に指定されたGitHubリポジトリへTerraformテンプレート一式を生成したプルリクエストを作成します。
そこから先はProtonは管理せず管理者がTerraformのデプロイパイプラインなりを実行する仕組みを提供する必要があります。(厳密にいうとプルリクエストが処理されて環境の作成が終わったかどうかのシグナルだけはProtonは受け付ける)


https://docs.aws.amazon.com/proton/latest/adminguide/ag-works-prov-methods.html より引用

GitHubと連携させる場合だと、開発者がProton環境をテンプレートを使って生成しようとすると、Protonはコンソールからの入力パラメータなどをTerraformの変数ファイルへ落とし込み、デプロイに必要なファイル一式をプッシュしてプルリクエストをGitHubへ作成します。
管理者は手動でプルリクエストをマージします。
マージを契機にGitHub ActionsでTerraformのデプロイワークフローを実行します。ワークフロー内でリソース生成後に完了シグナルをProtonに送信までしてやると、Proton側でも環境の生成を検知することが出来ます。

デプロイまでやってみた

Protonテンプレートはいけそうですが、自己管理型プロビジョニングのためのパイプラインの用意が大変そうです。
しかし、AWSからリポジトリテンプレートが提供されているのでこちらを使うことでひな形を用意することが出来ます。

以下から"Use this template"でリポジトリを作成します。

作成後、直下のGitHubConfiguration.yamlを使ってCloudFormationスタックを作成しましょう。
ParameterValueは作成したリポジトリ名にします。

$ aws cloudformation create-stack --stack-name aws-proton-terraform-role-stack \
   --template-body file:///$PWD/GitHubConfiguration.yaml \
   --parameters ParameterKey=FullRepoName,ParameterValue=Tak1wa/aws-proton-terraform-github-actions-sample \
   --capabilities CAPABILITY_NAMED_IAM
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/aws-proton-terraform-role-stack/9c883110-ac13-11ec-b805-0e40bee8d903"
}

このスタックで以下のようにGitHub ActionsからWebフェデレーション用のIAMロールが作成されます。
GitHub側のワークフローではこいつを使うのでアクセスキーやシークレットは不要になります。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:Tak1wa/aws-proton-terraform-github-actions-sample:*"
                }
            }
        }
    ]
}

上記スタックでロールとあわせてS3バケットも作成されます。
こちらはTerraformのステートに使われます。

これらのARNやバケット名をenv_config.jsonへ反映させます。
ちなみに、hoge-tfは作成されるProton環境名を指しています。

env_config.json

{
    "hogehoge-tf": {
        "role": "arn:aws:iam::123456789012:role/ExampleGithubRole",
        "region": "ap-northeast-1",
        "state_bucket":"aws-proton-terraform-bucket-123456789012"
    }
}

ではProtonからTerraformをバンドルしたテンプレートを使って環境を作成してみましょう。
自己管理型プロビジョニングを選択し、パイプラインが設定されているリポジトリ名を選択します。

作成後、環境のデプロイステータスがin progressのままで、プルリクエストが作成されていると思います。

サンプルテンプレートから作成したリポジトリであればチェック行われ、問題があればこの時点でエラーとなります。
問題なければマージしましょう。

ワークフローでは前述したProtonへの通知機能が含まれています。
TerraformからリソースのApplyが完了したのち、Proton側ではステータスがSucceededとなるのが確認出来るはずです。

今回用意したProtonテンプレートはシンプルにVPCとサブネットを1つづつ作成するものです。
AWSリソースもProtonコンソールから入力したパラメータが反映されて作成されていますね。

さいごに

本日はProtonでTerraformを使った自己管理型プロビジョニングを行ってみました。
AWSマネージド型プロビジョニングと比べるとさすがに大変ですね。

re:Invent2021のProtonセッションではTerraformサポートが注目機能としてプレビューでの紹介がされていましたが、それがGAした形です。
Terraformを使えるメリットを感じる方もいらっしゃると思いますのでProtonでTerraformを使いたいという方の参考になればと思います。