Resource Packs

Cloud

Example

Flavor

Feature

Postgres

Example: postgres resource based on GCP CloudSQL

Configuration

This example configures a postgres Resource Definition using GCP CloudSQL. A workload using the postgres resource to create database instance looks like:

containers:
  app:
    ...
    variables:
      DB_HOST: ${resources.db.host}
      DB_PORT: ${resources.db.port}
      DB_USERNAME: ${resources.db.username}
      DB_PASSWORD: ${resources.db.password}
      DB_NAME: ${resources.db.name}
resources:
  ...
  db:
    type: postgres

This example uses CloudSQL IAM Authentication, checkout ../mysql/README.md for an example using username and password authentication.

CloudSQL IAM Authentication is enabled by automatically injecting the Cloud SQL Auth Proxy as sidecar into each workload that is using a postgres resource.

Infrastructure setup

graph TD
subgraph GCP IAM
    gcp_db_serviceaccount[GCP Service Account - db user]
    gcp_k8s_serviceaccount[GCP Service Account - k8s user]
end

subgraph VPC
    subgraph server["PostgreSQL CloudSQL Database Instance"]
        database["CloudSQL Database"]
        user["CloudSQL User"]
    end

    subgraph GKE Cluster
        subgraph pod[workload pod]
            workload-container --> cloud-sql-proxy-container
            cloud-sql-proxy-container -- CloudSQL User with iam authentication using GCP Service Account [db user] --> database
        end
        service[K8s Service Account] --> gcp_k8s_serviceaccount
    end
    gcp_k8s_serviceaccount -- workload identity user --> gcp_db_serviceaccount
end

gcp_db_serviceaccount --> user
gcp_db_serviceaccount -- cloudsql.client & instanceUser --> server

Orchestrator setup

graph LR;
    workload_1 --> db_1["db_1, resource_type: postgres"]
    workload_2 --> db_2["db_2, resource_type: postgres"]
    workload_2 --> shared.db_1["shared.db_1, resource_type: postgres"]
    workload_3 --> shared.db_1["shared.db_1, resource_type: postgres"]
    db_1 --> server["main-postgres, resource_type: postgres-instance"]
    db_2 --> server
    shared.db_1 --> server

Terraform docs

Requirements

Name Version
terraform >= 1.3.0
google ~> 5.17
humanitec ~> 1.0

Providers

Name Version
google ~> 5.17
humanitec ~> 1.0

Modules

Name Source Version
gcp_service_account_workload ../../humanitec-resource-defs/gcp-service-account/workload n/a
iam_role_binding_service_account_workload_identity ../../humanitec-resource-defs/gcp-iam-policy-binding/basic n/a
k8s_service_account ../../humanitec-resource-defs/k8s/service-account n/a
postgres ../../humanitec-resource-defs/postgres/workload-identity n/a
postgres_instance ../../humanitec-resource-defs/postgres-instance/basic n/a
workload ../../humanitec-resource-defs/workload/service-account n/a

Resources

Name Type
google_compute_global_address.private_ip_address resource
google_project_iam_member.humanitec_provisioner resource
google_project_service.servicenetworking resource
google_service_account.humanitec_provisioner resource
google_service_account_key.humanitec_provisioner resource
google_service_networking_connection.private_vpc_connection resource
humanitec_application.example resource
humanitec_resource_account.humanitec_provisioner resource
humanitec_resource_definition_criteria.gcp_service_account_workload resource
humanitec_resource_definition_criteria.iam_role_binding_service_account_workload_identity resource
humanitec_resource_definition_criteria.k8s_service_account resource
humanitec_resource_definition_criteria.postgres resource
humanitec_resource_definition_criteria.postgres_instance resource
humanitec_resource_definition_criteria.workload resource
google_compute_network.network data source

Inputs

Name Description Type Default Required
private_network The VPC network from which the Cloud SQL instance is accessible for private IP. string n/a yes
project n/a string n/a yes
region GCP region string n/a yes
name Name of the example application string "hum-rp-postgres-example" no
prefix Prefix of the created resources string "hum-rp-postgres-ex-" no
resource_packs_gcp_rev n/a string "refs/heads/main" no
resource_packs_gcp_url n/a string "https://github.com/humanitec-architecture/resource-packs-gcp.git" no

main.tf (view on GitHub) :

# GCP service account used by Humanitec to provision resources

resource "google_service_account" "humanitec_provisioner" {
  account_id  = var.name
  description = "Account used by Humanitec to provision resources"
}

resource "google_project_iam_member" "humanitec_provisioner" {
  project = var.project
  role    = "roles/owner"
  member  = "serviceAccount:${google_service_account.humanitec_provisioner.email}"
}

resource "google_service_account_key" "humanitec_provisioner" {
  service_account_id = google_service_account.humanitec_provisioner.name
}

resource "humanitec_resource_account" "humanitec_provisioner" {
  id   = var.name
  name = var.name
  type = "gcp"

  credentials = base64decode(google_service_account_key.humanitec_provisioner.private_key)

  depends_on = [
    # Otherwise the account looses permissions before the resources are deleted
    google_project_iam_member.humanitec_provisioner
  ]
}

# Example application and resource definition criteria

resource "humanitec_application" "example" {
  id   = var.name
  name = var.name
}

# Postgres instance

locals {
  # Define the shared postgres-instance resource id and class
  postgres_instance_res_id = "main-postgres"
  postgres_instance_class  = "default"

  # Individual postgres resource class
  postgres_basic_class = "default"

  # Service account workload identity policy class
  postgres_service_account_workload_identity_policy_class = "postgres-service-account-workload-identity"
}


data "google_compute_network" "network" {
  name = var.private_network
}

resource "google_project_service" "servicenetworking" {
  service            = "servicenetworking.googleapis.com"
  disable_on_destroy = false
}

resource "google_compute_global_address" "private_ip_address" {
  name          = "${var.prefix}private-ip-address"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = data.google_compute_network.network.id
}

# In case of getting "Cannot modify allocated ranges in CreateConnection. Please use UpdateConnection.",
# temporarily set `reserved_peering_ranges = []` run apply, reset afterwards and apply again.
#
# This is required due to https://github.com/hashicorp/terraform-provider-google/issues/3294
resource "google_service_networking_connection" "private_vpc_connection" {
  network                 = data.google_compute_network.network.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]

  depends_on = [google_project_service.servicenetworking]
}

module "postgres_instance" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/postgres-instance/basic"

  prefix                 = var.prefix
  resource_packs_gcp_rev = var.resource_packs_gcp_rev
  resource_packs_gcp_url = var.resource_packs_gcp_url
  append_logs_to_error   = true
  driver_account         = humanitec_resource_account.humanitec_provisioner.id
  project                = var.project
  region                 = var.region

  database_version = "POSTGRES_15"
  tier             = "db-f1-micro"
  private_network  = data.google_compute_network.network.id

  depends_on = [google_service_networking_connection.private_vpc_connection]
}

resource "humanitec_resource_definition_criteria" "postgres_instance" {
  resource_definition_id = module.postgres_instance.id
  app_id                 = humanitec_application.example.id
  class                  = local.postgres_instance_class
  res_id                 = local.postgres_instance_res_id
  force_delete           = true
}

module "postgres" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/postgres/workload-identity"

  prefix                 = var.prefix
  resource_packs_gcp_rev = var.resource_packs_gcp_rev
  resource_packs_gcp_url = var.resource_packs_gcp_url
  append_logs_to_error   = true
  driver_account         = humanitec_resource_account.humanitec_provisioner.id
  project                = var.project
  region                 = var.region

  instance_resource     = "postgres-instance.${local.postgres_instance_class}#${local.postgres_instance_res_id}"
  policy_resource_class = local.postgres_service_account_workload_identity_policy_class
}

resource "humanitec_resource_definition_criteria" "postgres" {
  resource_definition_id = module.postgres.id
  app_id                 = humanitec_application.example.id
  class                  = local.postgres_basic_class
  force_delete           = true
}

# IAM role binding to be able to assume to database service account with workload identity
module "iam_role_binding_service_account_workload_identity" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/gcp-iam-policy-binding/basic"

  prefix = var.prefix
  name   = "gcp-iam-policy-binding-sa-workload-identity"

  type        = "service_account"
  scope_key   = "service_account_id"
  scope_value = "$${resources['postgres.${local.postgres_basic_class}'].outputs.service_account_id}"
  role        = "roles/iam.workloadIdentityUser"
}

resource "humanitec_resource_definition_criteria" "iam_role_binding_service_account_workload_identity" {
  resource_definition_id = module.iam_role_binding_service_account_workload_identity.id
  app_id                 = humanitec_application.example.id
  class                  = local.postgres_service_account_workload_identity_policy_class
  force_delete           = true
}

# Required resources for workload identity

module "k8s_service_account" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/k8s/service-account"

  prefix = var.prefix
}

resource "humanitec_resource_definition_criteria" "k8s_service_account" {
  resource_definition_id = module.k8s_service_account.id
  app_id                 = humanitec_application.example.id
  force_delete           = true
}

module "gcp_service_account_workload" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/gcp-service-account/workload"

  resource_packs_gcp_url = var.resource_packs_gcp_url
  resource_packs_gcp_rev = var.resource_packs_gcp_rev
  append_logs_to_error   = true
  driver_account         = humanitec_resource_account.humanitec_provisioner.id

  project = var.project
  prefix  = var.prefix

  name = "hrp-pg-$${context.res.id}"
}

resource "humanitec_resource_definition_criteria" "gcp_service_account_workload" {
  resource_definition_id = module.gcp_service_account_workload.id
  app_id                 = humanitec_application.example.id
  force_delete           = true
}

module "workload" {
  source = "github.com/humanitec-architecture/resource-packs-gcp//humanitec-resource-defs/workload/service-account"

  prefix = var.prefix
}

resource "humanitec_resource_definition_criteria" "workload" {
  resource_definition_id = module.workload.id
  app_id                 = humanitec_application.example.id
  force_delete           = true
}


providers.tf (view on GitHub) :

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.17"
    }
    humanitec = {
      source  = "humanitec/humanitec"
      version = "~> 1.0"
    }
  }

  required_version = ">= 1.3.0"
}

provider "humanitec" {}

provider "google" {
  project = var.project
  region  = var.region

  default_labels = {
    "managed_by" = "terraform"
    "source"     = "github.com/humanitec-architecture/resource-pack-gcp"
  }
}


terraform.tfvars.example (view on GitHub) :


# Name of the example application
name = "hum-rp-postgres-example"

# Prefix of the created resources
prefix = "hum-rp-postgres-ex-"

# The VPC network from which the Cloud SQL instance is accessible for private IP.
private_network = ""

project = ""

# GCP region
region = ""

resource_packs_gcp_rev = "refs/heads/main"
resource_packs_gcp_url = "https://github.com/humanitec-architecture/resource-packs-gcp.git"

variables.tf (view on GitHub) :

variable "name" {
  description = "Name of the example application"
  type        = string
  default     = "hum-rp-postgres-example"
}

variable "resource_packs_gcp_rev" {
  type    = string
  default = "refs/heads/main"
}

variable "resource_packs_gcp_url" {
  type    = string
  default = "https://github.com/humanitec-architecture/resource-packs-gcp.git"
}

variable "prefix" {
  description = "Prefix of the created resources"
  type        = string
  default     = "hum-rp-postgres-ex-"
}

variable "project" {
  type = string
}

variable "region" {
  description = "GCP region"
  type        = string
}

variable "private_network" {
  type        = string
  description = "The VPC network from which the Cloud SQL instance is accessible for private IP."
}

Top