diff --git a/docs/deployment/gcloud/README.md b/docs/deployment/gcloud/README.md index 2ac72278..58928269 100644 --- a/docs/deployment/gcloud/README.md +++ b/docs/deployment/gcloud/README.md @@ -1,19 +1,55 @@ # Google Cloud Run Deployment +## Overview + +This example provides a basic Terraform project to deploy the playground +on Google Cloud as a [Cloud Run](https://cloud.google.com/run?hl=en) app. + +HCL defines: + +* Cloud Run service with the app. +* Simple bucket with shared Go mod and WASM builds cache. +* IAM rules for a cache bucket. + ## Prerequisites * [Terraform](https://www.terraform.io/) or [OpenTofu](https://opentofu.org). -* [gcloud](https://cloud.google.com/sdk/docs/install) tool. +* [Google Cloud CLI (`gcloud`)](https://cloud.google.com/sdk/docs/install). * [Google Cloud](https://cloud.google.com/) project. -## Setup +## Deployment + +### First-time setup + +Initialize a Terraform project and prepare a TF variables file: -* Initialize project using `terraform init` command. -* Copy `example.tfvars` to `prod.tfvars` and edit the file. -* Prepare Terraform plan using variables file: \ - `terraform plan -var-file="prod.tfvars" -out=tfplan` -* Apply a plan using `terraform apply tfplan` command. +```shell +# Auth on gcloud and init TF project. +# This action should be called only once. +make init -## Configuration +# Create a var file from a template +# and fill it with correct values: +cp example.tfvars prod.tfvars +vim prod.tfvars +``` + +### App Configuration See environment variables section in [Docker](../docker/README.md) docs. + +### Deploying changes + +```shell +# Prepare a Terraform plan +make plan + +# Apply a plan +make apply +``` + +### Destroying a deployment + +```shell +make destroy +``` \ No newline at end of file diff --git a/docs/deployment/gcloud/main.tf b/docs/deployment/gcloud/main.tf index 398e91c9..28ec3042 100644 --- a/docs/deployment/gcloud/main.tf +++ b/docs/deployment/gcloud/main.tf @@ -1,11 +1,91 @@ -# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service - -# Beta provider is required for "empty_dir" storage type. +# Beta provider is required for "gcs" storage type. provider "google-beta" { project = var.project_id region = var.region } +# By default, TF will keep infra state in '*.tfstate' files. +# Uncomment this block if you with to keep TF state in a storage bucket: +#terraform { +# backend "gcs" { +# bucket = "" +# prefix = "tfstates" +# } +#} + +locals { + cache_bucket_name = "gpg-build-cache-${var.app_env}" + cache_bucket_mount = "/mnt/gpg-build-cache-${var.app_env}" + service_account_id = "gpg-svc-acc-${var.app_env}" +} + +# Service account to access playground's cache bucket. +# +# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account +resource "google_service_account" "run_sa" { + account_id = local.service_account_id + project = var.project_id + display_name = "Service Account for a Better Go Playground (${var.app_env})" +} + +# Bucket to keep cached WASM builds and downloaded Go modules. +# +# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket +resource "google_storage_bucket" "static" { + project = var.project_id + location = var.region + name = local.cache_bucket_name + public_access_prevention = "enforced" + + # Truncate Go module cache policy + lifecycle_rule { + condition { + age = 14 + matches_prefix = ["mod/"] + } + + action { + type = "Delete" + } + } + + # Truncate WASM builds cache policy + lifecycle_rule { + condition { + age = 7 + matches_prefix = ["builds/"] + } + + action { + type = "Delete" + } + } + + # Truncate incomplete uploads + lifecycle_rule { + condition { + age = 1 + } + action { + type = "AbortIncompleteMultipartUpload" + } + } +} + +# IAM rule to grant access for a cache bucket. +# +# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam +resource "google_storage_bucket_iam_binding" "bucket_access" { + bucket = google_storage_bucket.static.name + role = "roles/storage.objectAdmin" + members = [ + "serviceAccount:${google_service_account.run_sa.email}" + ] +} + +# The app service. +# +# See: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service resource "google_cloud_run_v2_service" "default" { provider = google-beta name = "gpg-${var.app_env}" @@ -13,16 +93,18 @@ resource "google_cloud_run_v2_service" "default" { launch_stage = "BETA" template { + # Use service account with cache bucket access + service_account = google_service_account.run_sa.email + scaling { min_instance_count = 1 max_instance_count = 5 } volumes { - name = "gpg-build-cache-${var.app_env}" - empty_dir { - medium = "MEMORY" - size_limit = "256Mi" + name = local.cache_bucket_name + gcs { + bucket = local.cache_bucket_name } } @@ -42,32 +124,50 @@ resource "google_cloud_run_v2_service" "default" { } } - env { - name = "APP_LOG_FORMAT" - value = "console" + # Use the cache bucket for WASM builds and Go modules. + volume_mounts { + name = local.cache_bucket_name + mount_path = local.cache_bucket_mount } - # Disable cache cleanup for stateless containers. env { - name = "APP_SKIP_MOD_CLEANUP" - value = "1" + name = "APP_BUILD_DIR" + value = "${local.cache_bucket_mount}/builds" } env { - name = "SENTRY_ENVIRONMENT" - value = var.app_env + name = "GOMODCACHE" + value = "${local.cache_bucket_mount}/mod" } + # Logging env { - name = "SENTRY_RELEASE" - value = "v${var.app_version}" + name = "APP_LOG_FORMAT" + value = "console" } + # Disable cache cleanup for stateless containers. env { - name = "SENTRY_DSN" - value = var.sentry_dsn + name = "APP_SKIP_MOD_CLEANUP" + value = "1" } + # Uncomment this if you're using Sentry. + # env { + # name = "SENTRY_ENVIRONMENT" + # value = var.app_env + # } + + # env { + # name = "SENTRY_RELEASE" + # value = "v${var.app_version}" + # } + + # env { + # name = "SENTRY_DSN" + # value = var.sentry_dsn + # } + dynamic "env" { for_each = var.env_vars content { @@ -76,17 +176,6 @@ resource "google_cloud_run_v2_service" "default" { } } - # Use WASM build cache directory. - env { - name = "APP_BUILD_DIR" - value = "/var/cache/wasm-builds" - } - - volume_mounts { - name = "gpg-build-cache-${var.app_env}" - mount_path = "/var/cache/wasm-builds" - } - startup_probe { http_get { path = "/api/version"