BeeX Advent Calendar 2022 4日目の記事です。
タイトルの内容を作りました。
やっていること
ECRとApp RunnerをTerraformでデプロイしています。ECRへのコンテナイメージのPushなどもTerraformの世界に閉じ込めています。
前提
- Docker
使い方
App Runnerに、 nginx
の 1.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