Terraformでコンテナイメージのビルドからデプロイまでを行う(AWS編)
概要
- Terraform の kreuzwerker/docker プロバイダーを使うことで、
terraform apply
で行う処理にコンテナイメージのビルドとイメージリポジトリーへの登録の処理も組み込むことができます。 - この記事ではコンテナイメージを使った AWS Lambda のデプロイをやります。
背景
- システム構築を行う際、以下のような手順になることがしばしばあります。
- コンテナリポジトリーを作る
- コンテナイメージの作成とコンテナリポジトリーへの登録 (
docker build
とdocker push
) - コンテナリポジトリーに登録したイメージを起動するインフラを作成する。
- 1 と 3 の処理は terraform で行い、 2 の処理は docker で行う、など各ステップに使用するツールが別になるため、全体のデプロイ処理のために複数ツールを使用するスクリプトを作ることになります。
- 加えてコンテナリポジトリーへの認証に awscli や docker の認証プラグインなどの準備も必要で、スクリプトを実行できる環境を準備するのは割と手間です。
- ※あとプロジェクトが Windows / Mac / Linux 混在環境の場合も対応が手間。
- Terraform の kreuzwerker/docker プロバイダーを使うことで、これを
terraform apply
で一括して行うことができます。 - さらに Terraform の実行自体も docker で行うことで、開発者がインストールするのは Docker だけという環境も実現できます。
実装例
AWS 上で、コンテナイメージを使う Lambda と API Gateway を通して Hello, World! を返す HTTP エンドポイントを作成するデモです。
こんな感じに docker compose さえ実行できれば、 terraform apply だけで HTTP エンドポイントを構築できます。
※ここでは AWS の認証についての設定等は省略しています。
$ docker compose run --rm terraform init
...
$ docker compose run --rm terraform apply
...
Outputs:
api_url = "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod"
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod
Hello, World!
$
設定のポイント
今回の kreuzwerker/docker の利用での設定のポイントを記載します。
なお、実行のために AWS への認証 (アクセスキーID等の認証情報) をいい感じに取り回す必要がありますが、そこについてはここでは省略します。
TerraformをDockerで起動&TerraformからDockerが使えるようにする
Terraform の Docker イメージを使用することで、プロジェクト参加者が Terraform のインストール手順を踏まなくてもとりあえず Terraform の実行を行えたり、プロジェクトごとに異なる Terraform バージョンを簡単に切り替えて利用できるので便利です。
さらに、今回使用する kreuzwerker/docker プロバイダーは docker デーモンとの通信を行うため、 /var/run/docker.sock
をバインドマウントします (docker outside of docker)。
docker-compose.yaml
が以下のようになります(関係部分だけ抜粋):
services:
terraform:
image: hashicorp/terraform:1.7.5
environment:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
- AWS_DEFAULT_REGION
volumes:
- .:/workspace
# docker outside of docker でterraformからdockerデーモンにアクセスさせる
- /var/run/docker.sock:/var/run/docker.sock
working_dir: /workspace
dockerプロバイダーでECRの認証を行う
AWS ECR 上のイメージリポジトリーにコンテナイメージを保存するには、認証が必要です。
通常、この認証は プライベートレジストリの認証 - Amazon ECR の通り、以下のようなコマンドを実行することで行います。
$ aws ecr get-login-password \
| docker login \
--username AWS \
--password-stdin (AWSアカウントID).dkr.ecr.region.amazonaws.com
これに相当する認証処理を、 以下のように行います :
data "aws_ecr_authorization_token" "token" {
}
provider "docker" {
registry_auth {
address = data.aws_ecr_authorization_token.token.proxy_endpoint
username = data.aws_ecr_authorization_token.token.user_name
password = data.aws_ecr_authorization_token.token.password
}
}
なお、このトークンの値は Terraform のステート(tfstate ファイル)に保存されることに注意してください。このため、以下の運用が必要です:
- ステートファイルは S3 などのバックエンドに保存するようにして、適切なアクセス制限を行う。
- Terraform の実行で使用する権限にはIAMロールなどを使用し、個人ユーザーの権限で実行しないようにする。
- そうでないと tfstate ファイルに保存された個人トークンをプロジェクト内の他のメンバーが使用できてしまう。
docker_imageリソースでコンテナイメージを作成する
docker_image リソース を使うことで、 docker build
を実行できます。
扱いが難しい点として、イメージの構築に使用するファイルが使用した際に docker build
を再実行するための指定を自前で行う必要があります。
ここでは、ビルドで使用するファイルをarchive_file データソースで zip にして、そのハッシュ値で再ビルドの判定を行うようにしています。この zip ファイルはハッシュ値の計算に使用するだけで、 docker build
の処理自体では特に使用しません。
# イメージのリビルド判定用
data "archive_file" "lambda" {
type = "zip"
output_path = "${path.module}/lambda.zip"
source_dir = "${path.module}/lambda"
# .dockerignore 相当の指定を行う。
excludes = setunion(
fileset("${path.module}/lambda", ".devcontainer/**/*"),
fileset("${path.module}/lambda", ".github/**/*"),
fileset("${path.module}/lambda", ".gitignore"),
)
}
resource "docker_image" "lambda" {
name = "${aws_ecr_repository.image_repository.repository_url}:latest"
platform = "linix/amd64"
keep_locally = true
build {
context = "${path.module}/lambda"
}
triggers = {
sha256 = data.archive_file.lambda.output_sha256
}
}
なお難点として、ローカルにイメージがない場合、 docker build
が実行されてしまいます。
CI/CD などで毎回 docker build
が実行される結果になります。
docker_registry_imageでコンテナイメージをpushする
docker_registry_imageリソース で docker push
を実行できます。
イメージを更新した場合に docker push
を再実行するための指定を自前で行う必要があります。
docker_image リソースと同じ zip のハッシュ値を使用します。
resource "docker_registry_image" "lambda" {
name = docker_image.lambda.name
keep_remotely = true
triggers = {
sha256 = data.archive_file.lambda.output_sha256
}
}
Lambdaで使用するイメージを指定する
イメージを更新したら新しいイメージがデプロイされるように、
以下のフォーマットのダイジェストを使用したイメージ URL を指定します:
AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/リポジトリー名@sha256:ダイジェスト
ダイジェストの部分は (sha256:ダイジェスト
のフォーマットで) docker_registry_image リソースの sha256_digest
で取得できます:
resource "aws_lambda_function" "lambda" {
function_name = local.function_name
role = aws_iam_role.lambda.arn
package_type = "Image"
image_uri = "${aws_ecr_repository.image_repository.repository_url}@${docker_registry_image.lambda.sha256_digest}"
publish = true
}
Discussion