TerraformでAWS App Runnerを爆速構築してみる

# はじめに
2021/5/19に突如登場したAWSの新サービス。
これまで ECS Fargate や Lambda といった実行基盤と同じような感じで、高い抽象度で数クリックでお手軽に構築できることがウリだ。

しかも、なんと[ローンチのその日のうちに Terraform がキャッチアップしてる](https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#3410-may-19-2021)ではないか！

これは、これまでそこそこ大変だったコンテナ環境の構築がどれくらいお手軽になったのか試さないワケにはいかないだろう！

# コンテンツとコンテナの準備
今回は、自分で作った Nginx の Web サービスを公開するという前提にしてみよう。
なので、テキトーなコンテンツを作って Nginx のコンテナに詰め込むようにする。

```contents/index.html
<html>
  <head>
    <meta charset="utf-8">
    <title>はじめてのTerraform＋AppRunner</title>
  </head>
  <body>
    <h1>Terraform＋AppRunnerでNginx起動成功！</h1>
  </body>
</html>
```

```Dockerfile:Dockerfile
FROM nginx:1.20-alpine

# copy static contents
COPY ./contents/ /usr/share/nginx/html/

# return
WORKDIR /

CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
```

# 自前 Nginx コンテナを ECR に PUSH する
ということで、チュートリアルや例では ECR Public Gallery を使っているが、自分の ECR を使用する。

```HCL
################################################################################
# ECR                                                                          #
################################################################################
resource "aws_ecr_repository" "nginx" {
  name = local.ecr_repository_name
}

data "aws_ecr_authorization_token" "token" {}

resource "null_resource" "image_push" {
  provisioner "local-exec" {
    command = <<-EOF
      docker build ../ -t ${aws_ecr_repository.nginx.repository_url}:latest; \
      docker login -u AWS -p ${data.aws_ecr_authorization_token.token.password} ${data.aws_ecr_authorization_token.token.proxy_endpoint}; \
      docker push ${aws_ecr_repository.nginx.repository_url}:latest
    EOF
  }
}
```

# App Runner を構築する
そして、いよいよ App Runner の構築だ。
これが ECS なら、クラスタ作ってサービス作ってタスク定義して LB 作ってリスナー作ってターゲットグループ作ってと色々なサービスを作る必要があるが、App Runner だとこんな感じになる。

```HCL
################################################################################
# App Runner                                                                   #
################################################################################
resource "aws_apprunner_service" "nginx" {
  depends_on = [null_resource.image_push]

  service_name = "nginx"

  source_configuration {
    image_repository {
      image_repository_type = "ECR"
      image_identifier      = "${aws_ecr_repository.nginx.repository_url}:latest"

      image_configuration {
        port = "80"
      }
    }

    authentication_configuration {
      access_role_arn = aws_iam_role.apprunner.arn
    }
  }
}
```

少ない！本当にこれだけで動いてしまうのか！？

と言いつつ、実は一つだけ足りていない。App Runner は ECR からコンテナイメージを Pull してくるので、ECR へのアクセス権限は最低限必要になる。
信頼されたエンティティに設定するプロバイダは ```build.apprunner.amazonaws.com``` である。

```HCL
######################################################################
# IAM Role for App Runner                                            #
######################################################################
resource "aws_iam_role" "apprunner" {
  name               = local.iam_role_name
  assume_role_policy = data.aws_iam_policy_document.apprunner_assume.json
}

data "aws_iam_policy_document" "apprunner_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "build.apprunner.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "apprunner" {
  role       = aws_iam_role.apprunner.name
  policy_arn = aws_iam_policy.apprunner_custom.arn
}

resource "aws_iam_policy" "apprunner_custom" {
  name   = local.iam_policy_name
  policy = data.aws_iam_policy_document.apprunner_custom.json
}

data "aws_iam_policy_document" "apprunner_custom" {
  statement {
    effect = "Allow"

    actions = [
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ecr:DescribeImages",
    ]

    resources = [
      "*",
    ]
  }
}
```

ちょっと増えた。が、それにしても少ない。これで全部だ。

それでは ```terraform apply``` してみよう。

```
(前略)
aws_apprunner_service.nginx: Still creating... [4m40s elapsed]
aws_apprunner_service.nginx: Creation complete after 4m44s [id=arn:aws:apprunner:ap-northeast-1:xxxxxxxxxxxx:service/nginx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
```

5分くらいで完成。早い！
マネージメントコンソールの画面は↓こんな感じだ。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/cf4643a2-caf0-e22c-181e-f9ad3f13cd20.png)


デフォルトのURLにアクセスしてみると……

![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/557548ea-9b9f-14ae-abaa-f1152baf8fe0.png)


無事、自分が作ったテキトーなコンテンツが表示された！
これはお手軽！

AWS App Runner は、お手軽ながらもオートスケーリングやカスタムドメインにも対応しているし、IAM による他のサービスのアクセス制御にも対応しているので、ガチガチではない標準的な要件の範囲であれば簡単に作れてしまうのは嬉しいところ。

どんどん活用していこう！

# (追記)カスタムドメインを設定する
さて、せっかくだからカスタムドメインを設定して自由なドメイン名でサービスを提供してみよう。
当然ながら、事前にドメインの取得が必要になるので、[過去の記事](https://qiita.com/neruneruo/items/041568a71e5c324568fb#1-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97)を参考にしながら、Route53 にホストゾーンの設定までは行っておく。

カスタムドメインの設定は、```aws_apprunner_custom_domain_association``` のリソースを使う。

```HCL
data "aws_route53_zone" "my_domain" {
  name = local.domain_name
}

resource "aws_apprunner_custom_domain_association" "my_domain" {
  service_arn          = aws_apprunner_service.nginx.arn
  domain_name          = "www.${data.aws_route53_zone.my_domain.name}"
  enable_www_subdomain = false
}
```

これを ```terraform apply``` すると、マネージメントコンソールのカスタムドメインのタブが、以下のように「証明書DNS検証の保留中」のステータスになる。

![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/ebcb8eda-6e8f-24da-a652-1c7d7b979c08.png)

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/bf5e311e-da96-5f9d-8ee5-0b467a7055d6.png)


ここで、以下のようにしてあげることで、DNS検証を行える。
実際は、Terraform は一息で apply して問題ない。

```HCL
resource "aws_route53_record" "www" {
  zone_id = data.aws_route53_zone.my_domain.zone_id
  name    = aws_apprunner_custom_domain_association.my_domain.domain_name
  type    = "CNAME"
  ttl     = "300"
  records = [aws_apprunner_custom_domain_association.my_domain.dns_target]
}

resource "aws_route53_record" "certificate_validation" {
  for_each = {
    for record in aws_apprunner_custom_domain_association.my_domain.certificate_validation_records : record.name => {
      name   = record.name
      record = record.value
    }
  }

  zone_id = data.aws_route53_zone.my_domain.zone_id
  name    = each.value.name
  type    = "CNAME"
  ttl     = "300"
  records = [each.value.record]
}
```

DNS検証を行ってしばらく待つと、ステータスが以下のように変わる。

![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/eada8461-64de-1ada-a1c2-c02e22948354.png)

これで、設定したドメインで、App Runner でのサービスにアクセスすることが可能になる。
やり方さえ覚えてしまえば、少ない IaC で設定が可能になる。素晴らしい！