TerraformからAWS SAMをデプロイしてみた
2019-06-26

TL; DR

  • terraform plan によるレビューはできないけど動かすことは出来る
  • sam build は基本初回しか動いてくれないので, 毎回 terraform taint null_resource.sam_build してあげると良い

モチベーション

  • 基本的にはterraformを使いたい
  • でもterraformでlambda扱うのとかしんどい
  • そこはAWS SAM使いたい
  • terraformからsam扱いたい

ディレクトリ構成


.
├── sam
│   ├── hello_world
│   │   ├── __init__.py
│   │   ├── app.py
│   │   └── requirements.txt
│   └── template.yaml
└── sam.tf

sam/ は sam init --runtime python3.7 で作成したものと同等です。

sam . tf


provider "aws" {}

resource "aws_s3_bucket" "sam_source" {
  bucket_prefix = "sam-source-"
  force_destroy = true
}

resource "null_resource" "sam_build" {
  provisioner "local-exec" {
    command = "sam build --base-dir sam/ --template sam/template.yaml --use-container"
  }

  depends_on = [aws_s3_bucket.sam_source]
}

resource "null_resource" "sam_package" {
  triggers = {
    sam_build_id = join(",", [null_resource.sam_build.id])
  }

  provisioner "local-exec" {
    command = "sam package --template-file .aws-sam/build/template.yaml --s3-bucket ${aws_s3_bucket.sam_source.id} --output-template-file sam-output.yaml"
  }
  depends_on = [aws_s3_bucket.sam_source, null_resource.sam_build]
}

data "local_file" "sam_output" {
  filename   = "sam-output.yaml"
  depends_on = [null_resource.sam_package]
}

resource "aws_cloudformation_stack" "sam" {
  name          = "sam-stack"
  template_body = data.local_file.sam_output.content
  capabilities  = ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
  depends_on    = [null_resource.sam_package, data.local_file.sam_output]
}

実行


# create
$ terraform plan
$ terraform apply -auto-approve

# update
$ terraform taint null_resource.sam_build
$ terraform plan
$ terraform apply -auto-approve

terraform plan での出力サンプル


$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.local_file.sam_output will be read during apply
  # (config refers to values not yet known)
 <= data "local_file" "sam_output"  {
      + content  = (known after apply)
      + filename = "sam-output.yaml"
      + id       = (known after apply)
    }

  # aws_cloudformation_stack.sam will be created
  + resource "aws_cloudformation_stack" "sam" {
      + capabilities  = [
          + "CAPABILITY_AUTO_EXPAND",
          + "CAPABILITY_NAMED_IAM",
        ]
      + id            = (known after apply)
      + name          = "sam-stack"
      + outputs       = (known after apply)
      + parameters    = (known after apply)
      + policy_body   = (known after apply)
      + template_body = (known after apply)
    }

  # aws_s3_bucket.sam_source will be created
  + resource "aws_s3_bucket" "sam_source" {
      + acceleration_status         = (known after apply)
      + acl                         = "private"
      + arn                         = (known after apply)
      + bucket                      = (known after apply)
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = "sam-source-"
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = true
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + versioning {
          + enabled    = (known after apply)
          + mfa_delete = (known after apply)
        }
    }

  # null_resource.sam_build will be created
  + resource "null_resource" "sam_build" {
      + id = (known after apply)
    }

  # null_resource.sam_package will be created
  + resource "null_resource" "sam_package" {
      + id       = (known after apply)
      + triggers = (known after apply)
    }

Plan: 4 to add, 0 to change, 0 to destroy.

普通にyamlを渡していれば + template_body = (known after apply) の部分にその内容が表示される(はず)のですが, 今回はこの段階では未定なのでこのような表記になっています。

ポイント

  • sam build , sam packagenull_resourcelocal-exec で実行。(2つのリソースに分けているけど, まとめてもいいかもしれない)
  • aws_cloudformation_stack.samtemplate_body にyamlファイルの中身を渡す際に, file ファンクションは使用せずに data.local_file を使用する。 file ファンクションで読み込むにはterraformコマンド実行時にそのファイルが存在していなければならない。しかし今回でいう sam-output.yaml ファイルはterraformコマンド実行時には存在していないためエラーとなる。 data.local_file にさらに depends_on = [null_resource.sam_package] で依存関係を定義してあげるとうまい具合に動的に遅延読み込みしてくれる。
  • 2回目以降の実行時には基本 null_resource.sam_build は実行されない。それだと困るので都度 terraform taint null_resource.sam_build で強制的に再実行されるようにしている。
  • null_resource.sam_package も基本そのままだと再実行されないが, triggers を設定しているので null_resource.sam_build が再作成されてidが変わると null_resource.sam_package が再度実行されるようになっている。

蛇足

  • terraform workspace でdefault以外を使っているとstateファイルのパスが変わるけど, そうなると terraform taint 実行時にいちいち -state=path でstateファイルを渡す必要があってめんどくさいんだけどいい方法ないもんだろうか?