-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This makes putting regionalized services behind GCLB with DNS and TLS much easier. Signed-off-by: Matt Moore <[email protected]>
- Loading branch information
Showing
5 changed files
with
253 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# `serverless-gclb` | ||
|
||
This module provisions a Google Cloud Load Balancer (GCLB) that sits in front of | ||
some number of regionalized Cloud Run services. | ||
|
||
```hcl | ||
// Create a network with several regional subnets | ||
module "networking" { | ||
source = "chainguard-dev/common/infra//modules/networking" | ||
name = "my-networking" | ||
project_id = var.project_id | ||
regions = [...] | ||
} | ||
resource "google_dns_managed_zone" "top-level-zone" { | ||
project = var.project_id | ||
name = "example-com" | ||
dns_name = "example.com." | ||
} | ||
module "serverless-gclb" { | ||
source = "chainguard-dev/common/infra//modules/serverless-gclb" | ||
name = "my-gclb" | ||
project_id = var.project_id | ||
dns_zone = google_dns_managed_zone.top-level-zone.name | ||
// Regions are all of the places that we have backends deployed. | ||
// Regions must be removed from serving before they are torn down. | ||
regions = keys(module.networking.regional-networks) | ||
serving_regions = keys(module.networking.regional-networks) | ||
public-services = { | ||
"foo.example.com" = { | ||
name = "my-foo-service" // e.g. from regional-go-service | ||
} | ||
} | ||
} | ||
``` | ||
|
||
<!-- BEGIN_TF_DOCS --> | ||
## Requirements | ||
|
||
No requirements. | ||
|
||
## Providers | ||
|
||
| Name | Version | | ||
|------|---------| | ||
| <a name="provider_google"></a> [google](#provider\_google) | n/a | | ||
| <a name="provider_random"></a> [random](#provider\_random) | n/a | | ||
|
||
## Modules | ||
|
||
| Name | Source | Version | | ||
|------|--------|---------| | ||
| <a name="module_recorder-dashboard"></a> [recorder-dashboard](#module\_recorder-dashboard) | ../dashboard/cloudevent-receiver | n/a | | ||
| <a name="module_this"></a> [this](#module\_this) | ../regional-go-service | n/a | | ||
| <a name="module_triggers"></a> [triggers](#module\_triggers) | ../cloudevent-trigger | n/a | | ||
|
||
## Resources | ||
|
||
| Name | Type | | ||
|------|------| | ||
| [google_bigquery_data_transfer_config.import-job](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_data_transfer_config) | resource | | ||
| [google_bigquery_dataset.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_dataset) | resource | | ||
| [google_bigquery_table.types](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table) | resource | | ||
| [google_bigquery_table_iam_binding.import-writes-to-tables](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table_iam_binding) | resource | | ||
| [google_service_account.import-identity](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | | ||
| [google_service_account.recorder](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | | ||
| [google_service_account_iam_binding.bq-dts-assumes-import-identity](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_binding) | resource | | ||
| [google_service_account_iam_binding.provisioner-acts-as-import-identity](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_binding) | resource | | ||
| [google_storage_bucket.recorder](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket) | resource | | ||
| [google_storage_bucket_iam_binding.import-reads-from-gcs-buckets](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam_binding) | resource | | ||
| [google_storage_bucket_iam_binding.recorder-writes-to-gcs-buckets](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam_binding) | resource | | ||
| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | | ||
| [random_id.trigger-suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | | ||
| [google_project.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source | | ||
|
||
## Inputs | ||
|
||
| Name | Description | Type | Default | Required | | ||
|------|-------------|------|---------|:--------:| | ||
| <a name="input_broker"></a> [broker](#input\_broker) | A map from each of the input region names to the name of the Broker topic in that region. | `map(string)` | n/a | yes | | ||
| <a name="input_deletion_protection"></a> [deletion\_protection](#input\_deletion\_protection) | Whether to enable deletion protection on data resources. | `bool` | `true` | no | | ||
| <a name="input_location"></a> [location](#input\_location) | The location to create the BigQuery dataset in, and in which to run the data transfer jobs from GCS. | `string` | `"US"` | no | | ||
| <a name="input_name"></a> [name](#input\_name) | n/a | `string` | n/a | yes | | ||
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes | | ||
| <a name="input_provisioner"></a> [provisioner](#input\_provisioner) | The identity as which this module will be applied (so it may be granted permission to 'act as' the DTS service account). This should be in the form expected by an IAM subject (e.g. user:sally@example.com) | `string` | n/a | yes | | ||
| <a name="input_regions"></a> [regions](#input\_regions) | A map from region names to a network and subnetwork. A recorder service and cloud storage bucket (into which the service writes events) will be created in each region. | <pre>map(object({<br> network = string<br> subnet = string<br> }))</pre> | n/a | yes | | ||
| <a name="input_retention-period"></a> [retention-period](#input\_retention-period) | The number of days to retain data in BigQuery. | `number` | n/a | yes | | ||
| <a name="input_types"></a> [types](#input\_types) | A map from cloudevent types to the BigQuery schema associated with them. | `map(string)` | n/a | yes | | ||
|
||
## Outputs | ||
|
||
No outputs. | ||
<!-- END_TF_DOCS --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Create the IP address for our LB to serve on. | ||
resource "google_compute_global_address" "this" { | ||
project = var.project_id | ||
name = var.name | ||
} | ||
|
||
// Create A records for each of our public service hostnames. | ||
resource "google_dns_record_set" "public-service" { | ||
for_each = var.public-services | ||
|
||
project = var.project_id | ||
name = "${each.key}." | ||
managed_zone = var.dns_zone | ||
type = "A" | ||
ttl = 60 | ||
|
||
rrdatas = [google_compute_global_address.this.address] | ||
} | ||
|
||
// Provision a managed SSL certificate for each of our public services. | ||
resource "google_compute_managed_ssl_certificate" "public-service" { | ||
for_each = var.public-services | ||
|
||
name = each.value.name | ||
|
||
managed { | ||
domains = [google_dns_record_set.public-service[each.key].name] | ||
} | ||
} | ||
|
||
// Create the cross-product of public services and regions so we can for_each over it. | ||
locals { | ||
regional-backends = merge([ | ||
for svcinfo in values(var.public-services) : merge([ | ||
for region in var.regions : { | ||
"${svcinfo.name}-${region}" : { | ||
name = svcinfo.name | ||
region = region | ||
} | ||
} | ||
]...) | ||
]...) | ||
} | ||
|
||
// Create a network endpoint group for each service in each region. | ||
resource "google_compute_region_network_endpoint_group" "regional-backends" { | ||
for_each = local.regional-backends | ||
|
||
name = each.value.name | ||
network_endpoint_type = "SERVERLESS" | ||
region = each.value.region | ||
cloud_run { | ||
service = each.value.name | ||
} | ||
} | ||
|
||
// Create a backend service for each public service with a backend in each region. | ||
resource "google_compute_backend_service" "public-services" { | ||
for_each = var.public-services | ||
|
||
project = var.project_id | ||
name = each.value.name | ||
|
||
// Create a backend for each region hosting this cloud run service. | ||
dynamic "backend" { | ||
for_each = toset(var.serving_regions) | ||
content { | ||
group = google_compute_region_network_endpoint_group.regional-backends["${each.value.name}-${backend.key}"]["id"] | ||
} | ||
} | ||
} | ||
|
||
// Create a URL map that routes each hostname to the appropriate backend service. | ||
resource "google_compute_url_map" "public-service" { | ||
project = var.project_id | ||
name = var.name | ||
|
||
default_url_redirect { | ||
host_redirect = "chainguard.dev" | ||
strip_query = true | ||
} | ||
|
||
// For each of the public services create a host rule. | ||
dynamic "host_rule" { | ||
for_each = var.public-services | ||
content { | ||
hosts = [host_rule.key] | ||
path_matcher = host_rule.value.name | ||
} | ||
} | ||
|
||
// For each of the public services create an empty path matcher | ||
// that routes to its backend service. | ||
dynamic "path_matcher" { | ||
for_each = var.public-services | ||
content { | ||
name = path_matcher.value.name | ||
default_service = google_compute_backend_service.public-services[path_matcher.key].id | ||
} | ||
} | ||
} | ||
|
||
// Create an HTTPS proxy for our URL map. | ||
resource "google_compute_target_https_proxy" "public-service" { | ||
project = var.project_id | ||
name = var.name | ||
url_map = google_compute_url_map.public-service.id | ||
|
||
ssl_certificates = [for domain, cert in google_compute_managed_ssl_certificate.public-service : cert.id] | ||
} | ||
|
||
// Attach the HTTPS proxy to the global IP address via a forwarding rule. | ||
resource "google_compute_global_forwarding_rule" "this" { | ||
project = var.project_id | ||
name = var.name | ||
ip_protocol = "TCP" | ||
load_balancing_scheme = "EXTERNAL" | ||
port_range = 443 | ||
ip_address = google_compute_global_address.this.id | ||
target = google_compute_target_https_proxy.public-service.id | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
variable "name" { | ||
type = string | ||
} | ||
|
||
variable "project_id" { | ||
type = string | ||
} | ||
|
||
variable "regions" { | ||
description = "The set of regions containing backends for the load balancer (regions must be added here before they can be added as serving regions)." | ||
default = ["us-central1"] | ||
} | ||
|
||
variable "serving_regions" { | ||
description = "The set of regions with backends suitable for serving traffic from the load balancer (regions must be removed from here before they can be removed from regions)." | ||
default = ["us-central1"] | ||
} | ||
|
||
variable "dns_zone" { | ||
type = string | ||
description = "The managed DNS zone in which to create record sets." | ||
} | ||
|
||
variable "public-services" { | ||
description = "A map from hostnames (managed by dns_zone), to the name of the regionalized cloud run service to which the hostname should be routed. A managed SSL certificate will be created for each hostname, and a DNS record set will be created for each hostname pointing to the load balancer's global IP address." | ||
type = map(object({ | ||
name = string | ||
})) | ||
} | ||