From d053050ed5ffc73cf63fcce2179d9ec5ee1ff621 Mon Sep 17 00:00:00 2001 From: Edvin N Date: Fri, 22 Dec 2023 18:53:36 +0100 Subject: [PATCH] Add CGP Cloud Run deployment documentation * Add docs for gcp cloud run * terraform gcp cloud run * Edit GCP Clound Run documentation for clarity --------- Co-authored-by: Christopher Charbonneau Wells <10456740+cdubz@users.noreply.github.com> --- docs/setup/deployment.md | 43 +++-- terraform/gcp-cloud-run/.terraform.lock.hcl | 40 +++++ terraform/gcp-cloud-run/main.tf | 187 ++++++++++++++++++++ terraform/gcp-cloud-run/output.tf | 3 + terraform/gcp-cloud-run/variables.tf | 20 +++ 5 files changed, 282 insertions(+), 11 deletions(-) create mode 100644 terraform/gcp-cloud-run/.terraform.lock.hcl create mode 100644 terraform/gcp-cloud-run/main.tf create mode 100644 terraform/gcp-cloud-run/output.tf create mode 100644 terraform/gcp-cloud-run/variables.tf diff --git a/docs/setup/deployment.md b/docs/setup/deployment.md index 0235fd45..c4ce10f5 100644 --- a/docs/setup/deployment.md +++ b/docs/setup/deployment.md @@ -89,9 +89,30 @@ for your instance of babybuddy. After that, you just have to push babybuddy code repository to the Git deployment URL of your Clever Cloud Python application. +## GCP Cloud Run + +Baby Buddy can be hosted serverless in GCP Cloud Run using configuration provided at +`terraform/gcp-cloud-run`. The configuration scales down to zero for cost effectiveness. +With this approach initial requests to the service after a long period will be slow but +subsequent requests will be much faster. A [billing account](https://cloud.google.com/billing/docs/how-to/create-billing-account) +mut be configured in GCP to use the configuration. + +The terraform code isn't production ready and is meant to be a good way of getting started. +No state strage is configured. See [storage options](https://cloud.google.com/run/docs/storage-options) +for information about how to configure persistant storage. + +Run `terraform init` from the configurtion directory to get started: + +```shell +git clone https://github.com/babybuddy/babybuddy.git +cd babybuddy/terraform/gcp-cloud-run +terraform init +terraform apply -var project_id= -var project_name= -var billing_account= +``` + ## Manual -There are many ways to deploy Baby Buddy manually to any server/VPS. The basic +There are many ways to deploy Baby Buddy manually to any server/VPS. The basic requirements are Python, a web server, an application server, and a database. ### Requirements @@ -139,7 +160,7 @@ and any number of children). ```shell cd /var/www/babybuddy/public ``` - + 6. Initiate and enter a Python environment with Pipenv locally. ```shell @@ -183,7 +204,7 @@ and any number of children). plugins = python3 project = babybuddy base_dir = /var/www/babybuddy - + chdir = %(base_dir)/public virtualenv = %(chdir)/.venv module = %(project).wsgi:application @@ -191,10 +212,10 @@ and any number of children). master = True vacuum = True ``` - + See the [uWSGI documentation](http://uwsgi-docs.readthedocs.io/en/latest/) for more advanced configuration details. - + See [Subdirectory configuration](subdirectory.md) for additional configuration required if Baby Buddy will be hosted in a subdirectory of another server. @@ -212,30 +233,30 @@ and any number of children). ``` Example config: - + ```nginx upstream babybuddy { server unix:///var/run/uwsgi/app/babybuddy/socket; } - + server { listen 80; server_name babybuddy.example.com; - + location / { uwsgi_pass babybuddy; include uwsgi_params; } - + location /media { alias /var/www/babybuddy/data/media; } } ``` - + See the [nginx documentation](https://nginx.org/en/docs/) for more advanced configuration details. - + See [Subdirectory configuration](subdirectory.md) for additional configuration required if Baby Buddy will be hosted in a subdirectory of another server. diff --git a/terraform/gcp-cloud-run/.terraform.lock.hcl b/terraform/gcp-cloud-run/.terraform.lock.hcl new file mode 100644 index 00000000..1d42e778 --- /dev/null +++ b/terraform/gcp-cloud-run/.terraform.lock.hcl @@ -0,0 +1,40 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "5.6.0" + hashes = [ + "h1:eZMUVtLYrZMNb5QeqWpWfeQv5wNWQMRhzVtr/Fo5Wis=", + "zh:102b6a2672fade82114eb14ed46923fb1b74be2aaca3a50b4f35f7057a9a94b9", + "zh:1a56b63175068c67efbe7d130986ba2839a938f5ffc96a14fd450153174dbfa3", + "zh:1ba1c5e0c86e8aaa8037406390846e78c89b63faf9e527c7874641f35d436e1b", + "zh:3f7161b9288b47cbe89d2f9675f78d83b58ad5880c793b01f50a71ee2583844b", + "zh:66912d6e4180dac37185d17424b345a9d4e3c3c791d45e0737b35e32c9536b35", + "zh:6f06f56e9fac2e55b50e74ffac42d9522bb379394e51dca1eddd4c3b7a68545c", + "zh:8741861ebfa13bb1ed74ea7f4865388a0725ca3a781b6d873ce45e6a4630fe41", + "zh:ae89a9c538665fbc30bb83aa3b13acb18d8380e551ccf242e1c0ab4d626089ab", + "zh:c510f8321c7599aa601b1870fdc0c76cbad3054ed5cc70fe8e37a13a8046a71f", + "zh:cf143a53d5a25c6216d09a9c0b115bb473ffcebd5c4c62b2b2594b1ebc13e662", + "zh:de05b957e5dfdbaf92db47cd9b3ef46a0f8d94599eea6d472928f33058856add", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/terraform/gcp-cloud-run/main.tf b/terraform/gcp-cloud-run/main.tf new file mode 100644 index 00000000..5444d781 --- /dev/null +++ b/terraform/gcp-cloud-run/main.tf @@ -0,0 +1,187 @@ +provider "google" { + region = var.region +} + +resource "google_project" "baby_buddy" { + name = var.project_name + project_id = var.project_id + billing_account = var.billing_account + auto_create_network = false +} + +locals { + services = toset(["run.googleapis.com", + "sqladmin.googleapis.com", + "sql-component.googleapis.com", + "secretmanager.googleapis.com" + ]) +} + +resource "google_project_service" "project_services" { + for_each = local.services + project = google_project.baby_buddy.project_id + service = each.value + disable_on_destroy = true + disable_dependent_services = true +} + +resource "random_password" "root_password" { + min_lower = 1 + min_numeric = 1 + min_upper = 1 + length = 19 + special = true + min_special = 1 + lifecycle { + ignore_changes = [ + min_lower, min_upper, min_numeric, special, min_special, length + ] + } +} + +resource "random_password" "django_secret_key" { + min_lower = 1 + min_numeric = 1 + min_upper = 1 + length = 20 + special = true + min_special = 1 + lifecycle { + ignore_changes = [ + min_lower, min_upper, min_numeric, special, min_special, length + ] + } +} + +resource "google_sql_database_instance" "baby_buddy" { + name = "babybuddy" + database_version = "POSTGRES_15" + root_password = random_password.root_password.result + settings { + tier = "db-f1-micro" + disk_autoresize = false + deletion_protection_enabled = false + insights_config { + query_insights_enabled = false + } + maintenance_window { + day = 1 + hour = 0 + } + } + + deletion_protection = "true" +} + +resource "google_secret_manager_secret" "postgres_password" { + secret_id = "postgres-password" + project = google_project.baby_buddy.project_id + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "postgres_password" { + secret = google_secret_manager_secret.postgres_password.name + secret_data = google_sql_database_instance.baby_buddy.root_password +} + +resource "google_secret_manager_secret_iam_member" "postgres_password" { + secret_id = google_secret_manager_secret.postgres_password.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_project.baby_buddy.number}-compute@developer.gserviceaccount.com" + depends_on = [google_secret_manager_secret.postgres_password] +} + +resource "google_secret_manager_secret" "django_secret_key" { + secret_id = "django-secret-key" + project = google_project.baby_buddy.project_id + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "django_secret_key" { + secret = google_secret_manager_secret.django_secret_key.name + secret_data = random_password.django_secret_key.result +} + +resource "google_secret_manager_secret_iam_member" "django_secret_key" { + secret_id = google_secret_manager_secret.django_secret_key.name + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_project.baby_buddy.number}-compute@developer.gserviceaccount.com" + depends_on = [google_secret_manager_secret.django_secret_key] +} + +resource "google_cloud_run_v2_service" "baby_buddy" { + name = "babybuddy" + location = var.region + ingress = "INGRESS_TRAFFIC_ALL" + project = google_project.baby_buddy.project_id + + template { + scaling { + max_instance_count = 2 + } + + volumes { + name = "cloudsql" + cloud_sql_instance { + instances = [google_sql_database_instance.baby_buddy.connection_name] + } + } + + containers { + name = "babybuddy-1" + image = "docker.io/linuxserver/babybuddy:latest" + + env { + name = "DB_HOST" + value = "/cloudsql/${google_project.baby_buddy.project_id}:${var.region}:${google_sql_database_instance.baby_buddy.name}" + } + env { + name = "DB_USER" + value = "postgres" + } + env { + name = "DB_NAME" + value = "postgres" + } + env { + name = "DB_ENGINE" + value = "django.db.backends.postgresql" + } + env { + name = "DB_PASSWORD" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.postgres_password.secret_id + version = google_secret_manager_secret_version.postgres_password.version + } + } + } + env { + name = "SECRET_KEY" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.django_secret_key.secret_id + version = google_secret_manager_secret_version.django_secret_key.version + } + } + } + volume_mounts { + name = "cloudsql" + mount_path = "/cloudsql" + } + } + } + + annotations = { + "foo" = "bar" + } + traffic { + type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" + percent = 100 + } + depends_on = [google_secret_manager_secret_version.postgres_password, google_secret_manager_secret_version.django_secret_key] +} diff --git a/terraform/gcp-cloud-run/output.tf b/terraform/gcp-cloud-run/output.tf new file mode 100644 index 00000000..d400bcce --- /dev/null +++ b/terraform/gcp-cloud-run/output.tf @@ -0,0 +1,3 @@ +output "url" { + value = google_cloud_run_v2_service.baby_buddy.uri +} diff --git a/terraform/gcp-cloud-run/variables.tf b/terraform/gcp-cloud-run/variables.tf new file mode 100644 index 00000000..ead057e1 --- /dev/null +++ b/terraform/gcp-cloud-run/variables.tf @@ -0,0 +1,20 @@ +variable "project_name" { + description = "project name" + type = string +} + +variable "project_id" { + description = "project id, remember the project id has to be unique" + type = string +} + +variable "billing_account" { + description = "value of the billing account id" + type = string + +} +variable "region" { + description = "which region to deploy to" + type = string + default = "europe-north1" +}