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

Terraform for GCP CI base #144

Merged
merged 6 commits into from
Aug 20, 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
2 changes: 1 addition & 1 deletion deployment/live/gcp/example-gcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ export TESSERA_BASE_NAME={VALUE}
```

Terraforming the project can be done by:
1. `cd` to the relevant `live` directory for the environment to deploy/change
1. `cd` to the relevant directory for the environment to deploy/change (e.g. `ci`)
2. Run `terragrunt apply`

16 changes: 16 additions & 0 deletions deployment/live/gcp/example-gcp/ci/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
terraform {
source = "${get_repo_root()}/deployment/modules/gcp//example-gcp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This double slash is a bit weird?

Copy link
Collaborator Author

@AlCutter AlCutter Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how you tell terraform about module "boundaries", with the double slash it brings in anything under there, without them it fails when you try to reference the sibling gcs from within the example-gc module.

This is a bit of a dusty corner in terraform, but it's why e.g. you had to put double slashes in for your distributor config which referenced other modules: https://github.com/transparency-dev/distributor/blob/main/deployment/modules/distributor/main.tf#L110

}

include "root" {
path = find_in_parent_folders()
expose = true
}

inputs = merge(
include.root.locals,
{
example_gcp_docker_image = "us-central1-docker.pkg.dev/trillian-tessera/docker-prod/example-gcp:latest"
log_origin = "example-gcp"
}
)
15 changes: 6 additions & 9 deletions deployment/live/gcp/example-gcp/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
terraform {
source = "${get_repo_root()}/deployment/modules/gcp/gcs"
source = "${get_repo_root()}/deployment/modules/gcp//example-gcp"
}

locals {
project_id = get_env("GOOGLE_PROJECT", "trillian-tessera")
location = get_env("GOOGLE_REGION", "us-central1")
base_name = get_env("TESSERA_BASE_NAME", "example-gcp")
env = path_relative_to_include()
project_id = get_env("GOOGLE_PROJECT", "trillian-tessera")
location = get_env("GOOGLE_REGION", "us-central1")
base_name = get_env("TESSERA_BASE_NAME", "${local.env}-example-gcp")
}

inputs = merge(
local,
{}
)

remote_state {
backend = "gcs"

config = {
project = local.project_id
location = local.location
bucket = "${local.project_id}-${local.base_name}-terraform-state"
prefix = "${local.env}/terraform.tfstate"

gcs_bucket_labels = {
name = "terraform_state_storage"
Expand Down
133 changes: 133 additions & 0 deletions deployment/modules/gcp/example-gcp/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
terraform {
backend "gcs" {}
}

## Call the Tessera GCP module
##
## This will configure all the storage infrastructure required to run a Tessera log on GCP.
module "gcs" {
source = "..//gcs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the double slash needed here? I think this is one of those cases where there is something needed about the 2 slashes but best to check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, same as above.


base_name = var.base_name
env = var.env
location = var.location
project_id = var.project_id
}

##
## Resources
##

# Enable Cloud Run API
resource "google_project_service" "cloudrun_api" {
service = "run.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "cloudkms_googleapis_com" {
service = "cloudkms.googleapis.com"
}

##
## KMS for log signing
##
resource "google_kms_key_ring" "log_signer" {
location = var.location
name = var.base_name
}

resource "google_kms_crypto_key" "log_signer" {
key_ring = google_kms_key_ring.log_signer.id
name = "log_signer"
purpose = "ASYMMETRIC_SIGN"
version_template {
algorithm = "EC_SIGN_ED25519"
}
}
resource "google_kms_crypto_key_version" "log_signer" {
crypto_key = google_kms_crypto_key.log_signer.id
}

###
### Set up Cloud Run service
###
resource "google_service_account" "cloudrun_service_account" {
account_id = "cloudrun-${var.env}-sa"
display_name = "Service Account for Cloud Run (${var.env})"
}
Comment on lines +53 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't strictly necessary as there's a default service account you could use. It's good practice though. We should probably write in some docs our approach to the example on how to balance best practice vs minimalism.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, guidance is a good idea. I'll merge this as-is while we chew on that.


resource "google_project_iam_member" "iam_act_as" {
project = var.project_id
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.cloudrun_service_account.email}"
}
resource "google_project_iam_member" "iam_metrics_writer" {
project = var.project_id
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${google_service_account.cloudrun_service_account.email}"
}
resource "google_spanner_database_iam_binding" "iam_spanner_database_user" {
project = var.project_id
instance = module.gcs.log_spanner_instance.name
database = module.gcs.log_spanner_db.name
role = "roles/spanner.databaseUser"

members = [
"serviceAccount:${google_service_account.cloudrun_service_account.email}"
]
}
resource "google_project_iam_member" "iam_service_agent" {
project = var.project_id
role = "roles/run.serviceAgent"
member = "serviceAccount:${google_service_account.cloudrun_service_account.email}"
}

locals {
spanner_db_full = "projects/${var.project_id}/instances/${module.gcs.log_spanner_instance.name}/databases/${module.gcs.log_spanner_db.name}"
}

resource "google_cloud_run_v2_service" "default" {
name = "example-service-${var.env}"
location = var.location
launch_stage = "GA"

template {
service_account = google_service_account.cloudrun_service_account.email
containers {
image = var.example_gcp_docker_image
name = "example-gcp"
args = [
"--logtostderr",
"--v=1",
"--bucket=${module.gcs.log_bucket.id}",
"--spanner=${local.spanner_db_full}",
"--project=${var.project_id}",
"--listen=:8080",
"--kms_key=${google_kms_crypto_key_version.log_signer.id}",
"--origin=${var.log_origin}",
]
ports {
container_port = 8080
}

startup_probe {
initial_delay_seconds = 1
timeout_seconds = 1
period_seconds = 10
failure_threshold = 3
tcp_socket {
port = 8080
}
}
}
}
client = "terraform"
depends_on = [
module.gcs,
google_project_service.cloudrun_api,
google_project_iam_member.iam_act_as,
google_project_iam_member.iam_metrics_writer,
google_project_iam_member.iam_service_agent,
google_spanner_database_iam_binding.iam_spanner_database_user,
]
}

29 changes: 29 additions & 0 deletions deployment/modules/gcp/example-gcp/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
variable "project_id" {
description = "GCP project ID where the log is hosted"
type = string
}

variable "base_name" {
description = "Base name to use when naming resources"
type = string
}

variable "location" {
description = "Location in which to create resources"
type = string
}

variable "env" {
description = "Environment name, e.g ci, prod, etc."
type = string
}

variable "example_gcp_docker_image" {
description = "The full image URL (path & tag) for the example-gcp Docker image to deploy"
type = string
}

variable "log_origin" {
description = "The origin string for the example log"
type = string
}
6 changes: 1 addition & 5 deletions deployment/modules/gcp/gcs/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
terraform {
backend "gcs" {}
}

# Services
resource "google_project_service" "serviceusage_googleapis_com" {
service = "serviceusage.googleapis.com"
Expand Down Expand Up @@ -52,7 +48,7 @@ resource "google_storage_bucket_iam_binding" "log_bucket_writer" {
resource "google_spanner_instance" "log_spanner" {
name = var.base_name
config = "regional-${var.location}"
display_name = "${var.base_name} Spanner Instance"
display_name = var.base_name
processing_units = 100
}

Expand Down
7 changes: 6 additions & 1 deletion deployment/modules/gcp/gcs/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ output "log_bucket" {
value = google_storage_bucket.log_bucket
}

output "log_spanner" {
output "log_spanner_db" {
description = "Log Spanner database"
value = google_spanner_database.log_db
}

output "log_spanner_instance" {
description = "Log Spanner instance"
value = google_spanner_instance.log_spanner
}

output "service_account_name" {
description = "Name of the service account with write permission for storage"
value = google_service_account.log_writer.member
Expand Down
5 changes: 5 additions & 0 deletions deployment/modules/gcp/gcs/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ variable "location" {
description = "Location in which to create resources"
type = string
}

variable "env" {
description = "Unique identifier for the env, e.g. ci or prod"
type = string
}
Loading