Merge branch 'master' into lcaggio/dp-dataaccess
This commit is contained in:
commit
35f39369c5
|
@ -37,7 +37,7 @@ jobs:
|
|||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@v1
|
||||
with:
|
||||
terraform_version: 1.3
|
||||
terraform_version: 1.3.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
|
|
@ -30,7 +30,7 @@ env:
|
|||
PYTEST_ADDOPTS: "--color=yes"
|
||||
PYTHON_VERSION: "3.10"
|
||||
TF_PLUGIN_CACHE_DIR: "/home/runner/.terraform.d/plugin-cache"
|
||||
TF_VERSION: 1.3.0
|
||||
TF_VERSION: 1.3.2
|
||||
|
||||
jobs:
|
||||
doc-examples:
|
||||
|
@ -48,10 +48,18 @@ jobs:
|
|||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
terraform_wrapper: false
|
||||
|
||||
# avoid conflicts with user-installed providers on local machines
|
||||
- name: Pin provider versions
|
||||
run: |
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf
|
||||
find -name versions.tf -exec cp default-versions.tf {} \;
|
||||
for f in $(find . -name versions.tf); do
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' $f;
|
||||
done
|
||||
|
||||
- name: Run tests on documentation examples
|
||||
id: pytest
|
||||
|
@ -76,15 +84,17 @@ jobs:
|
|||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@v1
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
terraform_wrapper: false
|
||||
|
||||
# avoid conflicts with user-installed providers on local machines
|
||||
- name: Pin provider versions
|
||||
run: |
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf
|
||||
find -name versions.tf -exec cp default-versions.tf {} \;
|
||||
for f in $(find . -name versions.tf); do
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' $f;
|
||||
done
|
||||
|
||||
- name: Run tests environments
|
||||
id: pytest
|
||||
|
@ -109,15 +119,17 @@ jobs:
|
|||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@v1
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
terraform_wrapper: false
|
||||
|
||||
# avoid conflicts with user-installed providers on local machines
|
||||
- name: Pin provider versions
|
||||
run: |
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf
|
||||
find -name versions.tf -exec cp default-versions.tf {} \;
|
||||
for f in $(find . -name versions.tf); do
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' $f;
|
||||
done
|
||||
|
||||
- name: Run tests modules
|
||||
id: pytest
|
||||
|
@ -142,15 +154,17 @@ jobs:
|
|||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@v1
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
terraform_wrapper: false
|
||||
|
||||
# avoid conflicts with user-installed providers on local machines
|
||||
- name: Pin provider versions
|
||||
run: |
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' default-versions.tf
|
||||
find -name versions.tf -exec cp default-versions.tf {} \;
|
||||
for f in $(find . -name versions.tf); do
|
||||
sed -i 's/>=\(.*# tftest\)/=\1/g' $f;
|
||||
done
|
||||
|
||||
- name: Run tests on FAST stages
|
||||
id: pytest
|
||||
|
|
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -8,6 +8,28 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### BLUEPRINTS
|
||||
|
||||
- [[#897](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/897)] Project-factory: allow folder_id to be defined in defaults_file ([Malet](https://github.com/Malet)) <!-- 2022-10-21 08:20:06+00:00 -->
|
||||
- [[#900](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/900)] Improve net dashboard variables ([juliocc](https://github.com/juliocc)) <!-- 2022-10-20 20:59:31+00:00 -->
|
||||
- [[#896](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/896)] Network Dashboard: CFv2 and performance improvements ([aurelienlegrand](https://github.com/aurelienlegrand)) <!-- 2022-10-19 16:59:29+00:00 -->
|
||||
- [[#871](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/871)] Firewall Policy Metrics, parallel writes, aligned timestamps ([maunope](https://github.com/maunope)) <!-- 2022-10-19 15:37:19+00:00 -->
|
||||
- [[#884](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/884)] BigQuery factory blueprint ([marcjwo](https://github.com/marcjwo)) <!-- 2022-10-18 15:07:16+00:00 -->
|
||||
- [[#889](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/889)] Minor fixes to PSC hybrid blueprint readmes ([LucaPrete](https://github.com/LucaPrete)) <!-- 2022-10-17 08:40:12+00:00 -->
|
||||
- [[#888](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/888)] Let the cloudsql module generate a random password ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-17 06:30:41+00:00 -->
|
||||
- [[#879](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/879)] New PSC hybrid blueprint ([LucaPrete](https://github.com/LucaPrete)) <!-- 2022-10-16 08:18:41+00:00 -->
|
||||
- [[#880](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/880)] **incompatible change:** Refactor net-vpc module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 09:02:34+00:00 -->
|
||||
- [[#872](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/872)] added support 2nd generation cloud function ([som-nitjsr](https://github.com/som-nitjsr)) <!-- 2022-10-13 06:09:00+00:00 -->
|
||||
- [[#875](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/875)] **incompatible change:** Refactor GKE nodepool for Terraform 1.3, refactor GKE blueprints and FAST stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 10:59:37+00:00 -->
|
||||
- [[#873](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/873)] Fix docker tag command and link to Cloud Shell in WP blueprint ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-11 12:40:25+00:00 -->
|
||||
- [[#870](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/870)] Temporarily revert to Terraform 1.3.1 to support Cloud Shell ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-10 09:36:41+00:00 -->
|
||||
- [[#856](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/856)] Add network firewall metrics to network dashboard ([maunope](https://github.com/maunope)) <!-- 2022-10-10 08:46:22+00:00 -->
|
||||
- [[#868](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/868)] **incompatible change:** Refactor GKE module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-10 07:38:21+00:00 -->
|
||||
- [[#818](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/818)] Example wordpress ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-07 14:24:38+00:00 -->
|
||||
- [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) <!-- 2022-10-07 07:50:43+00:00 -->
|
||||
- [[#854](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/854)] Added an example of a Nginx reverse proxy cluster using RMIGs ([rosmo](https://github.com/rosmo)) <!-- 2022-10-04 13:49:44+00:00 -->
|
||||
- [[#850](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/850)] Made sample alert creation optional ([maunope](https://github.com/maunope)) <!-- 2022-09-30 10:08:37+00:00 -->
|
||||
- [[#837](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/837)] Network dashboard: Subnet IP utilization update ([aurelienlegrand](https://github.com/aurelienlegrand)) <!-- 2022-09-30 08:51:16+00:00 -->
|
||||
- [[#848](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/848)] updated quota monitoring CF doc ([maunope](https://github.com/maunope)) <!-- 2022-09-29 17:55:22+00:00 -->
|
||||
- [[#847](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/847)] **incompatible change:** Quotas monitoring, time series format update ([maunope](https://github.com/maunope)) <!-- 2022-09-29 16:20:18+00:00 -->
|
||||
- [[#839](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/839)] **incompatible change:** Update to terraform 1.3 ([juliocc](https://github.com/juliocc)) <!-- 2022-09-28 11:25:27+00:00 -->
|
||||
- [[#828](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/828)] Update firewall rules. ([lcaggio](https://github.com/lcaggio)) <!-- 2022-09-20 15:24:12+00:00 -->
|
||||
- [[#813](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/813)] Add documentation example test for pf ([ludoo](https://github.com/ludoo)) <!-- 2022-09-14 12:34:30+00:00 -->
|
||||
|
@ -15,17 +37,54 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### DOCUMENTATION
|
||||
|
||||
- [[#806](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/806)] Companion Guide ([ajlopezn](https://github.com/ajlopezn)) <!-- 2022-09-12 07:11:03+00:00 -->
|
||||
- [[#898](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/898)] Update FAST bootstrap README.md ([juliocc](https://github.com/juliocc)) <!-- 2022-10-19 15:15:36+00:00 -->
|
||||
- [[#878](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/878)] chore: update cft and fabric ([bharathkkb](https://github.com/bharathkkb)) <!-- 2022-10-12 15:38:06+00:00 -->
|
||||
- [[#863](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/863)] Fabric vs CFT doc ([ludoo](https://github.com/ludoo)) <!-- 2022-10-07 12:47:51+00:00 -->
|
||||
- [[#806](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/806)] FAST Companion Guide ([ajlopezn](https://github.com/ajlopezn)) <!-- 2022-09-12 07:11:03+00:00 -->
|
||||
|
||||
### FAST
|
||||
|
||||
- [[#903](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/903)] Initial replacement for CI/CD stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-23 17:52:46+00:00 -->
|
||||
- [[#898](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/898)] Update FAST bootstrap README.md ([juliocc](https://github.com/juliocc)) <!-- 2022-10-19 15:15:36+00:00 -->
|
||||
- [[#880](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/880)] **incompatible change:** Refactor net-vpc module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 09:02:34+00:00 -->
|
||||
- [[#875](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/875)] **incompatible change:** Refactor GKE nodepool for Terraform 1.3, refactor GKE blueprints and FAST stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 10:59:37+00:00 -->
|
||||
- [[#566](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/566)] FAST: Separate network environment ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-10 09:50:08+00:00 -->
|
||||
- [[#870](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/870)] Temporarily revert to Terraform 1.3.1 to support Cloud Shell ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-10 09:36:41+00:00 -->
|
||||
- [[#868](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/868)] **incompatible change:** Refactor GKE module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-10 07:38:21+00:00 -->
|
||||
- [[#867](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/867)] FAST: Replace NVAs in 02-networking-nva with COS-based VMs ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-10 07:16:29+00:00 -->
|
||||
- [[#865](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/865)] Enable FAST 00-cicd provider test ([ludoo](https://github.com/ludoo)) <!-- 2022-10-07 11:20:57+00:00 -->
|
||||
- [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) <!-- 2022-10-07 07:50:43+00:00 -->
|
||||
- [[#858](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/858)] Default gcp-support to gcp-devops ([juliocc](https://github.com/juliocc)) <!-- 2022-10-06 12:58:26+00:00 -->
|
||||
- [[#842](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/842)] Comment redundant role in bootstrap stage, align IAM.md files, improve IAM tool ([ludoo](https://github.com/ludoo)) <!-- 2022-09-29 06:30:02+00:00 -->
|
||||
- [[#841](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/841)] FAST: revert 00-cicd provider changes ([ludoo](https://github.com/ludoo)) <!-- 2022-09-28 14:17:40+00:00 -->
|
||||
- [[#835](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/835)] Fix workflow-gitlab.yaml template rendering ([muresan](https://github.com/muresan)) <!-- 2022-09-22 12:26:22+00:00 -->
|
||||
- [[#828](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/828)] Update firewall rules. ([lcaggio](https://github.com/lcaggio)) <!-- 2022-09-20 15:24:12+00:00 -->
|
||||
- [[#807](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/807)] FAST: refactor Gitlab template ([ludoo](https://github.com/ludoo)) <!-- 2022-09-12 05:26:49+00:00 -->
|
||||
|
||||
### MODULES
|
||||
|
||||
- [[#840](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/840)] Refactor net-address module for 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-09-28 12:10:05+00:00 -->
|
||||
- [[#904](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/904)] Add missing description field ([dsbutler101](https://github.com/dsbutler101)) <!-- 2022-10-21 15:05:11+00:00 -->
|
||||
- [[#891](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/891)] Add internal_ips output to compute-vm module ([LucaPrete](https://github.com/LucaPrete)) <!-- 2022-10-21 08:38:27+00:00 -->
|
||||
- [[#890](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/890)] Add auto_delete and instance_redistribution_type to compute-vm and compute-mig modules. ([giovannibaratta](https://github.com/giovannibaratta)) <!-- 2022-10-16 19:19:46+00:00 -->
|
||||
- [[#883](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/883)] Fix csi-driver, logging and monitoring default values when autopilot … ([danielmarzini](https://github.com/danielmarzini)) <!-- 2022-10-14 15:30:54+00:00 -->
|
||||
- [[#880](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/880)] **incompatible change:** Refactor net-vpc module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 09:02:34+00:00 -->
|
||||
- [[#872](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/872)] added support 2nd generation cloud function ([som-nitjsr](https://github.com/som-nitjsr)) <!-- 2022-10-13 06:09:00+00:00 -->
|
||||
- [[#877](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/877)] fix autoscaling block ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 14:44:48+00:00 -->
|
||||
- [[#875](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/875)] **incompatible change:** Refactor GKE nodepool for Terraform 1.3, refactor GKE blueprints and FAST stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 10:59:37+00:00 -->
|
||||
- [[#870](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/870)] Temporarily revert to Terraform 1.3.1 to support Cloud Shell ([skalolazka](https://github.com/skalolazka)) <!-- 2022-10-10 09:36:41+00:00 -->
|
||||
- [[#869](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/869)] Fix optionals for resource_usage_export field in `gke-cluster` ([juliocc](https://github.com/juliocc)) <!-- 2022-10-10 09:04:44+00:00 -->
|
||||
- [[#868](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/868)] **incompatible change:** Refactor GKE module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-10 07:38:21+00:00 -->
|
||||
- [[#866](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/866)] Update ipprefix_by_netmask.sh in nva module ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-09 15:26:54+00:00 -->
|
||||
- [[#860](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/860)] **incompatible change:** Refactor compute-vm for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-07 08:53:53+00:00 -->
|
||||
- [[#861](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/861)] Leverage new shared VPC project config defaults across the repo ([juliocc](https://github.com/juliocc)) <!-- 2022-10-07 07:50:43+00:00 -->
|
||||
- [[#859](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/859)] Make project shared VPC fields optional ([juliocc](https://github.com/juliocc)) <!-- 2022-10-06 14:18:01+00:00 -->
|
||||
- [[#853](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/853)] Fixes NVA issue when health checks are not enabled ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-04 05:55:10+00:00 -->
|
||||
- [[#846](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/846)] COS based simple networking appliance ([sruffilli](https://github.com/sruffilli)) <!-- 2022-09-30 16:43:24+00:00 -->
|
||||
- [[#851](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/851)] nginx-tls: only use hostname part for TLS certificate ([rosmo](https://github.com/rosmo)) <!-- 2022-09-30 11:52:43+00:00 -->
|
||||
- [[#844](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/844)] Management of GCP project default service accounts ([ddaluka](https://github.com/ddaluka)) <!-- 2022-09-29 13:10:08+00:00 -->
|
||||
- [[#845](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/845)] added root password support for MS SQL Server ([cmalpe](https://github.com/cmalpe)) <!-- 2022-09-29 12:03:59+00:00 -->
|
||||
- [[#843](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/843)] Add support for disk encryption to instance templates in compute-vm module ([ludoo](https://github.com/ludoo)) <!-- 2022-09-29 07:01:16+00:00 -->
|
||||
- [[#840](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/840)] **incompatible change:** Refactor net-address module for 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-09-28 12:10:05+00:00 -->
|
||||
- [[#839](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/839)] **incompatible change:** Update to terraform 1.3 ([juliocc](https://github.com/juliocc)) <!-- 2022-09-28 11:25:27+00:00 -->
|
||||
- [[#824](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/824)] Add simple composer 2 blueprint ([lcaggio](https://github.com/lcaggio)) <!-- 2022-09-28 09:07:29+00:00 -->
|
||||
- [[#834](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/834)] Add support for service_label property in internal load balancer ([kmucha555](https://github.com/kmucha555)) <!-- 2022-09-21 21:30:35+00:00 -->
|
||||
|
@ -36,6 +95,15 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### TOOLS
|
||||
|
||||
- [[#902](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/902)] Bring back sorted variables check ([juliocc](https://github.com/juliocc)) <!-- 2022-10-20 17:08:17+00:00 -->
|
||||
- [[#887](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/887)] Disable parallel execution of tests and plugin cache ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 17:52:38+00:00 -->
|
||||
- [[#886](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/886)] Revert "Improve handling of tf plugin cache in tests" ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 17:35:31+00:00 -->
|
||||
- [[#885](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/885)] Improve handling of tf plugin cache in tests ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 17:14:47+00:00 -->
|
||||
- [[#881](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/881)] Run tests in parallel using `pytest-xdist` ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 12:56:16+00:00 -->
|
||||
- [[#876](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/876)] Make changelog tool slower to work around inconsistencies in API results ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 12:49:32+00:00 -->
|
||||
- [[#865](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/865)] Enable FAST 00-cicd provider test ([ludoo](https://github.com/ludoo)) <!-- 2022-10-07 11:20:57+00:00 -->
|
||||
- [[#864](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/864)] **incompatible change:** Bump terraform required version ([ludoo](https://github.com/ludoo)) <!-- 2022-10-07 10:51:56+00:00 -->
|
||||
- [[#842](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/842)] Comment redundant role in bootstrap stage, align IAM.md files, improve IAM tool ([ludoo](https://github.com/ludoo)) <!-- 2022-09-29 06:30:02+00:00 -->
|
||||
- [[#811](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/811)] Fix changelog generator ([ludoo](https://github.com/ludoo)) <!-- 2022-09-13 09:41:29+00:00 -->
|
||||
- [[#810](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/810)] Fully recursive e2e test runner for examples ([juliocc](https://github.com/juliocc)) <!-- 2022-09-12 12:35:46+00:00 -->
|
||||
|
||||
|
|
|
@ -141,9 +141,8 @@ module "project" {
|
|||
storage = [local.kms.europe.gcs]
|
||||
}
|
||||
shared_vpc_service_config = {
|
||||
attach = true
|
||||
host_project = "project-host"
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = "project-host"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -258,9 +257,8 @@ module "project" {
|
|||
source = "./modules/project"
|
||||
name = "prj-1"
|
||||
shared_vpc_service_config = {
|
||||
attach = true
|
||||
host_project = "project-host"
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = "project-host"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
# Cloud Foundation Fabric and Cloud Foundation Toolkit
|
||||
|
||||
This page highlights the main differences (both technical and philosophical) between Cloud Foundation Fabric and Cloud Foundation Toolkit for end users, to guide them in their decision making process for identifying the best suite of modules for their use cases.
|
||||
|
||||
## Cloud Foundation Fabric (a.k.a Fabric, this repo)
|
||||
|
||||
Fabric is a collection of Terraform modules and end to end examples meant to be cloned as a single unit and used as is for fast prototyping or decomposed and modified for usage in organizations.
|
||||
|
||||
## Cloud Foundation Toolkit (a.k.a CFT)
|
||||
|
||||
CFT is a collection of Terraform modules and examples with opinionated GCP best practices implemented as individual modules for gradual adoption and off the shelf usage in organizations.
|
||||
|
||||
## Key Differences
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td><strong>Fabric</strong>
|
||||
</td>
|
||||
<td><strong>CFT</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Target User</strong>
|
||||
</td>
|
||||
<td>Organizations interested in forking, maintaining and customizing Terraform modules.
|
||||
</td>
|
||||
<td>Organizations interested in using opinionated, prebuilt Terraform modules.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Configuration</strong>
|
||||
</td>
|
||||
<td>Less opinionated allowing end users higher flexibility.
|
||||
</td>
|
||||
<td>Opinionated by default, end users may need to fork if it does not meet their use case.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Extensibility</strong>
|
||||
</td>
|
||||
<td>Built with extensibility in mind catering to fork and use patterns. Modules are often lightweight and easy to adopt / tailor to specific use cases.
|
||||
</td>
|
||||
<td>Not built with fork and use extensibility catering primarily to off the shelf consumption. Modules are tailored towards common usecases and extensible via composition.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Config customization</strong>
|
||||
</td>
|
||||
<td>Prefer customization using variables via objects, tight variable space.
|
||||
</td>
|
||||
<td>Prefer customization using variables via primitives.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Examples</strong>
|
||||
</td>
|
||||
<td>Thorough examples for individual modules, and end to end examples composing multiple modules covering a wide variety of use cases from foundations to solutions.
|
||||
</td>
|
||||
<td>Examples for a module mostly focus on that individual module. Composition is often not shown in examples but in larger modules built using smaller modules.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Resources</strong>
|
||||
</td>
|
||||
<td>Leaner modules wrapping resources.
|
||||
</td>
|
||||
<td>Heavier root modules that often compose leaner sub modules wrapping resources.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Resource grouping</strong>
|
||||
</td>
|
||||
<td>Generally grouped by logical entities.
|
||||
</td>
|
||||
<td>Generally grouped by products/product areas.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Release Cadence</strong>
|
||||
</td>
|
||||
<td>Modules versioned and released together.
|
||||
</td>
|
||||
<td>Modules versioned and released individually.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Individual module usage</strong>
|
||||
</td>
|
||||
<td>Individual modules consumed directly using Git as a module source.
|
||||
<p>
|
||||
For production usage, customers are encouraged to “fork and own” their own repository.
|
||||
</td>
|
||||
<td>Individual repositories consumed via the Terraform registry.
|
||||
<p>
|
||||
For production/airgapped usage, customers may also mirror modules to a private registry.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Factories</strong>
|
||||
</td>
|
||||
<td>Fabric implements several "factories" in modules, where users can drive or automate Terraform via YAML files (projects, subnetworks, firewalls, etc.).
|
||||
</td>
|
||||
<td>CFT does not implement factories and generally show examples usable with variable definitions files (.tfvars).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Organizational adoption</strong>
|
||||
</td>
|
||||
<td>Mono repo cloned into an organizational VCS (or catalog) and separated into individual modules for internal consumption.
|
||||
</td>
|
||||
<td>Individual repos forked (for air gap) or wrapping upstream sources to create individual modules for internal consumption.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Distribution</strong>
|
||||
</td>
|
||||
<td>Distributed via Git/GitHub.
|
||||
</td>
|
||||
<td>Distributed via Git/GitHub and Terraform Registry.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Testing</strong>
|
||||
</td>
|
||||
<td>Every PR performs unit tests on modules, examples, and documentation snippets by evaluating a Terraform plan via Python <a href="https://pypi.org/project/tftest/">tftest</a> library.
|
||||
</td>
|
||||
<td>Every PR performs full end-to-end deployment with integration tests using the <a href="https://pkg.go.dev/github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test">blueprint test framework</a>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Similarities
|
||||
|
||||
* Both collections of modules are designed with stable interfaces that work well together with other modules in their ecosystem.
|
||||
* Both collections of modules require minimal variables and provide defaults.
|
||||
* Both collections of modules are well tested and documented with information about usage, code snippets and provide information about variables and outputs.
|
||||
|
||||
## Should you choose Fabric or CFT?
|
||||
|
||||
> You/Your organization is knowledgeable in Terraform and interested in forking and owning a collection of modules.
|
||||
|
||||
Fabric is a better choice as it bootstraps you with a collection of modules out of the box that can be customized exactly to fit your organization needs.
|
||||
|
||||
> You/Your organization is getting started with Terraform and interested in GCP best practices out of the box.
|
||||
|
||||
CFT is a better choice as it allows you to directly reference specific modules from the registry and provide opinionated configuration by default.
|
||||
|
||||
> You/Your organization is looking to rapidly prototype some functionality on GCP.
|
||||
|
||||
Fabric is a better choice. Being a mono repo it allows you to get started quickly with all your source code in one place for easier debugging.
|
||||
|
||||
> You/Your organization has existing infrastructure and processes but want to start adopting IaC gradually.
|
||||
|
||||
CFT is designed to be modular and off the shelf, providing higher level abstractions to product groups which allows certain teams to adopt Terraform without maintenance burden while allowing others to follow existing practices.
|
||||
|
||||
## Using Fabric and CFT together
|
||||
|
||||
Even with all the above points, it may be hard to make a decision. While the modules may have different patterns and philosophies, it is often possible to bring the best of both worlds together. Here are some tips to follow:
|
||||
|
||||
* Since modules work well together within their ecosystem, select logical boundaries for using Fabric or CFT. For example use CFT for deploying resources within projects but use Fabric for managing project creation and IAM.
|
||||
* Use strengths of each collection of modules to your advantage. Empower application teams to define their infrastructure as code using off the shelf CFT modules. Using Fabric, bootstrap your platform team with a collection of tailor built modules for your organization.
|
||||
* Lean into module composition and dependency inversion that both Fabric and CFT modules follow. For example, you can create a GKE cluster using either [Fabric](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/gke-cluster#gke-cluster-module) or [CFT](https://github.com/terraform-google-modules/terraform-google-kubernetes-engine) GKE module and then use either [Fabric](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/gke-hub#variables) or [CFT](https://github.com/terraform-google-modules/terraform-google-kubernetes-engine/tree/master/modules/fleet-membership) for setting up GKE Hub by passing in outputs from the GKE module.
|
|
@ -13,7 +13,7 @@ This repository provides **end-to-end blueprints** and a **suite of Terraform mo
|
|||
- reference [blueprints](./blueprints/) used to deep dive on network patterns or product features
|
||||
- a comprehensive source of lean [modules](./modules/dns) that lend themselves well to changes
|
||||
|
||||
The whole repository is meant to be cloned as a single unit, and then forked into separate owned repositories to seed production usage, or used as-is and periodically updated as a complete toolkit for prototyping. You can read more on this approach in our [contributing guide](./CONTRIBUTING.md).
|
||||
The whole repository is meant to be cloned as a single unit, and then forked into separate owned repositories to seed production usage, or used as-is and periodically updated as a complete toolkit for prototyping. You can read more on this approach in our [contributing guide](./CONTRIBUTING.md), and a comparison against similar toolkits [here](./FABRIC-AND-CFT.md).
|
||||
|
||||
## Organization blueprint (Fabric FAST)
|
||||
|
||||
|
|
|
@ -26,3 +26,7 @@ This is a non-exhaustive list of Fabric usage in the wild. Send us a PR if you s
|
|||
- [Hub-and-spoke network architecture](https://cloud.google.com/architecture/deploy-hub-spoke-vpc-network-topology)
|
||||
- [Deploy a hub-and-spoke network using VPC Network Peering](https://cloud.google.com/architecture/deploy-hub-spoke-network-using-peering)
|
||||
- [Deploy a hub-and-spoke network using Cloud VPN](https://cloud.google.com/architecture/deploy-hub-spoke-network-using-vpn)
|
||||
|
||||
## Third-party reviews
|
||||
|
||||
- [Google Cloud Landing Zone Comparison](https://www.meshcloud.io/2022/09/09/gcp-landing-zone-comparison/) by Meshcloud.
|
||||
|
|
|
@ -8,7 +8,7 @@ Currently available blueprints:
|
|||
- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/gcs-to-bq-with-least-privileges/), [Cloud Storage to Bigquery with Cloud Dataflow with least privileges](./data-solutions/gcs-to-bq-with-least-privileges/), [Data Platform Foundations](./data-solutions/data-platform-foundations/), [SQL Server AlwaysOn availability groups blueprint](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2/)
|
||||
- **factories** - [The why and the how of resource factories](./factories/README.md)
|
||||
- **GKE** - [GKE multitenant fleet](./gke/multitenant-fleet/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [Binary Authorization Pipeline](./gke/binauthz/), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api/)
|
||||
- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [PSC for on-premises Cloud Function invocation](./networking/private-cloud-function-from-onprem/), [decentralized firewall](./networking/decentralized-firewall)
|
||||
- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [Connecting to on-premise services leveraging PSC and hybrid NEGs](./networking/psc-hybrid/), [decentralized firewall](./networking/decentralized-firewall)
|
||||
- **serverless** - [Multi-region deployments for API Gateway](./serverless/api-gateway/)
|
||||
- **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift)
|
||||
|
||||
|
|
|
@ -44,10 +44,9 @@ module "vpc" {
|
|||
name = "${local.prefix}vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.subnet_ip_cidr_block
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
secondary_ip_range = null
|
||||
ip_cidr_range = var.subnet_ip_cidr_block
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -69,8 +68,6 @@ module "server" {
|
|||
network_interfaces = [{
|
||||
network = var.network_config == null ? module.vpc[0].self_link : var.network_config.network
|
||||
subnetwork = var.network_config == null ? module.vpc[0].subnet_self_links["${var.region}/subnet"] : var.network_config.subnet
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
metadata = {
|
||||
# Enables OpenSSH in the Windows instance
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,10 +48,9 @@ module "vpc" {
|
|||
project_id = module.project.project_id
|
||||
name = var.name
|
||||
subnets = [{
|
||||
ip_cidr_range = "192.168.0.0/24"
|
||||
name = "${var.name}-default"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "192.168.0.0/24"
|
||||
name = "${var.name}-default"
|
||||
region = var.region
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -104,8 +103,6 @@ module "simple-vm-example" {
|
|||
network_interfaces = [{
|
||||
network = module.vpc.self_link
|
||||
subnetwork = try(module.vpc.subnet_self_links["${var.region}/${var.name}-default"], "")
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
tags = ["${var.project_id}-test-feed", "shared-test-feed"]
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,10 +37,9 @@ module "vpc" {
|
|||
project_id = module.project.project_id
|
||||
name = var.name
|
||||
subnets = [{
|
||||
ip_cidr_range = "192.168.0.0/24"
|
||||
name = "${var.name}-default"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "192.168.0.0/24"
|
||||
name = "${var.name}-default"
|
||||
region = var.region
|
||||
}]
|
||||
}
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,9 @@ module "shared-vpc" {
|
|||
|
||||
subnets = [
|
||||
{
|
||||
name = "subnet-01"
|
||||
ip_cidr_range = "10.10.1.0/24"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
name = "subnet-01"
|
||||
ip_cidr_range = "10.10.1.0/24"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -37,8 +37,7 @@ module "project-host" {
|
|||
services = var.project_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = [] # defined later
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ Note: To grant a user a role, take a look at the [Granting and Revoking Access](
|
|||
|
||||
Click on the button below, sign in if required and when the prompt appears, click on “confirm”.
|
||||
|
||||
[<p align="center"> <img alt="Open Cloudshell" width = "300px" src="shell_button.png" /> </p>](https://goo.gle/GoCloudArmor)
|
||||
[![Open Cloudshell](shell_button.png)](https://goo.gle/GoCloudArmor)
|
||||
|
||||
This will clone the repository to your cloud shell and a screen like this one will appear:
|
||||
|
||||
|
|
|
@ -43,22 +43,19 @@ module "vpc" {
|
|||
name = "${local.prefix}vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.0.1.0/24"
|
||||
name = "subnet-ew1"
|
||||
region = "europe-west1"
|
||||
secondary_ip_range = null
|
||||
ip_cidr_range = "10.0.1.0/24"
|
||||
name = "subnet-ew1"
|
||||
region = "europe-west1"
|
||||
},
|
||||
{
|
||||
ip_cidr_range = "10.0.2.0/24"
|
||||
name = "subnet-ue1"
|
||||
region = "us-east1"
|
||||
secondary_ip_range = null
|
||||
ip_cidr_range = "10.0.2.0/24"
|
||||
name = "subnet-ue1"
|
||||
region = "us-east1"
|
||||
},
|
||||
{
|
||||
ip_cidr_range = "10.0.3.0/24"
|
||||
name = "subnet-uw1"
|
||||
region = "us-west1"
|
||||
secondary_ip_range = null
|
||||
ip_cidr_range = "10.0.3.0/24"
|
||||
name = "subnet-uw1"
|
||||
region = "us-west1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -94,13 +91,9 @@ module "instance_template_ew1" {
|
|||
network_interfaces = [{
|
||||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["europe-west1/subnet-ew1"]
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
boot_disk = {
|
||||
image = "projects/debian-cloud/global/images/family/debian-11"
|
||||
type = "pd-ssd"
|
||||
size = 10
|
||||
}
|
||||
metadata = {
|
||||
startup-script-url = "gs://cloud-training/gcpnet/httplb/startup.sh"
|
||||
|
@ -119,13 +112,9 @@ module "instance_template_ue1" {
|
|||
network_interfaces = [{
|
||||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["us-east1/subnet-ue1"]
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
boot_disk = {
|
||||
image = "projects/debian-cloud/global/images/family/debian-11"
|
||||
type = "pd-ssd"
|
||||
size = 10
|
||||
}
|
||||
metadata = {
|
||||
startup-script-url = "gs://cloud-training/gcpnet/httplb/startup.sh"
|
||||
|
@ -146,12 +135,9 @@ module "vm_siege" {
|
|||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["us-west1/subnet-uw1"]
|
||||
nat = true
|
||||
addresses = null
|
||||
}]
|
||||
boot_disk = {
|
||||
image = "projects/debian-cloud/global/images/family/debian-11"
|
||||
type = "pd-ssd"
|
||||
size = 10
|
||||
}
|
||||
metadata = {
|
||||
startup-script = <<EOT
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,31 +3,36 @@
|
|||
This repository provides an end-to-end solution to gather some GCP Networking quotas and limits (that cannot be seen in the GCP console today) and display them in a dashboard.
|
||||
The goal is to allow for better visibility of these limits, facilitating capacity planning and avoiding hitting these limits.
|
||||
|
||||
Here is an blueprint of dashboard you can get with this solution:
|
||||
Here is an example of dashboard you can get with this solution:
|
||||
|
||||
<img src="metric.png" width="640px">
|
||||
|
||||
Here you see utilization (usage compared to the limit) for a specific metric (number of instances per VPC) for multiple VPCs and projects.
|
||||
|
||||
3 metrics are created: Usage, limit and utilization. You can follow each of these and create alerting policies if a threshold is reached.
|
||||
Three metric descriptors are created for each monitored resource: usage, limit and utilization. You can follow each of these and create alerting policies if a threshold is reached.
|
||||
|
||||
## Usage
|
||||
|
||||
Clone this repository, then go through the following steps to create resources:
|
||||
- Create a terraform.tfvars file with the following content:
|
||||
- organization_id = "[YOUR-ORG-ID]"
|
||||
- billing_account = "[YOUR-BILLING-ACCOUNT]"
|
||||
- organization_id = "<YOUR-ORG-ID>"
|
||||
- billing_account = "<YOUR-BILLING-ACCOUNT>"
|
||||
- monitoring_project_id = "project-0" # Monitoring project where the dashboard will be created and the solution deployed
|
||||
- monitored_projects_list = ["project-1", "project2"] # Projects to be monitored by the solution
|
||||
- monitored_folders_list = ["folder_id"] # Folders to be monitored by the solution
|
||||
- v2 = true|false # Set to true to use V2 Cloud Functions environment
|
||||
- `terraform init`
|
||||
- `terraform apply`
|
||||
|
||||
Once the resources are deployed, go to the following page to see the dashboard: https://console.cloud.google.com/monitoring/dashboards?project=<YOUR-MONITORING-PROJECT>.
|
||||
A dashboard called "quotas-utilization" should be created.
|
||||
|
||||
The Cloud Function runs every 5 minutes by default so you should start getting some data points after a few minutes.
|
||||
The Cloud Function runs every 10 minutes by default so you should start getting some data points after a few minutes.
|
||||
You can use the metric explorer to view the data points for the different custom metrics created: https://console.cloud.google.com/monitoring/metrics-explorer?project=<YOUR-MONITORING-PROJECT>.
|
||||
You can change this frequency by modifying the "schedule_cron" variable in variables.tf.
|
||||
|
||||
Note that some charts in the dashboard align values over 1h so you might need to wait 1h to see charts on the dashboard views.
|
||||
|
||||
Once done testing, you can clean up resources by running `terraform destroy`.
|
||||
|
||||
## Supported limits and quotas
|
||||
|
@ -43,18 +48,37 @@ The Cloud Function currently tracks usage, limit and utilization of:
|
|||
- internal forwarding rules for internal L7 load balancers per VPC peering group
|
||||
- Dynamic routes per VPC
|
||||
- Dynamic routes per VPC peering group
|
||||
- IP utilization per subnet (% of IP addresses used in a subnet)
|
||||
- VPC firewall rules per project (VPC drill down is available for usage)
|
||||
- Tuples per Firewall Policy
|
||||
|
||||
It writes this values to custom metrics in Cloud Monitoring and creates a dashboard to visualize the current utilization of these metrics in Cloud Monitoring.
|
||||
|
||||
Note that metrics are created in the cloud-function/metrics.yaml file.
|
||||
|
||||
You can also edit default limits for a specific network in that file. See the blueprint for `vpc_peering_per_network`.
|
||||
You can also edit default limits for a specific network in that file. See the example for `vpc_peering_per_network`.
|
||||
|
||||
## Next steps and ideas
|
||||
In a future release, we could support:
|
||||
- Static routes per VPC / per VPC peering group
|
||||
- Dynamic routes per VPC peering group
|
||||
- Google managed VPCs that are peered with PSA (such as Cloud SQL or Memorystore)
|
||||
- Subnet IP ranges utilization
|
||||
|
||||
If you are interested in this and/or would like to contribute, please contact legranda@google.com.
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [billing_account](variables.tf#L17) | The ID of the billing account to associate this project with | <code></code> | ✓ | |
|
||||
| [monitored_projects_list](variables.tf#L36) | ID of the projects to be monitored (where limits and quotas data will be pulled) | <code>list(string)</code> | ✓ | |
|
||||
| [organization_id](variables.tf#L47) | The organization id for the associated services | <code></code> | ✓ | |
|
||||
| [prefix](variables.tf#L51) | Customer name to use as prefix for monitoring project | <code></code> | ✓ | |
|
||||
| [cf_version](variables.tf#L21) | Cloud Function version 2nd Gen or 1st Gen. Possible options: 'V1' or 'V2'.Use CFv2 if your Cloud Function timeouts after 9 minutes. By default it is using CFv1. | <code></code> | | <code>V1</code> |
|
||||
| [monitored_folders_list](variables.tf#L30) | ID of the projects to be monitored (where limits and quotas data will be pulled) | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [monitoring_project_id](variables.tf#L41) | Monitoring project where the dashboard will be created and the solution deployed; a project will be created if set to empty string | <code></code> | | |
|
||||
| [project_monitoring_services](variables.tf#L55) | Service APIs enabled in the monitoring project if it will be created. | <code></code> | | <code title="[ "artifactregistry.googleapis.com", "cloudasset.googleapis.com", "cloudbilling.googleapis.com", "cloudbuild.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudscheduler.googleapis.com", "compute.googleapis.com", "cloudfunctions.googleapis.com", "iam.googleapis.com", "iamcredentials.googleapis.com", "logging.googleapis.com", "monitoring.googleapis.com", "run.googleapis.com", "serviceusage.googleapis.com" ]">[…]</code> |
|
||||
| [region](variables.tf#L75) | Region used to deploy the cloud functions and scheduler | <code></code> | | <code>europe-west1</code> |
|
||||
| [schedule_cron](variables.tf#L80) | Cron format schedule to run the Cloud Function. Default is every 10 minutes. | <code></code> | | <code>*/10 * * * *</code> |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -12,26 +12,69 @@
|
|||
# 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.
|
||||
#
|
||||
# CFv2 define whether to use Cloud function 2nd generation or 1st generation
|
||||
|
||||
from code import interact
|
||||
import re
|
||||
from distutils.command.config import config
|
||||
import os
|
||||
from pickletools import int4
|
||||
import time
|
||||
from google.cloud import monitoring_v3, asset_v1
|
||||
from google.protobuf import field_mask_pb2
|
||||
from googleapiclient import discovery
|
||||
from metrics import ilb_fwrules, instances, networks, metrics, limits, peerings, routes
|
||||
from metrics import ilb_fwrules, firewall_policies, instances, networks, metrics, limits, peerings, routes, subnets, vpc_firewalls
|
||||
|
||||
CF_VERSION = os.environ.get("CF_VERSION")
|
||||
|
||||
|
||||
def get_monitored_projects_list(config):
|
||||
'''
|
||||
Gets the projects to be monitored from the MONITORED_FOLDERS_LIST environment variable.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
monitored_projects (List of strings): Full list of projects to be monitored
|
||||
'''
|
||||
monitored_projects = config["monitored_projects"]
|
||||
monitored_folders = os.environ.get("MONITORED_FOLDERS_LIST").split(",")
|
||||
|
||||
# Handling empty monitored folders list
|
||||
if monitored_folders == ['']:
|
||||
monitored_folders = []
|
||||
|
||||
# Gets all projects under each monitored folder (and even in sub folders)
|
||||
for folder in monitored_folders:
|
||||
read_mask = field_mask_pb2.FieldMask()
|
||||
read_mask.FromJsonString('name,versionedResources')
|
||||
|
||||
response = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"folders/{folder}",
|
||||
"asset_types": ["cloudresourcemanager.googleapis.com/Project"],
|
||||
"read_mask": read_mask
|
||||
})
|
||||
|
||||
for resource in response:
|
||||
for versioned in resource.versioned_resources:
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
if field_name == "projectId":
|
||||
project_id = field_value
|
||||
# Avoid duplicate
|
||||
if project_id not in monitored_projects:
|
||||
monitored_projects.append(project_id)
|
||||
|
||||
print("List of projects to be monitored:")
|
||||
print(monitored_projects)
|
||||
|
||||
return monitored_projects
|
||||
|
||||
|
||||
def monitoring_interval():
|
||||
'''
|
||||
Creates the monitoring interval of 24 hours
|
||||
|
||||
Returns:
|
||||
monitoring_v3.TimeInterval: Moinitoring time interval of 24h
|
||||
'''
|
||||
Creates the monitoring interval of 24 hours
|
||||
Returns:
|
||||
monitoring_v3.TimeInterval: Monitoring time interval of 24h
|
||||
'''
|
||||
now = time.time()
|
||||
seconds = int(now)
|
||||
nanos = int((now - seconds) * 10**9)
|
||||
|
@ -54,7 +97,7 @@ config = {
|
|||
# list of projects from which function will get quotas information
|
||||
"monitored_projects":
|
||||
os.environ.get("MONITORED_PROJECTS_LIST").split(","),
|
||||
"monitoring_project_link":
|
||||
"monitoring_project":
|
||||
os.environ.get('MONITORING_PROJECT_ID'),
|
||||
"monitoring_project_link":
|
||||
f"projects/{os.environ.get('MONITORING_PROJECT_ID')}",
|
||||
|
@ -78,26 +121,42 @@ config = {
|
|||
"discovery_client": discovery.build('compute', 'v1'),
|
||||
"asset_client": asset_v1.AssetServiceClient(),
|
||||
"monitoring_client": monitoring_v3.MetricServiceClient()
|
||||
}
|
||||
},
|
||||
# Improve performance for Asset Inventory queries on large environments
|
||||
"page_size":
|
||||
500,
|
||||
"series_buffer": [],
|
||||
}
|
||||
|
||||
|
||||
def main(event, context):
|
||||
def main(event, context=None):
|
||||
'''
|
||||
Cloud Function Entry point, called by the scheduler.
|
||||
Cloud Function Entry point, called by the scheduler.
|
||||
Parameters:
|
||||
event: Not used for now (Pubsub trigger)
|
||||
context: Not used for now (Pubsub trigger)
|
||||
Returns:
|
||||
'Function executed successfully'
|
||||
'''
|
||||
# Handling empty monitored projects list
|
||||
if config["monitored_projects"] == ['']:
|
||||
config["monitored_projects"] = []
|
||||
|
||||
Parameters:
|
||||
event: Not used for now (Pubsub trigger)
|
||||
context: Not used for now (Pubsub trigger)
|
||||
Returns:
|
||||
'Function executed successfully'
|
||||
'''
|
||||
# Gets projects and folders to be monitored
|
||||
config["monitored_projects"] = get_monitored_projects_list(config)
|
||||
|
||||
# Keep the monitoring interval up2date during each run
|
||||
config["monitoring_interval"] = monitoring_interval()
|
||||
|
||||
metrics_dict, limits_dict = metrics.create_metrics(
|
||||
config["monitoring_project_link"])
|
||||
config["monitoring_project_link"], config)
|
||||
project_quotas_dict = limits.get_quota_project_limit(config)
|
||||
|
||||
firewalls_dict = vpc_firewalls.get_firewalls_dict(config)
|
||||
firewall_policies_dict = firewall_policies.get_firewall_policies_dict(config)
|
||||
|
||||
# IP utilization subnet level metrics
|
||||
subnets.get_subnets(config, metrics_dict)
|
||||
|
||||
# Asset inventory queries
|
||||
gce_instance_dict = instances.get_gce_instance_dict(config)
|
||||
|
@ -105,48 +164,65 @@ def main(event, context):
|
|||
l7_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L7")
|
||||
subnet_range_dict = networks.get_subnet_ranges_dict(config)
|
||||
|
||||
# Per Network metrics
|
||||
instances.get_gce_instances_data(config, metrics_dict, gce_instance_dict,
|
||||
limits_dict['number_of_instances_limit'])
|
||||
ilb_fwrules.get_forwarding_rules_data(
|
||||
config, metrics_dict, l4_forwarding_rules_dict,
|
||||
limits_dict['internal_forwarding_rules_l4_limit'], "L4")
|
||||
ilb_fwrules.get_forwarding_rules_data(
|
||||
config, metrics_dict, l7_forwarding_rules_dict,
|
||||
limits_dict['internal_forwarding_rules_l7_limit'], "L7")
|
||||
peerings.get_vpc_peering_data(config, metrics_dict,
|
||||
limits_dict['number_of_vpc_peerings_limit'])
|
||||
dynamic_routes_dict = routes.get_dynamic_routes(
|
||||
config, metrics_dict, limits_dict['dynamic_routes_per_network_limit'])
|
||||
try:
|
||||
|
||||
# Per VPC peering group metrics
|
||||
metrics.get_pgg_data(
|
||||
config,
|
||||
metrics_dict["metrics_per_peering_group"]["instance_per_peering_group"],
|
||||
gce_instance_dict, config["limit_names"]["GCE_INSTANCES"],
|
||||
limits_dict['number_of_instances_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["l4_forwarding_rules_per_peering_group"], l4_forwarding_rules_dict,
|
||||
config["limit_names"]["L4"],
|
||||
limits_dict['internal_forwarding_rules_l4_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["l7_forwarding_rules_per_peering_group"], l7_forwarding_rules_dict,
|
||||
config["limit_names"]["L7"],
|
||||
limits_dict['internal_forwarding_rules_l7_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["subnet_ranges_per_peering_group"], subnet_range_dict,
|
||||
config["limit_names"]["SUBNET_RANGES"],
|
||||
limits_dict['number_of_subnet_IP_ranges_ppg_limit'])
|
||||
routes.get_dynamic_routes_ppg(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["dynamic_routes_per_peering_group"], dynamic_routes_dict,
|
||||
limits_dict['number_of_subnet_IP_ranges_ppg_limit'])
|
||||
# Per Project metrics
|
||||
vpc_firewalls.get_firewalls_data(config, metrics_dict, project_quotas_dict,
|
||||
firewalls_dict)
|
||||
# Per Firewall Policy metrics
|
||||
firewall_policies.get_firewal_policies_data(config, metrics_dict,
|
||||
firewall_policies_dict)
|
||||
# Per Network metrics
|
||||
instances.get_gce_instances_data(config, metrics_dict, gce_instance_dict,
|
||||
limits_dict['number_of_instances_limit'])
|
||||
ilb_fwrules.get_forwarding_rules_data(
|
||||
config, metrics_dict, l4_forwarding_rules_dict,
|
||||
limits_dict['internal_forwarding_rules_l4_limit'], "L4")
|
||||
ilb_fwrules.get_forwarding_rules_data(
|
||||
config, metrics_dict, l7_forwarding_rules_dict,
|
||||
limits_dict['internal_forwarding_rules_l7_limit'], "L7")
|
||||
peerings.get_vpc_peering_data(config, metrics_dict,
|
||||
limits_dict['number_of_vpc_peerings_limit'])
|
||||
dynamic_routes_dict = routes.get_dynamic_routes(
|
||||
config, metrics_dict, limits_dict['dynamic_routes_per_network_limit'])
|
||||
|
||||
return 'Function executed successfully'
|
||||
# Per VPC peering group metrics
|
||||
metrics.get_pgg_data(
|
||||
config,
|
||||
metrics_dict["metrics_per_peering_group"]["instance_per_peering_group"],
|
||||
gce_instance_dict, config["limit_names"]["GCE_INSTANCES"],
|
||||
limits_dict['number_of_instances_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["l4_forwarding_rules_per_peering_group"], l4_forwarding_rules_dict,
|
||||
config["limit_names"]["L4"],
|
||||
limits_dict['internal_forwarding_rules_l4_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["l7_forwarding_rules_per_peering_group"], l7_forwarding_rules_dict,
|
||||
config["limit_names"]["L7"],
|
||||
limits_dict['internal_forwarding_rules_l7_ppg_limit'])
|
||||
metrics.get_pgg_data(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["subnet_ranges_per_peering_group"], subnet_range_dict,
|
||||
config["limit_names"]["SUBNET_RANGES"],
|
||||
limits_dict['number_of_subnet_IP_ranges_ppg_limit'])
|
||||
routes.get_dynamic_routes_ppg(
|
||||
config, metrics_dict["metrics_per_peering_group"]
|
||||
["dynamic_routes_per_peering_group"], dynamic_routes_dict,
|
||||
limits_dict['dynamic_routes_per_peering_group_limit'])
|
||||
except Exception as e:
|
||||
print("Error writing metrics")
|
||||
print(e)
|
||||
finally:
|
||||
metrics.flush_series_buffer(config)
|
||||
|
||||
return 'Function execution completed'
|
||||
|
||||
|
||||
if CF_VERSION == "V2":
|
||||
import functions_framework
|
||||
main_http = functions_framework.http(main)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(None, None)
|
||||
main(None, None)
|
|
@ -14,6 +14,17 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
---
|
||||
metrics_per_subnet:
|
||||
ip_usage_per_subnet:
|
||||
usage:
|
||||
name: number_of_ip_used
|
||||
description: Number of used IP addresses in the subnet.
|
||||
utilization:
|
||||
name: ip_addresses_per_subnet_utilization
|
||||
description: Percentage of IP used in the subnet.
|
||||
limit:
|
||||
name: number_of_max_ip
|
||||
description: Number of available IP addresses in the subnet.
|
||||
metrics_per_network:
|
||||
instance_per_network:
|
||||
usage:
|
||||
|
@ -60,7 +71,7 @@ metrics_per_network:
|
|||
name: internal_forwarding_rules_l4_limit
|
||||
description: Number of Internal Forwarding Rules for Internal L4 Load Balancers - limit.
|
||||
values:
|
||||
default_value: 300
|
||||
default_value: 500
|
||||
utilization:
|
||||
name: internal_forwarding_rules_l4_utilization
|
||||
description: Number of Internal Forwarding Rules for Internal L4 Load Balancers - utilization.
|
||||
|
@ -97,7 +108,7 @@ metrics_per_peering_group:
|
|||
name: internal_forwarding_rules_l4_ppg_limit
|
||||
description: Number of Internal Forwarding Rules for Internal L4 Load Balancers per VPC peering group - effective limit.
|
||||
values:
|
||||
default_value: 300
|
||||
default_value: 500
|
||||
utilization:
|
||||
name: internal_forwarding_rules_l4_ppg_utilization
|
||||
description: Number of Internal Forwarding Rules for Internal L4 Load Balancers per VPC peering group - utilization.
|
||||
|
@ -149,3 +160,29 @@ metrics_per_peering_group:
|
|||
utilization:
|
||||
name: dynamic_routes_per_peering_group_utilization
|
||||
description: Number of Dynamic routes per peering group - utilization.
|
||||
metrics_per_project:
|
||||
firewalls:
|
||||
usage:
|
||||
name: firewalls_per_project_vpc_usage
|
||||
description: Number of VPC firewall rules in a project - usage.
|
||||
limit:
|
||||
# Firewalls limit is per project and we get the limit for the GCP quota API in vpc_firewalls.py
|
||||
name: firewalls_per_project_limit
|
||||
description: Number of VPC firewall rules in a project - limit.
|
||||
utilization:
|
||||
name: firewalls_per_project_utilization
|
||||
description: Number of VPC firewall rules in a project - utilization.
|
||||
metrics_per_firewall_policy:
|
||||
firewall_policy_tuples:
|
||||
usage:
|
||||
name: firewall_policy_tuples_per_policy_usage
|
||||
description: Number of tuples in a firewall policy - usage.
|
||||
limit:
|
||||
# This limit is not visibile through Google APIs, set default_value
|
||||
name: firewall_policy_tuples_per_policy_limit
|
||||
description: Number of tuples in a firewall policy - limit.
|
||||
values:
|
||||
default_value: 2000
|
||||
utilization:
|
||||
name: firewall_policy_tuples_per_policy_utilization
|
||||
description: Number of tuples in a firewall policy - utilization.
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from collections import defaultdict
|
||||
from pydoc import doc
|
||||
from collections import defaultdict
|
||||
from google.protobuf import field_mask_pb2
|
||||
from . import metrics, networks, limits
|
||||
|
||||
|
||||
def get_firewall_policies_dict(config: dict):
|
||||
'''
|
||||
Calls the Asset Inventory API to get all Firewall Policies under the GCP organization
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
firewal_policies_dict (dictionary of dictionary): Keys are policy ids, subkeys are policy field values
|
||||
'''
|
||||
|
||||
firewall_policies_dict = defaultdict(int)
|
||||
read_mask = field_mask_pb2.FieldMask()
|
||||
read_mask.FromJsonString('name,versionedResources')
|
||||
|
||||
response = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/FirewallPolicy"],
|
||||
"read_mask": read_mask,
|
||||
})
|
||||
for resource in response:
|
||||
for versioned in resource.versioned_resources:
|
||||
firewall_policy = dict()
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
firewall_policy[field_name] = field_value
|
||||
firewall_policies_dict[firewall_policy['id']] = firewall_policy
|
||||
return firewall_policies_dict
|
||||
|
||||
|
||||
def get_firewal_policies_data(config, metrics_dict, firewall_policies_dict):
|
||||
'''
|
||||
Gets the data for VPC Firewall lorem ipsum
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
|
||||
firewall_policies_dict (dictionary of of dictionary of string: string): Keys are policies ids, subkeys are policies values
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
|
||||
current_tuples_limit = None
|
||||
try:
|
||||
current_tuples_limit = metrics_dict["metrics_per_firewall_policy"][
|
||||
"firewall_policy_tuples"]["limit"]["values"]["default_value"]
|
||||
except Exception:
|
||||
print(
|
||||
f"Could not determine number of tuples metric limit due to missing default value"
|
||||
)
|
||||
if current_tuples_limit < 0:
|
||||
print(
|
||||
f"Could not determine number of tuples metric limit as default value is <= 0"
|
||||
)
|
||||
|
||||
timestamp = time.time()
|
||||
for firewall_policy_key in firewall_policies_dict:
|
||||
firewall_policy = firewall_policies_dict[firewall_policy_key]
|
||||
|
||||
# may either be a org, a folder, or a project
|
||||
# folder and org require to split {folder,organization}\/\w+
|
||||
parent = re.search("(\w+$)", firewall_policy["parent"]).group(
|
||||
1) if "parent" in firewall_policy else re.search(
|
||||
"([\d,a-z,-]+)(\/[\d,a-z,-]+\/firewallPolicies/[\d,a-z,-]*$)",
|
||||
firewall_policy["selfLink"]).group(1)
|
||||
parent_type = re.search("(^\w+)", firewall_policy["parent"]).group(
|
||||
1) if "parent" in firewall_policy else "projects"
|
||||
|
||||
metric_labels = {'parent': parent, 'parent_type': parent_type}
|
||||
|
||||
metric_labels["name"] = firewall_policy[
|
||||
"displayName"] if "displayName" in firewall_policy else firewall_policy[
|
||||
"name"]
|
||||
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_firewall_policy"]
|
||||
[f"firewall_policy_tuples"]["usage"]["name"],
|
||||
firewall_policy['ruleTupleCount'], metric_labels, timestamp=timestamp)
|
||||
if not current_tuples_limit == None and current_tuples_limit > 0:
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_firewall_policy"]
|
||||
[f"firewall_policy_tuples"]["limit"]["name"], current_tuples_limit,
|
||||
metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_firewall_policy"]
|
||||
[f"firewall_policy_tuples"]["utilization"]["name"],
|
||||
firewall_policy['ruleTupleCount'] / current_tuples_limit,
|
||||
metric_labels, timestamp=timestamp)
|
||||
|
||||
print(f"Buffered number tuples per Firewall Policy")
|
|
@ -14,6 +14,8 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from collections import defaultdict
|
||||
from google.protobuf import field_mask_pb2
|
||||
from . import metrics, networks, limits
|
||||
|
@ -35,12 +37,12 @@ def get_forwarding_rules_dict(config, layer: str):
|
|||
|
||||
forwarding_rules_dict = defaultdict(int)
|
||||
|
||||
# TODO: Paging?
|
||||
response = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/ForwardingRule"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
|
||||
for resource in response:
|
||||
|
@ -75,15 +77,17 @@ def get_forwarding_rules_data(config, metrics_dict, forwarding_rules_dict,
|
|||
Returns:
|
||||
None
|
||||
'''
|
||||
for project in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project)
|
||||
|
||||
timestamp = time.time()
|
||||
for project_id in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project_id)
|
||||
|
||||
current_quota_limit = limits.get_quota_current_limit(
|
||||
config, f"projects/{project}", config["limit_names"][layer])
|
||||
config, f"projects/{project_id}", config["limit_names"][layer])
|
||||
|
||||
if current_quota_limit is None:
|
||||
print(
|
||||
f"Could not write {layer} forwarding rules to metric for projects/{project} due to missing quotas"
|
||||
f"Could not determine {layer} forwarding rules to metric for projects/{project_id} due to missing quotas"
|
||||
)
|
||||
continue
|
||||
|
||||
|
@ -95,20 +99,24 @@ def get_forwarding_rules_data(config, metrics_dict, forwarding_rules_dict,
|
|||
usage = 0
|
||||
if net['self_link'] in forwarding_rules_dict:
|
||||
usage = forwarding_rules_dict[net['self_link']]
|
||||
metrics.write_data_to_metric(
|
||||
config, project, usage, metrics_dict["metrics_per_network"]
|
||||
|
||||
metric_labels = {
|
||||
'project': project_id,
|
||||
'network_name': net['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
[f"{layer.lower()}_forwarding_rules_per_network"]["usage"]["name"],
|
||||
net['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, net['limit'], metrics_dict["metrics_per_network"]
|
||||
usage, metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
[f"{layer.lower()}_forwarding_rules_per_network"]["limit"]["name"],
|
||||
net['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, usage / net['limit'],
|
||||
metrics_dict["metrics_per_network"]
|
||||
net['limit'], metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
[f"{layer.lower()}_forwarding_rules_per_network"]["utilization"]
|
||||
["name"], net['network_name'])
|
||||
["name"], usage / net['limit'], metric_labels, timestamp=timestamp)
|
||||
|
||||
print(
|
||||
f"Wrote number of {layer} forwarding rules to metric for projects/{project}"
|
||||
)
|
||||
f"Buffered number of {layer} forwarding rules to metric for projects/{project_id}"
|
||||
)
|
|
@ -14,6 +14,8 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from code import interact
|
||||
from collections import defaultdict
|
||||
from . import metrics, networks, limits
|
||||
|
@ -36,6 +38,7 @@ def get_gce_instance_dict(config: dict):
|
|||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Instance"],
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
for resource in response:
|
||||
for field_name, field_value in resource.additional_attributes.items():
|
||||
|
@ -61,15 +64,16 @@ def get_gce_instances_data(config, metrics_dict, gce_instance_dict, limit_dict):
|
|||
Returns:
|
||||
gce_instance_dict
|
||||
'''
|
||||
|
||||
for project in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project)
|
||||
timestamp = time.time()
|
||||
for project_id in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project_id)
|
||||
|
||||
current_quota_limit = limits.get_quota_current_limit(
|
||||
config, f"projects/{project}", config["limit_names"]["GCE_INSTANCES"])
|
||||
config, f"projects/{project_id}",
|
||||
config["limit_names"]["GCE_INSTANCES"])
|
||||
if current_quota_limit is None:
|
||||
print(
|
||||
f"Could not write number of instances for projects/{project} due to missing quotas"
|
||||
f"Could not determine number of instances for projects/{project_id} due to missing quotas"
|
||||
)
|
||||
|
||||
current_quota_limit_view = metrics.customize_quota_view(current_quota_limit)
|
||||
|
@ -81,15 +85,19 @@ def get_gce_instances_data(config, metrics_dict, gce_instance_dict, limit_dict):
|
|||
if net['self_link'] in gce_instance_dict:
|
||||
usage = gce_instance_dict[net['self_link']]
|
||||
|
||||
metrics.write_data_to_metric(
|
||||
config, project, usage, metrics_dict["metrics_per_network"]
|
||||
["instance_per_network"]["usage"]["name"], net['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, net['limit'], metrics_dict["metrics_per_network"]
|
||||
["instance_per_network"]["limit"]["name"], net['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, usage / net['limit'],
|
||||
metrics_dict["metrics_per_network"]["instance_per_network"]
|
||||
["utilization"]["name"], net['network_name'])
|
||||
metric_labels = {
|
||||
'project': project_id,
|
||||
'network_name': net['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["instance_per_network"]
|
||||
["usage"]["name"], usage, metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["instance_per_network"]
|
||||
["limit"]["name"], net['limit'], metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["instance_per_network"]
|
||||
["utilization"]["name"], usage / net['limit'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
|
||||
print(f"Wrote number of instances to metric for projects/{project}")
|
||||
print(f"Buffered number of instances to metric for projects/{project_id}")
|
||||
|
|
|
@ -14,11 +14,67 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from google.api_core import exceptions
|
||||
from google.cloud import monitoring_v3
|
||||
from . import metrics
|
||||
|
||||
|
||||
def get_quotas_dict(quotas_list):
|
||||
'''
|
||||
Creates a dictionary of quotas from a list, with lower case quota name as keys
|
||||
Parameters:
|
||||
quotas_array (array): array of quotas
|
||||
Returns:
|
||||
quotas_dict (dict): dictionary of quotas
|
||||
'''
|
||||
quota_keys = [q['metric'] for q in quotas_list]
|
||||
quotas_dict = dict()
|
||||
i = 0
|
||||
for key in quota_keys:
|
||||
if ("metric" in quotas_list[i]):
|
||||
del (quotas_list[i]["metric"])
|
||||
quotas_dict[key.lower()] = quotas_list[i]
|
||||
i += 1
|
||||
return quotas_dict
|
||||
|
||||
|
||||
def get_quota_project_limit(config, regions=["global"]):
|
||||
'''
|
||||
Retrieves limit for a specific project quota
|
||||
Parameters:
|
||||
project_link (string): Project link.
|
||||
Returns:
|
||||
quotas (dict): quotas for all selected regions, default 'global'
|
||||
'''
|
||||
try:
|
||||
request = {}
|
||||
quotas = dict()
|
||||
for project in config["monitored_projects"]:
|
||||
quotas[project] = dict()
|
||||
if regions != ["global"]:
|
||||
for region in regions:
|
||||
request = config["clients"]["discovery_client"].compute.regions().get(
|
||||
region=region, project=project)
|
||||
response = request.execute()
|
||||
quotas[project][region] = get_quotas_dict(response['quotas'])
|
||||
else:
|
||||
region = "global"
|
||||
request = config["clients"]["discovery_client"].projects().get(
|
||||
project=project, fields="quotas")
|
||||
response = request.execute()
|
||||
quotas[project][region] = get_quotas_dict(response['quotas'])
|
||||
|
||||
return quotas
|
||||
except exceptions.PermissionDenied as err:
|
||||
print(
|
||||
f"Warning: error reading quotas for {project}. " +
|
||||
f"This can happen if you don't have permissions on the project, for example if the project is in another organization or a Google managed project"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def get_ppg(network_link, limit_dict):
|
||||
'''
|
||||
Checks if this network has a specific limit for a metric, if so, returns that limit, if not, returns the default limit.
|
||||
|
@ -119,6 +175,8 @@ def count_effective_limit(config, project_id, network_dict, usage_metric_name,
|
|||
None
|
||||
'''
|
||||
|
||||
timestamp = time.time()
|
||||
|
||||
if network_dict['peerings'] == []:
|
||||
return
|
||||
|
||||
|
@ -161,11 +219,16 @@ def count_effective_limit(config, project_id, network_dict, usage_metric_name,
|
|||
# Calculates effective limit: Step 4: Find maximum from step 1 and step 3
|
||||
effective_limit = max(limit_step1, limit_step3)
|
||||
utilization = peering_group_usage / effective_limit
|
||||
|
||||
metrics.write_data_to_metric(config, project_id, peering_group_usage,
|
||||
usage_metric_name, network_dict['network_name'])
|
||||
metrics.write_data_to_metric(config, project_id, effective_limit,
|
||||
limit_metric_name, network_dict['network_name'])
|
||||
metrics.write_data_to_metric(config, project_id, utilization,
|
||||
utilization_metric_name,
|
||||
network_dict['network_name'])
|
||||
metric_labels = {
|
||||
'project': project_id,
|
||||
'network_name': network_dict['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(config, usage_metric_name,
|
||||
peering_group_usage, metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(config, limit_metric_name,
|
||||
effective_limit, metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(config, utilization_metric_name,
|
||||
utilization, metric_labels,
|
||||
timestamp=timestamp)
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
from curses import KEY_MARK
|
||||
import re
|
||||
import time
|
||||
import yaml
|
||||
from google.api import metric_pb2 as ga_metric
|
||||
|
@ -21,39 +23,41 @@ from google.cloud import monitoring_v3
|
|||
from . import peerings, limits, networks
|
||||
|
||||
|
||||
def create_metrics(monitoring_project):
|
||||
def create_metrics(monitoring_project, config):
|
||||
'''
|
||||
Creates all Cloud Monitoring custom metrics based on the metric.yaml file
|
||||
|
||||
Parameters:
|
||||
monitoring_project (string): the project where the metrics are written to
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions
|
||||
limits_dict (dictionary of dictionary of string: int): limits_dict[metric_name]: dict[network_name] = limit_value
|
||||
'''
|
||||
client = monitoring_v3.MetricServiceClient()
|
||||
client = config["clients"]["monitoring_client"]
|
||||
existing_metrics = []
|
||||
for desc in client.list_metric_descriptors(name=monitoring_project):
|
||||
existing_metrics.append(desc.type)
|
||||
limits_dict = {}
|
||||
|
||||
with open("metrics.yaml", 'r') as stream:
|
||||
with open("./metrics.yaml", 'r') as stream:
|
||||
try:
|
||||
metrics_dict = yaml.safe_load(stream)
|
||||
|
||||
for metric_list in metrics_dict.values():
|
||||
for metric in metric_list.values():
|
||||
for metric_name, metric in metric_list.items():
|
||||
for sub_metric_key, sub_metric in metric.items():
|
||||
metric_link = f"custom.googleapis.com/{sub_metric['name']}"
|
||||
# If the metric doesn't exist yet, then we create it
|
||||
if metric_link not in existing_metrics:
|
||||
create_metric(sub_metric["name"], sub_metric["description"],
|
||||
monitoring_project)
|
||||
# Parse limits (both default values and network specific ones)
|
||||
if sub_metric_key == "limit":
|
||||
monitoring_project, config)
|
||||
# Parse limits for network and peering group metrics
|
||||
# Subnet level metrics have a different limit: the subnet IP range size
|
||||
if sub_metric_key == "limit" and metric_name != "ip_usage_per_subnet":
|
||||
limits_dict_for_metric = {}
|
||||
for network_link, limit_value in sub_metric["values"].items():
|
||||
limits_dict_for_metric[network_link] = limit_value
|
||||
if "values" in sub_metric:
|
||||
for network_link, limit_value in sub_metric["values"].items():
|
||||
limits_dict_for_metric[network_link] = limit_value
|
||||
limits_dict[sub_metric["name"]] = limits_dict_for_metric
|
||||
|
||||
return metrics_dict, limits_dict
|
||||
|
@ -61,18 +65,18 @@ def create_metrics(monitoring_project):
|
|||
print(exc)
|
||||
|
||||
|
||||
def create_metric(metric_name, description, monitoring_project):
|
||||
def create_metric(metric_name, description, monitoring_project, config):
|
||||
'''
|
||||
Creates a Cloud Monitoring metric based on the parameter given if the metric is not already existing
|
||||
|
||||
Parameters:
|
||||
metric_name (string): Name of the metric to be created
|
||||
description (string): Description of the metric to be created
|
||||
monitoring_project (string): the project where the metrics are written to
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
client = monitoring_v3.MetricServiceClient()
|
||||
client = config["clients"]["monitoring_client"]
|
||||
|
||||
descriptor = ga_metric.MetricDescriptor()
|
||||
descriptor.type = f"custom.googleapis.com/{metric_name}"
|
||||
|
@ -84,32 +88,35 @@ def create_metric(metric_name, description, monitoring_project):
|
|||
print("Created {}.".format(descriptor.name))
|
||||
|
||||
|
||||
def write_data_to_metric(config, monitored_project_id, value, metric_name,
|
||||
network_name):
|
||||
def append_data_to_series_buffer(config, metric_name, metric_value,
|
||||
metric_labels, timestamp=None):
|
||||
'''
|
||||
Writes data to Cloud Monitoring custom metrics.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
monitored_project_id: ID of the project where the resource lives (will be added as a label)
|
||||
value (int): Value for the data point of the metric.
|
||||
metric_name (string): Name of the metric
|
||||
network_name (string): Name of the network (will be added as a label)
|
||||
metric_value (int): Value for the data point of the metric.
|
||||
matric_labels (dictionary of dictionary of string: string): metric labels names and values
|
||||
timestamp (float): seconds since the epoch, in UTC
|
||||
Returns:
|
||||
usage (int): Current usage for that network.
|
||||
limit (int): Current usage for that network.
|
||||
'''
|
||||
client = monitoring_v3.MetricServiceClient()
|
||||
|
||||
# Configurable buffer size to improve performance when writing datapoints to metrics
|
||||
buffer_len = 10
|
||||
|
||||
series = monitoring_v3.TimeSeries()
|
||||
series.metric.type = f"custom.googleapis.com/{metric_name}"
|
||||
series.resource.type = "global"
|
||||
series.metric.labels["network_name"] = network_name
|
||||
series.metric.labels["project"] = monitored_project_id
|
||||
|
||||
now = time.time()
|
||||
seconds = int(now)
|
||||
nanos = int((now - seconds) * 10**9)
|
||||
for label_name in metric_labels:
|
||||
if (metric_labels[label_name] != None):
|
||||
series.metric.labels[label_name] = metric_labels[label_name]
|
||||
|
||||
timestamp = timestamp if timestamp != None else time.time()
|
||||
seconds = int(timestamp)
|
||||
nanos = int((timestamp - seconds) * 10**9)
|
||||
interval = monitoring_v3.TimeInterval(
|
||||
{"end_time": {
|
||||
"seconds": seconds,
|
||||
|
@ -118,24 +125,43 @@ def write_data_to_metric(config, monitored_project_id, value, metric_name,
|
|||
point = monitoring_v3.Point({
|
||||
"interval": interval,
|
||||
"value": {
|
||||
"double_value": value
|
||||
"double_value": metric_value
|
||||
}
|
||||
})
|
||||
series.points = [point]
|
||||
|
||||
# TODO: sometimes this cashes with 'DeadlineExceeded: 504 Deadline expired before operation could complete' error
|
||||
# Implement exponential backoff retries?
|
||||
config["series_buffer"].append(series)
|
||||
if len(config["series_buffer"]) >= buffer_len:
|
||||
flush_series_buffer(config)
|
||||
|
||||
|
||||
def flush_series_buffer(config):
|
||||
'''
|
||||
writes buffered metrics to Google Cloud Monitoring, empties buffer upon failure
|
||||
config (dict): The dict containing config like clients and limits
|
||||
'''
|
||||
try:
|
||||
client.create_time_series(name=config["monitoring_project_link"],
|
||||
time_series=[series])
|
||||
if config["series_buffer"] and len(config["series_buffer"]) > 0:
|
||||
client = config["clients"]["monitoring_client"]
|
||||
client.create_time_series(name=config["monitoring_project_link"],
|
||||
time_series=config["series_buffer"])
|
||||
series_names = [
|
||||
re.search("\/(.+$)", series.metric.type).group(1)
|
||||
for series in config["series_buffer"]
|
||||
]
|
||||
print("Wrote time series: ", series_names)
|
||||
except Exception as e:
|
||||
print("Error while flushing series buffer")
|
||||
print(e)
|
||||
|
||||
config["series_buffer"] = []
|
||||
|
||||
|
||||
def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
|
||||
'''
|
||||
This function gets the usage, limit and utilization per VPC peering group for a specific metric for all projects to be monitored.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
|
||||
|
@ -145,18 +171,18 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
|
|||
Returns:
|
||||
None
|
||||
'''
|
||||
for project in config["monitored_projects"]:
|
||||
network_dict_list = peerings.gather_peering_data(config, project)
|
||||
for project_id in config["monitored_projects"]:
|
||||
network_dict_list = peerings.gather_peering_data(config, project_id)
|
||||
# Network dict list is a list of dictionary (one for each network)
|
||||
# For each network, this dictionary contains:
|
||||
# project_id, network_name, network_id, usage, limit, peerings (list of peered networks)
|
||||
# peerings is a list of dictionary (one for each peered network) and contains:
|
||||
# project_id, network_name, network_id
|
||||
current_quota_limit = limits.get_quota_current_limit(
|
||||
config, f"projects/{project}", limit_metric)
|
||||
config, f"projects/{project_id}", limit_metric)
|
||||
if current_quota_limit is None:
|
||||
print(
|
||||
f"Could not write number of L7 forwarding rules to metric for projects/{project} due to missing quotas"
|
||||
f"Could not determine number of L7 forwarding rules to metric for projects/{project_id} due to missing quotas"
|
||||
)
|
||||
continue
|
||||
|
||||
|
@ -166,10 +192,10 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
|
|||
for network_dict in network_dict_list:
|
||||
if network_dict['network_id'] == 0:
|
||||
print(
|
||||
f"Could not write {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project} due to missing permissions."
|
||||
f"Could not determine {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id} due to missing permissions."
|
||||
)
|
||||
continue
|
||||
network_link = f"https://www.googleapis.com/compute/v1/projects/{project}/global/networks/{network_dict['network_name']}"
|
||||
network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network_dict['network_name']}"
|
||||
|
||||
limit = networks.get_limit_network(network_dict, network_link,
|
||||
current_quota_limit_view, limit_dict)
|
||||
|
@ -194,7 +220,7 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
|
|||
limit_metric)
|
||||
if current_peered_quota_limit is None:
|
||||
print(
|
||||
f"Could not write metrics for peering to projects/{peered_network_dict['project_id']} due to missing quotas"
|
||||
f"Could not determine metrics for peering to projects/{peered_network_dict['project_id']} due to missing quotas"
|
||||
)
|
||||
continue
|
||||
|
||||
|
@ -208,20 +234,19 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
|
|||
peered_network_dict["usage"] = peered_usage
|
||||
peered_network_dict["limit"] = peered_limit
|
||||
|
||||
limits.count_effective_limit(config, project, network_dict,
|
||||
limits.count_effective_limit(config, project_id, network_dict,
|
||||
metric_dict["usage"]["name"],
|
||||
metric_dict["limit"]["name"],
|
||||
metric_dict["utilization"]["name"],
|
||||
limit_dict)
|
||||
print(
|
||||
f"Wrote {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project}"
|
||||
f"Buffered {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id}"
|
||||
)
|
||||
|
||||
|
||||
def customize_quota_view(quota_results):
|
||||
'''
|
||||
Customize the quota output for an easier parsable output.
|
||||
|
||||
Parameters:
|
||||
quota_results (string): Input from get_quota_current_usage or get_quota_current_limit. Contains the Current usage or limit for all networks in that project.
|
||||
Returns:
|
||||
|
@ -235,4 +260,4 @@ def customize_quota_view(quota_results):
|
|||
for val in result.points:
|
||||
quotaViewJson.update({'value': val.value.int64_value})
|
||||
quotaViewList.append(quotaViewJson)
|
||||
return quotaViewList
|
||||
return quotaViewList
|
|
@ -40,6 +40,7 @@ def get_subnet_ranges_dict(config: dict):
|
|||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Subnetwork"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
for resource in response:
|
||||
ranges = 0
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from . import metrics, networks, limits
|
||||
|
||||
|
||||
|
@ -28,40 +30,53 @@ def get_vpc_peering_data(config, metrics_dict, limit_dict):
|
|||
Returns:
|
||||
None
|
||||
'''
|
||||
timestamp = time.time()
|
||||
for project in config["monitored_projects"]:
|
||||
active_vpc_peerings, vpc_peerings = gather_vpc_peerings_data(
|
||||
config, project, limit_dict)
|
||||
|
||||
for peering in active_vpc_peerings:
|
||||
metrics.write_data_to_metric(
|
||||
config, project, peering['active_peerings'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_active_per_network"]
|
||||
["usage"]["name"], peering['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, peering['network_limit'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_active_per_network"]
|
||||
["limit"]["name"], peering['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project,
|
||||
peering['active_peerings'] / peering['network_limit'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_active_per_network"]
|
||||
["utilization"]["name"], peering['network_name'])
|
||||
print("Wrote number of active VPC peerings to custom metric for project:",
|
||||
project)
|
||||
metric_labels = {
|
||||
'project': project,
|
||||
'network_name': peering['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["vpc_peering_active_per_network"]["usage"]["name"],
|
||||
peering['active_peerings'], metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["vpc_peering_active_per_network"]["limit"]["name"],
|
||||
peering['network_limit'], metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["vpc_peering_active_per_network"]["utilization"]["name"],
|
||||
peering['active_peerings'] / peering['network_limit'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
print(
|
||||
"Buffered number of active VPC peerings to custom metric for project:",
|
||||
project)
|
||||
|
||||
for peering in vpc_peerings:
|
||||
metrics.write_data_to_metric(
|
||||
config, project, peering['peerings'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["usage"]["name"], peering['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, peering['network_limit'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["limit"]["name"], peering['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project, peering['peerings'] / peering['network_limit'],
|
||||
metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["utilization"]["name"], peering['network_name'])
|
||||
print("Wrote number of VPC peerings to custom metric for project:", project)
|
||||
metric_labels = {
|
||||
'project': project,
|
||||
'network_name': peering['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["usage"]["name"], peering['peerings'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["limit"]["name"], peering['network_limit'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
|
||||
["utilization"]["name"],
|
||||
peering['peerings'] / peering['network_limit'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
print("Buffered number of VPC peerings to custom metric for project:",
|
||||
project)
|
||||
|
||||
|
||||
def gather_peering_data(config, project_id):
|
||||
|
|
|
@ -37,6 +37,7 @@ def get_routers(config):
|
|||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Router"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
for resource in response:
|
||||
network_link = None
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from collections import defaultdict
|
||||
from . import metrics, networks, limits, peerings, routers
|
||||
|
||||
|
@ -88,16 +90,17 @@ def get_dynamic_routes(config, metrics_dict, limits_dict):
|
|||
routers_dict = routers.get_routers(config)
|
||||
dynamic_routes_dict = defaultdict(int)
|
||||
|
||||
for project_id in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project_id)
|
||||
timestamp = time.time()
|
||||
for project in config["monitored_projects"]:
|
||||
network_dict = networks.get_networks(config, project)
|
||||
|
||||
for network in network_dict:
|
||||
sum_routes = get_routes_for_network(config, network['self_link'],
|
||||
project_id, routers_dict)
|
||||
dynamic_routes_dict[network['self_link']] = sum_routes
|
||||
for net in network_dict:
|
||||
sum_routes = get_routes_for_network(config, net['self_link'], project,
|
||||
routers_dict)
|
||||
dynamic_routes_dict[net['self_link']] = sum_routes
|
||||
|
||||
if network['self_link'] in limits_dict:
|
||||
limit = limits_dict[network['self_link']]
|
||||
if net['self_link'] in limits_dict:
|
||||
limit = limits_dict[net['self_link']]
|
||||
else:
|
||||
if 'default_value' in limits_dict:
|
||||
limit = limits_dict['default_value']
|
||||
|
@ -106,21 +109,21 @@ def get_dynamic_routes(config, metrics_dict, limits_dict):
|
|||
break
|
||||
|
||||
utilization = sum_routes / limit
|
||||
metric_labels = {'project': project, 'network_name': net['network_name']}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["usage"]["name"], sum_routes,
|
||||
metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["limit"]["name"], limit, metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["utilization"]["name"], utilization,
|
||||
metric_labels, timestamp=timestamp)
|
||||
|
||||
metrics.write_data_to_metric(
|
||||
config, project_id, sum_routes, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["usage"]["name"],
|
||||
network['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project_id, limit, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["limit"]["name"],
|
||||
network['network_name'])
|
||||
metrics.write_data_to_metric(
|
||||
config, project_id, utilization, metrics_dict["metrics_per_network"]
|
||||
["dynamic_routes_per_network"]["utilization"]["name"],
|
||||
network['network_name'])
|
||||
|
||||
print("Wrote metrics for dynamic routes for VPCs in project", project_id)
|
||||
print("Buffered metrics for dynamic routes for VPCs in project", project)
|
||||
|
||||
return dynamic_routes_dict
|
||||
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from . import metrics
|
||||
from google.protobuf import field_mask_pb2
|
||||
from google.protobuf.json_format import MessageToDict
|
||||
import ipaddress
|
||||
|
||||
|
||||
def get_all_subnets(config):
|
||||
'''
|
||||
Returns a dictionary with subnet level informations (such as IP utilization)
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
subnet_dict (dictionary of String: dictionary): Key is the project_id, value is a nested dictionary with subnet_region/subnet_name as the key.
|
||||
'''
|
||||
subnet_dict = {}
|
||||
read_mask = field_mask_pb2.FieldMask()
|
||||
read_mask.FromJsonString('name,versionedResources')
|
||||
|
||||
response = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ['compute.googleapis.com/Subnetwork'],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
|
||||
for asset in response:
|
||||
for versioned in asset.versioned_resources:
|
||||
subnet_name = ""
|
||||
network_name = ""
|
||||
project_id = ""
|
||||
ip_cidr_range = ""
|
||||
subnet_region = ""
|
||||
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
if field_name == 'name':
|
||||
subnet_name = field_value
|
||||
elif field_name == 'network':
|
||||
# Network self link format:
|
||||
# "https://www.googleapis.com/compute/v1/projects/<PROJECT_ID>/global/networks/<NETWORK_NAME>"
|
||||
project_id = field_value.split('/')[6]
|
||||
network_name = field_value.split('/')[-1]
|
||||
elif field_name == 'ipCidrRange':
|
||||
ip_cidr_range = field_value
|
||||
elif field_name == 'region':
|
||||
subnet_region = field_value.split('/')[-1]
|
||||
|
||||
net = ipaddress.ip_network(ip_cidr_range)
|
||||
# Note that 4 IP addresses are reserved by GCP in all subnets
|
||||
# Source: https://cloud.google.com/vpc/docs/subnets#reserved_ip_addresses_in_every_subnet
|
||||
total_ip_addresses = int(net.num_addresses) - 4
|
||||
|
||||
if project_id not in subnet_dict:
|
||||
subnet_dict[project_id] = {}
|
||||
subnet_dict[project_id][f"{subnet_region}/{subnet_name}"] = {
|
||||
'name': subnet_name,
|
||||
'region': subnet_region,
|
||||
'ip_cidr_range': ip_cidr_range,
|
||||
'total_ip_addresses': total_ip_addresses,
|
||||
'used_ip_addresses': 0,
|
||||
'network_name': network_name
|
||||
}
|
||||
|
||||
return subnet_dict
|
||||
|
||||
|
||||
def compute_subnet_utilization(config, all_subnets_dict):
|
||||
'''
|
||||
Counts resources (VMs, ILBs, reserved IPs) using private IPs in the different subnets.
|
||||
Parameters:
|
||||
config (dict): Dict containing config like clients and limits
|
||||
all_subnets_dict (dict): Dict containing the information for each subnets in the GCP organization
|
||||
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
read_mask = field_mask_pb2.FieldMask()
|
||||
read_mask.FromJsonString('name,versionedResources')
|
||||
|
||||
response_vm = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Instance"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
|
||||
# Counting IP addresses for GCE instances (VMs)
|
||||
for asset in response_vm:
|
||||
for versioned in asset.versioned_resources:
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
# TODO: Handle multi-NIC
|
||||
if field_name == 'networkInterfaces':
|
||||
response_dict = MessageToDict(list(field_value._pb)[0])
|
||||
# Subnet self link:
|
||||
# https://www.googleapis.com/compute/v1/projects/<project_id>/regions/<subnet_region>/subnetworks/<subnet_name>
|
||||
subnet_region = response_dict['subnetwork'].split('/')[-3]
|
||||
subnet_name = response_dict['subnetwork'].split('/')[-1]
|
||||
# Network self link:
|
||||
# https://www.googleapis.com/compute/v1/projects/<project_id>/global/networks/<network_name>
|
||||
project_id = response_dict['network'].split('/')[6]
|
||||
network_name = response_dict['network'].split('/')[-1]
|
||||
|
||||
all_subnets_dict[project_id][f"{subnet_region}/{subnet_name}"][
|
||||
'used_ip_addresses'] += 1
|
||||
|
||||
response_ilb = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/ForwardingRule"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
|
||||
# Counting IP addresses for GCE Internal Load Balancers
|
||||
for asset in response_ilb:
|
||||
internal = False
|
||||
psc = False
|
||||
project_id = ''
|
||||
subnet_name = ''
|
||||
subnet_region = ''
|
||||
address = ''
|
||||
for versioned in asset.versioned_resources:
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
if 'loadBalancingScheme' in field_name and field_value in ['INTERNAL', 'INTERNAL_MANAGED']:
|
||||
internal = True
|
||||
# We want to count only accepted PSC endpoint Forwarding Rule
|
||||
# If the PSC endpoint Forwarding Rule is pending, we will count it in the reserved IP addresses
|
||||
elif field_name == 'pscConnectionStatus' and field_value == 'ACCEPTED':
|
||||
psc = True
|
||||
elif field_name == 'IPAddress':
|
||||
address = field_value
|
||||
elif field_name == 'network':
|
||||
project_id = field_value.split('/')[6]
|
||||
elif 'subnetwork' in field_name:
|
||||
subnet_name = field_value.split('/')[-1]
|
||||
subnet_region = field_value.split('/')[-3]
|
||||
|
||||
if internal:
|
||||
all_subnets_dict[project_id][f"{subnet_region}/{subnet_name}"][
|
||||
'used_ip_addresses'] += 1
|
||||
elif psc:
|
||||
# PSC endpoint asset doesn't contain the subnet information in Asset Inventory
|
||||
# We need to find the correct subnet with IP address matching
|
||||
ip_address = ipaddress.ip_address(address)
|
||||
for subnet_key, subnet_dict in all_subnets_dict[project_id].items():
|
||||
if ip_address in ipaddress.ip_network(subnet_dict['ip_cidr_range']):
|
||||
all_subnets_dict[project_id][subnet_key]['used_ip_addresses'] += 1
|
||||
|
||||
response_reserved_ips = config["clients"][
|
||||
"asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Address"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
|
||||
# Counting IP addresses for GCE Reserved IPs (ex: PSC, Cloud DNS Inbound policies, reserved GCE IPs)
|
||||
for asset in response_reserved_ips:
|
||||
purpose = ""
|
||||
status = ""
|
||||
project_id = ""
|
||||
network_name = ""
|
||||
subnet_name = ""
|
||||
subnet_region = ""
|
||||
address = ""
|
||||
prefixLength = ""
|
||||
for versioned in asset.versioned_resources:
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
if field_name == 'purpose':
|
||||
purpose = field_value
|
||||
elif field_name == 'region':
|
||||
subnet_region = field_value.split('/')[-1]
|
||||
elif field_name == 'status':
|
||||
status = field_value
|
||||
elif field_name == 'address':
|
||||
address = field_value
|
||||
elif field_name == 'network':
|
||||
network_name = field_value.split('/')[-1]
|
||||
project_id = field_value.split('/')[6]
|
||||
elif field_name == 'subnetwork':
|
||||
subnet_name = field_value.split('/')[-1]
|
||||
project_id = field_value.split('/')[6]
|
||||
elif field_name == 'prefixLength':
|
||||
prefixLength = field_value
|
||||
|
||||
# Rserved IP addresses for GCE instances or PSC Forwarding Rule PENDING state
|
||||
if purpose == "GCE_ENDPOINT" and status == "RESERVED":
|
||||
all_subnets_dict[project_id][f"{subnet_region}/{subnet_name}"][
|
||||
'used_ip_addresses'] += 1
|
||||
# Cloud DNS inbound policy
|
||||
elif purpose == "DNS_RESOLVER":
|
||||
all_subnets_dict[project_id][f"{subnet_region}/{subnet_name}"][
|
||||
'used_ip_addresses'] += 1
|
||||
# PSA Range for Cloud SQL, MemoryStore, etc.
|
||||
elif purpose == "VPC_PEERING":
|
||||
# TODO: PSA range to be handled later
|
||||
# print("PSA range to be handled later:", address, prefixLength, network_name)
|
||||
continue
|
||||
|
||||
|
||||
def get_subnets(config, metrics_dict):
|
||||
'''
|
||||
Writes all subnet metrics to custom metrics.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
|
||||
all_subnets_dict = get_all_subnets(config)
|
||||
# Updates all_subnets_dict with the IP utilization info
|
||||
compute_subnet_utilization(config, all_subnets_dict)
|
||||
|
||||
timestamp = time.time()
|
||||
for project_id in config["monitored_projects"]:
|
||||
if project_id not in all_subnets_dict:
|
||||
continue
|
||||
for subnet_dict in all_subnets_dict[project_id].values():
|
||||
ip_utilization = 0
|
||||
if subnet_dict['used_ip_addresses'] > 0:
|
||||
ip_utilization = subnet_dict['used_ip_addresses'] / subnet_dict[
|
||||
'total_ip_addresses']
|
||||
|
||||
# Building unique identifier with subnet region/name
|
||||
subnet_id = f"{subnet_dict['region']}/{subnet_dict['name']}"
|
||||
metric_labels = {
|
||||
'project': project_id,
|
||||
'network_name': subnet_dict['network_name'],
|
||||
'subnet_id': subnet_id
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_subnet"]["ip_usage_per_subnet"]
|
||||
["usage"]["name"], subnet_dict['used_ip_addresses'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_subnet"]["ip_usage_per_subnet"]
|
||||
["limit"]["name"], subnet_dict['total_ip_addresses'], metric_labels,
|
||||
timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_subnet"]["ip_usage_per_subnet"]
|
||||
["utilization"]["name"], ip_utilization, metric_labels,
|
||||
timestamp=timestamp)
|
||||
|
||||
print("Buffered metrics for subnet ip utilization for VPCs in project",
|
||||
project_id)
|
|
@ -0,0 +1,122 @@
|
|||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from collections import defaultdict
|
||||
from pydoc import doc
|
||||
from collections import defaultdict
|
||||
from google.protobuf import field_mask_pb2
|
||||
from . import metrics, networks, limits
|
||||
|
||||
|
||||
def get_firewalls_dict(config: dict):
|
||||
'''
|
||||
Calls the Asset Inventory API to get all VPC Firewall Rules under the GCP organization.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
Returns:
|
||||
firewalls_dict (dictionary of dictionary: int): Keys are projects, subkeys are networks, values count #of VPC Firewall Rules
|
||||
'''
|
||||
|
||||
firewalls_dict = defaultdict(int)
|
||||
read_mask = field_mask_pb2.FieldMask()
|
||||
read_mask.FromJsonString('name,versionedResources')
|
||||
|
||||
response = config["clients"]["asset_client"].search_all_resources(
|
||||
request={
|
||||
"scope": f"organizations/{config['organization']}",
|
||||
"asset_types": ["compute.googleapis.com/Firewall"],
|
||||
"read_mask": read_mask,
|
||||
"page_size": config["page_size"],
|
||||
})
|
||||
for resource in response:
|
||||
project_id = re.search("(compute.googleapis.com/projects/)([\w\-\d]+)",
|
||||
resource.name).group(2)
|
||||
network_name = ""
|
||||
for versioned in resource.versioned_resources:
|
||||
for field_name, field_value in versioned.resource.items():
|
||||
if field_name == "network":
|
||||
network_name = re.search("[a-z0-9\-]*$", field_value).group(0)
|
||||
firewalls_dict[project_id] = defaultdict(
|
||||
int
|
||||
) if not project_id in firewalls_dict else firewalls_dict[project_id]
|
||||
firewalls_dict[project_id][
|
||||
network_name] = 1 if not network_name in firewalls_dict[
|
||||
project_id] else firewalls_dict[project_id][network_name] + 1
|
||||
break
|
||||
break
|
||||
return firewalls_dict
|
||||
|
||||
|
||||
def get_firewalls_data(config, metrics_dict, project_quotas_dict,
|
||||
firewalls_dict):
|
||||
'''
|
||||
Gets the data for VPC Firewall Rules per VPC Network and writes it to the metric defined in vpc_firewalls_metric.
|
||||
|
||||
Parameters:
|
||||
config (dict): The dict containing config like clients and limits
|
||||
metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
|
||||
project_quotas_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value.
|
||||
firewalls_dict (dictionary of of dictionary of string: string): Keys are projects, subkeys are networks, values count #of VPC Firewall Rules
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
|
||||
timestamp = time.time()
|
||||
for project_id in config["monitored_projects"]:
|
||||
|
||||
current_quota_limit = project_quotas_dict[project_id]['global']["firewalls"]
|
||||
if current_quota_limit is None:
|
||||
print(
|
||||
f"Could not determine VPC firewal rules to metric for projects/{project_id} due to missing quotas"
|
||||
)
|
||||
continue
|
||||
|
||||
network_dict = networks.get_networks(config, project_id)
|
||||
|
||||
project_usage = 0
|
||||
for net in network_dict:
|
||||
usage = 0
|
||||
if project_id in firewalls_dict and net['network_name'] in firewalls_dict[
|
||||
project_id]:
|
||||
usage = firewalls_dict[project_id][net['network_name']]
|
||||
project_usage += usage
|
||||
metric_labels = {
|
||||
'project': project_id,
|
||||
'network_name': net['network_name']
|
||||
}
|
||||
metrics.append_data_to_series_buffer(
|
||||
config,
|
||||
metrics_dict["metrics_per_project"][f"firewalls"]["usage"]["name"],
|
||||
usage, metric_labels, timestamp=timestamp)
|
||||
|
||||
metric_labels = {'project': project_id}
|
||||
# firewall quotas are per project, not per single VPC
|
||||
metrics.append_data_to_series_buffer(
|
||||
config,
|
||||
metrics_dict["metrics_per_project"][f"firewalls"]["limit"]["name"],
|
||||
current_quota_limit['limit'], metric_labels, timestamp=timestamp)
|
||||
metrics.append_data_to_series_buffer(
|
||||
config, metrics_dict["metrics_per_project"][f"firewalls"]["utilization"]
|
||||
["name"], project_usage / current_quota_limit['limit']
|
||||
if current_quota_limit['limit'] != 0 else 0, metric_labels,
|
||||
timestamp=timestamp)
|
||||
print(
|
||||
f"Buffered number of VPC Firewall Rules to metric for projects/{project_id}"
|
||||
)
|
|
@ -7,4 +7,5 @@ google-cloud-monitoring==2.9.1
|
|||
oauth2client==4.1.3
|
||||
google-api-core==2.7.0
|
||||
PyYAML==6.0
|
||||
google-cloud-asset==3.8.1
|
||||
google-cloud-asset==3.8.1
|
||||
functions-framework==3.*
|
|
@ -1,385 +1,609 @@
|
|||
{
|
||||
"displayName": "quotas_utilization",
|
||||
"category": "CUSTOM",
|
||||
"displayName": "quotas_utilization_updated",
|
||||
"mosaicLayout": {
|
||||
"columns": 12,
|
||||
"tiles": [
|
||||
{
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "internal_forwarding_rules_l4_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "1800s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 0
|
||||
},
|
||||
{
|
||||
"yPos": 12,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "internal_forwarding_rules_l7_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 12
|
||||
},
|
||||
{
|
||||
"yPos": 8,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "number_of_instances_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 8
|
||||
},
|
||||
{
|
||||
"xPos": 6,
|
||||
"yPos": 4,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "number_of_vpc_peerings_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_vpc_peerings_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_vpc_peerings_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 4
|
||||
},
|
||||
{
|
||||
"yPos": 4,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "number_of_active_vpc_peerings_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_active_vpc_peerings_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_active_vpc_peerings_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_INTERPOLATE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 4
|
||||
},
|
||||
{
|
||||
"yPos": 16,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "subnet_IP_ranges_ppg_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_subnet_IP_ranges_ppg_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_subnet_IP_ranges_ppg_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 16
|
||||
},
|
||||
{
|
||||
"xPos": 6,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "internal_forwarding_rules_l4_ppg_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_ppg_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_ppg_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 0
|
||||
},
|
||||
{
|
||||
"xPos": 6,
|
||||
"yPos": 12,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "internal_forwarding_rules_l7_ppg_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_ppg_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_ppg_utilization\" resource.type=\"global\"",
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 12
|
||||
},
|
||||
{
|
||||
"xPos": 6,
|
||||
"yPos": 8,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "number_of_instances_ppg_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_ppg_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "3600s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_NEXT_OLDER"
|
||||
}
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_ppg_utilization\" resource.type=\"global\""
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "3600s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 8
|
||||
},
|
||||
{
|
||||
"xPos": 6,
|
||||
"yPos": 16,
|
||||
"width": 6,
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "dynamic_routes_per_network_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"filter": "metric.type=\"custom.googleapis.com/dynamic_routes_per_network_utilization\" resource.type=\"global\"",
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
},
|
||||
"secondaryAggregation": {
|
||||
"alignmentPeriod": "60s"
|
||||
}
|
||||
"filter": "metric.type=\"custom.googleapis.com/dynamic_routes_per_network_utilization\" resource.type=\"global\""
|
||||
}
|
||||
},
|
||||
"plotType": "LINE",
|
||||
"minAlignmentPeriod": "60s",
|
||||
"targetAxis": "Y1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
},
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 20
|
||||
},
|
||||
{
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "firewalls_per_project_vpc_usage",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_SUM",
|
||||
"groupByFields": [
|
||||
"metric.label.\"project\""
|
||||
],
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/firewalls_per_project_vpc_usage\" resource.type=\"global\""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 24
|
||||
},
|
||||
{
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "firewalls_per_project_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_MAX",
|
||||
"groupByFields": [
|
||||
"metric.label.\"project\""
|
||||
],
|
||||
"perSeriesAligner": "ALIGN_MAX"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/firewalls_per_project_utilization\" resource.type=\"global\""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 24
|
||||
},
|
||||
{
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "tuples_per_firewall_policy_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/firewall_policy_tuples_per_policy_utilization\" resource.type=\"global\""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 0,
|
||||
"yPos": 28
|
||||
},
|
||||
{
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "ip_addresses_per_subnet_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/ip_addresses_per_subnet_utilization\" resource.type=\"global\""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 16
|
||||
},
|
||||
{
|
||||
"height": 4,
|
||||
"widget": {
|
||||
"title": "dynamic_routes_ppg_utilization",
|
||||
"xyChart": {
|
||||
"chartOptions": {
|
||||
"mode": "COLOR"
|
||||
},
|
||||
"dataSets": [
|
||||
{
|
||||
"minAlignmentPeriod": "60s",
|
||||
"plotType": "LINE",
|
||||
"targetAxis": "Y1",
|
||||
"timeSeriesQuery": {
|
||||
"apiSource": "DEFAULT_CLOUD",
|
||||
"timeSeriesFilter": {
|
||||
"aggregation": {
|
||||
"alignmentPeriod": "60s",
|
||||
"crossSeriesReducer": "REDUCE_NONE",
|
||||
"perSeriesAligner": "ALIGN_MEAN"
|
||||
},
|
||||
"filter": "metric.type=\"custom.googleapis.com/dynamic_routes_per_peering_group_utilization\" resource.type=\"global\""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timeshiftDuration": "0s",
|
||||
"yAxis": {
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 6,
|
||||
"xPos": 6,
|
||||
"yPos": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
*/
|
||||
|
||||
locals {
|
||||
project_id_list = toset(var.monitored_projects_list)
|
||||
projects = join(",", local.project_id_list)
|
||||
project_ids = toset(var.monitored_projects_list)
|
||||
projects = join(",", local.project_ids)
|
||||
|
||||
folder_ids = toset(var.monitored_folders_list)
|
||||
folders = join(",", local.folder_ids)
|
||||
monitoring_project = var.monitoring_project_id == "" ? module.project-monitoring[0].project_id : var.monitoring_project_id
|
||||
}
|
||||
|
||||
|
@ -47,6 +50,8 @@ module "service-account-function" {
|
|||
# Required IAM permissions for this service account are:
|
||||
# 1) compute.networkViewer on projects to be monitored (I gave it at organization level for now for simplicity)
|
||||
# 2) monitoring viewer on the projects to be monitored (I gave it at organization level for now for simplicity)
|
||||
# 3) if you dont have permission to create service account and assign permission at organization Level, move these 3 roles to project level.
|
||||
|
||||
iam_organization_roles = {
|
||||
"${var.organization_id}" = [
|
||||
"roles/compute.networkViewer",
|
||||
|
@ -57,16 +62,32 @@ module "service-account-function" {
|
|||
|
||||
iam_project_roles = {
|
||||
"${local.monitoring_project}" = [
|
||||
"roles/monitoring.metricWriter"
|
||||
"roles/monitoring.metricWriter",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module "service-account-scheduler" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
project_id = local.monitoring_project
|
||||
name = "sa-scheduler"
|
||||
generate_key = false
|
||||
|
||||
iam_project_roles = {
|
||||
"${local.monitoring_project}" = [
|
||||
"roles/run.invoker",
|
||||
"roles/cloudfunctions.invoker"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
################################################
|
||||
# Cloud Function configuration (& Scheduler) #
|
||||
# you can comment out the pub/sub call in case of 2nd generation function
|
||||
################################################
|
||||
|
||||
module "pubsub" {
|
||||
|
||||
source = "../../../modules/pubsub"
|
||||
project_id = local.monitoring_project
|
||||
name = "network-dashboard-pubsub"
|
||||
|
@ -78,6 +99,7 @@ module "pubsub" {
|
|||
}
|
||||
|
||||
resource "google_cloud_scheduler_job" "job" {
|
||||
count = var.cf_version == "V2" ? 0 : 1
|
||||
project = local.monitoring_project
|
||||
region = var.region
|
||||
name = "network-dashboard-scheduler"
|
||||
|
@ -89,8 +111,28 @@ resource "google_cloud_scheduler_job" "job" {
|
|||
data = base64encode("test")
|
||||
}
|
||||
}
|
||||
#http trigger for 2nd generation function
|
||||
|
||||
resource "google_cloud_scheduler_job" "job_httptrigger" {
|
||||
count = var.cf_version == "V2" ? 1 : 0
|
||||
project = local.monitoring_project
|
||||
region = var.region
|
||||
name = "network-dashboard-scheduler"
|
||||
schedule = var.schedule_cron
|
||||
time_zone = "UTC"
|
||||
|
||||
http_target {
|
||||
http_method = "POST"
|
||||
uri = module.cloud-function.uri
|
||||
|
||||
oidc_token {
|
||||
service_account_email = module.service-account-scheduler.email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "cloud-function" {
|
||||
v2 = var.cf_version == "V2"
|
||||
source = "../../../modules/cloud-function"
|
||||
project_id = local.monitoring_project
|
||||
name = "network-dashboard-cloud-function"
|
||||
|
@ -99,6 +141,7 @@ module "cloud-function" {
|
|||
location = var.region
|
||||
lifecycle_delete_age = null
|
||||
}
|
||||
region = var.region
|
||||
|
||||
bundle_config = {
|
||||
source_dir = "cloud-function"
|
||||
|
@ -107,20 +150,25 @@ module "cloud-function" {
|
|||
}
|
||||
|
||||
function_config = {
|
||||
timeout = 180
|
||||
timeout = 480 # Timeout in seconds, increase it if your CF timeouts and use v2 if > 9 minutes.
|
||||
entry_point = "main"
|
||||
runtime = "python39"
|
||||
instances = 1
|
||||
memory = 256
|
||||
memory = 256 # Memory in MB
|
||||
|
||||
}
|
||||
|
||||
environment_variables = {
|
||||
MONITORED_PROJECTS_LIST = local.projects
|
||||
MONITORED_FOLDERS_LIST = local.folders
|
||||
MONITORING_PROJECT_ID = local.monitoring_project
|
||||
ORGANIZATION_ID = var.organization_id
|
||||
CF_VERSION = var.cf_version
|
||||
}
|
||||
|
||||
service_account = module.service-account-function.email
|
||||
# Internal only doesn't seem to work with CFv2:
|
||||
ingress_settings = var.cf_version == "V2" ? "ALLOW_ALL" : "ALLOW_INTERNAL_ONLY"
|
||||
|
||||
trigger_config = {
|
||||
event = "google.pubsub.topic.publish"
|
||||
|
|
|
@ -30,8 +30,7 @@ module "project-hub" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = [] # defined later
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,10 +40,9 @@ module "vpc-hub" {
|
|||
name = "vpc-hub"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.0.10.0/24"
|
||||
name = "subnet-hub-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.0.10.0/24"
|
||||
name = "subnet-hub-1"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -58,9 +56,8 @@ module "project-svc-hub" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_service_config = {
|
||||
attach = true
|
||||
host_project = module.project-hub.project_id
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = module.project-hub.project_id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,8 +70,7 @@ module "project-prod" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = [] # defined later
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,10 +80,9 @@ module "vpc-prod" {
|
|||
name = "vpc-prod"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.0.20.0/24"
|
||||
name = "subnet-prod-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.0.20.0/24"
|
||||
name = "subnet-prod-1"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -101,9 +96,8 @@ module "project-svc-prod" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_service_config = {
|
||||
attach = true
|
||||
host_project = module.project-prod.project_id
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = module.project-prod.project_id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,8 +110,7 @@ module "project-dev" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = [] # defined later
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,10 +120,9 @@ module "vpc-dev" {
|
|||
name = "vpc-dev"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.0.30.0/24"
|
||||
name = "subnet-dev-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.0.30.0/24"
|
||||
name = "subnet-dev-1"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -144,9 +136,8 @@ module "project-svc-dev" {
|
|||
services = var.project_vm_services
|
||||
|
||||
shared_vpc_service_config = {
|
||||
attach = true
|
||||
host_project = module.project-dev.project_id
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = module.project-dev.project_id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,38 +14,48 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "organization_id" {
|
||||
description = "The organization id for the associated services"
|
||||
}
|
||||
|
||||
variable "billing_account" {
|
||||
description = "The ID of the billing account to associate this project with"
|
||||
}
|
||||
|
||||
variable "cf_version" {
|
||||
description = "Cloud Function version 2nd Gen or 1st Gen. Possible options: 'V1' or 'V2'.Use CFv2 if your Cloud Function timeouts after 9 minutes. By default it is using CFv1."
|
||||
default = "V1"
|
||||
validation {
|
||||
condition = var.cf_version == "V1" || var.cf_version == "V2"
|
||||
error_message = "The value of cf_version must be either V1 or V2."
|
||||
}
|
||||
}
|
||||
|
||||
variable "monitored_folders_list" {
|
||||
type = list(string)
|
||||
description = "ID of the projects to be monitored (where limits and quotas data will be pulled)"
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "monitored_projects_list" {
|
||||
type = list(string)
|
||||
description = "ID of the projects to be monitored (where limits and quotas data will be pulled)"
|
||||
}
|
||||
|
||||
variable "monitoring_project_id" {
|
||||
description = "Monitoring project where the dashboard will be created and the solution deployed; a project will be created if set to empty string"
|
||||
default = ""
|
||||
}
|
||||
|
||||
|
||||
variable "organization_id" {
|
||||
description = "The organization id for the associated services"
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
description = "Customer name to use as prefix for monitoring project"
|
||||
default = ""
|
||||
}
|
||||
|
||||
# TODO: support folder instead of a list of projects?
|
||||
variable "monitored_projects_list" {
|
||||
type = list(string)
|
||||
description = "ID of the projects to be monitored (where limits and quotas data will be pulled)"
|
||||
}
|
||||
|
||||
variable "schedule_cron" {
|
||||
description = "Cron format schedule to run the Cloud Function. Default is every 5 minutes."
|
||||
default = "*/5 * * * *"
|
||||
}
|
||||
|
||||
variable "project_monitoring_services" {
|
||||
description = "Service APIs enabled in the monitoring project if it will be created."
|
||||
default = [
|
||||
"artifactregistry.googleapis.com",
|
||||
"cloudasset.googleapis.com",
|
||||
"cloudbilling.googleapis.com",
|
||||
"cloudbuild.googleapis.com",
|
||||
|
@ -57,11 +67,17 @@ variable "project_monitoring_services" {
|
|||
"iamcredentials.googleapis.com",
|
||||
"logging.googleapis.com",
|
||||
"monitoring.googleapis.com",
|
||||
"serviceusage.googleapis.com",
|
||||
"run.googleapis.com",
|
||||
"serviceusage.googleapis.com"
|
||||
]
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Region used to deploy the cloud functions and scheduler"
|
||||
default = "europe-west1"
|
||||
}
|
||||
}
|
||||
|
||||
variable "schedule_cron" {
|
||||
description = "Cron format schedule to run the Cloud Function. Default is every 10 minutes."
|
||||
default = "*/10 * * * *"
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,10 +56,9 @@ module "vpc" {
|
|||
name = "image-builder"
|
||||
subnets = [
|
||||
{
|
||||
name = local.compute_subnet_name
|
||||
ip_cidr_range = var.cidrs.image-builder
|
||||
region = var.region
|
||||
secondary_ip_range = null
|
||||
name = local.compute_subnet_name
|
||||
ip_cidr_range = var.cidrs.image-builder
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,25 @@ Regardless of its specific purpose, this blueprint is also useful in showing how
|
|||
|
||||
The solution is designed so that the Cloud Function arguments that control function execution (eg to set which project quotas to monitor) are defined in the Cloud Scheduler payload set in the PubSub message, so that a single function can be used for different configurations by creating more schedules.
|
||||
|
||||
Quota time series are stored using a [custom metric](https://cloud.google.com/monitoring/custom-metrics) with the `custom.googleapis.com/quota/gce` type and [gauge kind](https://cloud.google.com/monitoring/api/v3/kinds-and-types#metric-kinds), tracking the ratio between quota and limit as double to aid in visualization and alerting. Labels are set with the quota name, project id (which may differ from the monitoring workspace projects), value, and limit. This is how they look like in the metrics explorer.
|
||||
Quota time series are stored using [custom metrics](https://cloud.google.com/monitoring/custom-metrics) with metric type for usage, limit and utilization; metric types are named using a common prefix and two tokens joined by a `-` character:
|
||||
|
||||
<img src="explorer.png" width="640px" alt="GCP resource diagram">
|
||||
- `prefix` (custom.googleapis.com/quota/)
|
||||
- `quota name`
|
||||
- `{usage,limit,utilization}`
|
||||
|
||||
The solution also creates a basic monitoring alert policy, to demonstrate how to raise alerts when any of the tracked quota ratios go over a predefined threshold.
|
||||
e.g:
|
||||
|
||||
- `custom.googleapis.com/quota/firewalls_usage`
|
||||
- `custom.googleapis.com/quota/firewalls_limit`
|
||||
- `custom.googleapis.com/quota/firewalls_utilization`
|
||||
|
||||
All custom metrics are associated to the `global` resource type and use [gauge kind](https://cloud.google.com/monitoring/api/v3/kinds-and-types#metric-kinds)
|
||||
|
||||
Labels are set with project id (which may differ from the monitoring workspace projects) and region (quotas that are not region specific are labelled `global`), this is how a usage/limit/utilization triplet looks in in Metrics Explorer
|
||||
|
||||
<img src="explorer.png" width="640px" alt="GCP Metrics Explorer, usage, limit and utilization view sample">
|
||||
|
||||
The solution can also create a basic monitoring alert policy, to demonstrate how to raise alerts when quotas utilization goes over a predefined threshold, to enable it, set variable `alert_create` to true and reapply main.tf after main.py has run at least one and quota monitoring metrics have been creaed.
|
||||
|
||||
## Running the blueprint
|
||||
|
||||
|
@ -28,12 +42,13 @@ Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/c
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [project_id](variables.tf#L35) | Project id that references existing project. | <code>string</code> | ✓ | |
|
||||
| [bundle_path](variables.tf#L17) | Path used to write the intermediate Cloud Function code bundle. | <code>string</code> | | <code>"./bundle.zip"</code> |
|
||||
| [name](variables.tf#L23) | Arbitrary string used to name created resources. | <code>string</code> | | <code>"quota-monitor"</code> |
|
||||
| [project_create](variables.tf#L29) | Create project instead ofusing an existing one. | <code>bool</code> | | <code>false</code> |
|
||||
| [quota_config](variables.tf#L40) | Cloud function configuration. | <code title="object({ filters = list(string) projects = list(string) regions = list(string) })">object({…})</code> | | <code title="{ filters = null projects = null regions = null }">{…}</code> |
|
||||
| [region](variables.tf#L54) | Compute region used in the example. | <code>string</code> | | <code>"europe-west1"</code> |
|
||||
| [schedule_config](variables.tf#L60) | Schedule timer configuration in crontab format. | <code>string</code> | | <code>"0 * * * *"</code> |
|
||||
| [project_id](variables.tf#L41) | Project id that references existing project. | <code>string</code> | ✓ | |
|
||||
| [alert_create](variables.tf#L17) | Enables the creation of a sample monitoring alert, false by default. | <code>bool</code> | | <code>false</code> |
|
||||
| [bundle_path](variables.tf#L23) | Path used to write the intermediate Cloud Function code bundle. | <code>string</code> | | <code>"./bundle.zip"</code> |
|
||||
| [name](variables.tf#L29) | Arbitrary string used to name created resources. | <code>string</code> | | <code>"quota-monitor"</code> |
|
||||
| [project_create](variables.tf#L35) | Create project instead of using an existing one. | <code>bool</code> | | <code>false</code> |
|
||||
| [quota_config](variables.tf#L46) | Cloud function configuration. | <code title="object({ filters = list(string) projects = list(string) regions = list(string) })">object({…})</code> | | <code title="{ filters = null projects = null regions = null }">{…}</code> |
|
||||
| [region](variables.tf#L60) | Compute region used in the example. | <code>string</code> | | <code>"europe-west1"</code> |
|
||||
| [schedule_config](variables.tf#L66) | Schedule timer configuration in crontab format. | <code>string</code> | | <code>"0 * * * *"</code> |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
# 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.
|
||||
|
||||
"""Sync GCE quota usage to Stackdriver for multiple projects.
|
||||
|
||||
This tool fetches global and/or regional quotas from the GCE API for
|
||||
|
@ -25,20 +24,26 @@ import datetime
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import click
|
||||
|
||||
from google.api_core.exceptions import GoogleAPIError
|
||||
from google.api import label_pb2 as ga_label
|
||||
from google.api import metric_pb2 as ga_metric
|
||||
from google.cloud import monitoring_v3
|
||||
|
||||
import googleapiclient.discovery
|
||||
import googleapiclient.errors
|
||||
|
||||
|
||||
_BATCH_SIZE = 5
|
||||
_METRIC_KIND = monitoring_v3.enums.MetricDescriptor.MetricKind.GAUGE
|
||||
_METRIC_TYPE = 'custom.googleapis.com/quota/gce'
|
||||
_METRIC_KIND = ga_metric.MetricDescriptor.MetricKind.GAUGE
|
||||
_METRIC_TYPE_STEM = 'custom.googleapis.com/quota/'
|
||||
|
||||
_USAGE = "usage"
|
||||
_LIMIT = "limit"
|
||||
_UTILIZATION = "utilization"
|
||||
|
||||
|
||||
def _add_series(project_id, series, client=None):
|
||||
|
@ -52,11 +57,11 @@ def _add_series(project_id, series, client=None):
|
|||
instead of obtaining a new one
|
||||
"""
|
||||
client = client or monitoring_v3.MetricServiceClient()
|
||||
project_name = client.project_path(project_id)
|
||||
project_name = client.common_project_path(project_id)
|
||||
if isinstance(series, monitoring_v3.types.TimeSeries):
|
||||
series = [series]
|
||||
try:
|
||||
client.create_time_series(project_name, series)
|
||||
client.create_time_series(name=project_name, time_series=series)
|
||||
except GoogleAPIError as e:
|
||||
raise RuntimeError('Error from monitoring API: %s' % e)
|
||||
|
||||
|
@ -95,7 +100,7 @@ def _fetch_quotas(project, region='global', compute=None):
|
|||
(project, region))
|
||||
|
||||
|
||||
def _get_series(metric_labels, value, metric_type=_METRIC_TYPE, dt=None):
|
||||
def _get_series(metric_labels, value, metric_type, timestamp, dt=None):
|
||||
"""Create a Stackdriver monitoring time series from value and labels.
|
||||
|
||||
Args:
|
||||
|
@ -109,28 +114,45 @@ def _get_series(metric_labels, value, metric_type=_METRIC_TYPE, dt=None):
|
|||
series.resource.type = 'global'
|
||||
for label in metric_labels:
|
||||
series.metric.labels[label] = metric_labels[label]
|
||||
point = series.points.add()
|
||||
point = monitoring_v3.types.Point()
|
||||
point.value.double_value = value
|
||||
point.interval.end_time.FromDatetime(dt or datetime.datetime.utcnow())
|
||||
|
||||
seconds = int(timestamp)
|
||||
nanos = int((timestamp - seconds) * 10**9)
|
||||
interval = monitoring_v3.TimeInterval(
|
||||
{"end_time": {
|
||||
"seconds": seconds,
|
||||
"nanos": nanos
|
||||
}})
|
||||
point.interval = interval
|
||||
|
||||
series.points.append(point)
|
||||
return series
|
||||
|
||||
|
||||
def _quota_to_series(project, region, quota):
|
||||
"""Convert API quota objects to Stackdriver monitoring time series.
|
||||
def _quota_to_series_triplet(project, region, quota):
|
||||
"""Convert API quota objects to three Stackdriver monitoring time series: usage, limit and utilization
|
||||
|
||||
Args:
|
||||
project: set in converted time series labels
|
||||
region: set in converted time series labels
|
||||
quota: quota object received from the GCE API
|
||||
"""
|
||||
labels = dict((k, str(v)) for k, v in quota.items())
|
||||
labels = dict()
|
||||
labels['project'] = project
|
||||
labels['region'] = region
|
||||
|
||||
try:
|
||||
value = quota['usage'] / float(quota['limit'])
|
||||
utilization = quota['usage'] / float(quota['limit'])
|
||||
except ZeroDivisionError:
|
||||
value = 0
|
||||
return _get_series(labels, value)
|
||||
utilization = 0
|
||||
now = time.time()
|
||||
metric_type_prefix = _METRIC_TYPE_STEM + quota['metric'].lower() + '_'
|
||||
return [
|
||||
_get_series(labels, quota['usage'], metric_type_prefix + _USAGE, now),
|
||||
_get_series(labels, quota['limit'], metric_type_prefix + _LIMIT, now),
|
||||
_get_series(labels, utilization, metric_type_prefix + _UTILIZATION, now),
|
||||
]
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@ -178,8 +200,8 @@ def _main(monitoring_project, gce_project=None, gce_region=None, verbose=False,
|
|||
logging.debug('projects %s regions %s', gce_projects, gce_regions)
|
||||
logging.debug('keywords %s', keywords)
|
||||
quotas = []
|
||||
compute = googleapiclient.discovery.build(
|
||||
'compute', 'v1', cache_discovery=False)
|
||||
compute = googleapiclient.discovery.build('compute', 'v1',
|
||||
cache_discovery=False)
|
||||
for project in gce_projects:
|
||||
logging.debug('project %s', project)
|
||||
for region in gce_regions:
|
||||
|
@ -191,8 +213,11 @@ def _main(monitoring_project, gce_project=None, gce_region=None, verbose=False,
|
|||
logging.debug('quota %s', quota)
|
||||
quotas.append((project, region, quota))
|
||||
client, i = monitoring_v3.MetricServiceClient(), 0
|
||||
|
||||
x = len(quotas)
|
||||
while i < len(quotas):
|
||||
series = [_quota_to_series(*q) for q in quotas[i:i + _BATCH_SIZE]]
|
||||
series = sum(
|
||||
[_quota_to_series_triplet(*q) for q in quotas[i:i + _BATCH_SIZE]], [])
|
||||
_add_series(monitoring_project, series, client)
|
||||
i += _BATCH_SIZE
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 149 KiB |
|
@ -106,14 +106,16 @@ resource "google_project_iam_member" "quota_viewer" {
|
|||
member = module.cf.service_account_iam_email
|
||||
}
|
||||
|
||||
|
||||
resource "google_monitoring_alert_policy" "alert_policy" {
|
||||
count = var.alert_create ? 1 : 0
|
||||
project = module.project.project_id
|
||||
display_name = "Quota monitor"
|
||||
combiner = "OR"
|
||||
conditions {
|
||||
display_name = "simple quota threshold"
|
||||
display_name = "simple quota threshold for cpus utilization"
|
||||
condition_threshold {
|
||||
filter = "metric.type=\"custom.googleapis.com/quota/gce\" resource.type=\"global\""
|
||||
filter = "metric.type=\"custom.googleapis.com/quota/cpus_utilization\" resource.type=\"global\""
|
||||
threshold_value = 0.75
|
||||
comparison = "COMPARISON_GT"
|
||||
duration = "0s"
|
||||
|
@ -133,10 +135,11 @@ resource "google_monitoring_alert_policy" "alert_policy" {
|
|||
name = var.name
|
||||
}
|
||||
documentation {
|
||||
content = "GCE quota over threshold."
|
||||
content = "GCE cpus quota over threshold."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resource "random_pet" "random" {
|
||||
length = 1
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "alert_create" {
|
||||
description = "Enables the creation of a sample monitoring alert, false by default."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "bundle_path" {
|
||||
description = "Path used to write the intermediate Cloud Function code bundle."
|
||||
type = string
|
||||
|
@ -27,7 +33,7 @@ variable "name" {
|
|||
}
|
||||
|
||||
variable "project_create" {
|
||||
description = "Create project instead ofusing an existing one."
|
||||
description = "Create project instead of using an existing one."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,9 @@ module "vpc" {
|
|||
name = "vpc"
|
||||
subnets = [
|
||||
{
|
||||
name = "apps"
|
||||
ip_cidr_range = "10.8.32.0/24"
|
||||
region = var.region
|
||||
secondary_ip_range = null
|
||||
name = "apps"
|
||||
ip_cidr_range = "10.8.32.0/24"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -58,10 +58,9 @@ module "landing-vpc" {
|
|||
name = "landing-vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.vpc_config.ip_cidr_range
|
||||
name = "landing-vpc-${var.vpc_config.region}"
|
||||
region = var.vpc_config.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.vpc_config.ip_cidr_range
|
||||
name = "landing-vpc-${var.vpc_config.region}"
|
||||
region = var.vpc_config.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ If `project_create` is left to `null`, the identity performing the deployment ne
|
|||
|
||||
Click on the image below, sign in if required and when the prompt appears, click on “confirm”.
|
||||
|
||||
[<p align="center"> <img alt="Open Cloudshell" width = "300px" src="images/button.png" /> </p>](https://goo.gle/GoCloudSQL)
|
||||
[![Open Cloudshell](images/button.png)](https://goo.gle/GoCloudSQL)
|
||||
|
||||
This will clone the repository to your cloud shell and a screen like this one will appear:
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ locals {
|
|||
local.data_eng_principals_iam,
|
||||
[module.service-account-sql.iam_email]
|
||||
)
|
||||
# compute engeneering
|
||||
# compute engineering
|
||||
"roles/compute.instanceAdmin.v1" = local.data_eng_principals_iam
|
||||
"roles/compute.osLogin" = local.data_eng_principals_iam
|
||||
"roles/compute.viewer" = local.data_eng_principals_iam
|
||||
|
@ -94,9 +94,8 @@ module "project" {
|
|||
]
|
||||
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
|
||||
service_encryption_key_ids = {
|
||||
|
@ -116,10 +115,9 @@ module "vpc" {
|
|||
name = "vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.0.0.0/20"
|
||||
name = "subnet"
|
||||
region = var.regions.primary
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.0.0.0/20"
|
||||
name = "subnet"
|
||||
region = var.regions.primary
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -51,10 +51,9 @@ module "vpc" {
|
|||
name = var.vpc_name
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.vpc_ip_cidr_range
|
||||
name = var.vpc_subnet_name
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.vpc_ip_cidr_range
|
||||
name = var.vpc_subnet_name
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,20 +96,20 @@ service_encryption_keys = {
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [prefix](variables.tf#L81) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L95) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | |
|
||||
| [composer_config](variables.tf#L17) | Composer environemnt configuration. See [attribute reference](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/composer_environment#argument-reference---cloud-composer-2) for details on settings variables. | <code title="object({ environment_size = string software_config = any workloads_config = object({ scheduler = object( { cpu = number memory_gb = number storage_gb = number count = number } ) web_server = object( { cpu = number memory_gb = number storage_gb = number } ) worker = object( { cpu = number memory_gb = number storage_gb = number min_count = number max_count = number } ) }) })">object({…})</code> | | <code title="{ environment_size = "ENVIRONMENT_SIZE_SMALL" software_config = { image_version = "composer-2-airflow-2" env_variables = { FOO = "bar" } } workloads_config = null }">{…}</code> |
|
||||
| [iam_groups_map](variables.tf#L61) | Map of Role => groups to be added on the project. Example: { \"roles/composer.admin\" = [\"group:gcp-data-engineers@example.com\"]}. | <code>map(list(string))</code> | | <code>null</code> |
|
||||
| [network_config](variables.tf#L67) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object({ host_project = string network_self_link = string subnet_self_link = string composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L86) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L100) | Region where instances will be deployed. | <code>string</code> | | <code>"europe-west1"</code> |
|
||||
| [service_encryption_keys](variables.tf#L106) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [prefix](variables.tf#L78) | Unique prefix used for resource names. Not used for project if 'project_create' is null. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L92) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | |
|
||||
| [composer_config](variables.tf#L17) | Composer environment configuration. It accepts only following attributes: `environment_size`, `software_config` and `workloads_config`. See [attribute reference](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/composer_environment#argument-reference---cloud-composer-2) for details on settings variables. | <code title="object({ environment_size = string software_config = any workloads_config = object({ scheduler = object( { cpu = number memory_gb = number storage_gb = number count = number } ) web_server = object( { cpu = number memory_gb = number storage_gb = number } ) worker = object( { cpu = number memory_gb = number storage_gb = number min_count = number max_count = number } ) }) })">object({…})</code> | | <code title="{ environment_size = "ENVIRONMENT_SIZE_SMALL" software_config = { image_version = "composer-2-airflow-2" } workloads_config = null }">{…}</code> |
|
||||
| [iam_groups_map](variables.tf#L58) | Map of Role => groups to be added on the project. Example: { \"roles/composer.admin\" = [\"group:gcp-data-engineers@example.com\"]}. | <code>map(list(string))</code> | | <code>null</code> |
|
||||
| [network_config](variables.tf#L64) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object({ host_project = string network_self_link = string subnet_self_link = string composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L83) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L97) | Reagion where instances will be deployed. | <code>string</code> | | <code>"europe-west1"</code> |
|
||||
| [service_encryption_keys](variables.tf#L103) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use. | <code>map(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [composer_airflow_uri](outputs.tf#L22) | The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.. | |
|
||||
| [composer_dag_gcs](outputs.tf#L17) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | |
|
||||
| [composer_airflow_uri](outputs.tf#L17) | The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.. | |
|
||||
| [composer_dag_gcs](outputs.tf#L22) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -97,9 +97,8 @@ module "project" {
|
|||
]
|
||||
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
|
||||
service_encryption_key_ids = {
|
||||
|
@ -121,7 +120,7 @@ module "vpc" {
|
|||
ip_cidr_range = "10.0.0.0/20"
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
secondary_ip_range = {
|
||||
secondary_ip_ranges = {
|
||||
pods = "10.10.8.0/22"
|
||||
services = "10.10.12.0/24"
|
||||
}
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
output "composer_dag_gcs" {
|
||||
description = "The Cloud Storage prefix of the DAGs for the Cloud Composer environment."
|
||||
value = google_composer_environment.env.config[0].dag_gcs_prefix
|
||||
}
|
||||
|
||||
output "composer_airflow_uri" {
|
||||
description = "The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.."
|
||||
value = google_composer_environment.env.config[0].airflow_uri
|
||||
}
|
||||
|
||||
output "composer_dag_gcs" {
|
||||
description = "The Cloud Storage prefix of the DAGs for the Cloud Composer environment."
|
||||
value = google_composer_environment.env.config[0].dag_gcs_prefix
|
||||
}
|
||||
|
|
|
@ -74,9 +74,8 @@ module "load-project" {
|
|||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
}
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,10 +110,9 @@ module "load-vpc" {
|
|||
name = "${var.prefix}-default"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.10.0.0/24"
|
||||
name = "default"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.10.0.0/24"
|
||||
name = "default"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -92,9 +92,8 @@ module "orch-project" {
|
|||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
}
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +121,7 @@ module "orch-vpc" {
|
|||
ip_cidr_range = "10.10.0.0/24"
|
||||
name = "default"
|
||||
region = var.region
|
||||
secondary_ip_range = {
|
||||
secondary_ip_ranges = {
|
||||
pods = "10.10.8.0/22"
|
||||
services = "10.10.12.0/24"
|
||||
}
|
||||
|
|
|
@ -72,9 +72,8 @@ module "transf-project" {
|
|||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
}
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,10 +134,9 @@ module "transf-vpc" {
|
|||
name = "${var.prefix}-default"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = "10.10.0.0/24"
|
||||
name = "default"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = "10.10.0.0/24"
|
||||
name = "default"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -251,11 +251,11 @@ You can find examples in the `[demo](./demo)` folder.
|
|||
| [folder_id](variables.tf#L53) | Folder to be used for the networking resources in folders/nnnn format. | <code>string</code> | ✓ | |
|
||||
| [organization_domain](variables.tf#L98) | Organization domain. | <code>string</code> | ✓ | |
|
||||
| [prefix](variables.tf#L103) | Unique prefix used for resource names. | <code>string</code> | ✓ | |
|
||||
| [composer_config](variables.tf#L22) | Cloud Composer config. | <code title="object({ node_count = number airflow_version = string env_variables = map(string) })">object({…})</code> | | <code title="{ node_count = 3 airflow_version = "composer-1.17.5-airflow-2.1.4" env_variables = {} }">{…}</code> |
|
||||
| [composer_config](variables.tf#L22) | Cloud Composer config. | <code title="object({ node_count = number airflow_version = string env_variables = map(string) })">object({…})</code> | | <code title="{ node_count = 3 airflow_version = "composer-1-airflow-2" env_variables = {} }">{…}</code> |
|
||||
| [data_catalog_tags](variables.tf#L36) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | <code>map(map(list(string)))</code> | | <code title="{ "3_Confidential" = null "2_Private" = null "1_Sensitive" = null }">{…}</code> |
|
||||
| [data_force_destroy](variables.tf#L47) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | <code>bool</code> | | <code>false</code> |
|
||||
| [groups](variables.tf#L64) | User groups. | <code>map(string)</code> | | <code title="{ data-analysts = "gcp-data-analysts" data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [location](variables.tf#L58) | Location used for multi-regional resources. | <code>string</code> | | <code>"eu"</code> |
|
||||
| [groups](variables.tf#L58) | User groups. | <code>map(string)</code> | | <code title="{ data-analysts = "gcp-data-analysts" data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [location](variables.tf#L68) | Location used for multi-regional resources. | <code>string</code> | | <code>"eu"</code> |
|
||||
| [network_config](variables.tf#L74) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object({ host_project = string network_self_link = string subnet_self_links = object({ load = string transformation = string orchestration = string }) composer_ip_ranges = object({ cloudsql = string gke_master = string web_server = string }) composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</code> | | <code>null</code> |
|
||||
| [project_services](variables.tf#L108) | List of core services enabled on all projects. | <code>list(string)</code> | | <code title="[ "cloudresourcemanager.googleapis.com", "iam.googleapis.com", "serviceusage.googleapis.com", "stackdriver.googleapis.com" ]">[…]</code> |
|
||||
| [project_suffix](variables.tf#L119) | Suffix used only for project ids. | <code>string</code> | | <code>null</code> |
|
||||
|
|
|
@ -55,12 +55,6 @@ variable "folder_id" {
|
|||
type = string
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
description = "Location used for multi-regional resources."
|
||||
type = string
|
||||
default = "eu"
|
||||
}
|
||||
|
||||
variable "groups" {
|
||||
description = "User groups."
|
||||
type = map(string)
|
||||
|
@ -71,6 +65,12 @@ variable "groups" {
|
|||
}
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
description = "Location used for multi-regional resources."
|
||||
type = string
|
||||
default = "eu"
|
||||
}
|
||||
|
||||
variable "network_config" {
|
||||
description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values."
|
||||
type = object({
|
||||
|
|
|
@ -41,7 +41,7 @@ module "project" {
|
|||
"storage-component.googleapis.com"
|
||||
]
|
||||
policy_boolean = {
|
||||
# "constraints/compute.requireOsLogin" = false
|
||||
# "constraints/compute.requireOsLogin" = false
|
||||
# Example of applying a project wide policy, mainly useful for Composer
|
||||
}
|
||||
service_encryption_key_ids = {
|
||||
|
@ -61,10 +61,9 @@ module "vpc" {
|
|||
name = "${var.prefix}-vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.vpc_config.ip_cidr_range
|
||||
name = "${var.prefix}-subnet"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.vpc_config.ip_cidr_range
|
||||
name = "${var.prefix}-subnet"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,8 @@ __Note__: To grant a user a role, take a look at the [Granting and Revoking Acce
|
|||
|
||||
Click on the button below, sign in if required and when the prompt appears, click on “confirm”.
|
||||
|
||||
[<p align="center"> <img alt="Open Cloudshell" width = "250" src="shell_button.png" /> </p>](https://goo.gle/GoDataPipe)
|
||||
|
||||
[![Open Cloudshell](shell_button.png)](https://goo.gle/GoDataPipe)
|
||||
|
||||
This will clone the repository to your cloud shell and a screen like this one will appear:
|
||||
|
||||
|
|
|
@ -125,9 +125,8 @@ module "project" {
|
|||
iam = var.project_create != null ? local.iam : {}
|
||||
iam_additive = var.project_create == null ? local.iam : {}
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,9 @@ module "vpc" {
|
|||
name = "${var.prefix}-vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.vpc_subnet_range
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.vpc_subnet_range
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ module "project" {
|
|||
iam = {}
|
||||
iam_additive = {}
|
||||
shared_vpc_service_config = var.shared_vpc_project_id == null ? null : {
|
||||
attach = true
|
||||
host_project = var.shared_vpc_project_id
|
||||
service_identity_iam = {}
|
||||
attach = true
|
||||
host_project = var.shared_vpc_project_id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,10 +58,9 @@ module "vpc" {
|
|||
name = var.network
|
||||
subnets = var.project_create != null ? [
|
||||
{
|
||||
ip_cidr_range = var.vpc_ip_cidr_range
|
||||
name = var.subnetwork
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.vpc_ip_cidr_range
|
||||
name = var.subnetwork
|
||||
region = var.region
|
||||
}
|
||||
] : []
|
||||
vpc_create = var.project_create != null ? true : false
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# Google Cloud BQ Factory
|
||||
|
||||
This module allows creation and management of BigQuery datasets and views as well as tables by defining them in well formatted `yaml` files.
|
||||
|
||||
Yaml abstraction for BQ can simplify users onboarding and also makes creation of tables easier compared to HCL.
|
||||
|
||||
Subfolders distinguish between views and tables and ensures easier navigation for users.
|
||||
|
||||
This factory is based on the [BQ dataset module](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/bigquery-dataset) which currently only supports tables and views. As soon as external table and materialized view support is added, factory will be enhanced accordingly.
|
||||
|
||||
You can create as many files as you like, the code will loop through it and create the required variables in order to execute everything accordingly.
|
||||
|
||||
## Example
|
||||
|
||||
### Terraform code
|
||||
|
||||
```hcl
|
||||
module "bq" {
|
||||
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/bigquery-dataset"
|
||||
|
||||
for_each = local.output
|
||||
project_id = var.project_id
|
||||
id = each.key
|
||||
views = try(each.value.views, null)
|
||||
tables = try(each.value.tables, null)
|
||||
}
|
||||
# tftest skip
|
||||
```
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
```bash
|
||||
base_folder
|
||||
│
|
||||
├── tables
|
||||
│ ├── table_a.yaml
|
||||
│ ├── table_b.yaml
|
||||
├── views
|
||||
│ ├── view_a.yaml
|
||||
│ ├── view_b.yaml
|
||||
```
|
||||
|
||||
## YAML structure and definition formatting
|
||||
|
||||
### Tables
|
||||
|
||||
Table definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following:
|
||||
|
||||
```yaml
|
||||
|
||||
dataset: # required name of the dataset the table is to be placed in
|
||||
table: # required descriptive name of the table
|
||||
schema: # required schema in JSON FORMAT Example: [{name: "test", type: "STRING"},{name: "test2", type: "INT64"}]
|
||||
labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"}
|
||||
use_legacy_sql: boolean # not required, defaults to false
|
||||
deletion_protection: boolean # not required, defaults to false
|
||||
```
|
||||
|
||||
### Views
|
||||
View definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following:
|
||||
|
||||
```yaml
|
||||
dataset: # required, name of the dataset the view is to be placed in
|
||||
view: # required, descriptive name of the view
|
||||
query: # required, SQL Query for the view in quotes
|
||||
labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"}
|
||||
use_legacy_sql: bool # not required, defaults to false
|
||||
deletion_protection: bool # not required, defaults to false
|
||||
```
|
||||
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [project_id](variables.tf#L27) | Project ID | <code>string</code> | ✓ | |
|
||||
| [tables_dir](variables.tf#L22) | Relative path for the folder storing table data. | <code>string</code> | ✓ | |
|
||||
| [views_dir](variables.tf#L17) | Relative path for the folder storing view data. | <code>string</code> | ✓ | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] add external table support
|
||||
- [ ] add materialized view support
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright 2022 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 {
|
||||
views = {
|
||||
for f in fileset("${var.views_dir}", "**/*.yaml") :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${var.views_dir}/${f}"))
|
||||
}
|
||||
|
||||
tables = {
|
||||
for f in fileset("${var.tables_dir}", "**/*.yaml") :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${var.tables_dir}/${f}"))
|
||||
}
|
||||
|
||||
output = {
|
||||
for dataset in distinct([for v in values(merge(local.views, local.tables)) : v.dataset]) :
|
||||
dataset => {
|
||||
"views" = {
|
||||
for k, v in local.views :
|
||||
v.view => {
|
||||
friendly_name = v.view
|
||||
labels = try(v.labels, null)
|
||||
query = v.query
|
||||
use_legacy_sql = try(v.use_legacy_sql, false)
|
||||
deletion_protection = try(v.deletion_protection, false)
|
||||
}
|
||||
if v.dataset == dataset
|
||||
},
|
||||
"tables" = {
|
||||
for k, v in local.tables :
|
||||
v.table => {
|
||||
friendly_name = v.table
|
||||
labels = try(v.labels, null)
|
||||
options = try(v.options, null)
|
||||
partitioning = try(v.partitioning, null)
|
||||
schema = jsonencode(v.schema)
|
||||
use_legacy_sql = try(v.use_legacy_sql, false)
|
||||
deletion_protection = try(v.deletion_protection, false)
|
||||
}
|
||||
if v.dataset == dataset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "bq" {
|
||||
source = "../../../modules/bigquery-dataset"
|
||||
|
||||
for_each = local.output
|
||||
project_id = var.project_id
|
||||
id = each.key
|
||||
views = try(each.value.views, null)
|
||||
tables = try(each.value.tables, null)
|
||||
}
|
|
@ -14,11 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Output files persistence to local filesystem.
|
||||
|
||||
resource "local_file" "tfvars" {
|
||||
for_each = var.outputs_location == null ? {} : { 1 = 1 }
|
||||
file_permission = "0644"
|
||||
filename = "${pathexpand(var.outputs_location)}/tfvars/00-cicd.auto.tfvars.json"
|
||||
content = jsonencode(local.tfvars)
|
||||
variable "views_dir" {
|
||||
description = "Relative path for the folder storing view data."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "tables_dir" {
|
||||
description = "Relative path for the folder storing table data."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "project_id" {
|
||||
description = "Project ID"
|
||||
type = string
|
||||
|
||||
}
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ module "vpc" {
|
|||
ip_cidr_range = var.subnet_cidr_block
|
||||
name = "subnet"
|
||||
region = var.region
|
||||
secondary_ip_range = {
|
||||
secondary_ip_ranges = {
|
||||
pods = var.pods_cidr_block
|
||||
services = var.services_cidr_block
|
||||
}
|
||||
|
@ -83,31 +83,29 @@ module "nat" {
|
|||
}
|
||||
|
||||
module "cluster" {
|
||||
source = "../../../modules/gke-cluster"
|
||||
project_id = module.project.project_id
|
||||
name = "${local.prefix}cluster"
|
||||
location = var.zone
|
||||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["${var.region}/subnet"]
|
||||
secondary_range_pods = "pods"
|
||||
secondary_range_services = "services"
|
||||
source = "../../../modules/gke-cluster"
|
||||
project_id = module.project.project_id
|
||||
name = "${local.prefix}cluster"
|
||||
location = var.zone
|
||||
vpc_config = {
|
||||
master_ipv4_cidr_block = var.master_cidr_block
|
||||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["${var.region}/subnet"]
|
||||
}
|
||||
private_cluster_config = {
|
||||
enable_private_nodes = true
|
||||
enable_private_endpoint = false
|
||||
master_ipv4_cidr_block = var.master_cidr_block
|
||||
master_global_access = false
|
||||
}
|
||||
workload_identity = true
|
||||
}
|
||||
|
||||
module "cluster_nodepool" {
|
||||
source = "../../../modules/gke-nodepool"
|
||||
project_id = module.project.project_id
|
||||
cluster_name = module.cluster.name
|
||||
location = var.zone
|
||||
name = "nodepool"
|
||||
node_service_account_create = true
|
||||
initial_node_count = 3
|
||||
source = "../../../modules/gke-nodepool"
|
||||
project_id = module.project.project_id
|
||||
cluster_name = module.cluster.name
|
||||
location = var.zone
|
||||
name = "nodepool"
|
||||
service_account = {}
|
||||
node_count = { initial = 3 }
|
||||
}
|
||||
|
||||
module "kms" {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Multi-cluster mesh on GKE (fleet API)
|
||||
|
||||
The following blueprint shows how to create a multi-cluster mesh for two private clusters on GKE. Anthos Service Mesh with automatic control plane management is set up for clusters using the Fleet API. This can only be done if the clusters are in a single project and in the same VPC. In this particular case both clusters having being deployed to different subnets in a shared VPC.
|
||||
The following blueprint shows how to create a multi-cluster mesh for two private clusters on GKE. Anthos Service Mesh with automatic control plane management is set up for clusters using the Fleet API. This can only be done if the clusters are in a single project and in the same VPC. In this particular case both clusters having being deployed to different subnets in a shared VPC.
|
||||
|
||||
The diagram below depicts the architecture of the blueprint.
|
||||
|
||||
|
@ -39,14 +39,26 @@ Once terraform completes do the following:
|
|||
|
||||
ansible-playbook -v playbook.yaml
|
||||
|
||||
|
||||
## Testing the blueprint
|
||||
|
||||
The last two commands executed with Ansible Send requests from a sleep pod to the hello-world service from both clusters. If you see in the output of those two commands responses from alternative versions, everything works as expected.
|
||||
|
||||
Once done testing, you can clean up resources by running `terraform destroy`.
|
||||
|
||||
<!-- TFDOC OPTS files:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Files
|
||||
|
||||
| name | description | modules | resources |
|
||||
|---|---|---|---|
|
||||
| [ansible.tf](./ansible.tf) | Ansible generated files. | | <code>local_file</code> |
|
||||
| [gke.tf](./gke.tf) | GKE cluster and hub resources. | <code>gke-cluster</code> · <code>gke-hub</code> · <code>gke-nodepool</code> | |
|
||||
| [main.tf](./main.tf) | Project resources. | <code>project</code> | |
|
||||
| [variables.tf](./variables.tf) | Module variables. | | |
|
||||
| [vm.tf](./vm.tf) | Management server. | <code>compute-vm</code> | |
|
||||
| [vpc.tf](./vpc.tf) | Networking resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> | |
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Ansible generated files.
|
||||
|
||||
resource "local_file" "vars_file" {
|
||||
content = templatefile("${path.module}/templates/vars.yaml.tpl", {
|
||||
istio_version = var.istio_version
|
||||
region = var.region
|
||||
clusters = keys(var.clusters_config)
|
||||
service_account_email = module.mgmt_server.service_account_email
|
||||
project_id = module.fleet_project.project_id
|
||||
})
|
||||
filename = "${path.module}/ansible/vars/vars.yaml"
|
||||
file_permission = "0666"
|
||||
}
|
||||
|
||||
resource "local_file" "gssh_file" {
|
||||
content = templatefile("${path.module}/templates/gssh.sh.tpl", {
|
||||
project_id = var.mgmt_project_id
|
||||
zone = var.mgmt_server_config.zone
|
||||
})
|
||||
filename = "${path.module}/ansible/gssh.sh"
|
||||
file_permission = "0777"
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description GKE cluster and hub resources.
|
||||
|
||||
module "clusters" {
|
||||
for_each = var.clusters_config
|
||||
source = "../../../modules/gke-cluster"
|
||||
project_id = module.fleet_project.project_id
|
||||
name = each.key
|
||||
location = var.region
|
||||
vpc_config = {
|
||||
network = module.svpc.self_link
|
||||
subnetwork = module.svpc.subnet_self_links["${var.region}/subnet-${each.key}"]
|
||||
master_authorized_ranges = merge({
|
||||
mgmt : var.mgmt_subnet_cidr_block
|
||||
},
|
||||
{ for key, config in var.clusters_config :
|
||||
"pods-${key}" => config.pods_cidr_block if key != each.key
|
||||
})
|
||||
master_ipv4_cidr_block = each.value.master_cidr_block
|
||||
}
|
||||
private_cluster_config = {
|
||||
enable_private_endpoint = true
|
||||
master_global_access = true
|
||||
}
|
||||
release_channel = "REGULAR"
|
||||
labels = {
|
||||
mesh_id = "proj-${module.fleet_project.number}"
|
||||
}
|
||||
}
|
||||
|
||||
module "cluster_nodepools" {
|
||||
for_each = var.clusters_config
|
||||
source = "../../../modules/gke-nodepool"
|
||||
project_id = module.fleet_project.project_id
|
||||
cluster_name = module.clusters[each.key].name
|
||||
location = var.region
|
||||
name = "nodepool-${each.key}"
|
||||
node_count = { initial = 1 }
|
||||
service_account = {}
|
||||
tags = ["${each.key}-node"]
|
||||
}
|
||||
|
||||
module "hub" {
|
||||
source = "../../../modules/gke-hub"
|
||||
project_id = module.fleet_project.project_id
|
||||
clusters = { for k, v in module.clusters : k => v.id }
|
||||
features = {
|
||||
appdevexperience = false
|
||||
configmanagement = false
|
||||
identityservice = false
|
||||
multiclusteringress = null
|
||||
servicemesh = true
|
||||
multiclusterservicediscovery = false
|
||||
}
|
||||
depends_on = [
|
||||
module.fleet_project
|
||||
]
|
||||
}
|
|
@ -14,8 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Project resources.
|
||||
|
||||
locals {
|
||||
np_service_account_iam_email = [for k, v in module.cluster_nodepools : v.service_account_iam_email]
|
||||
np_service_account_iam_email = [
|
||||
for k, v in module.cluster_nodepools : v.service_account_iam_email
|
||||
]
|
||||
}
|
||||
|
||||
module "host_project" {
|
||||
|
@ -24,8 +28,7 @@ module "host_project" {
|
|||
parent = var.parent
|
||||
name = var.host_project_id
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = []
|
||||
enabled = true
|
||||
}
|
||||
services = [
|
||||
"container.googleapis.com"
|
||||
|
@ -91,173 +94,3 @@ module "fleet_project" {
|
|||
disable_dependent_services = true
|
||||
}
|
||||
}
|
||||
|
||||
module "svpc" {
|
||||
source = "../../../modules/net-vpc"
|
||||
project_id = module.host_project.project_id
|
||||
name = "svpc"
|
||||
mtu = 1500
|
||||
subnets = concat([for key, config in var.clusters_config : {
|
||||
ip_cidr_range = config.subnet_cidr_block
|
||||
name = "subnet-${key}"
|
||||
region = var.region
|
||||
secondary_ip_range = {
|
||||
pods = config.pods_cidr_block
|
||||
services = config.services_cidr_block
|
||||
}
|
||||
}], [{
|
||||
ip_cidr_range = var.mgmt_subnet_cidr_block
|
||||
name = "subnet-mgmt"
|
||||
region = var.mgmt_server_config.region
|
||||
secondary_ip_range = null
|
||||
}])
|
||||
}
|
||||
|
||||
module "mgmt_server" {
|
||||
source = "../../../modules/compute-vm"
|
||||
project_id = module.mgmt_project.project_id
|
||||
zone = var.mgmt_server_config.zone
|
||||
name = "mgmt"
|
||||
instance_type = var.mgmt_server_config.instance_type
|
||||
network_interfaces = [{
|
||||
network = module.svpc.self_link
|
||||
subnetwork = module.svpc.subnet_self_links["${var.mgmt_server_config.region}/subnet-mgmt"]
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
service_account_create = true
|
||||
boot_disk = {
|
||||
image = var.mgmt_server_config.image
|
||||
type = var.mgmt_server_config.disk_type
|
||||
size = var.mgmt_server_config.disk_size
|
||||
}
|
||||
}
|
||||
|
||||
module "clusters" {
|
||||
for_each = var.clusters_config
|
||||
source = "../../../modules/gke-cluster"
|
||||
project_id = module.fleet_project.project_id
|
||||
name = each.key
|
||||
location = var.region
|
||||
network = module.svpc.self_link
|
||||
subnetwork = module.svpc.subnet_self_links["${var.region}/subnet-${each.key}"]
|
||||
secondary_range_pods = "pods"
|
||||
secondary_range_services = "services"
|
||||
private_cluster_config = {
|
||||
enable_private_nodes = true
|
||||
enable_private_endpoint = true
|
||||
master_ipv4_cidr_block = each.value.master_cidr_block
|
||||
master_global_access = true
|
||||
}
|
||||
master_authorized_ranges = merge({
|
||||
mgmt : var.mgmt_subnet_cidr_block
|
||||
},
|
||||
{ for key, config in var.clusters_config :
|
||||
"pods-${key}" => config.pods_cidr_block if key != each.key
|
||||
})
|
||||
enable_autopilot = false
|
||||
release_channel = "REGULAR"
|
||||
workload_identity = true
|
||||
labels = {
|
||||
mesh_id = "proj-${module.fleet_project.number}"
|
||||
}
|
||||
}
|
||||
|
||||
module "cluster_nodepools" {
|
||||
for_each = var.clusters_config
|
||||
source = "../../../modules/gke-nodepool"
|
||||
project_id = module.fleet_project.project_id
|
||||
cluster_name = module.clusters[each.key].name
|
||||
location = var.region
|
||||
name = "nodepool-${each.key}"
|
||||
node_service_account_create = true
|
||||
initial_node_count = 1
|
||||
node_machine_type = "e2-standard-4"
|
||||
node_tags = ["${each.key}-node"]
|
||||
}
|
||||
|
||||
module "firewall" {
|
||||
source = "../../../modules/net-vpc-firewall"
|
||||
project_id = module.host_project.project_id
|
||||
network = module.svpc.name
|
||||
custom_rules = merge({ allow-mesh = {
|
||||
description = "Allow "
|
||||
direction = "INGRESS"
|
||||
action = "allow"
|
||||
sources = []
|
||||
ranges = [for k, v in var.clusters_config : v.pods_cidr_block]
|
||||
targets = [for k, v in var.clusters_config : "${k}-node"]
|
||||
use_service_accounts = false
|
||||
rules = [{ protocol = "tcp", ports = null },
|
||||
{ protocol = "udp", ports = null },
|
||||
{ protocol = "icmp", ports = null },
|
||||
{ protocol = "esp", ports = null },
|
||||
{ protocol = "ah", ports = null },
|
||||
{ protocol = "sctp", ports = null }]
|
||||
extra_attributes = {
|
||||
priority = 900
|
||||
}
|
||||
} },
|
||||
{ for k, v in var.clusters_config : "allow-${k}-istio" => {
|
||||
description = "Allow "
|
||||
direction = "INGRESS"
|
||||
action = "allow"
|
||||
sources = []
|
||||
ranges = [v.master_cidr_block]
|
||||
targets = ["${k}-node"]
|
||||
use_service_accounts = false
|
||||
rules = [{ protocol = "tcp", ports = [8080, 15014, 15017] }]
|
||||
extra_attributes = {
|
||||
priority = 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module "nat" {
|
||||
source = "../../../modules/net-cloudnat"
|
||||
project_id = module.host_project.project_id
|
||||
region = var.region
|
||||
name = "nat"
|
||||
router_create = true
|
||||
router_network = module.svpc.name
|
||||
}
|
||||
|
||||
module "hub" {
|
||||
source = "../../../modules/gke-hub"
|
||||
project_id = module.fleet_project.project_id
|
||||
clusters = { for k, v in module.clusters : k => v.id }
|
||||
features = {
|
||||
appdevexperience = false
|
||||
configmanagement = false
|
||||
identityservice = false
|
||||
multiclusteringress = null
|
||||
servicemesh = true
|
||||
multiclusterservicediscovery = false
|
||||
}
|
||||
depends_on = [
|
||||
module.fleet_project
|
||||
]
|
||||
}
|
||||
|
||||
resource "local_file" "vars_file" {
|
||||
content = templatefile("${path.module}/templates/vars.yaml.tpl", {
|
||||
istio_version = var.istio_version
|
||||
region = var.region
|
||||
clusters = keys(var.clusters_config)
|
||||
service_account_email = module.mgmt_server.service_account_email
|
||||
project_id = module.fleet_project.project_id
|
||||
})
|
||||
filename = "${path.module}/ansible/vars/vars.yaml"
|
||||
file_permission = "0666"
|
||||
}
|
||||
|
||||
resource "local_file" "gssh_file" {
|
||||
content = templatefile("${path.module}/templates/gssh.sh.tpl", {
|
||||
project_id = var.mgmt_project_id
|
||||
zone = var.mgmt_server_config.zone
|
||||
})
|
||||
filename = "${path.module}/ansible/gssh.sh"
|
||||
file_permission = "0777"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Management server.
|
||||
|
||||
module "mgmt_server" {
|
||||
source = "../../../modules/compute-vm"
|
||||
project_id = module.mgmt_project.project_id
|
||||
zone = var.mgmt_server_config.zone
|
||||
name = "mgmt"
|
||||
instance_type = var.mgmt_server_config.instance_type
|
||||
network_interfaces = [{
|
||||
network = module.svpc.self_link
|
||||
subnetwork = module.svpc.subnet_self_links["${var.mgmt_server_config.region}/subnet-mgmt"]
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
service_account_create = true
|
||||
boot_disk = {
|
||||
image = var.mgmt_server_config.image
|
||||
type = var.mgmt_server_config.disk_type
|
||||
size = var.mgmt_server_config.disk_size
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Networking resources.
|
||||
|
||||
module "svpc" {
|
||||
source = "../../../modules/net-vpc"
|
||||
project_id = module.host_project.project_id
|
||||
name = "svpc"
|
||||
mtu = 1500
|
||||
subnets = concat([for key, config in var.clusters_config : {
|
||||
ip_cidr_range = config.subnet_cidr_block
|
||||
name = "subnet-${key}"
|
||||
region = var.region
|
||||
secondary_ip_ranges = {
|
||||
pods = config.pods_cidr_block
|
||||
services = config.services_cidr_block
|
||||
}
|
||||
}], [{
|
||||
ip_cidr_range = var.mgmt_subnet_cidr_block
|
||||
name = "subnet-mgmt"
|
||||
region = var.mgmt_server_config.region
|
||||
}])
|
||||
}
|
||||
|
||||
module "firewall" {
|
||||
source = "../../../modules/net-vpc-firewall"
|
||||
project_id = module.host_project.project_id
|
||||
network = module.svpc.name
|
||||
custom_rules = merge({ allow-mesh = {
|
||||
description = "Allow "
|
||||
direction = "INGRESS"
|
||||
action = "allow"
|
||||
sources = []
|
||||
ranges = [for k, v in var.clusters_config : v.pods_cidr_block]
|
||||
targets = [for k, v in var.clusters_config : "${k}-node"]
|
||||
use_service_accounts = false
|
||||
rules = [{ protocol = "tcp", ports = null },
|
||||
{ protocol = "udp", ports = null },
|
||||
{ protocol = "icmp", ports = null },
|
||||
{ protocol = "esp", ports = null },
|
||||
{ protocol = "ah", ports = null },
|
||||
{ protocol = "sctp", ports = null }]
|
||||
extra_attributes = {
|
||||
priority = 900
|
||||
}
|
||||
} },
|
||||
{ for k, v in var.clusters_config : "allow-${k}-istio" => {
|
||||
description = "Allow "
|
||||
direction = "INGRESS"
|
||||
action = "allow"
|
||||
sources = []
|
||||
ranges = [v.master_cidr_block]
|
||||
targets = ["${k}-node"]
|
||||
use_service_accounts = false
|
||||
rules = [{ protocol = "tcp", ports = [8080, 15014, 15017] }]
|
||||
extra_attributes = {
|
||||
priority = 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module "nat" {
|
||||
source = "../../../modules/net-cloudnat"
|
||||
project_id = module.host_project.project_id
|
||||
region = var.region
|
||||
name = "nat"
|
||||
router_create = true
|
||||
router_network = module.svpc.name
|
||||
}
|
||||
|
|
@ -41,21 +41,28 @@ The overall architecture is based on the following design decisions:
|
|||
|
||||
## Basic usage
|
||||
|
||||
The following example shows how to deploy a single cluster and a single node pool
|
||||
The following example shows how to deploy two clusters and one node pool for each
|
||||
|
||||
```hcl
|
||||
module "gke" {
|
||||
locals {
|
||||
cluster_defaults = {
|
||||
private_cluster_config = {
|
||||
enable_private_endpoint = true
|
||||
master_global_access = true
|
||||
}
|
||||
}
|
||||
subnet_self_links = {
|
||||
ew1 = "projects/prj-host/regions/europe-west1/subnetworks/gke-0"
|
||||
ew3 = "projects/prj-host/regions/europe-west3/subnetworks/gke-0"
|
||||
}
|
||||
}
|
||||
|
||||
module "gke-fleet" {
|
||||
source = "./fabric/blueprints/gke/multitenant-fleet/"
|
||||
project_id = var.project_id
|
||||
billing_account_id = var.billing_account_id
|
||||
folder_id = var.folder_id
|
||||
prefix = "myprefix"
|
||||
vpc_config = {
|
||||
host_project_id = "my-host-project-id"
|
||||
vpc_self_link = "projects/my-host-project-id/global/networks/my-network"
|
||||
}
|
||||
|
||||
authenticator_security_group = "gke-rbac-base@example.com"
|
||||
group_iam = {
|
||||
"gke-admin@example.com" = [
|
||||
"roles/container.admin"
|
||||
|
@ -66,129 +73,52 @@ module "gke" {
|
|||
"cicd@my-cicd-project.iam.gserviceaccount.com"
|
||||
]
|
||||
}
|
||||
|
||||
clusters = {
|
||||
mycluster = {
|
||||
cluster_autoscaling = null
|
||||
description = "My cluster"
|
||||
dns_domain = null
|
||||
location = "europe-west1"
|
||||
labels = {}
|
||||
net = {
|
||||
master_range = "172.17.16.0/28"
|
||||
pods = "pods"
|
||||
services = "services"
|
||||
subnet = "projects/my-host-project-id/regions/europe-west1/subnetworks/mycluster-subnet"
|
||||
cluster-0 = {
|
||||
location = "europe-west1"
|
||||
private_cluster_config = local.cluster_defaults.private_cluster_config
|
||||
vpc_config = {
|
||||
subnetwork = local.subnet_self_links.ew1
|
||||
master_ipv4_cidr_block = "172.16.10.0/28"
|
||||
}
|
||||
}
|
||||
cluster-1 = {
|
||||
location = "europe-west3"
|
||||
private_cluster_config = local.cluster_defaults.private_cluster_config
|
||||
vpc_config = {
|
||||
subnetwork = local.subnet_self_links.ew3
|
||||
master_ipv4_cidr_block = "172.16.20.0/28"
|
||||
}
|
||||
overrides = null
|
||||
}
|
||||
}
|
||||
nodepools = {
|
||||
mycluster = {
|
||||
mynodepool = {
|
||||
initial_node_count = 1
|
||||
node_count = 1
|
||||
node_type = "n2-standard-4"
|
||||
overrides = null
|
||||
spot = false
|
||||
cluster-0 = {
|
||||
nodepool-0 = {
|
||||
node_config = {
|
||||
disk_type = "pd-balanced"
|
||||
machine_type = "n2-standard-4"
|
||||
spot = true
|
||||
}
|
||||
}
|
||||
}
|
||||
cluster-1 = {
|
||||
nodepool-0 = {
|
||||
node_config = {
|
||||
disk_type = "pd-balanced"
|
||||
machine_type = "n2-standard-4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=5 resources=26
|
||||
```
|
||||
|
||||
## Creating Multiple Clusters
|
||||
|
||||
The following example shows how to deploy two clusters with different configurations.
|
||||
|
||||
The first cluster `cluster-euw1` defines the mandatory configuration parameters (description, location, network setup) and inherits the some defaults from the `cluster_defaults` and `nodepool_deaults` variables. These two variables are used whenever the `override` key of the `clusters` and `nodepools` variables are set to `null`.
|
||||
|
||||
On the other hand, the second cluster (`cluster-euw3`) defines its own configuration by providing a value to the `overrides` key.
|
||||
|
||||
```hcl
|
||||
module "gke" {
|
||||
source = "./fabric/blueprints/gke/multitenant-fleet/"
|
||||
project_id = var.project_id
|
||||
billing_account_id = var.billing_account_id
|
||||
folder_id = var.folder_id
|
||||
prefix = "myprefix"
|
||||
vpc_config = {
|
||||
host_project_id = "my-host-project-id"
|
||||
vpc_self_link = "projects/my-host-project-id/global/networks/my-network"
|
||||
}
|
||||
clusters = {
|
||||
cluster-euw1 = {
|
||||
cluster_autoscaling = null
|
||||
description = "Cluster for europ-west1"
|
||||
dns_domain = null
|
||||
location = "europe-west1"
|
||||
labels = {}
|
||||
net = {
|
||||
master_range = "172.17.16.0/28"
|
||||
pods = "pods"
|
||||
services = "services"
|
||||
subnet = "projects/my-host-project-id/regions/europe-west1/subnetworks/euw1-subnet"
|
||||
}
|
||||
overrides = null
|
||||
}
|
||||
cluster-euw3 = {
|
||||
cluster_autoscaling = null
|
||||
description = "Cluster for europe-west3"
|
||||
dns_domain = null
|
||||
location = "europe-west3"
|
||||
labels = {}
|
||||
net = {
|
||||
master_range = "172.17.17.0/28"
|
||||
pods = "pods"
|
||||
services = "services"
|
||||
subnet = "projects/my-host-project-id/regions/europe-west3/subnetworks/euw3-subnet"
|
||||
}
|
||||
overrides = {
|
||||
cloudrun_config = false
|
||||
database_encryption_key = null
|
||||
gcp_filestore_csi_driver_config = true
|
||||
master_authorized_ranges = {
|
||||
rfc1918_1 = "10.0.0.0/8"
|
||||
}
|
||||
max_pods_per_node = 64
|
||||
pod_security_policy = true
|
||||
release_channel = "STABLE"
|
||||
vertical_pod_autoscaling = false
|
||||
}
|
||||
}
|
||||
}
|
||||
nodepools = {
|
||||
cluster-euw1 = {
|
||||
pool-euw1 = {
|
||||
initial_node_count = 1
|
||||
node_count = 1
|
||||
node_type = "n2-standard-4"
|
||||
overrides = null
|
||||
spot = false
|
||||
}
|
||||
}
|
||||
cluster-euw3 = {
|
||||
pool-euw3 = {
|
||||
initial_node_count = 1
|
||||
node_count = 1
|
||||
node_type = "n2-standard-4"
|
||||
overrides = {
|
||||
image_type = "UBUNTU_CONTAINERD"
|
||||
max_pods_per_node = 64
|
||||
node_locations = []
|
||||
node_tags = []
|
||||
node_taints = []
|
||||
}
|
||||
spot = true
|
||||
}
|
||||
}
|
||||
vpc_self_link = "projects/prj-host/global/networks/prod-0"
|
||||
}
|
||||
}
|
||||
# tftest modules=7 resources=28
|
||||
# tftest modules=7 resources=26
|
||||
```
|
||||
|
||||
## Multiple clusters with GKE Fleet
|
||||
## GKE Fleet
|
||||
|
||||
This example deploys two clusters and configures several GKE Fleet features:
|
||||
|
||||
|
@ -198,72 +128,57 @@ This example deploys two clusters and configures several GKE Fleet features:
|
|||
- The two clusters are configured to use the `default` Config Management template.
|
||||
|
||||
```hcl
|
||||
locals {
|
||||
subnet_self_links = {
|
||||
ew1 = "projects/prj-host/regions/europe-west1/subnetworks/gke-0"
|
||||
ew3 = "projects/prj-host/regions/europe-west3/subnetworks/gke-0"
|
||||
}
|
||||
}
|
||||
|
||||
module "gke" {
|
||||
source = "./fabric/blueprints/gke/multitenant-fleet/"
|
||||
project_id = var.project_id
|
||||
billing_account_id = var.billing_account_id
|
||||
folder_id = var.folder_id
|
||||
prefix = "myprefix"
|
||||
vpc_config = {
|
||||
host_project_id = "my-host-project-id"
|
||||
vpc_self_link = "projects/my-host-project-id/global/networks/my-network"
|
||||
}
|
||||
clusters = {
|
||||
cluster-euw1 = {
|
||||
cluster_autoscaling = null
|
||||
description = "Cluster for europe-west1"
|
||||
dns_domain = null
|
||||
location = "europe-west1"
|
||||
labels = {}
|
||||
net = {
|
||||
master_range = "172.17.16.0/28"
|
||||
pods = "pods"
|
||||
services = "services"
|
||||
subnet = "projects/my-host-project-id/regions/europe-west1/subnetworks/euw1-subnet"
|
||||
cluster-0 = {
|
||||
location = "europe-west1"
|
||||
vpc_config = {
|
||||
subnetwork = local.subnet_self_links.ew1
|
||||
}
|
||||
overrides = null
|
||||
}
|
||||
cluster-euw3 = {
|
||||
cluster_autoscaling = null
|
||||
description = "Cluster for europe-west3"
|
||||
dns_domain = null
|
||||
location = "europe-west3"
|
||||
labels = {}
|
||||
net = {
|
||||
master_range = "172.17.17.0/28"
|
||||
pods = "pods"
|
||||
services = "services"
|
||||
subnet = "projects/my-host-project-id/regions/europe-west3/subnetworks/euw3-subnet"
|
||||
cluster-1 = {
|
||||
location = "europe-west3"
|
||||
vpc_config = {
|
||||
subnetwork = local.subnet_self_links.ew3
|
||||
}
|
||||
overrides = null
|
||||
}
|
||||
}
|
||||
nodepools = {
|
||||
cluster-euw1 = {
|
||||
pool-euw1 = {
|
||||
initial_node_count = 1
|
||||
node_count = 1
|
||||
node_type = "n2-standard-4"
|
||||
overrides = null
|
||||
spot = false
|
||||
cluster-0 = {
|
||||
nodepool-0 = {
|
||||
node_config = {
|
||||
disk_type = "pd-balanced"
|
||||
machine_type = "n2-standard-4"
|
||||
spot = true
|
||||
}
|
||||
}
|
||||
}
|
||||
cluster-euw3 = {
|
||||
pool-euw3 = {
|
||||
initial_node_count = 1
|
||||
node_count = 1
|
||||
node_type = "n2-standard-4"
|
||||
overrides = null
|
||||
spot = true
|
||||
cluster-1 = {
|
||||
nodepool-0 = {
|
||||
node_config = {
|
||||
disk_type = "pd-balanced"
|
||||
machine_type = "n2-standard-4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fleet_features = {
|
||||
appdevexperience = false
|
||||
configmanagement = true
|
||||
identityservice = true
|
||||
multiclusteringress = "cluster-euw1"
|
||||
multiclusteringress = "cluster-0"
|
||||
multiclusterservicediscovery = true
|
||||
servicemesh = true
|
||||
}
|
||||
|
@ -301,58 +216,57 @@ module "gke" {
|
|||
}
|
||||
}
|
||||
fleet_configmanagement_clusters = {
|
||||
default = ["cluster-euw1", "cluster-euw3"]
|
||||
default = ["cluster-0", "cluster-1"]
|
||||
}
|
||||
vpc_config = {
|
||||
host_project_id = "my-host-project-id"
|
||||
vpc_self_link = "projects/prj-host/global/networks/prod-0"
|
||||
}
|
||||
}
|
||||
|
||||
# tftest modules=8 resources=39
|
||||
# tftest modules=8 resources=35
|
||||
```
|
||||
|
||||
<!-- TFDOC OPTS files:1 show_extra:1 -->
|
||||
<!-- TFDOC OPTS files:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Files
|
||||
|
||||
| name | description | modules |
|
||||
|---|---|---|
|
||||
| [gke-clusters.tf](./gke-clusters.tf) | None | <code>gke-cluster</code> |
|
||||
| [gke-hub.tf](./gke-hub.tf) | None | <code>gke-hub</code> |
|
||||
| [gke-nodepools.tf](./gke-nodepools.tf) | None | <code>gke-nodepool</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>bigquery-dataset</code> · <code>project</code> |
|
||||
| [gke-clusters.tf](./gke-clusters.tf) | GKE clusters. | <code>gke-cluster</code> |
|
||||
| [gke-hub.tf](./gke-hub.tf) | GKE hub configuration. | <code>gke-hub</code> |
|
||||
| [gke-nodepools.tf](./gke-nodepools.tf) | GKE nodepools. | <code>gke-nodepool</code> |
|
||||
| [main.tf](./main.tf) | Project and usage dataset. | <code>bigquery-dataset</code> · <code>project</code> |
|
||||
| [outputs.tf](./outputs.tf) | Output variables. | |
|
||||
| [variables.tf](./variables.tf) | Module variables. | |
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default | producer |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [billing_account_id](variables.tf#L23) | Billing account id. | <code>string</code> | ✓ | | |
|
||||
| [clusters](variables.tf#L57) | | <code title="map(object({ cluster_autoscaling = object({ cpu_min = number cpu_max = number memory_min = number memory_max = number }) description = string dns_domain = string labels = map(string) location = string net = object({ master_range = string pods = string services = string subnet = string }) overrides = object({ cloudrun_config = bool database_encryption_key = string master_authorized_ranges = map(string) max_pods_per_node = number pod_security_policy = bool release_channel = string vertical_pod_autoscaling = bool gcp_filestore_csi_driver_config = bool }) }))">map(object({…}))</code> | ✓ | | |
|
||||
| [folder_id](variables.tf#L158) | Folder used for the GKE project in folders/nnnnnnnnnnn format. | <code>string</code> | ✓ | | |
|
||||
| [nodepools](variables.tf#L201) | | <code title="map(map(object({ node_count = number node_type = string initial_node_count = number overrides = object({ image_type = string max_pods_per_node = number node_locations = list(string) node_tags = list(string) node_taints = list(string) }) spot = bool })))">map(map(object({…})))</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L231) | Prefix used for resources that need unique names. | <code>string</code> | ✓ | | |
|
||||
| [project_id](variables.tf#L236) | ID of the project that will contain all the clusters. | <code>string</code> | ✓ | | |
|
||||
| [vpc_config](variables.tf#L248) | Shared VPC project and VPC details. | <code title="object({ host_project_id = string vpc_self_link = string })">object({…})</code> | ✓ | | |
|
||||
| [authenticator_security_group](variables.tf#L17) | Optional group used for Groups for GKE. | <code>string</code> | | <code>null</code> | |
|
||||
| [cluster_defaults](variables.tf#L28) | Default values for optional cluster configurations. | <code title="object({ cloudrun_config = bool database_encryption_key = string master_authorized_ranges = map(string) max_pods_per_node = number pod_security_policy = bool release_channel = string vertical_pod_autoscaling = bool gcp_filestore_csi_driver_config = bool })">object({…})</code> | | <code title="{ cloudrun_config = false database_encryption_key = null master_authorized_ranges = { rfc1918_1 = "10.0.0.0/8" rfc1918_2 = "172.16.0.0/12" rfc1918_3 = "192.168.0.0/16" } max_pods_per_node = 110 pod_security_policy = false release_channel = "STABLE" vertical_pod_autoscaling = false gcp_filestore_csi_driver_config = false }">{…}</code> | |
|
||||
| [dns_domain](variables.tf#L90) | Domain name used for clusters, prefixed by each cluster name. Leave null to disable Cloud DNS for GKE. | <code>string</code> | | <code>null</code> | |
|
||||
| [fleet_configmanagement_clusters](variables.tf#L96) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [fleet_configmanagement_templates](variables.tf#L103) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | <code title="map(object({ binauthz = bool config_sync = object({ git = object({ gcp_service_account_email = string https_proxy = string policy_dir = string secret_type = string sync_branch = string sync_repo = string sync_rev = string sync_wait_secs = number }) prevent_drift = string source_format = string }) hierarchy_controller = object({ enable_hierarchical_resource_quota = bool enable_pod_tree_labels = bool }) policy_controller = object({ audit_interval_seconds = number exemptable_namespaces = list(string) log_denies_enabled = bool referential_rules_enabled = bool template_library_installed = bool }) version = string }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [fleet_features](variables.tf#L138) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | <code title="object({ appdevexperience = bool configmanagement = bool identityservice = bool multiclusteringress = string multiclusterservicediscovery = bool servicemesh = bool })">object({…})</code> | | <code>null</code> | |
|
||||
| [fleet_workload_identity](variables.tf#L151) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | <code>bool</code> | | <code>false</code> | |
|
||||
| [group_iam](variables.tf#L163) | Project-level IAM bindings for groups. Use group emails as keys, list of roles as values. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam](variables.tf#L170) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [labels](variables.tf#L177) | Project-level labels. | <code>map(string)</code> | | <code>{}</code> | |
|
||||
| [nodepool_defaults](variables.tf#L183) | | <code title="object({ image_type = string max_pods_per_node = number node_locations = list(string) node_tags = list(string) node_taints = list(string) })">object({…})</code> | | <code title="{ image_type = "COS_CONTAINERD" max_pods_per_node = 110 node_locations = null node_tags = null node_taints = [] }">{…}</code> | |
|
||||
| [peering_config](variables.tf#L218) | Configure peering with the control plane VPC. Requires compute.networks.updatePeering. Set to null if you don't want to update the default peering configuration. | <code title="object({ export_routes = bool import_routes = bool })">object({…})</code> | | <code title="{ export_routes = true // TODO(jccb) is there any situation where the control plane VPC would export any routes? import_routes = false }">{…}</code> | |
|
||||
| [project_services](variables.tf#L241) | Additional project services to enable. | <code>list(string)</code> | | <code>[]</code> | |
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [billing_account_id](variables.tf#L17) | Billing account id. | <code>string</code> | ✓ | |
|
||||
| [folder_id](variables.tf#L129) | Folder used for the GKE project in folders/nnnnnnnnnnn format. | <code>string</code> | ✓ | |
|
||||
| [prefix](variables.tf#L176) | Prefix used for resources that need unique names. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L181) | ID of the project that will contain all the clusters. | <code>string</code> | ✓ | |
|
||||
| [vpc_config](variables.tf#L193) | Shared VPC project and VPC details. | <code title="object({ host_project_id = string vpc_self_link = string })">object({…})</code> | ✓ | |
|
||||
| [clusters](variables.tf#L22) | Clusters configuration. Refer to the gke-cluster module for type details. | <code title="map(object({ cluster_autoscaling = optional(any) description = optional(string) enable_addons = optional(any, { horizontal_pod_autoscaling = true, http_load_balancing = true }) enable_features = optional(any, { workload_identity = true }) issue_client_certificate = optional(bool, false) labels = optional(map(string)) location = string logging_config = optional(list(string), ["SYSTEM_COMPONENTS"]) maintenance_config = optional(any, { daily_window_start_time = "03:00" recurring_window = null maintenance_exclusion = [] }) max_pods_per_node = optional(number, 110) min_master_version = optional(string) monitoring_config = optional(list(string), ["SYSTEM_COMPONENTS"]) node_locations = optional(list(string)) private_cluster_config = optional(any) release_channel = optional(string) vpc_config = object({ subnetwork = string network = optional(string) secondary_range_blocks = optional(object({ pods = string services = string })) secondary_range_names = optional(object({ pods = string services = string }), { pods = "pods", services = "services" }) master_authorized_ranges = optional(map(string)) master_ipv4_cidr_block = optional(string) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [fleet_configmanagement_clusters](variables.tf#L67) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [fleet_configmanagement_templates](variables.tf#L74) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | <code title="map(object({ binauthz = bool config_sync = object({ git = object({ gcp_service_account_email = string https_proxy = string policy_dir = string secret_type = string sync_branch = string sync_repo = string sync_rev = string sync_wait_secs = number }) prevent_drift = string source_format = string }) hierarchy_controller = object({ enable_hierarchical_resource_quota = bool enable_pod_tree_labels = bool }) policy_controller = object({ audit_interval_seconds = number exemptable_namespaces = list(string) log_denies_enabled = bool referential_rules_enabled = bool template_library_installed = bool }) version = string }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [fleet_features](variables.tf#L109) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | <code title="object({ appdevexperience = bool configmanagement = bool identityservice = bool multiclusteringress = string multiclusterservicediscovery = bool servicemesh = bool })">object({…})</code> | | <code>null</code> |
|
||||
| [fleet_workload_identity](variables.tf#L122) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | <code>bool</code> | | <code>false</code> |
|
||||
| [group_iam](variables.tf#L134) | Project-level IAM bindings for groups. Use group emails as keys, list of roles as values. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L141) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [labels](variables.tf#L148) | Project-level labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [nodepools](variables.tf#L154) | Nodepools configuration. Refer to the gke-nodepool module for type details. | <code title="map(map(object({ gke_version = optional(string) labels = optional(map(string), {}) max_pods_per_node = optional(number) name = optional(string) node_config = optional(any, { disk_type = "pd-balanced" }) node_count = optional(map(number), { initial = 1 }) node_locations = optional(list(string)) nodepool_config = optional(any) pod_range = optional(any) reservation_affinity = optional(any) service_account = optional(any) sole_tenant_nodegroup = optional(string) tags = optional(list(string)) taints = optional(list(any)) })))">map(map(object({…})))</code> | | <code>{}</code> |
|
||||
| [project_services](variables.tf#L186) | Additional project services to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive | consumers |
|
||||
|---|---|:---:|---|
|
||||
| [cluster_ids](outputs.tf#L22) | Cluster ids. | | |
|
||||
| [clusters](outputs.tf#L17) | Cluster resources. | | |
|
||||
| [project_id](outputs.tf#L29) | GKE project id. | | |
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [cluster_ids](outputs.tf#L22) | Cluster ids. | |
|
||||
| [clusters](outputs.tf#L17) | Cluster resources. | |
|
||||
| [project_id](outputs.tf#L29) | GKE project id. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -14,103 +14,30 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
locals {
|
||||
clusters = {
|
||||
for name, config in var.clusters :
|
||||
name => merge(config, {
|
||||
overrides = coalesce(config.overrides, var.cluster_defaults)
|
||||
})
|
||||
}
|
||||
}
|
||||
# tfdoc:file:description GKE clusters.
|
||||
|
||||
module "gke-cluster" {
|
||||
source = "../../../modules/gke-cluster"
|
||||
for_each = local.clusters
|
||||
for_each = var.clusters
|
||||
name = each.key
|
||||
project_id = module.gke-project-0.project_id
|
||||
cluster_autoscaling = each.value.cluster_autoscaling
|
||||
description = each.value.description
|
||||
location = each.value.location
|
||||
network = var.vpc_config.vpc_self_link
|
||||
subnetwork = each.value.net.subnet
|
||||
secondary_range_pods = each.value.net.pods
|
||||
secondary_range_services = each.value.net.services
|
||||
enable_features = each.value.enable_features
|
||||
issue_client_certificate = each.value.issue_client_certificate
|
||||
labels = each.value.labels
|
||||
addons = {
|
||||
cloudrun_config = each.value.overrides.cloudrun_config
|
||||
dns_cache_config = true
|
||||
http_load_balancing = true
|
||||
gce_persistent_disk_csi_driver_config = true
|
||||
horizontal_pod_autoscaling = true
|
||||
config_connector_config = true
|
||||
kalm_config = false
|
||||
gcp_filestore_csi_driver_config = each.value.overrides.gcp_filestore_csi_driver_config
|
||||
gke_backup_agent_config = false
|
||||
# enable only if enable_dataplane_v2 is changed to false below
|
||||
network_policy_config = false
|
||||
istio_config = {
|
||||
enabled = false
|
||||
tls = false
|
||||
}
|
||||
}
|
||||
# change these here for all clusters if absolutely needed
|
||||
authenticator_security_group = var.authenticator_security_group
|
||||
enable_dataplane_v2 = true
|
||||
enable_l4_ilb_subsetting = false
|
||||
enable_intranode_visibility = true
|
||||
enable_shielded_nodes = true
|
||||
workload_identity = true
|
||||
private_cluster_config = {
|
||||
enable_private_nodes = true
|
||||
enable_private_endpoint = false
|
||||
master_ipv4_cidr_block = each.value.net.master_range
|
||||
master_global_access = true
|
||||
}
|
||||
dns_config = each.value.dns_domain == null ? null : {
|
||||
cluster_dns = "CLOUD_DNS"
|
||||
cluster_dns_scope = "VPC_SCOPE"
|
||||
cluster_dns_domain = "${each.key}.${var.dns_domain}"
|
||||
}
|
||||
logging_config = ["SYSTEM_COMPONENTS", "WORKLOADS"]
|
||||
monitoring_config = ["SYSTEM_COMPONENTS", "WORKLOADS"]
|
||||
|
||||
peering_config = var.peering_config == null ? null : {
|
||||
export_routes = var.peering_config.export_routes
|
||||
import_routes = var.peering_config.import_routes
|
||||
project_id = var.vpc_config.host_project_id
|
||||
}
|
||||
resource_usage_export_config = {
|
||||
enabled = true
|
||||
dataset = module.gke-dataset-resource-usage.dataset_id
|
||||
}
|
||||
# TODO: the attributes below are "primed" from project-level defaults
|
||||
# in locals, merge defaults with cluster-level stuff
|
||||
# TODO(jccb): change fabric module
|
||||
database_encryption = (
|
||||
each.value.overrides.database_encryption_key == null
|
||||
? {
|
||||
enabled = false
|
||||
state = null
|
||||
key_name = null
|
||||
}
|
||||
: {
|
||||
enabled = true
|
||||
state = "ENCRYPTED"
|
||||
key_name = each.value.overrides.database_encryption_key
|
||||
}
|
||||
)
|
||||
default_max_pods_per_node = each.value.overrides.max_pods_per_node
|
||||
master_authorized_ranges = each.value.overrides.master_authorized_ranges
|
||||
pod_security_policy = each.value.overrides.pod_security_policy
|
||||
release_channel = each.value.overrides.release_channel
|
||||
vertical_pod_autoscaling = each.value.overrides.vertical_pod_autoscaling
|
||||
# dynamic "cluster_autoscaling" {
|
||||
# for_each = each.value.cluster_autoscaling == null ? {} : { 1 = 1 }
|
||||
# content {
|
||||
# enabled = true
|
||||
# cpu_min = each.value.cluster_autoscaling.cpu_min
|
||||
# cpu_max = each.value.cluster_autoscaling.cpu_max
|
||||
# memory_min = each.value.cluster_autoscaling.memory_min
|
||||
# memory_max = each.value.cluster_autoscaling.memory_max
|
||||
# }
|
||||
# }
|
||||
location = each.value.location
|
||||
logging_config = each.value.logging_config
|
||||
maintenance_config = each.value.maintenance_config
|
||||
max_pods_per_node = each.value.max_pods_per_node
|
||||
min_master_version = each.value.min_master_version
|
||||
monitoring_config = each.value.monitoring_config
|
||||
node_locations = each.value.node_locations
|
||||
private_cluster_config = each.value.private_cluster_config
|
||||
release_channel = each.value.release_channel
|
||||
vpc_config = merge(each.value.vpc_config, {
|
||||
network = coalesce(
|
||||
each.value.vpc_config.network, var.vpc_config.vpc_self_link
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description GKE hub configuration.
|
||||
|
||||
locals {
|
||||
fleet_enabled = (
|
||||
var.fleet_features != null || var.fleet_workload_identity
|
||||
|
|
|
@ -14,53 +14,38 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description GKE nodepools.
|
||||
|
||||
locals {
|
||||
nodepools = merge([
|
||||
for cluster, nodepools in var.nodepools : {
|
||||
for nodepool, config in nodepools :
|
||||
"${cluster}/${nodepool}" => merge(config, {
|
||||
name = nodepool
|
||||
cluster = cluster
|
||||
overrides = coalesce(config.overrides, var.nodepool_defaults)
|
||||
name = nodepool
|
||||
cluster = cluster
|
||||
})
|
||||
}
|
||||
]...)
|
||||
}
|
||||
|
||||
module "gke-nodepool" {
|
||||
source = "../../../modules/gke-nodepool"
|
||||
for_each = local.nodepools
|
||||
name = each.value.name
|
||||
project_id = module.gke-project-0.project_id
|
||||
cluster_name = module.gke-cluster[each.value.cluster].name
|
||||
location = module.gke-cluster[each.value.cluster].location
|
||||
initial_node_count = each.value.initial_node_count
|
||||
node_machine_type = each.value.node_type
|
||||
node_spot = each.value.spot
|
||||
|
||||
node_count = each.value.node_count
|
||||
# node_count = (
|
||||
# each.value.autoscaling_config == null ? each.value.node_count : null
|
||||
# )
|
||||
# dynamic "autoscaling_config" {
|
||||
# for_each = each.value.autoscaling_config == null ? {} : { 1 = 1 }
|
||||
# content {
|
||||
# min_node_count = each.value.autoscaling_config.min_node_count
|
||||
# max_node_count = each.value.autoscaling_config.max_node_count
|
||||
# }
|
||||
# }
|
||||
|
||||
# overrides
|
||||
node_locations = each.value.overrides.node_locations
|
||||
max_pods_per_node = each.value.overrides.max_pods_per_node
|
||||
node_image_type = each.value.overrides.image_type
|
||||
node_tags = each.value.overrides.node_tags
|
||||
node_taints = each.value.overrides.node_taints
|
||||
|
||||
management_config = {
|
||||
auto_repair = true
|
||||
auto_upgrade = true
|
||||
}
|
||||
|
||||
node_service_account_create = true
|
||||
source = "../../../modules/gke-nodepool"
|
||||
for_each = local.nodepools
|
||||
name = each.value.name
|
||||
project_id = module.gke-project-0.project_id
|
||||
cluster_name = module.gke-cluster[each.value.cluster].name
|
||||
location = module.gke-cluster[each.value.cluster].location
|
||||
gke_version = each.value.gke_version
|
||||
labels = each.value.labels
|
||||
max_pods_per_node = each.value.max_pods_per_node
|
||||
node_config = each.value.node_config
|
||||
node_count = each.value.node_count
|
||||
node_locations = each.value.node_locations
|
||||
nodepool_config = each.value.nodepool_config
|
||||
pod_range = each.value.pod_range
|
||||
reservation_affinity = each.value.reservation_affinity
|
||||
service_account = each.value.service_account
|
||||
sole_tenant_nodegroup = each.value.sole_tenant_nodegroup
|
||||
tags = each.value.tags
|
||||
taints = each.value.taints
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Project and usage dataset.
|
||||
|
||||
module "gke-project-0" {
|
||||
source = "../../../modules/project"
|
||||
billing_account = var.billing_account_id
|
||||
|
|
|
@ -14,83 +14,54 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "authenticator_security_group" {
|
||||
description = "Optional group used for Groups for GKE."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "billing_account_id" {
|
||||
description = "Billing account id."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cluster_defaults" {
|
||||
description = "Default values for optional cluster configurations."
|
||||
type = object({
|
||||
cloudrun_config = bool
|
||||
database_encryption_key = string
|
||||
master_authorized_ranges = map(string)
|
||||
max_pods_per_node = number
|
||||
pod_security_policy = bool
|
||||
release_channel = string
|
||||
vertical_pod_autoscaling = bool
|
||||
gcp_filestore_csi_driver_config = bool
|
||||
})
|
||||
default = {
|
||||
# TODO: review defaults
|
||||
cloudrun_config = false
|
||||
database_encryption_key = null
|
||||
master_authorized_ranges = {
|
||||
rfc1918_1 = "10.0.0.0/8"
|
||||
rfc1918_2 = "172.16.0.0/12"
|
||||
rfc1918_3 = "192.168.0.0/16"
|
||||
}
|
||||
max_pods_per_node = 110
|
||||
pod_security_policy = false
|
||||
release_channel = "STABLE"
|
||||
vertical_pod_autoscaling = false
|
||||
gcp_filestore_csi_driver_config = false
|
||||
}
|
||||
}
|
||||
|
||||
variable "clusters" {
|
||||
description = ""
|
||||
description = "Clusters configuration. Refer to the gke-cluster module for type details."
|
||||
type = map(object({
|
||||
cluster_autoscaling = object({
|
||||
cpu_min = number
|
||||
cpu_max = number
|
||||
memory_min = number
|
||||
memory_max = number
|
||||
cluster_autoscaling = optional(any)
|
||||
description = optional(string)
|
||||
enable_addons = optional(any, {
|
||||
horizontal_pod_autoscaling = true, http_load_balancing = true
|
||||
})
|
||||
description = string
|
||||
dns_domain = string
|
||||
labels = map(string)
|
||||
location = string
|
||||
net = object({
|
||||
master_range = string
|
||||
pods = string
|
||||
services = string
|
||||
subnet = string
|
||||
enable_features = optional(any, {
|
||||
workload_identity = true
|
||||
})
|
||||
overrides = object({
|
||||
cloudrun_config = bool
|
||||
database_encryption_key = string
|
||||
# binary_authorization = bool
|
||||
master_authorized_ranges = map(string)
|
||||
max_pods_per_node = number
|
||||
pod_security_policy = bool
|
||||
release_channel = string
|
||||
vertical_pod_autoscaling = bool
|
||||
gcp_filestore_csi_driver_config = bool
|
||||
issue_client_certificate = optional(bool, false)
|
||||
labels = optional(map(string))
|
||||
location = string
|
||||
logging_config = optional(list(string), ["SYSTEM_COMPONENTS"])
|
||||
maintenance_config = optional(any, {
|
||||
daily_window_start_time = "03:00"
|
||||
recurring_window = null
|
||||
maintenance_exclusion = []
|
||||
})
|
||||
max_pods_per_node = optional(number, 110)
|
||||
min_master_version = optional(string)
|
||||
monitoring_config = optional(list(string), ["SYSTEM_COMPONENTS"])
|
||||
node_locations = optional(list(string))
|
||||
private_cluster_config = optional(any)
|
||||
release_channel = optional(string)
|
||||
vpc_config = object({
|
||||
subnetwork = string
|
||||
network = optional(string)
|
||||
secondary_range_blocks = optional(object({
|
||||
pods = string
|
||||
services = string
|
||||
}))
|
||||
secondary_range_names = optional(object({
|
||||
pods = string
|
||||
services = string
|
||||
}), { pods = "pods", services = "services" })
|
||||
master_authorized_ranges = optional(map(string))
|
||||
master_ipv4_cidr_block = optional(string)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
variable "dns_domain" {
|
||||
description = "Domain name used for clusters, prefixed by each cluster name. Leave null to disable Cloud DNS for GKE."
|
||||
type = string
|
||||
default = null
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "fleet_configmanagement_clusters" {
|
||||
|
@ -180,52 +151,26 @@ variable "labels" {
|
|||
default = {}
|
||||
}
|
||||
|
||||
variable "nodepool_defaults" {
|
||||
description = ""
|
||||
type = object({
|
||||
image_type = string
|
||||
max_pods_per_node = number
|
||||
node_locations = list(string)
|
||||
node_tags = list(string)
|
||||
node_taints = list(string)
|
||||
})
|
||||
default = {
|
||||
image_type = "COS_CONTAINERD"
|
||||
max_pods_per_node = 110
|
||||
node_locations = null
|
||||
node_tags = null
|
||||
node_taints = []
|
||||
}
|
||||
}
|
||||
|
||||
variable "nodepools" {
|
||||
description = ""
|
||||
description = "Nodepools configuration. Refer to the gke-nodepool module for type details."
|
||||
type = map(map(object({
|
||||
node_count = number
|
||||
node_type = string
|
||||
initial_node_count = number
|
||||
overrides = object({
|
||||
image_type = string
|
||||
max_pods_per_node = number
|
||||
node_locations = list(string)
|
||||
node_tags = list(string)
|
||||
node_taints = list(string)
|
||||
})
|
||||
spot = bool
|
||||
gke_version = optional(string)
|
||||
labels = optional(map(string), {})
|
||||
max_pods_per_node = optional(number)
|
||||
name = optional(string)
|
||||
node_config = optional(any, { disk_type = "pd-balanced" })
|
||||
node_count = optional(map(number), { initial = 1 })
|
||||
node_locations = optional(list(string))
|
||||
nodepool_config = optional(any)
|
||||
pod_range = optional(any)
|
||||
reservation_affinity = optional(any)
|
||||
service_account = optional(any)
|
||||
sole_tenant_nodegroup = optional(string)
|
||||
tags = optional(list(string))
|
||||
taints = optional(list(any))
|
||||
})))
|
||||
}
|
||||
|
||||
variable "peering_config" {
|
||||
description = "Configure peering with the control plane VPC. Requires compute.networks.updatePeering. Set to null if you don't want to update the default peering configuration."
|
||||
type = object({
|
||||
export_routes = bool
|
||||
import_routes = bool
|
||||
})
|
||||
default = {
|
||||
export_routes = true
|
||||
// TODO(jccb) is there any situation where the control plane VPC would export any routes?
|
||||
import_routes = false
|
||||
}
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
|
|
|
@ -39,11 +39,16 @@ It is meant to be used as a starting point for most Shared VPC configurations, a
|
|||
<a href="./ilb-next-hop/" title="ILB as next hop"><img src="./ilb-next-hop/diagram.png" align="left" width="280px"></a> This [blueprint](./ilb-next-hop/) allows testing [ILB as next hop](https://cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) using simple Linux gateway VMS between two VPCs, to emulate virtual appliances. An optional additional ILB can be enabled to test multiple load balancer configurations and hashing.
|
||||
<br clear="left">
|
||||
|
||||
### Calling a private Cloud Function from On-premises
|
||||
### Calling a private Cloud Function from on-premises
|
||||
|
||||
<a href="./private-cloud-function-from-onprem/" title="Private Cloud Function from On-premises"><img src="./private-cloud-function-from-onprem/diagram.png" align="left" width="280px"></a> This [blueprint](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis).
|
||||
<br clear="left">
|
||||
|
||||
### Calling on-premise services through PSC and hybrid NEGs
|
||||
|
||||
<a href="./psc-hybrid/" title="Hybrid connectivity to on-premise services thrugh PSC"><img src="./psc-hybrid/diagram.png" align="left" width="280px"></a> This [blueprint](./psc-hybrid/) shows how to privately connect to on-premise services (IP + port) from GCP, leveraging [Private Service Connect (PSC)](https://cloud.google.com/vpc/docs/private-service-connect) and [Hybrid Network Endpoint Groups](https://cloud.google.com/load-balancing/docs/negs/hybrid-neg-concepts).
|
||||
<br clear="left">
|
||||
|
||||
### Decentralized firewall management
|
||||
|
||||
<a href="./decentralized-firewall/" title="Decentralized firewall management"><img src="./decentralized-firewall/diagram.png" align="left" width="280px"></a> This [blueprint](./decentralized-firewall/) shows how a decentralized firewall management can be organized using the [firewall factory](../factories/net-vpc-firewall-yaml/).
|
||||
|
|
|
@ -25,8 +25,7 @@ module "project-host-prod" {
|
|||
services = var.project_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = []
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,8 +38,7 @@ module "project-host-dev" {
|
|||
services = var.project_services
|
||||
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = []
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,10 +52,9 @@ module "vpc-prod" {
|
|||
name = "prod-vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.ip_ranges.prod
|
||||
name = "prod"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.ip_ranges.prod
|
||||
name = "prod"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -68,10 +65,9 @@ module "vpc-dev" {
|
|||
name = "dev-vpc"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.ip_ranges.dev
|
||||
name = "dev"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.ip_ranges.dev
|
||||
name = "dev"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,8 +48,7 @@ module "project-host" {
|
|||
"logging.googleapis.com"
|
||||
]
|
||||
shared_vpc_host_config = {
|
||||
enabled = true
|
||||
service_projects = []
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,16 +58,14 @@ module "vpc" {
|
|||
name = "vpc"
|
||||
subnets = [
|
||||
{
|
||||
name = "apps"
|
||||
ip_cidr_range = var.cidrs.apps
|
||||
region = var.region
|
||||
secondary_ip_range = null
|
||||
name = "apps"
|
||||
ip_cidr_range = var.cidrs.apps
|
||||
region = var.region
|
||||
},
|
||||
{
|
||||
name = "proxy"
|
||||
ip_cidr_range = var.cidrs.proxy
|
||||
region = var.region
|
||||
secondary_ip_range = null
|
||||
name = "proxy"
|
||||
ip_cidr_range = var.cidrs.proxy
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -156,13 +153,9 @@ module "squid-vm" {
|
|||
network_interfaces = [{
|
||||
network = module.vpc.self_link
|
||||
subnetwork = module.vpc.subnet_self_links["${var.region}/proxy"]
|
||||
nat = false
|
||||
addresses = null
|
||||
}]
|
||||
boot_disk = {
|
||||
image = "cos-cloud/cos-stable"
|
||||
type = "pd-standard"
|
||||
size = 10
|
||||
}
|
||||
service_account = module.service-account-squid.email
|
||||
service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,10 +52,9 @@ module "vpc-hub" {
|
|||
name = "${local.prefix}hub"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.ip_ranges.hub
|
||||
name = "${local.prefix}hub-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.ip_ranges.hub
|
||||
name = "${local.prefix}hub-1"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -86,10 +85,9 @@ module "vpc-spoke-1" {
|
|||
name = "${local.prefix}spoke-1"
|
||||
subnets = [
|
||||
{
|
||||
ip_cidr_range = var.ip_ranges.spoke-1
|
||||
name = "${local.prefix}spoke-1-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {}
|
||||
ip_cidr_range = var.ip_ranges.spoke-1
|
||||
name = "${local.prefix}spoke-1-1"
|
||||
region = var.region
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -131,7 +129,7 @@ module "vpc-spoke-2" {
|
|||
ip_cidr_range = var.ip_ranges.spoke-2
|
||||
name = "${local.prefix}spoke-2-1"
|
||||
region = var.region
|
||||
secondary_ip_range = {
|
||||
secondary_ip_ranges = {
|
||||
pods = var.ip_secondary_ranges.spoke-2-pods
|
||||
services = var.ip_secondary_ranges.spoke-2-services
|
||||
}
|
||||
|
@ -237,41 +235,41 @@ module "service-account-gce" {
|
|||
################################################################################
|
||||
|
||||
module "cluster-1" {
|
||||
source = "../../../modules/gke-cluster"
|
||||
name = "${local.prefix}cluster-1"
|
||||
project_id = module.project.project_id
|
||||
location = "${var.region}-b"
|
||||
network = module.vpc-spoke-2.self_link
|
||||
subnetwork = module.vpc-spoke-2.subnet_self_links["${var.region}/${local.prefix}spoke-2-1"]
|
||||
secondary_range_pods = "pods"
|
||||
secondary_range_services = "services"
|
||||
default_max_pods_per_node = 32
|
||||
source = "../../../modules/gke-cluster"
|
||||
name = "${local.prefix}cluster-1"
|
||||
project_id = module.project.project_id
|
||||
location = "${var.region}-b"
|
||||
vpc_config = {
|
||||
network = module.vpc-spoke-2.self_link
|
||||
subnetwork = module.vpc-spoke-2.subnet_self_links["${var.region}/${local.prefix}spoke-2-1"]
|
||||
master_authorized_ranges = {
|
||||
for name, range in var.ip_ranges : name => range
|
||||
}
|
||||
master_ipv4_cidr_block = var.private_service_ranges.spoke-2-cluster-1
|
||||
}
|
||||
max_pods_per_node = 32
|
||||
labels = {
|
||||
environment = "test"
|
||||
}
|
||||
master_authorized_ranges = {
|
||||
for name, range in var.ip_ranges : name => range
|
||||
}
|
||||
private_cluster_config = {
|
||||
enable_private_nodes = true
|
||||
enable_private_endpoint = true
|
||||
master_ipv4_cidr_block = var.private_service_ranges.spoke-2-cluster-1
|
||||
master_global_access = true
|
||||
}
|
||||
peering_config = {
|
||||
export_routes = true
|
||||
import_routes = false
|
||||
project_id = null
|
||||
peering_config = {
|
||||
export_routes = true
|
||||
import_routes = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "cluster-1-nodepool-1" {
|
||||
source = "../../../modules/gke-nodepool"
|
||||
name = "${local.prefix}nodepool-1"
|
||||
project_id = module.project.project_id
|
||||
location = module.cluster-1.location
|
||||
cluster_name = module.cluster-1.name
|
||||
node_service_account = module.service-account-gke-node.email
|
||||
source = "../../../modules/gke-nodepool"
|
||||
name = "${local.prefix}nodepool-1"
|
||||
project_id = module.project.project_id
|
||||
location = module.cluster-1.location
|
||||
cluster_name = module.cluster-1.name
|
||||
service_account = {
|
||||
email = module.service-account-gke-node.email
|
||||
}
|
||||
}
|
||||
|
||||
# roles assigned via this module use non-authoritative IAM bindings at the
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ module "dev-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.dev-0-r1
|
||||
name = "${local.prefix}dev-0"
|
||||
region = var.regions.r1
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.dev-0-r1, {}
|
||||
)
|
||||
},
|
||||
|
@ -31,7 +31,7 @@ module "dev-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.dev-0-r2
|
||||
name = "${local.prefix}dev-0"
|
||||
region = var.regions.r2
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.dev-0-r2, {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ module "landing-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.land-0-r1
|
||||
name = "${local.prefix}lnd-0"
|
||||
region = var.regions.r1
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.land-0-r1, {}
|
||||
)
|
||||
},
|
||||
|
@ -31,7 +31,7 @@ module "landing-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.land-0-r2
|
||||
name = "${local.prefix}lnd-0"
|
||||
region = var.regions.r2
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.land-0-r2, {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ module "prod-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.prod-0-r1
|
||||
name = "${local.prefix}prd-0"
|
||||
region = var.regions.r1
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.prod-0-r1, {}
|
||||
)
|
||||
},
|
||||
|
@ -31,7 +31,7 @@ module "prod-vpc" {
|
|||
ip_cidr_range = var.ip_ranges.prod-0-r2
|
||||
name = "${local.prefix}prd-0"
|
||||
region = var.regions.r2
|
||||
secondary_ip_range = try(
|
||||
secondary_ip_ranges = try(
|
||||
var.ip_secondary_ranges.prod-0-r2, {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_version = ">= 1.3.1"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = ">= 4.32.0" # tftest
|
||||
version = ">= 4.36.0" # tftest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue