Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve Cloud Run deployment template #404

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions docs/deployment/gcloud/README.md
Original file line number Diff line number Diff line change
@@ -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
```
149 changes: 119 additions & 30 deletions docs/deployment/gcloud/main.tf
Original file line number Diff line number Diff line change
@@ -1,28 +1,110 @@
# 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 = "<YOUR BUCKET NAME>"
# 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}"
location = var.region
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
}
}

Expand All @@ -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 {
Expand All @@ -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"
Expand Down
Loading