diff --git a/modules/microservices/README.md b/modules/microservices/README.md new file mode 100644 index 0000000..827a9d9 --- /dev/null +++ b/modules/microservices/README.md @@ -0,0 +1,59 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >=1.7 | +| [google](#requirement\_google) | >= 5.22.0 | +| [google-beta](#requirement\_google-beta) | >= 5.22.0 | + +## Providers + +| Name | Version | +|------|---------| +| [google](#provider\_google) | >= 5.22.0 | +| [google-beta](#provider\_google-beta) | >= 5.22.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [google-beta_google_vpc_access_connector.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_vpc_access_connector) | resource | +| [google_cloud_run_v2_service.backend](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service) | resource | +| [google_cloud_run_v2_service.frontend](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service) | resource | +| [google_cloud_run_v2_service_iam_member.backend_invoker](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service_iam_member) | resource | +| [google_cloud_run_v2_service_iam_member.frontend_invoker](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service_iam_member) | resource | +| [google_compute_global_address.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address) | resource | +| [google_compute_network.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network) | resource | +| [google_project_iam_member.backend_executor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource | +| [google_project_iam_member.frontend_executor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource | +| [google_project_service.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_service) | resource | +| [google_service_account.backend_executor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | +| [google_service_account.backend_invoker](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | +| [google_service_account.frontend_executor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | +| [google_service_networking_connection.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_networking_connection) | resource | +| [google_sql_database.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database) | resource | +| [google_sql_database_instance.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance) | resource | +| [google_sql_user.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_user) | resource | +| [google_project.main](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [backend](#input\_backend) | The backend settings |
object({| n/a | yes | +| [db](#input\_db) | The Cloud SQL settings |
name = string
image = string
max_instance_count = optional(number, 1)
min_instance_count = optional(number, 0)
concurrency = optional(number, 80)
timeout_seconds = optional(number, 60)
cpu = optional(string, "1000m")
memory = optional(string, "1024Mi")
env = object({
HOSTNAME = string
DB_USER = string
DB_PWD = string
DB_NAME = string
DB_TCPHOST = string
DB_PORT = number
})
executor = object({
id = string
roles = optional(
set(string), [
"roles/cloudsql.client",
"roles/cloudtrace.agent",
]
)
})
invoker = object({
ids = set(string)
emails = optional(
set(string), []
)
})
})
object({| n/a | yes | +| [frontend](#input\_frontend) | The frontend settings |
instance_name = string
database_name = string
version = optional(string, "db-f1-micro")
charset = optional(string, "utf8mb4")
collation = optional(string, "utf8mb4_unicode_ci")
})
object({| n/a | yes | +| [location](#input\_location) | The location of the resource. | `string` | n/a | yes | +| [project\_id](#input\_project\_id) | The ID of Google Cloud Platform. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [enabled\_apis](#output\_enabled\_apis) | Already enabled APIs list. | + \ No newline at end of file diff --git a/modules/microservices/architecture.drawio.svg b/modules/microservices/architecture.drawio.svg new file mode 100644 index 0000000..f2d9b4f --- /dev/null +++ b/modules/microservices/architecture.drawio.svg @@ -0,0 +1,120 @@ + diff --git a/modules/microservices/backend.tf b/modules/microservices/backend.tf new file mode 100644 index 0000000..1fc406a --- /dev/null +++ b/modules/microservices/backend.tf @@ -0,0 +1,103 @@ +resource "google_cloud_run_v2_service" "backend" { + name = var.backend.name + location = var.location + + template { + scaling { + max_instance_count = var.backend.max_instance_count + min_instance_count = var.backend.min_instance_count + } + + vpc_access { + connector = google_vpc_access_connector.main.id + egress = "PRIVATE_RANGES_ONLY" + } + + service_account = google_service_account.backend_executor.email + + containers { + image = var.backend.image + + startup_probe { + initial_delay_seconds = 10 + timeout_seconds = 240 + period_seconds = 240 + failure_threshold = 3 + + http_get { + path = "/api/v1/healthz" + port = 8080 + } + } + + liveness_probe { + initial_delay_seconds = 10 + timeout_seconds = 60 + period_seconds = 60 + failure_threshold = 3 + + http_get { + path = "/api/v1/healthz" + port = 8080 + } + } + + resources { + cpu_idle = true + limits = { + cpu = var.backend.cpu + memory = var.backend.memory + } + } + + dynamic "env" { + for_each = var.backend.env + content { + name = env.key + value = env.value + } + } + } + } + + depends_on = [ + google_project_service.main, + google_sql_user.main, + ] +} + +resource "google_service_account" "backend_executor" { + project = var.project_id + account_id = var.backend.executor.id + display_name = "The Service Account for Backend executor" +} + +resource "google_project_iam_member" "backend_executor" { + for_each = var.backend.executor.roles + role = each.value + + project = var.project_id + member = "serviceAccount:${google_service_account.backend_executor.email}" +} + +resource "google_service_account" "backend_invoker" { + for_each = var.backend.invoker.ids + account_id = each.value + + project = var.project_id + display_name = "The Service Account for Backend invoker" +} + +resource "google_cloud_run_v2_service_iam_member" "backend_invoker" { + for_each = setunion( + [google_service_account.frontend_executor.email], + [for invoker in google_service_account.backend_invoker : invoker.email], + var.backend.invoker.emails, + ) + member = "serviceAccount:${each.value}" + + project = google_cloud_run_v2_service.backend.project + location = google_cloud_run_v2_service.backend.location + name = google_cloud_run_v2_service.backend.name + role = "roles/run.invoker" +} diff --git a/modules/microservices/db.tf b/modules/microservices/db.tf new file mode 100644 index 0000000..ab3c079 --- /dev/null +++ b/modules/microservices/db.tf @@ -0,0 +1,29 @@ +resource "google_sql_database_instance" "main" { + name = var.db.instance_name + database_version = "MYSQL_8_0" + + settings { + tier = "db-f1-micro" + + ip_configuration { + ipv4_enabled = false + private_network = google_compute_network.main.self_link + ssl_mode = "ALLOW_UNENCRYPTED_AND_ENCRYPTED" + } + } + + deletion_protection = false +} + +resource "google_sql_database" "main" { + name = var.db.name + instance = google_sql_database_instance.main.name + charset = var.db.charset + collation = var.db.collation +} + +resource "google_sql_user" "main" { + name = var.backend.env.DB_USER + instance = google_sql_database_instance.main.name + password = var.backend.env.DB_PWD +} diff --git a/modules/microservices/frontend.tf b/modules/microservices/frontend.tf new file mode 100644 index 0000000..557a955 --- /dev/null +++ b/modules/microservices/frontend.tf @@ -0,0 +1,88 @@ +locals { + frontend = { + BASE_URL = google_cloud_run_v2_service.backend.uri + } +} + +resource "google_cloud_run_v2_service" "frontend" { + name = var.frontend.name + location = var.location + + template { + scaling { + max_instance_count = var.frontend.max_instance_count + min_instance_count = var.frontend.min_instance_count + } + + service_account = google_service_account.frontend_executor.email + + containers { + image = var.frontend.image + + startup_probe { + initial_delay_seconds = 10 + timeout_seconds = 240 + period_seconds = 240 + failure_threshold = 3 + + http_get { + path = "/api/v1/healthz" + port = 8080 + } + } + + liveness_probe { + initial_delay_seconds = 10 + timeout_seconds = 120 + period_seconds = 120 + failure_threshold = 3 + + http_get { + path = "/api/v1/healthz" + port = 8080 + } + } + + resources { + cpu_idle = true + limits = { + cpu = var.frontend.cpu + memory = var.frontend.memory + } + } + + dynamic "env" { + for_each = local.frontend + content { + name = env.key + value = env.value + } + } + } + } + + depends_on = [ + google_project_service.main, + ] +} + +resource "google_service_account" "frontend_executor" { + project = var.project_id + account_id = var.frontend.executor.id + display_name = "The Service Account for Frontend executor" +} + +resource "google_project_iam_member" "frontend_executor" { + for_each = var.project_id.executor.roles + project = var.project_id + role = each.value + member = "serviceAccount:${google_service_account.frontend_executor.email}" +} + +resource "google_cloud_run_v2_service_iam_member" "frontend_invoker" { + project = google_cloud_run_v2_service.frontend.project + location = google_cloud_run_v2_service.frontend.location + name = google_cloud_run_v2_service.frontend.name + role = "roles/run.invoker" + member = "allUsers" +} diff --git a/modules/microservices/main.tf b/modules/microservices/main.tf new file mode 100644 index 0000000..ff776fa --- /dev/null +++ b/modules/microservices/main.tf @@ -0,0 +1,20 @@ +locals { + apis = toset([ + "cloudbuild.googleapis.com", + "compute.googleapis.com", + "iam.googleapis.com", + "run.googleapis.com", + "servicenetworking.googleapis.com", + "vpcaccess.googleapis.com", + ]) +} + +data "google_project" "main" {} + +resource "google_project_service" "main" { + for_each = local.apis + + project = data.google_project.main.project_id + service = each.value + disable_on_destroy = false +} diff --git a/modules/microservices/outputs.tf b/modules/microservices/outputs.tf new file mode 100644 index 0000000..e611503 --- /dev/null +++ b/modules/microservices/outputs.tf @@ -0,0 +1,4 @@ +output "enabled_apis" { + description = "Already enabled APIs list." + value = [for api in google_project_service.main : api.service] +} diff --git a/modules/microservices/provider.tf b/modules/microservices/provider.tf new file mode 100644 index 0000000..3fffb54 --- /dev/null +++ b/modules/microservices/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">=1.7" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.22.0" + } + google-beta = { + source = "hashicorp/google" + version = ">= 5.22.0" + } + } +} diff --git a/modules/microservices/variables.tf b/modules/microservices/variables.tf new file mode 100644 index 0000000..32d1858 --- /dev/null +++ b/modules/microservices/variables.tf @@ -0,0 +1,85 @@ +variable "project_id" { + description = "The ID of Google Cloud Platform." + type = string +} + +variable "location" { + description = "The location of the resource." + type = string +} + +variable "backend" { + description = "The backend settings" + type = object({ + name = string + image = string + max_instance_count = optional(number, 1) + min_instance_count = optional(number, 0) + concurrency = optional(number, 80) + timeout_seconds = optional(number, 60) + cpu = optional(string, "1000m") + memory = optional(string, "1024Mi") + env = object({ + HOSTNAME = string + DB_USER = string + DB_PWD = string + DB_NAME = string + DB_TCPHOST = string + DB_PORT = number + }) + executor = object({ + id = string + roles = optional( + set(string), [ + "roles/cloudsql.client", + "roles/cloudtrace.agent", + ] + ) + }) + invoker = object({ + ids = set(string) + emails = optional( + set(string), [] + ) + }) + }) +} + +variable "frontend" { + description = "The frontend settings" + type = object({ + name = string + image = string + max_instance_count = optional(number, 1) + min_instance_count = optional(number, 0) + concurrency = optional(number, 80) + timeout_seconds = optional(number, 60) + cpu = optional(string, "1000m") + memory = optional(string, "1024Mi") + executor = object({ + id = string + roles = optional( + set(string), [ + "roles/cloudtrace.agent", + ] + ) + }) + invoker = object({ + ids = set(string) + emails = optional( + set(string), [] + ) + }) + }) +} + +variable "db" { + description = "The Cloud SQL settings" + type = object({ + instance_name = string + database_name = string + version = optional(string, "db-f1-micro") + charset = optional(string, "utf8mb4") + collation = optional(string, "utf8mb4_unicode_ci") + }) +} diff --git a/modules/microservices/vpc.tf b/modules/microservices/vpc.tf new file mode 100644 index 0000000..f2a96a3 --- /dev/null +++ b/modules/microservices/vpc.tf @@ -0,0 +1,30 @@ +resource "google_compute_network" "main" { + name = var.backend.name + auto_create_subnetworks = false +} + +resource "google_compute_global_address" "main" { + name = var.backend.name + purpose = "VPC_PEERING" + address_type = "INTERNAL" + address = "10.20.0.0" + prefix_length = 16 + network = google_compute_network.main.self_link +} + +resource "google_service_networking_connection" "main" { + network = google_compute_network.main.self_link + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [ + google_compute_global_address.main.name + ] +} + +resource "google_vpc_access_connector" "main" { + provider = google-beta + name = var.backend.name + region = var.location + ip_cidr_range = "10.30.0.0/28" + network = google_compute_network.main.name + machine_type = "e2-micro" +}
name = string
image = string
max_instance_count = optional(number, 1)
min_instance_count = optional(number, 0)
concurrency = optional(number, 80)
timeout_seconds = optional(number, 60)
cpu = optional(string, "1000m")
memory = optional(string, "1024Mi")
executor = object({
id = string
roles = optional(
set(string), [
"roles/cloudtrace.agent",
]
)
})
invoker = object({
ids = set(string)
emails = optional(
set(string), []
)
})
})