Merge branch 'master' of github.com:GoogleCloudPlatform/cloud-foundation-fabric

This commit is contained in:
Ludo 2024-05-14 17:13:06 +02:00
commit c494715e9a
No known key found for this signature in database
20 changed files with 4890 additions and 3 deletions

View File

@ -4,7 +4,7 @@ This section provides **[networking blueprints](./networking/)** that implement
Currently available blueprints:
- **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/)
- **apigee** - [Apigee X foundations](./apigee/apigee-x-foundations/). [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/)
- **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Network Quota Monitoring](./cloud-operations/network-quota-monitoring), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/compute-quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation)
- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Minimal Data Platform](./data-solutions/data-platform-minimal), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/bq-ml)
- **factories** - [Fabric resource factories](./factories)

View File

@ -4,9 +4,15 @@ The blueprints in this folder contain a variety of deployment scenarios for Apig
## Blueprints
### Apigee X foundations
<a href="./apigee-x-foundations/" title="Apigee X foundations"><img src="./apigee-x-foundations/diagram1.png" align="left" width="280px"></a> This [blueprint](./apigee-x-foundations/) creates all the resources necessary to set up Apigee X on Google Cloud.
<br clear="left">
### Apigee Hybrid on GKE
<a href="./hybrid-gke/" title="Apigee Hybrid on GKE"><img src="./hybrid-gke/diagram.png" align="left" width="280px"></a> This [blueprint](./hybrid-gke/) shows how to do a non-prod deployment of Apigee Hybrid on GKE(../factories/net-vpc-firewall-yaml/).
<a href="./hybrid-gke/" title="Apigee Hybrid on GKE"><img src="./hybrid-gke/diagram.png" align="left" width="280px"></a> This [blueprint](./hybrid-gke/) shows how to do a non-prod deployment of Apigee Hybrid on GKE.
<br clear="left">

View File

@ -0,0 +1,481 @@
# Apigee X Foundations
This blueprint creates all the resources necessary to set up Apigee X on Google Cloud.
Apigee can be exposed to clients using Regional Internal Application Load Balancer, Global External Application Load Balancer or both. When using the Regional Internal Application Load Balancer, used self-managed certificates (incuding self-signed certificates generated in this same module). When using the Global External Application Load Balancer Google-managed certificates or self-managed certificates (including self-signed certificates generated in this same module). When using Cross-region Internal Application Load Balancer a certificate manager needs to be used and it needs to be created in the same project as Apigee.
Find below a few examples of different Apigee architectures that can be created using this module.
## Examples
* [Examples](#examples)
* [Apigee X in service project with shared VPC peered and exposed with Global External Application LB and Regional Internal Application LB](#apigee-x-in-service-project-with-shared-vpc-peered-and-exposed-with-global-external-application-lb-and-regional-internal-application-lb)
* [Apigee X in service project with local VPC peered and exposed using Global LB and Internal Cross-region Application LB](#apigee-x-in-service-project-with-local-vpc-peered-and-exposed-using-global-lb-and-internal-cross-region-application-lb)
* [Apigee X in service project with peering disabled and exposed using Global LB](#apigee-x-in-service-project-with-peering-disabled-and-exposed-using-global-lb)
* [Apigee X in standalone project with peering enabled and exposed with Regional Internal LB](#apigee-x-in-standalone-project-with-peering-enabled-and-exposed-with-regional-internal-lb)
* [Apigee X in standalone project with peering disabled and exposed using Global External Application LB](#apigee-x-in-standalone-project-with-peering-disabled-and-exposed-using-global-external-application-lb)
* [Variables](#files)
* [Variables](#variables)
* [Outputs](#outputs)
### Apigee X in service project with shared VPC peered and exposed with Global External Application LB and Regional Internal Application LB
![Diagram](./diagram1.png)
```hcl
module "apigee-x-foundations" {
source = "./fabric/blueprints/apigee/apigee-x-foundations"
project_config = {
billing_account_id = var.billing_account_id
parent = var.folder_id
name = var.project_id
iam = {
"roles/apigee.admin" = ["group:apigee-admins@myorg.com"]
}
shared_vpc_service_config = {
host_project = "my-host-project"
}
}
apigee_config = {
addons_config = {
api_security = true
}
organization = {
analytics_region = "europe-west1"
}
envgroups = {
apis = [
"apis.external.myorg.com",
"apis.internal.myorg.com"
]
}
environments = {
apis = {
envgroups = ["apis"]
}
}
instances = {
europe-west1 = {
external = true
runtime_ip_cidr_range = "10.0.0.0/22"
troubleshooting_ip_cidr_range = "192.168.0.0/18"
environments = ["apis"]
}
}
endpoint_attachments = {
endpoint-backend-ew1 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1"
}
}
}
network_config = {
shared_vpc = {
name = "my-shared-vpc"
subnets = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-ew1"
}
subnets_psc = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-ew1"
}
}
}
ext_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
int_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
}
# tftest modules=7 resources=42
```
### Apigee X in service project with local VPC peered and exposed using Global LB and Internal Cross-region Application LB
![Diagram](./diagram2.png)
```hcl
module "apigee-x-foundations" {
source = "./fabric/blueprints/apigee/apigee-x-foundations"
project_config = {
billing_account_id = "1234-5678-0000"
parent = "folders/123456789"
name = "my-project"
iam = {
"roles/apigee.admin" = ["group:apigee-admins@myorg.com"]
}
shared_vpc_service_config = {
host_project = "my-host-project"
}
}
apigee_config = {
addons_config = {
api_security = true
}
organization = {
analytics_region = "europe-west1"
billing_type = "PAYG"
}
envgroups = {
apis = [
"apis.external.myorg.com",
"apis.internal.myorg.com"
]
}
environments = {
apis = {
envgroups = ["apis"]
type = "COMPREHENSIVE"
}
}
instances = {
europe-west1 = {
runtime_ip_cidr_range = "10.0.0.0/22"
troubleshooting_ip_cidr_range = "192.168.0.0/28"
environments = ["apis"]
}
europe-west4 = {
runtime_ip_cidr_range = "10.0.4.0/22"
troubleshooting_ip_cidr_range = "192.168.0.16/28"
environments = ["apis"]
}
}
endpoint_attachments = {
endpoint-backend-ew1 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west1/serviceAttachments/my-service-attachment-ew1"
dns_names = [
"backend.myorg.com"
]
}
endpoint-backend-ew4 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew4"
dns_names = [
"backend.myorg.com"
]
}
}
}
network_config = {
shared_vpc = {
name = "my-shared-vpc"
subnets = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-eu1"
europe-west4 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-eu4"
}
subnets_psc = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-eu1"
europe-west4 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-eu4"
}
}
apigee_vpc = {
auto_create = true
}
}
ext_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
int_cross_region_lb_config = {
certificate_manager_certificates = [
"projects/myprj/locations/global/certificates/certificate"
]
}
}
# tftest modules=10 resources=62
```
### Apigee X in service project with peering disabled and exposed using Global LB
![Diagram](./diagram3.png)
```hcl
module "apigee-x-foundations" {
source = "./fabric/blueprints/apigee/apigee-x-foundations"
project_config = {
billing_account_id = "1234-5678-0000"
parent = "folders/123456789"
name = "my-project"
iam = {
"roles/apigee.admin" = ["group:apigee-admins@myorg.com"]
}
shared_vpc_service_config = {
host_project = "my-host-project"
}
}
apigee_config = {
addons_config = {
api_security = true
}
organization = {
analytics_region = "europe-west1"
disable_vpc_peering = true
}
envgroups = {
apis = [
"apis.external.myorg.com"
]
}
environments = {
apis = {
envgroups = ["apis"]
}
}
instances = {
europe-west1 = {
runtime_ip_cidr_range = "10.0.0.0/22"
troubleshooting_ip_cidr_range = "192.168.0.0/18"
environments = ["apis"]
}
}
endpoint_attachments = {
endpoint-backend-ew1 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1"
}
}
disable_vpc_peering = true
}
network_config = {
shared_vpc = {
name = "my-shared-vpc"
subnets = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-ew1"
}
subnets_psc = {
europe-west1 = "projects/my-host-project/regions/europe-west4/subnetworks/my-subnet-psc-ew1"
}
}
}
ext_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
}
# tftest modules=6 resources=36
```
### Apigee X in standalone project with peering enabled and exposed with Regional Internal LB
![Diagram](./diagram4.png)
```hcl
module "apigee-x-foundations" {
source = "./fabric/blueprints/apigee/apigee-x-foundations"
project_config = {
billing_account_id = "1234-5678-0000"
parent = "folders/123456789"
name = "my-project"
iam = {
"roles/apigee.admin" = ["group:apigee-admins@myorg.com"]
}
}
apigee_config = {
addons_config = {
api_security = true
}
organization = {
analytics_region = "europe-west1"
}
envgroups = {
apis = [
"apis.internal.myorg.com"
]
}
environments = {
apis = {
envgroups = ["apis"]
}
}
instances = {
europe-west1 = {
runtime_ip_cidr_range = "172.16.0.0/22"
troubleshooting_ip_cidr_range = "192.168.0.0/18"
environments = ["apis"]
}
}
endpoint_attachments = {
endpoint-backend-ew1 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1"
dns_names = [
"backend.myorg.com"
]
}
}
}
network_config = {
apigee_vpc = {
subnets = {
europe-west1 = {
ip_cidr_range = "10.0.0.0/29"
}
}
subnets_proxy_only = {
europe-west1 = {
ip_cidr_range = "10.1.0.0/26"
}
}
subnets_psc = {
europe-west1 = {
ip_cidr_range = "10.0.1.0/29"
}
}
}
}
int_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
}
# tftest modules=8 resources=48
```
### Apigee X in standalone project with peering disabled and exposed using Global External Application LB
![Diagram](./diagram5.png)
```hcl
module "apigee-x-foundations" {
source = "./fabric/blueprints/apigee/apigee-x-foundations"
project_config = {
billing_account_id = "1234-5678-0000"
parent = "folders/123456789"
name = "my-project"
iam = {
"roles/apigee.admin" = ["group:apigee-admins@myorg.com"]
}
}
apigee_config = {
addons_config = {
api_security = true
}
organization = {
analytics_region = "europe-west1"
disable_vpc_peering = true
}
envgroups = {
apis = [
"apis.external.myorg.com",
"apis.internal.myorg.com"
]
}
environments = {
apis = {
envgroups = ["apis"]
}
}
instances = {
europe-west1 = {
environments = ["apis"]
}
}
endpoint_attachments = {
endpoint-backend-ew1 = {
region = "europe-west1"
service_attachment = "projects/a58971796302e0142p-tp/regions/europe-west4/serviceAttachments/my-service-attachment-ew1"
}
}
disable_vpc_peering = true
}
network_config = {
apigee_vpc = {
auto_create = true
subnets = {
europe-west1 = {
ip_cidr_range = "10.0.0.0/29"
}
}
subnets_psc = {
europe-west1 = {
ip_cidr_range = "10.0.1.0/29"
}
}
}
}
ext_lb_config = {
ssl_certificates = {
create_configs = {
default = {
certificate = "PEM-Encoded certificate string"
private_key = "PEM-Encoded private key string"
}
}
}
}
enable_monitoring = true
}
# tftest modules=8 resources=55
```
<!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC -->
## Files
| name | description | modules | resources |
|---|---|---|---|
| [apigee.tf](./apigee.tf) | None | <code>apigee</code> | |
| [dns.tf](./dns.tf) | None | | |
| [kms.tf](./kms.tf) | None | <code>kms</code> | <code>random_id</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>net-vpc</code> · <code>project</code> | |
| [monitoring.tf](./monitoring.tf) | None | <code>cloud-function-v2</code> | |
| [northbound.tf](./northbound.tf) | None | <code>net-lb-app-ext</code> · <code>net-lb-app-int</code> · <code>net-lb-app-int-cross-region</code> | <code>google_compute_region_network_endpoint_group</code> · <code>google_compute_security_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | |
| [variables.tf](./variables.tf) | Module variables. | | |
## Variables
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
| [apigee_config](variables.tf#L17) | Apigee configuration. | <code title="object&#40;&#123;&#10; addons_config &#61; optional&#40;object&#40;&#123;&#10; advanced_api_ops &#61; optional&#40;bool, false&#41;&#10; api_security &#61; optional&#40;bool, false&#41;&#10; connectors_platform &#61; optional&#40;bool, false&#41;&#10; integration &#61; optional&#40;bool, false&#41;&#10; monetization &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#41;&#10; organization &#61; object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string, &#34;Terraform-managed&#34;&#41;&#10; billing_type &#61; optional&#40;string&#41;&#10; database_encryption_key &#61; optional&#40;string&#41;&#10; analytics_region &#61; optional&#40;string, &#34;europe-west1&#34;&#41;&#10; retention &#61; optional&#40;string&#41;&#10; disable_vpc_peering &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#10; envgroups &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; environments &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; display_name &#61; optional&#40;string&#41;&#10; envgroups &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; role &#61; string&#10; members &#61; list&#40;string&#41;&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; role &#61; string&#10; member &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; node_config &#61; optional&#40;object&#40;&#123;&#10; min_node_count &#61; optional&#40;number&#41;&#10; max_node_count &#61; optional&#40;number&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; type &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; instances &#61; optional&#40;map&#40;object&#40;&#123;&#10; disk_encryption_key &#61; optional&#40;string&#41;&#10; environments &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; external &#61; optional&#40;bool, true&#41;&#10; runtime_ip_cidr_range &#61; optional&#40;string&#41;&#10; troubleshooting_ip_cidr_range &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; endpoint_attachments &#61; optional&#40;map&#40;object&#40;&#123;&#10; region &#61; string&#10; service_attachment &#61; string&#10; dns_names &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [project_config](variables.tf#L271) | Project configuration. | <code title="object&#40;&#123;&#10; billing_account_id &#61; optional&#40;string&#41;&#10; compute_metadata &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; custom_roles &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; default_service_account &#61; optional&#40;string, &#34;keep&#34;&#41;&#10; descriptive_name &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; group_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; role &#61; string&#10; members &#61; list&#40;string&#41;&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; role &#61; string&#10; member &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; lien_reason &#61; optional&#40;string&#41;&#10; logging_data_access &#61; optional&#40;map&#40;map&#40;list&#40;string&#41;&#41;&#41;, &#123;&#125;&#41;&#10; log_exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; logging_sinks &#61; optional&#40;map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; iam &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10; unique_writer &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; name &#61; string&#10; org_policies &#61; optional&#40;map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; project_create &#61; optional&#40;bool, true&#41;&#10; vpc_sc &#61; optional&#40;object&#40;&#123;&#10; perimeter_name &#61; string&#10; perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; is_dry_run &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; shared_vpc_host_config &#61; optional&#40;object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;&#10; shared_vpc_service_config &#61; optional&#40;object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;&#10; skip_delete &#61; optional&#40;bool, false&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [enable_monitoring](variables.tf#L87) | Boolean flag indicating whether an custom metric to monitor instances should be created in Cloud monitoring. | <code>bool</code> | | <code>false</code> | |
| [ext_lb_config](variables.tf#L93) | External application load balancer configuration. | <code title="object&#40;&#123;&#10; log_sample_rate &#61; optional&#40;number&#41;&#10; outlier_detection &#61; optional&#40;object&#40;&#123;&#10; consecutive_errors &#61; optional&#40;number&#41;&#10; consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_consecutive_errors &#61; optional&#40;number&#41;&#10; enforcing_consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_success_rate &#61; optional&#40;number&#41;&#10; max_ejection_percent &#61; optional&#40;number&#41;&#10; success_rate_minimum_hosts &#61; optional&#40;number&#41;&#10; success_rate_request_volume &#61; optional&#40;number&#41;&#10; success_rate_stdev_factor &#61; optional&#40;number&#41;&#10; base_ejection_time &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; interval &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; security_policy &#61; optional&#40;object&#40;&#123;&#10; advanced_options_config &#61; optional&#40;object&#40;&#123;&#10; json_parsing &#61; optional&#40;object&#40;&#123;&#10; enable &#61; optional&#40;bool, false&#41;&#10; content_types &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; log_level &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; adaptive_protection_config &#61; optional&#40;object&#40;&#123;&#10; layer_7_ddos_defense_config &#61; optional&#40;object&#40;&#123;&#10; enable &#61; optional&#40;bool, false&#41;&#10; rule_visibility &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; auto_deploy_config &#61; optional&#40;object&#40;&#123;&#10; load_threshold &#61; optional&#40;number&#41;&#10; confidence_threshold &#61; optional&#40;number&#41;&#10; impacted_baseline_threshold &#61; optional&#40;number&#41;&#10; expiration_sec &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; rate_limit_threshold &#61; optional&#40;object&#40;&#123;&#10; count &#61; number&#10; interval_sec &#61; number&#10; &#125;&#41;&#41;&#10; forbidden_src_ip_ranges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; forbidden_regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; preconfigured_waf_rules &#61; optional&#40;map&#40;object&#40;&#123;&#10; sensitivity &#61; optional&#40;number&#41;&#10; opt_in_rule_ids &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; opt_out_rule_ids &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ssl_certificates &#61; object&#40;&#123;&#10; certificate_ids &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; create_configs &#61; optional&#40;map&#40;object&#40;&#123;&#10; certificate &#61; string&#10; private_key &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; managed_configs &#61; optional&#40;map&#40;object&#40;&#123;&#10; domains &#61; list&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; self_signed_configs &#61; optional&#40;list&#40;string&#41;, null&#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [int_cross_region_lb_config](variables.tf#L164) | Internal application load balancer configuration. | <code title="object&#40;&#123;&#10; log_sample_rate &#61; optional&#40;number&#41;&#10; outlier_detection &#61; optional&#40;object&#40;&#123;&#10; consecutive_errors &#61; optional&#40;number&#41;&#10; consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_consecutive_errors &#61; optional&#40;number&#41;&#10; enforcing_consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_success_rate &#61; optional&#40;number&#41;&#10; max_ejection_percent &#61; optional&#40;number&#41;&#10; success_rate_minimum_hosts &#61; optional&#40;number&#41;&#10; success_rate_request_volume &#61; optional&#40;number&#41;&#10; success_rate_stdev_factor &#61; optional&#40;number&#41;&#10; base_ejection_time &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; interval &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; certificate_manager_certificates &#61; optional&#40;list&#40;string&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [int_lb_config](variables.tf#L192) | Internal application load balancer configuration. | <code title="object&#40;&#123;&#10; log_sample_rate &#61; optional&#40;number&#41;&#10; outlier_detection &#61; optional&#40;object&#40;&#123;&#10; consecutive_errors &#61; optional&#40;number&#41;&#10; consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_consecutive_errors &#61; optional&#40;number&#41;&#10; enforcing_consecutive_gateway_failure &#61; optional&#40;number&#41;&#10; enforcing_success_rate &#61; optional&#40;number&#41;&#10; max_ejection_percent &#61; optional&#40;number&#41;&#10; success_rate_minimum_hosts &#61; optional&#40;number&#41;&#10; success_rate_request_volume &#61; optional&#40;number&#41;&#10; success_rate_stdev_factor &#61; optional&#40;number&#41;&#10; base_ejection_time &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; interval &#61; optional&#40;object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ssl_certificates &#61; object&#40;&#123;&#10; certificate_ids &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; create_configs &#61; optional&#40;map&#40;object&#40;&#123;&#10; certificate &#61; string&#10; private_key &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; self_signed_configs &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [network_config](variables.tf#L228) | Network configuration. | <code title="object&#40;&#123;&#10; shared_vpc &#61; optional&#40;object&#40;&#123;&#10; name &#61; string&#10; subnets &#61; map&#40;string&#41;&#10; subnets_psc &#61; map&#40;string&#41;&#10; &#125;&#41;&#41;&#10; apigee_vpc &#61; optional&#40;object&#40;&#123;&#10; name &#61; optional&#40;string&#41;&#10; auto_create &#61; optional&#40;bool, true&#41;&#10; subnets &#61; optional&#40;map&#40;object&#40;&#123;&#10; id &#61; optional&#40;string&#41;&#10; name &#61; optional&#40;string&#41;&#10; ip_cidr_range &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; subnets_proxy_only &#61; optional&#40;map&#40;object&#40;&#123;&#10; name &#61; optional&#40;string&#41;&#10; ip_cidr_range &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; subnets_psc &#61; optional&#40;map&#40;object&#40;&#123;&#10; id &#61; optional&#40;string&#41;&#10; name &#61; optional&#40;string&#41;&#10; ip_cidr_range &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
| [endpoint_attachment_hosts](outputs.tf#L17) | Endpoint attachment hosts. | | |
| [ext_lb_ip_address](outputs.tf#L22) | External IP address. | | |
| [instance_service_attachments](outputs.tf#L27) | Instance service attachments. | | |
| [int_cross_region_lb_ip_addresses](outputs.tf#L32) | Internal IP addresses. | | |
| [int_lb_ip_addresses](outputs.tf#L37) | Internal IP addresses. | | |
| [project_id](outputs.tf#L42) | Project. | | |
<!-- END TFDOC -->

View File

@ -0,0 +1,37 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module "apigee" {
source = "../../../modules/apigee"
project_id = module.project.project_id
organization = merge(var.apigee_config.organization, var.network_config.apigee_vpc != null && !var.apigee_config.organization.disable_vpc_peering ? {
authorized_network = module.apigee_vpc[0].id
} : var.network_config.shared_vpc != null && !var.apigee_config.organization.disable_vpc_peering ? {
authorized_network = module.shared_vpc[0].id
} : {},
var.apigee_config.organization.database_encryption_key == null ? {} : {
database_encryption_key = module.database_kms[0].keys["database-key"].id
}, {
runtime_type = "CLOUD"
})
envgroups = var.apigee_config.envgroups
environments = var.apigee_config.environments
instances = { for k, v in var.apigee_config.instances : k => merge(v, v.disk_encryption_key == null ? {
disk_encryption_key = module.disks_kms[k].key_ids["disk-key"]
} : {}) }
endpoint_attachments = var.apigee_config.endpoint_attachments
addons_config = var.apigee_config.addons_config
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,56 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
dns_names = [for v1 in flatten([for k2, v2 in var.apigee_config.endpoint_attachments :
[for v3 in coalesce(v2.dns_names, []) : {
endpoint_attachment = k2
dns_name = split(".", v3)
}
if v3 != null]]) : {
endpoint_attachment = v1.endpoint_attachment
domain = length(v1.dns_name) == 1 ? "." : "${join(".", slice(v1.dns_name, 1, length(v1.dns_name)))}."
name = length(v1.dns_name) == 1 ? "*." : v1.dns_name[0]
}]
peered_domains = distinct([for v in local.dns_names : v.domain])
private_dns_zones = { for k1, v1 in { for v2 in local.peered_domains :
v2 => distinct([for v3 in local.dns_names : v3.name if v3.domain == v2]) } : k1 =>
{ for v4 in v1 : "A ${v4}" => {
geo_routing = [for v5 in local.dns_names :
{
location = var.apigee_config.endpoint_attachments[v5.endpoint_attachment].region
records = [module.apigee.endpoint_attachment_hosts[v5.endpoint_attachment]]
}
if v5.domain == k1 && v5.name == v4] }
}
}
}
module "private_dns_zones" {
for_each = (var.network_config.apigee_vpc == null || var.apigee_config.organization.disable_vpc_peering
? {} :
local.private_dns_zones)
source = "../../../modules/dns"
project_id = module.project.project_id
name = trimsuffix(replace(each.key, ".", "-"), "-")
zone_config = {
domain = each.key
private = {
client_networks = [module.apigee_vpc[0].self_link]
}
}
recordsets = each.value
}

View File

@ -0,0 +1,138 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const functions = require("@google-cloud/functions-framework");
const monitoring = require("@google-cloud/monitoring");
const logging = require("@google-cloud/logging");
const { LoggingBunyan } = require("@google-cloud/logging-bunyan");
const bunyan = require("bunyan");
const loggingBunyan = new LoggingBunyan();
const logger = bunyan.createLogger({
name: "instance-monitor",
streams: [{ stream: process.stdout, level: "info" }, loggingBunyan.stream("info")],
});
const SEVERITY_THRESHOLD = logging.Severity.warning;
const METRIC_DESCRIPTION = "Apigee instance health.";
const METRIC_DISPLAY_NAME = "Apigee instance health.";
const METRIC_TYPE = "custom.googleapis.com/apigee/instance_health";
const METRIC_KIND = "GAUGE";
const METRIC_VALUE_TYPE = "BOOL";
const METRIC_UNIT = "1";
const METRIC_LABELS = [
{
key: "org",
valueType: "STRING",
description: "The name of the apigee organization.",
},
{
key: "instance_id",
valueType: "STRING",
description: "The ID of the apigee instance.",
}
];
const RESOURCE_TYPE = "global";
const METRIC_DESCRIPTOR = {
description: METRIC_DESCRIPTION,
displayName: METRIC_DISPLAY_NAME,
type: METRIC_TYPE,
metricKind: METRIC_KIND,
valueType: METRIC_VALUE_TYPE,
unit: METRIC_UNIT,
labels: METRIC_LABELS,
};
const client = new monitoring.MetricServiceClient();
async function createMetricDescriptor(projectId, metricDescriptor) {
const request = {
name: client.projectPath(projectId),
metricDescriptor: metricDescriptor,
};
return await client.createMetricDescriptor(request);
}
async function getMetricDescriptor(projectId, metricType) {
const request = {
name: client.projectMetricDescriptorPath(projectId, metricType),
};
return await client.getMetricDescriptor(request);
}
async function writeTimeSeriesData(projectId, metricType, resourceType, value, metricLabels) {
const dataPoint = {
interval: {
endTime: {
seconds: Date.now() / 1000,
},
},
value: {
boolValue: value,
},
};
const timeSeriesData = {
metric: {
type: metricType,
labels: metricLabels,
},
resource: {
type: resourceType,
labels: {
project_id: projectId,
},
},
points: [dataPoint],
};
const request = {
name: client.projectPath(projectId),
timeSeries: [timeSeriesData],
};
return await client.createTimeSeries(request);
}
async function processEvent(cloudEvent) {
const [, projectId, instanceId] = /^organizations\/(.+)\/instances\/(.+)$/g.exec(cloudEvent.resourcename);
const severity = logging.Severity[cloudEvent.data.severity.toLowerCase()];
const value = severity >= SEVERITY_THRESHOLD;
if (!value) {
logger.error(`Instance ${instanceId} in ${organization} is down`);
}
try {
logger.debug("Checking if metric exists...");
const result = await getMetricDescriptor(projectId, METRIC_TYPE);
logger.debug("Metric already exists", result);
} catch (error) {
logger.debug("Metric does not exist. Creating it...");
const result = await createMetricDescriptor(projectId, METRIC_DESCRIPTOR);
logger.debug("Metric created", result);
}
logger.debug("Writing data point...");
await writeTimeSeriesData(projectId, METRIC_TYPE, RESOURCE_TYPE, value, {
org: projectId,
instance_id: instanceId,
});
}
functions.cloudEvent("writeMetric", async cloudEvent => {
logger.debug("Notification received. Let's process it...");
processEvent(cloudEvent);
logger.debug("Notification processed.");
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
{
"name": "instance-checker",
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@google-cloud/functions-framework": "^3.3.0",
"@google-cloud/logging-bunyan": "^5.0.0",
"bunyan": "^1.8.15",
"@google-cloud/monitoring": "^3.0.5"
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
resource "random_id" "database_kms" {
byte_length = 4
}
resource "random_id" "disks_kms" {
for_each = var.apigee_config.instances
byte_length = 4
}
module "database_kms" {
count = try(var.apigee_config.organization.database_encryption_key, null) == null ? 1 : 0
source = "../../../modules/kms"
project_id = module.project.project_id
keyring = {
location = "global"
name = "apigee-${random_id.database_kms.hex}"
}
keys = {
database-key = {
purpose = "ENCRYPT_DECRYPT"
rotation_period = "2592000s"
labels = null
iam = {
"roles/cloudkms.cryptoKeyEncrypterDecrypter" = ["serviceAccount:${module.project.service_accounts.robots.apigee}"]
}
}
}
}
module "disks_kms" {
for_each = var.apigee_config.instances
source = "../../../modules/kms"
project_id = module.project.project_id
keyring = {
location = each.key
name = "apigee-${each.key}-${random_id.disks_kms[each.key].hex}"
}
keys = {
disk-key = {
purpose = "ENCRYPT_DECRYPT"
rotation_period = "2592000s"
labels = null
iam = {
"roles/cloudkms.cryptoKeyEncrypterDecrypter" = ["serviceAccount:${module.project.service_accounts.robots.apigee}"]
}
}
}
}

View File

@ -0,0 +1,108 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module "project" {
source = "../../../modules/project"
billing_account = var.project_config.billing_account_id
compute_metadata = var.project_config.compute_metadata
custom_roles = var.project_config.custom_roles
default_service_account = var.project_config.default_service_account
iam = var.project_config.iam
iam_bindings = var.project_config.iam_bindings
iam_bindings_additive = var.project_config.iam_bindings_additive
labels = var.project_config.labels
lien_reason = var.project_config.lien_reason
logging_data_access = var.project_config.logging_data_access
logging_exclusions = var.project_config.log_exclusions
logging_sinks = var.project_config.logging_sinks
metric_scopes = var.project_config.metric_scopes
name = var.project_config.name
org_policies = var.project_config.org_policies
parent = var.project_config.parent
prefix = var.project_config.prefix
services = distinct(concat(var.project_config.services, [
"apigee.googleapis.com",
"cloudkms.googleapis.com",
"compute.googleapis.com",
"eventarc.googleapis.com",
"dns.googleapis.com",
"iam.googleapis.com",
"servicenetworking.googleapis.com",
], var.enable_monitoring ? [
"cloudbuild.googleapis.com",
"cloudfunctions.googleapis.com",
"logging.googleapis.com",
"monitoring.googleapis.com",
"pubsub.googleapis.com",
"run.googleapis.com"
] : []))
shared_vpc_service_config = var.project_config.shared_vpc_service_config
skip_delete = var.project_config.skip_delete
tag_bindings = var.project_config.tag_bindings
}
module "shared_vpc" {
count = var.network_config.shared_vpc == null ? 0 : 1
source = "../../../modules/net-vpc"
project_id = var.project_config.shared_vpc_service_config.host_project
name = var.network_config.shared_vpc.name
vpc_create = false
}
module "apigee_vpc" {
count = var.network_config.apigee_vpc == null ? 0 : 1
source = "../../../modules/net-vpc"
project_id = module.project.project_id
name = coalesce(var.network_config.apigee_vpc.name, "apigee-vpc")
vpc_create = var.network_config.apigee_vpc.auto_create
psa_configs = [{
ranges = merge(flatten([for k, v in var.apigee_config.instances : merge(
v.runtime_ip_cidr_range == null ? {} : { "apigee-22-${k}" = v.runtime_ip_cidr_range },
v.troubleshooting_ip_cidr_range == null ? {} : { "apigee-28-${k}" = v.troubleshooting_ip_cidr_range }
)])...)
export_routes = true
import_routes = false
peered_domains = local.peered_domains
}]
subnets = [for k, v in var.network_config.apigee_vpc.subnets :
{
name = coalesce(v.name, "subnet-${k}")
region = k
ip_cidr_range = v.ip_cidr_range
description = "Subnet in ${k} region"
}
if v.ip_cidr_range != null && (var.int_cross_region_lb_config != null || nonsensitive(var.int_lb_config != null))]
subnets_proxy_only = [for k, v in var.network_config.apigee_vpc.subnets_proxy_only :
{
name = coalesce(v.name, "subnet-proxy-only-${k}")
region = k
ip_cidr_range = v.ip_cidr_range
description = "Proxy-only subnet in ${k} region"
global = var.int_cross_region_lb_config != null
}
if v.ip_cidr_range != null && (var.int_cross_region_lb_config != null || nonsensitive(var.int_lb_config != null))]
subnets_psc = [for k, v in var.network_config.apigee_vpc.subnets_psc :
{
name = coalesce(v.name, "subnet-psc-${k}")
region = k
ip_cidr_range = v.ip_cidr_range
description = "PSC Subnet in ${k} region"
global = var.int_cross_region_lb_config != null
}
if v.ip_cidr_range != null]
}

View File

@ -0,0 +1,52 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module "instance_monitor_function" {
count = var.enable_monitoring && length(var.apigee_config.instances) > 0 ? 1 : 0
source = "../../../modules/cloud-function-v2"
project_id = module.project.project_id
name = "instance-monitor"
bucket_name = module.project.project_id
bucket_config = {
}
bundle_config = {
source_dir = "${path.module}/functions/instance-monitor"
output_path = "bundle.zip"
}
function_config = {
entry_point = "writeMetric"
runtime = "nodejs20"
timeout = 180
}
trigger_config = {
event_type = "google.cloud.audit.log.v1.written"
region = "global"
event_filters = [
{
attribute = "serviceName"
value = "apigee.googleapis.com"
},
{
attribute = "methodName"
value = "google.cloud.apigee.v1.RuntimeService.ReportInstanceStatus"
},
]
service_account_create = true
retry_policy = "RETRY_POLICY_DO_NOT_RETRY"
}
region = var.apigee_config.organization.analytics_region
service_account_create = true
}

View File

@ -0,0 +1,248 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
preconfigured_waf_rules = { for k, v in try(var.ext_lb_config.security_policy.preconfigured_waf_rules, {}) : k =>
merge(v.sensitivity == null ? {} : {
sensitivity = v.sensitivity
},
length(v.opt_in_rule_ids) > 0 ? {
opt_in_rule_ids = v.opt_in_rule_ids
} : {},
length(v.opt_out_rule_ids) > 0 ? {
opt_out_rule_ids = v.opt_out_rule_ids
} : {})
}
network = try(module.shared_vpc[0].id, module.apigee_vpc[0].id)
neg_subnets = (var.network_config.shared_vpc == null ?
(try(var.network_config.apigee_vpc.auto_create, false) ?
{ for k, v in module.apigee_vpc[0].subnets_psc : v.region => v.id } :
{ for k, v in var.network_config.apigee_vpc.subnets_psc : v => v.id }) :
var.network_config.shared_vpc.subnets_psc
)
ilb_subnets = (var.network_config.shared_vpc == null ?
(try(var.network_config.apigee_vpc.auto_create, false) ?
{ for k, v in module.apigee_vpc[0].subnets : v.region => v.id } :
{ for k, v in var.network_config.apigee_vpc.subnets : v => v.id }) :
var.network_config.shared_vpc.subnets
)
ext_instances = var.ext_lb_config == null ? {} : { for k, v in local.neg_subnets : k => module.apigee.instances[k] }
int_instances = var.int_lb_config == null ? {} : { for k, v in local.ilb_subnets : k => module.apigee.instances[k] }
int_cross_region_instances = var.int_cross_region_lb_config == null ? {} : { for k, v in local.ilb_subnets : k => module.apigee.instances[k] }
}
resource "google_compute_region_network_endpoint_group" "psc_negs" {
for_each = local.neg_subnets
project = module.project.project_id
region = each.key
name = "apigee-${each.key}"
network_endpoint_type = "PRIVATE_SERVICE_CONNECT"
psc_target_service = module.apigee.instances[each.key].service_attachment
network = local.network
subnetwork = each.value
}
module "ext_lb" {
count = length(local.ext_instances) > 0 ? 1 : 0
source = "../../../modules/net-lb-app-ext"
name = "ext-lb"
project_id = module.project.project_id
protocol = "HTTPS"
use_classic_version = false
backend_service_configs = {
default = {
backends = [for k, v in local.ext_instances : { backend = google_compute_region_network_endpoint_group.psc_negs[k].id }]
protocol = "HTTPS"
health_checks = []
outlier_detection = var.ext_lb_config.outlier_detection
security_policy = try(google_compute_security_policy.policy[0].name, null)
log_sample_rate = var.ext_lb_config.log_sample_rate
}
}
health_check_configs = {
default = {
https = { port_specification = "USE_SERVING_PORT" }
}
}
ssl_certificates = var.ext_lb_config.ssl_certificates
}
module "int_lb" {
for_each = local.int_instances
source = "../../../modules/net-lb-app-int"
name = "${each.key}-int-lb"
project_id = module.project.project_id
region = each.key
protocol = "HTTPS"
backend_service_configs = {
default = {
backends = [{
group = google_compute_region_network_endpoint_group.psc_negs[each.key].id
}]
outlier_detection = var.int_lb_config.outlier_detection
health_checks = []
log_sample_rate = var.int_lb_config.log_sample_rate
}
}
ssl_certificates = var.int_lb_config.ssl_certificates
vpc_config = {
network = local.network
subnetwork = local.ilb_subnets[each.key]
}
}
module "int_cross_region_lb" {
count = length(local.int_cross_region_instances) > 0 ? 1 : 0
source = "../../../modules/net-lb-app-int-cross-region"
name = "int-cross-region-lb"
project_id = module.project.project_id
protocol = "HTTPS"
backend_service_configs = {
default = {
backends = [for k, v in google_compute_region_network_endpoint_group.psc_negs : {
group = v.id
}]
outlier_detection = var.int_cross_region_lb_config.outlier_detection
health_checks = []
log_sample_rate = var.int_cross_region_lb_config.log_sample_rate
}
}
https_proxy_config = {
certificate_manager_certificates = var.int_cross_region_lb_config.certificate_manager_certificates
}
vpc_config = {
network = local.network
subnetworks = local.ilb_subnets
}
}
resource "google_compute_security_policy" "policy" {
provider = google-beta
count = try(var.ext_lb_config.security_policy, null) == null ? 0 : 1
name = "cloud-armor-security-policy"
description = "Cloud Armor Security Policy"
project = module.project.project_id
dynamic "advanced_options_config" {
for_each = try(var.ext_lb_config, null) == null ? [] : [""]
content {
json_parsing = try(var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.enable, false) ? "DISABLED" : "STANDARD"
dynamic "json_custom_config" {
for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.content_types, null) == null ? [] : [""]
content {
content_types = var.ext_lb_config.security_policy.adaptive_protection_config.json_parsing.content_types
}
}
log_level = var.ext_lb_config.security_policy.advanced_options_config.log_level
}
}
dynamic "adaptive_protection_config" {
for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config, null) == null ? [] : [""]
content {
dynamic "layer_7_ddos_defense_config" {
for_each = try(var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config, null) == null ? [] : [""]
content {
enable = var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config.enable
rule_visibility = var.ext_lb_config.security_policy.adaptive_protection_config.layer_7_ddos_defense_config.rule_visibility
}
}
dynamic "auto_deploy_config" {
for_each = try(var.int_lb_config.security_policy.adaptive_protection_config.auto_deploy_config, null) == null ? [] : [""]
content {
load_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.load_threshold
confidence_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.confidence_threshold
impacted_baseline_threshold = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.impacted_baseline_threshold
expiration_sec = var.ext_lb_config.security_policy.adaptive_protection_config.auto_deploy_config.expiration_sec
}
}
}
}
type = "CLOUD_ARMOR"
dynamic "rule" {
for_each = try(var.ext_lb_config.security_policy.rate_limit_threshold, null) == null ? [] : [""]
content {
action = "throttle"
priority = 3000
rate_limit_options {
enforce_on_key = "ALL"
conform_action = "allow"
exceed_action = "deny(429)"
rate_limit_threshold {
count = var.ext_lb_config.security_policy.rate_limit_threshold.count
interval_sec = var.ext_lb_config.security_policy.rate_limit_threshold.interval_sec
}
}
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Rate limit all user IPs"
}
}
dynamic "rule" {
for_each = try(length(var.ext_lb_config.security_policy.forbidden_src_ip_ranges), 0) > 0 ? [""] : []
content {
action = "deny(403)"
priority = 5000
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = var.ext_lb_config.security_policy.forbidden_src_ip_ranges
}
}
description = "Deny access to IPs in specific ranges"
}
}
dynamic "rule" {
for_each = try(length(var.ext_lb_config.security_policy.forbidden_regions), 0) > 0 ? [""] : []
content {
action = "deny(403)"
priority = 7000
match {
expr {
expression = "origin.region_code.matches(\"^${join("|", var.ext_lb_config.security_policy.forbidden_regions)}$\")"
}
}
description = "Block users from forbidden regions"
}
}
dynamic "rule" {
for_each = local.preconfigured_waf_rules
content {
action = "deny(403)"
priority = 10000 + index(keys(var.ext_lb_config.security_policy.preconfigured_waf_rules), rule.key) * 1000
match {
expr {
expression = "evaluatePreconfiguredWaf(\"${rule.key}\"${length(rule.value) > 0 ? join("", [",", jsonencode(rule.value)]) : ""})"
}
}
description = "Preconfigured WAF rule (${rule.key})"
}
}
rule {
action = "allow"
priority = 2147483647
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "default rule"
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
output "endpoint_attachment_hosts" {
description = "Endpoint attachment hosts."
value = module.apigee.endpoint_attachment_hosts
}
output "ext_lb_ip_address" {
description = "External IP address."
value = var.ext_lb_config != null && length(local.ext_instances) > 0 ? module.ext_lb[0].address : null
}
output "instance_service_attachments" {
description = "Instance service attachments."
value = { for k, v in module.apigee.instances : k => v.service_attachment }
}
output "int_cross_region_lb_ip_addresses" {
description = "Internal IP addresses."
value = var.int_cross_region_lb_config != null && length(local.int_cross_region_instances) > 0 ? module.int_cross_region_lb[0].addresses : null
}
output "int_lb_ip_addresses" {
description = "Internal IP addresses."
value = var.int_lb_config != null && length(local.int_instances) > 0 ? { for k, v in module.int_lb : k => v.address } : null
}
output "project_id" {
description = "Project."
value = module.project.project_id
}

View File

@ -0,0 +1,360 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
variable "apigee_config" {
description = "Apigee configuration."
type = object({
addons_config = optional(object({
advanced_api_ops = optional(bool, false)
api_security = optional(bool, false)
connectors_platform = optional(bool, false)
integration = optional(bool, false)
monetization = optional(bool, false)
}))
organization = object({
display_name = optional(string)
description = optional(string, "Terraform-managed")
billing_type = optional(string)
database_encryption_key = optional(string)
analytics_region = optional(string, "europe-west1")
retention = optional(string)
disable_vpc_peering = optional(bool, false)
})
envgroups = optional(map(list(string)), {})
environments = optional(map(object({
description = optional(string)
display_name = optional(string)
envgroups = optional(list(string), [])
iam = optional(map(list(string)), {})
iam_bindings = optional(map(object({
role = string
members = list(string)
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
})), {})
iam_bindings_additive = optional(map(object({
role = string
member = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
})), {})
node_config = optional(object({
min_node_count = optional(number)
max_node_count = optional(number)
}), {})
type = optional(string)
})), {})
instances = optional(map(object({
disk_encryption_key = optional(string)
environments = optional(list(string), [])
external = optional(bool, true)
runtime_ip_cidr_range = optional(string)
troubleshooting_ip_cidr_range = optional(string)
})), {})
endpoint_attachments = optional(map(object({
region = string
service_attachment = string
dns_names = optional(list(string), [])
})), {})
})
validation {
condition = (!var.apigee_config.organization.disable_vpc_peering ||
alltrue([for k, v in var.apigee_config.endpoint_attachments : length(v.dns_names) == 0]))
error_message = "If disable_vpc_peering is true for the organization, DNS names cannot be used for endpoint attachments."
}
nullable = false
}
variable "enable_monitoring" {
description = "Boolean flag indicating whether an custom metric to monitor instances should be created in Cloud monitoring."
type = bool
default = false
}
variable "ext_lb_config" {
description = "External application load balancer configuration."
type = object({
log_sample_rate = optional(number)
outlier_detection = optional(object({
consecutive_errors = optional(number)
consecutive_gateway_failure = optional(number)
enforcing_consecutive_errors = optional(number)
enforcing_consecutive_gateway_failure = optional(number)
enforcing_success_rate = optional(number)
max_ejection_percent = optional(number)
success_rate_minimum_hosts = optional(number)
success_rate_request_volume = optional(number)
success_rate_stdev_factor = optional(number)
base_ejection_time = optional(object({
seconds = number
nanos = optional(number)
}))
interval = optional(object({
seconds = number
nanos = optional(number)
}))
}))
security_policy = optional(object({
advanced_options_config = optional(object({
json_parsing = optional(object({
enable = optional(bool, false)
content_types = optional(list(string))
}))
log_level = optional(string)
}))
adaptive_protection_config = optional(object({
layer_7_ddos_defense_config = optional(object({
enable = optional(bool, false)
rule_visibility = optional(string)
}))
auto_deploy_config = optional(object({
load_threshold = optional(number)
confidence_threshold = optional(number)
impacted_baseline_threshold = optional(number)
expiration_sec = optional(number)
}))
}))
rate_limit_threshold = optional(object({
count = number
interval_sec = number
}))
forbidden_src_ip_ranges = optional(list(string), [])
forbidden_regions = optional(list(string), [])
preconfigured_waf_rules = optional(map(object({
sensitivity = optional(number)
opt_in_rule_ids = optional(list(string), [])
opt_out_rule_ids = optional(list(string), [])
})))
}))
ssl_certificates = object({
certificate_ids = optional(list(string), [])
create_configs = optional(map(object({
certificate = string
private_key = string
})), {})
managed_configs = optional(map(object({
domains = list(string)
description = optional(string)
})), {})
self_signed_configs = optional(list(string), null)
})
})
default = null
}
variable "int_cross_region_lb_config" {
description = "Internal application load balancer configuration."
type = object({
log_sample_rate = optional(number)
outlier_detection = optional(object({
consecutive_errors = optional(number)
consecutive_gateway_failure = optional(number)
enforcing_consecutive_errors = optional(number)
enforcing_consecutive_gateway_failure = optional(number)
enforcing_success_rate = optional(number)
max_ejection_percent = optional(number)
success_rate_minimum_hosts = optional(number)
success_rate_request_volume = optional(number)
success_rate_stdev_factor = optional(number)
base_ejection_time = optional(object({
seconds = number
nanos = optional(number)
}))
interval = optional(object({
seconds = number
nanos = optional(number)
}))
}))
certificate_manager_certificates = optional(list(string))
})
default = null
}
variable "int_lb_config" {
description = "Internal application load balancer configuration."
type = object({
log_sample_rate = optional(number)
outlier_detection = optional(object({
consecutive_errors = optional(number)
consecutive_gateway_failure = optional(number)
enforcing_consecutive_errors = optional(number)
enforcing_consecutive_gateway_failure = optional(number)
enforcing_success_rate = optional(number)
max_ejection_percent = optional(number)
success_rate_minimum_hosts = optional(number)
success_rate_request_volume = optional(number)
success_rate_stdev_factor = optional(number)
base_ejection_time = optional(object({
seconds = number
nanos = optional(number)
}))
interval = optional(object({
seconds = number
nanos = optional(number)
}))
}))
ssl_certificates = object({
certificate_ids = optional(list(string), [])
create_configs = optional(map(object({
certificate = string
private_key = string
})), {})
self_signed_configs = optional(list(string), [])
})
})
default = null
}
variable "network_config" {
description = "Network configuration."
type = object({
shared_vpc = optional(object({
name = string
subnets = map(string)
subnets_psc = map(string)
}))
apigee_vpc = optional(object({
name = optional(string)
auto_create = optional(bool, true)
subnets = optional(map(object({
id = optional(string)
name = optional(string)
ip_cidr_range = optional(string)
})), {})
subnets_proxy_only = optional(map(object({
name = optional(string)
ip_cidr_range = string
})), {})
subnets_psc = optional(map(object({
id = optional(string)
name = optional(string)
ip_cidr_range = optional(string)
})), {})
}))
})
nullable = false
default = {}
validation {
condition = var.network_config.shared_vpc != null || var.network_config.apigee_vpc != null
error_message = "Shared VPC and/or local VPC details need to be provided."
}
validation {
condition = alltrue([for k, v in try(var.network_config.apigee_vpc.subnets, {}) : (v.id != null || v.ip_cidr_range != null) && !(v.id != null && v.ip_cidr_range != null)])
error_message = "An IP CIDR range and id cannot be specified at the same time for a subnet."
}
validation {
condition = alltrue([for k, v in try(var.network_config.apigee_vpc.subnets_psc, {}) : (v.id != null || v.ip_cidr_range != null) && !(v.id != null && v.ip_cidr_range != null)])
error_message = "An IP CIDR range and id cannot be specified at the same time for a PSC subnet."
}
}
variable "project_config" {
description = "Project configuration."
type = object({
billing_account_id = optional(string)
compute_metadata = optional(map(string), {})
contacts = optional(map(list(string)), {})
custom_roles = optional(map(list(string)), {})
default_service_account = optional(string, "keep")
descriptive_name = optional(string)
iam = optional(map(list(string)), {})
group_iam = optional(map(list(string)), {})
iam_bindings = optional(map(object({
role = string
members = list(string)
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
})), {})
iam_bindings_additive = optional(map(object({
role = string
member = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
})), {})
labels = optional(map(string), {})
lien_reason = optional(string)
logging_data_access = optional(map(map(list(string))), {})
log_exclusions = optional(map(string), {})
logging_sinks = optional(map(object({
bq_partitioned_table = optional(bool)
description = optional(string)
destination = string
disabled = optional(bool, false)
exclusions = optional(map(string), {})
filter = string
iam = optional(bool, true)
type = string
unique_writer = optional(bool, true)
})), {})
metric_scopes = optional(list(string), [])
name = string
org_policies = optional(map(object({
inherit_from_parent = optional(bool) # for list policies only.
reset = optional(bool)
rules = optional(list(object({
allow = optional(object({
all = optional(bool)
values = optional(list(string))
}))
deny = optional(object({
all = optional(bool)
values = optional(list(string))
}))
enforce = optional(bool) # for boolean policies only.
condition = optional(object({
description = optional(string)
expression = optional(string)
location = optional(string)
title = optional(string)
}), {})
})), [])
})), {})
parent = optional(string)
prefix = optional(string)
project_create = optional(bool, true)
vpc_sc = optional(object({
perimeter_name = string
perimeter_bridges = optional(list(string), [])
is_dry_run = optional(bool, false)
}))
services = optional(list(string), [])
shared_vpc_host_config = optional(object({
enabled = bool
service_projects = optional(list(string), [])
}))
shared_vpc_service_config = optional(object({
host_project = string
service_identity_iam = optional(map(list(string)), {})
service_iam_grants = optional(list(string), [])
}))
skip_delete = optional(bool, false)
tag_bindings = optional(map(string))
})
}

View File

@ -1,4 +1,4 @@
# Internal Application Load Balancer Module
# Cross-region Internal Application Load Balancer Module
This module allows managing Cross-regional Internal HTTP/HTTPS Load Balancers (L7 ILBs). It's designed to expose the full configuration of the underlying resources, and to facilitate common usage patterns by providing sensible defaults, and optionally managing prerequisite resources like health checks, instance groups, etc.