AWS App Runner+ECRをTerraformでデプロイするサンプルコード(Docker化済み)

BeeX Advent Calendar 2022 4日目の記事です。

タイトルの内容を作りました。

github.com

やっていること

ECRとApp RunnerをTerraformでデプロイしています。ECRへのコンテナイメージのPushなどもTerraformの世界に閉じ込めています。

前提

  • Docker

使い方

App Runnerに、 nginx1.20 タグのコンテナイメージをデプロイしてみます。

ローカルにあるコンテナイメージをデプロイする仕様にしてあるので、該当のイメージを docker pull しておきます。

docker pull nginx:1.20

リポジトリを取得します。

git clone https://github.com/yomon8/apprunner-ecr-terraform.git
cd apprunner-ecr-terraform/

tf.sh がDockerを使った terraform コマンドのラッパーに なっています。最初に terraform init をするのですが、この場合は以下になります。

実行すると docker build が走り、 terraform init が実行されます。

./tf.sh init

terraform apply も実行してみます。

./tf.sh apply

必要なパラメータを入力します。( tf.sh は引数取れるようになっているので -var-var-file で渡すことも可能です)

var.aws_profile
  Enter a value: default

var.aws_region
  Enter a value: ap-northeast-1

var.local_image_name
  Enter a value: nginx

var.local_image_tag
  Enter a value: 1.20

内容確認してyesで環境がデプロイされます。

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

ECRが作られて、そこにコンテナイメージがPushされ、そのイメージを使ってApp Runnerが起動するところまでの処理が動きます。

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

Outputs:

service_url = "xxxxxxx.ap-northeast-1.awsapprunner.com"
https://xxxxxxx.ap-northeast-1.awsapprunner.com

ポイント

ECRのPush

App RunnerはECRのPushをトリガーに、新しいコンテナをサービスにデプロイすることが可能です。 この部分に少し工夫を入れています。

挙動確認

先程と同じパラメータで terraform apply を実行しても以下のように変化が無いので処理は動きません。

./tf.sh apply
# 省略
No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
# 省略

今度はイメージを nginx:1.20 -> nginx:1.22 に変更してみます。イメージをpullしておきます。

docker pull nginx:1.22

パラメータも変更します。

var.local_image_tag
  Enter a value: 1.22

そうするとタグに紐付いたイメージのIDの変更が検知され、terraformを通してECRへのPush処理が動きます。

./tf apply
# 省略

Terraform will perform the following actions:

  # module.ecr.null_resource.push_image must be replaced
-/+ resource "null_resource" "push_image" {
      ~ id       = "238268467668811125" -> (known after apply)
      ~ triggers = { # forces replacement
          ~ "image_id" = "sha256:0584b370e957bf9d09e10f424859a02ab0fda255103f75b3f8c7d410a4e96ed5" -> "sha256:404359394820dad4c8f210f935939f5890a02ccf82302e1a1068bd0723149736"
        }
    }
# 省略

App Runnerのデプロイ処理が動き出すのがイベントログから確認できます。

暫くすると新しいNginxのバージョンに切り替わります。

実装ポイント

まず以下のように外部コマンドを実行して、パラメータで指定されたイメージのIDを取得します。

data "external" "image_hash" {
  program = [
    "/bin/sh", "${path.module}/get_id.sh", "${var.local_image_name}", "${var.local_image_tag}"
  ]
}

ここで実行されているスクリプトは以下の通りで、docker inspect で取得されるIDをJSON形式で返します。(JSON形式でないとTerraform側で扱えないため)

#!/bin/sh
docker inspect --format='{"id":"{{index .Id}}"}' $1:$2

上記で取得したIDをトリガーとすることで、イメージの変更があった時のみApp Runnerにデプロイが走ります。

resource "null_resource" "push_image" {
  triggers = {
    image_id = data.external.image_hash.result["id"]
  }
  provisioner "local-exec" {
    // ローカルのスクリプトを呼び出す
    command = "sh ${path.module}/push_ecr.sh"

    // スクリプト環境変数
    environment = {
      AWS_PROFILE      = var.aws_profile
      AWS_ACCOUNT_ID   = var.aws_account_id
      AWS_REGION       = var.aws_region
      ECR_URI          = "${aws_ecr_repository.image.repository_url}:${var.image_tag}"
      LOCAL_IMAGE_NAME = var.local_image_name
      LOCAL_IMAGE_TAG  = var.local_image_tag
    }
  }
}

provisionerとして push_ecr.sh スクリプトが呼ばれています。こちらは記事下部のURLサイトを参考に作りました。ECRへのPushもTerraformの世界で完結できます。

#!/bin/sh
aws ecr get-login-password --profile ${AWS_PROFILE} --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
docker tag ${LOCAL_IMAGE_NAME}:${LOCAL_IMAGE_TAG} ${ECR_URI}
docker push ${ECR_URI}

Docker

上のスクリプトでDockerを呼んでいるのですが、そもそもTerraform自体もDockerの上で動かしています。Dockerの上でDockerを動かすにはdind (Docker In Docker) というコンテナを使うと便利です。

https://hub.docker.com/_/docker

Dockerfileが何やら色々とインストールしていますが、alpine上にterraformとawscliを入れる必要があり、awscliの前提となるglibcのインストールが必要で、その部分が長くなっています。参考URLにあるStackOverflowの情報を参考に作っています。

掃除

最後はdestroyして環境消しておきます。

./tf.sh destroy

参考URL

qiita.com

stackoverflow.com