Terraformでコンテナイメージのビルドからデプロイまでを行う(GCP編)
概要
- Terraform の kreuzwerker/docker プロバイダーを使うことで、
terraform apply
で行う処理にコンテナイメージのビルドとイメージリポジトリーへの登録の処理も組み込むことができます。 - この記事では
terraform apply
だけでコンテナイメージのビルドから Cloud Run のデプロイをやります。 - Terraformでコンテナイメージのビルドからデプロイまでを行う(AWS編) の GCP 版です。該当記事と違いがあるポイントだけ読む方は以下の部分をご覧ください:
背景
- システム構築を行う際、以下のような手順になることがしばしばあります。
- コンテナリポジトリーを作る
- コンテナイメージの作成とコンテナリポジトリーへの登録 (
docker build
とdocker push
) - コンテナリポジトリーに登録したイメージを起動するインフラを作成する。
- 1 と 3 の処理は terraform で行い、 2 の処理は docker で行う、など各ステップに使用するツールが別になるため、全体のデプロイ処理のために複数ツールを使用するスクリプトを作ることになります。
- 加えてコンテナレジストリーへの認証に docker の認証プラグインなどの準備も必要で、スクリプトを実行できる環境を準備するのは割と手間です。
- ※あとプロジェクトが Windows / Mac / Linux 混在環境の場合も対応が手間。
- Terraform の kreuzwerker/docker プロバイダーを使うことで、これを
terraform apply
で一括して行うことができます。 - さらに Terraform の実行自体も docker で行うことで、開発者がインストールするのは Docker だけという環境も実現できます。
実装例
Google Cloud Run で Web アプリケーションを作成するデモです。
こんな感じに docker compose さえ実行できれば、 terraform apply だけで HTTP エンドポイントを構築できます。
$ export CLOUDSDK_CORE_PROJECT=your_project_name
$ docker compose run --rm terraform init
...
$ docker compose run --rm terraform apply
...
Outputs:
webapp_uri = "https://xxxxxxx.a.run.app"
$ curl https://xxxxxxx.a.run.app
Hello, World!
$
設定のポイント
今回の kreuzwerker/docker の利用での設定のポイントを記載します。
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:
- CLOUDSDK_CORE_PROJECT
volumes:
- .:/workspace
# docker outside of docker でterraformからdockerデーモンにアクセスさせる
- /var/run/docker.sock:/var/run/docker.sock
working_dir: /workspace
dockerプロバイダーでArtifact Registryの認証を行う
Artifact Registryで作ったイメージレジストリーにコンテナイメージを保存するには、認証が必要です。
この認証には gcloud auth configure-docker ...
コマンドでセットアップされる gcloud CLI 認証ヘルパーを使用するか、 docker-credential-gcr
をインストールして使用するケースが多いでしょう。
今回は代わりに サービスアカウントキー を使った認証を使用します。
Terraform 内でレジストリーにアクセスするためだけのサービスアカウントを作成し、その認証キーを使用します (該当コード):
resource "google_service_account_key" "imagepush" {
service_account_id = google_service_account.imagepush.name
}
...
provider "docker" {
registry_auth {
address = "https://${google_artifact_registry_repository.image_registry.location}-docker.pkg.dev"
username = "_json_key_base64"
password = google_service_account_key.imagepush.private_key
}
}
認証キーが Terraform のステート(tfstate ファイル)に保存されることに注意してください。このため、ステートファイルは S3 などのバックエンドに保存するようにして、適切なアクセス制限を行う必要があります。
一応、万一認証キーが漏れたとしても、サービスアカウントを専用に作ることで影響範囲を該当のArtifact Registryだけに抑えられます。
docker_imageリソースでコンテナイメージを作成する
docker_image リソース を使うことで、 docker build
を実行できます。
扱いが難しい点として、イメージの構築に使用するファイルが使用した際に docker build
を再実行するための指定を自前で行う必要があります。
ここでは、ビルドで使用するファイルをarchive_file データソースで zip にして、そのハッシュ値で再ビルドの判定を行うようにしています。この zip ファイルはハッシュ値の計算に使用するだけで、 docker build
の処理自体では特に使用しません。
# イメージのリビルド判定用
data "archive_file" "webapp" {
type = "zip"
output_path = "${path.module}/webapp.zip"
source_dir = "${path.module}/webapp"
# .dockerignore 相当の指定を行う。
excludes = setunion(
fileset("${path.module}/webapp", ".dockerignore"),
)
}
resource "docker_image" "webapp" {
name = "${local.registry_uri}/webapp:latest"
platform = "linix/amd64"
keep_locally = true
build {
context = "${path.module}/webapp"
}
triggers = {
sha256 = data.archive_file.webapp.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" "webapp" {
name = docker_image.webapp.name
keep_remotely = true
triggers = {
sha256 = data.archive_file.webapp.output_sha256
}
}
CloudRunで使用するイメージを指定する
イメージを更新したら新しいイメージがデプロイされるように、
以下のフォーマットのダイジェストを使用したイメージ URL を指定します:
ロケーション-docker.pkg.dev/プロジェクトID/レジストリー名/リポジトリー名@sha256:ダイジェスト
ダイジェストの部分は (sha256:ダイジェスト
のフォーマットで) docker_registry_image リソースの sha256_digest
で取得できます:
locals {
registry_host = "${google_artifact_registry_repository.image_registry.location}-docker.pkg.dev"
registry_uri = "${local.registry_host}/${data.google_project.project.project_id}/${google_artifact_registry_repository.image_registry.name}"
}
...
locals {
repo_image_uri = "${local.registry_uri}/webapp@${docker_registry_image.webapp.sha256_digest}"
}
resource "google_cloud_run_v2_service" "webapp" {
name = "${var.basename}-service"
location = "asia-northeast1"
template {
containers {
image = local.repo_image_uri
}
}
depends_on = [
google_project_service.run,
]
}
Discussion