From f11571a42c5ca15cffce64c00d5e8e6f86f675a2 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 6 May 2021 08:16:57 +0200 Subject: [PATCH 01/17] Make cluster creation optional in Shared VPC example (#233) * make GKE creation optional in shared vpc example * change README title --- networking/shared-vpc-gke/README.md | 9 +++- networking/shared-vpc-gke/main.tf | 24 ++++++---- networking/shared-vpc-gke/outputs.tf | 8 ++-- networking/shared-vpc-gke/variables.tf | 64 ++++++++++++++------------ 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/networking/shared-vpc-gke/README.md b/networking/shared-vpc-gke/README.md index c9c564e94..0085b961f 100644 --- a/networking/shared-vpc-gke/README.md +++ b/networking/shared-vpc-gke/README.md @@ -1,6 +1,10 @@ -# Shared VPC with GKE example +# Shared VPC with optional GKE cluster -This sample creates a basic [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) setup using one host project and two service projects, each with a specific subnet in the shared VPC. The setup also includes the specific IAM-level configurations needed for [GKE on Shared VPC](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc) to enable cluster creation in one of the two service projects. +This sample creates a basic [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) setup using one host project and two service projects, each with a specific subnet in the shared VPC. + +The setup also includes the specific IAM-level configurations needed for [GKE on Shared VPC](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc) in one of the two service projects, and optionally creates a cluster with a single nodepool. + +If you only need a basic Shared VPC, or prefer creating a cluster manually, set the `cluster_create` variable to `False`. The sample has been purposefully kept simple so that it can be used as a basis for different Shared VPC configurations. This is the high level diagram: @@ -46,6 +50,7 @@ There's a minor glitch that can surface running `terraform destroy`, where the s | billing_account_id | Billing account id used as default for new projects. | string | ✓ | | | prefix | Prefix used for resources that need unique names. | string | ✓ | | | root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | +| *cluster_create* | Create GKE cluster and nodepool. | bool | | true | | *ip_ranges* | Subnet IP CIDR ranges. | map(string) | | ... | | *ip_secondary_ranges* | Secondary IP CIDR ranges. | map(string) | | ... | | *owners_gce* | GCE project owners, in IAM format. | list(string) | | [] | diff --git a/networking/shared-vpc-gke/main.tf b/networking/shared-vpc-gke/main.tf index 0540e6ca2..90f49d198 100644 --- a/networking/shared-vpc-gke/main.tf +++ b/networking/shared-vpc-gke/main.tf @@ -70,12 +70,18 @@ module "project-svc-gke" { attach = true host_project = module.project-host.project_id } - iam = { - "roles/container.developer" = [module.vm-bastion.service_account_iam_email], - "roles/logging.logWriter" = [module.cluster-1-nodepool-1.service_account_iam_email], - "roles/monitoring.metricWriter" = [module.cluster-1-nodepool-1.service_account_iam_email], - "roles/owner" = var.owners_gke - } + iam = merge( + { + "roles/container.developer" = [module.vm-bastion.service_account_iam_email] + "roles/owner" = var.owners_gke + }, + var.cluster_create + ? { + "roles/logging.logWriter" = [module.cluster-1-nodepool-1.0.service_account_iam_email] + "roles/monitoring.metricWriter" = [module.cluster-1-nodepool-1.0.service_account_iam_email] + } + : {} + ) } ################################################################################ @@ -193,6 +199,7 @@ module "vm-bastion" { module "cluster-1" { source = "../../modules/gke-cluster" + count = var.cluster_create ? 1 : 0 name = "cluster-1" project_id = module.project-svc-gke.project_id location = "${var.region}-b" @@ -217,9 +224,10 @@ module "cluster-1" { module "cluster-1-nodepool-1" { source = "../../modules/gke-nodepool" + count = var.cluster_create ? 1 : 0 name = "nodepool-1" project_id = module.project-svc-gke.project_id - location = module.cluster-1.location - cluster_name = module.cluster-1.name + location = module.cluster-1.0.location + cluster_name = module.cluster-1.0.name node_service_account_create = true } diff --git a/networking/shared-vpc-gke/outputs.tf b/networking/shared-vpc-gke/outputs.tf index 3e601105c..4a936c98c 100644 --- a/networking/shared-vpc-gke/outputs.tf +++ b/networking/shared-vpc-gke/outputs.tf @@ -14,9 +14,11 @@ output "gke_clusters" { description = "GKE clusters information." - value = { - cluster-1 = module.cluster-1.endpoint - } + value = ( + var.cluster_create + ? { cluster-1 = module.cluster-1.0.endpoint } + : {} + ) } output "projects" { diff --git a/networking/shared-vpc-gke/variables.tf b/networking/shared-vpc-gke/variables.tf index 5529d1b7d..24f8c58b6 100644 --- a/networking/shared-vpc-gke/variables.tf +++ b/networking/shared-vpc-gke/variables.tf @@ -17,6 +17,30 @@ variable "billing_account_id" { type = string } +variable "cluster_create" { + description = "Create GKE cluster and nodepool." + type = bool + default = true +} + +variable "ip_ranges" { + description = "Subnet IP CIDR ranges." + type = map(string) + default = { + gce = "10.0.16.0/24" + gke = "10.0.32.0/24" + } +} + +variable "ip_secondary_ranges" { + description = "Secondary IP CIDR ranges." + type = map(string) + default = { + gke-pods = "10.128.0.0/18" + gke-services = "172.16.0.0/24" + } +} + variable "owners_gce" { description = "GCE project owners, in IAM format." type = list(string) @@ -40,35 +64,6 @@ variable "prefix" { type = string } -variable "region" { - description = "Region used." - type = string - default = "europe-west1" -} - -variable "root_node" { - description = "Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'." - type = string -} - -variable "ip_ranges" { - description = "Subnet IP CIDR ranges." - type = map(string) - default = { - gce = "10.0.16.0/24" - gke = "10.0.32.0/24" - } -} - -variable "ip_secondary_ranges" { - description = "Secondary IP CIDR ranges." - type = map(string) - default = { - gke-pods = "10.128.0.0/18" - gke-services = "172.16.0.0/24" - } -} - variable "private_service_ranges" { description = "Private service IP CIDR ranges." type = map(string) @@ -85,3 +80,14 @@ variable "project_services" { "stackdriver.googleapis.com", ] } + +variable "region" { + description = "Region used." + type = string + default = "europe-west1" +} + +variable "root_node" { + description = "Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'." + type = string +} From a1814a5b16828579cdb08a4ca09ba6602658d833 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 6 May 2021 08:17:28 +0200 Subject: [PATCH 02/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 344a5847f..7daba6810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - added support for `CORS` to the `gcs` module +- make cluster creation optional in the Shared VPC example ## [4.7.0] - 2021-04-21 From 875b78617125da562983a1ddb46bf6630dc7a90c Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 6 May 2021 12:07:24 +0200 Subject: [PATCH 03/17] Optional create for service accounts --- modules/iam-service-account/README.md | 1 + modules/iam-service-account/main.tf | 19 ++++++++++++++++--- modules/iam-service-account/outputs.tf | 4 ++-- modules/iam-service-account/variables.tf | 6 ++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md index f12542edb..fb1449b76 100644 --- a/modules/iam-service-account/README.md +++ b/modules/iam-service-account/README.md @@ -41,6 +41,7 @@ module "myproject-default-service-accounts" { | *iam_project_roles* | Project roles granted to the service account, by project id. | map(list(string)) | | {} | | *iam_storage_roles* | Storage roles granted to the service account, by bucket name. | map(list(string)) | | {} | | *prefix* | Prefix applied to service account names. | string | | null | +| *service_account_create* | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | ## Outputs diff --git a/modules/iam-service-account/main.tf b/modules/iam-service-account/main.tf index 071d7a14a..2095ec5a1 100644 --- a/modules/iam-service-account/main.tf +++ b/modules/iam-service-account/main.tf @@ -57,10 +57,23 @@ locals { : map("", null) , {}) prefix = var.prefix != null ? "${var.prefix}-" : "" - resource_iam_email = "serviceAccount:${google_service_account.service_account.email}" + resource_iam_email = "serviceAccount:${local.service_account.email}" + service_account = ( + var.service_account_create + ? try(google_service_account.service_account.0, null) + : try(data.google_service_account.service_account.0, null) + ) +} + + +data "google_service_account" "service_account" { + count = var.service_account_create ? 0 : 1 + project = var.project_id + account_id = "${local.prefix}${var.name}" } resource "google_service_account" "service_account" { + count = var.service_account_create ? 1 : 0 project = var.project_id account_id = "${local.prefix}${var.name}" display_name = var.display_name @@ -68,12 +81,12 @@ resource "google_service_account" "service_account" { resource "google_service_account_key" "key" { for_each = var.generate_key ? { 1 = 1 } : {} - service_account_id = google_service_account.service_account.email + service_account_id = local.service_account.email } resource "google_service_account_iam_binding" "roles" { for_each = var.iam - service_account_id = google_service_account.service_account.name + service_account_id = local.service_account.name role = each.key members = each.value } diff --git a/modules/iam-service-account/outputs.tf b/modules/iam-service-account/outputs.tf index 2b53cc6f7..642cbb89a 100644 --- a/modules/iam-service-account/outputs.tf +++ b/modules/iam-service-account/outputs.tf @@ -16,12 +16,12 @@ output "service_account" { description = "Service account resource." - value = google_service_account.service_account + value = local.service_account } output "email" { description = "Service account email." - value = google_service_account.service_account.email + value = local.service_account.email } output "iam_email" { diff --git a/modules/iam-service-account/variables.tf b/modules/iam-service-account/variables.tf index ed32f6167..f3106e884 100644 --- a/modules/iam-service-account/variables.tf +++ b/modules/iam-service-account/variables.tf @@ -77,3 +77,9 @@ variable "project_id" { description = "Project id where service account will be created." type = string } + +variable "service_account_create" { + description = "Create service account. When set to false, uses a data source to reference an existing service account." + type = bool + default = true +} From b69ea6e07e34d6bcebe881777d51a48744d64f61 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 6 May 2021 14:34:05 +0200 Subject: [PATCH 04/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7daba6810..ea81cdb22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. - added support for `CORS` to the `gcs` module - make cluster creation optional in the Shared VPC example +- make service account creation optional in `iam-service-account` module ## [4.7.0] - 2021-04-21 From 10ec705f893dcf3955263dac5cd784857f9e3383 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 12 May 2021 10:33:56 +0200 Subject: [PATCH 05/17] OpenShift cluster setup in new third party solutions section (#237) * ocp folder * WIP - first part of the README, Python script * include boilerplate in Python script * WIP - README * README completed, tested * top-level and section READMEs * Update README.md * mark yaml template as safe from boilerplate checks * fix error in prepare script * set a null default for the post bootstrap variable * use count for consistency in all the bootstrap resources * use index instead of element in bootstrap ig --- README.md | 1 + third-party-solutions/README.md | 9 + third-party-solutions/openshift/README.md | 239 +++++++++++++++ third-party-solutions/openshift/diagram.png | Bin 0 -> 102091 bytes .../openshift/install-config.tpl.yml | 39 +++ third-party-solutions/openshift/prepare.py | 290 ++++++++++++++++++ .../openshift/requirements.txt | 3 + third-party-solutions/openshift/tf/README.md | 31 ++ .../openshift/tf/bootstrap.tf | 94 ++++++ third-party-solutions/openshift/tf/dns.tf | 68 ++++ .../openshift/tf/firewall.tf | 137 +++++++++ third-party-solutions/openshift/tf/iam.tf | 76 +++++ third-party-solutions/openshift/tf/ilb.tf | 68 ++++ third-party-solutions/openshift/tf/main.tf | 79 +++++ third-party-solutions/openshift/tf/masters.tf | 65 ++++ third-party-solutions/openshift/tf/outputs.tf | 47 +++ .../openshift/tf/variables.tf | 138 +++++++++ 17 files changed, 1384 insertions(+) create mode 100644 third-party-solutions/README.md create mode 100644 third-party-solutions/openshift/README.md create mode 100644 third-party-solutions/openshift/diagram.png create mode 100644 third-party-solutions/openshift/install-config.tpl.yml create mode 100755 third-party-solutions/openshift/prepare.py create mode 100644 third-party-solutions/openshift/requirements.txt create mode 100644 third-party-solutions/openshift/tf/README.md create mode 100644 third-party-solutions/openshift/tf/bootstrap.tf create mode 100644 third-party-solutions/openshift/tf/dns.tf create mode 100644 third-party-solutions/openshift/tf/firewall.tf create mode 100644 third-party-solutions/openshift/tf/iam.tf create mode 100644 third-party-solutions/openshift/tf/ilb.tf create mode 100644 third-party-solutions/openshift/tf/main.tf create mode 100644 third-party-solutions/openshift/tf/masters.tf create mode 100644 third-party-solutions/openshift/tf/outputs.tf create mode 100644 third-party-solutions/openshift/tf/variables.tf diff --git a/README.md b/README.md index a3229e899..375221d81 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Currently available examples: - **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) - **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/) - **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](.//cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq) +- **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift) For more information see the README files in the [foundations](./foundations/), [networking](./networking/), [data solutions](./data-solutions/) and [cloud operations](./cloud-operations/) folders. diff --git a/third-party-solutions/README.md b/third-party-solutions/README.md new file mode 100644 index 000000000..b3e2d5f61 --- /dev/null +++ b/third-party-solutions/README.md @@ -0,0 +1,9 @@ +# Third Party Solutions + +The examples in this folder show how to automate installation of specific third party products on GCP, following typical best practices. + +## Examples + +### OpenShift cluster bootstrap on Shared VPC + + This [example](./openshift/) shows how to quickly bootstrap an OpenShift 4.7 cluster on GCP, using typical enterprise features like Shared VPC and CMEK for instance disks. diff --git a/third-party-solutions/openshift/README.md b/third-party-solutions/openshift/README.md new file mode 100644 index 000000000..333fac45c --- /dev/null +++ b/third-party-solutions/openshift/README.md @@ -0,0 +1,239 @@ +# OpenShift on GCP user-provisioned infrastructure + +This example shows how to quickly install OpenShift 4.7 on GCP user-provided infrastructure (UPI), combining [different](https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-gcp-user-infra-vpc.html) official [installation](https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-restricted-networks-gcp.html) documents into a single setup, that uses a Python script for the initial configuration via the `openshift-install` command, and a set of Terraform files to bootstrap the cluster. + +Its main features are: + +- remove some dependencies (eg public DNS zone) by generating the yaml file used to seed the install process +- automate the edits required to manifest files during the install process +- use Terraform to bring up the bootstrap and control plane resources +- tightly couple the install configuration and bootstrap phases via a single set of Terraform variables +- allow worker management via native OpenShift machine sets + +Several GCP features and best practices are directly supported: + +- internal-only clusters with no dependency on public DNS zones or load balancers +- Shared VPC support with optional separate subnets for masters, workers and load balancers +- optional encryption keys for instance disks +- optional proxy settings + +The example uses a Python script to drive install configuration, and a set of Terraform files to bootstrap the cluster. The resulting infrastructure is shown in this diagram, which includes the prerequisite resources created by this example with blue icons, and the optional resources provided externally with grey icons: + +![High-level diagram](diagram.png "High-level diagram") + +## Prerequisites + +### OpenShift commands and pull secret + +From the [OpenShift GCP UPI documentation](https://cloud.redhat.com/openshift/install/gcp/user-provisioned), download + +- the Installer CLI +- the Command Line CLI +- your pull secret + +*Optional:* if you want to use a specific GCP RHCOS image, download it from the [RedHat library](https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/4.7/4.7.7/), then import it as a GCE image and configure the relevant Terraform variable before bootstrap. + +### GCP projects and VPC + +This example is designed to fit into enterprise GCP foundations, and assumes a Shared VPC and its associated host and service projects are already available. + +If you don't have them yet, you can quickly bring them up using [our example](../../networking/shared-vpc-gke). Just remember to set the `cluster_create` variable to `false` in the Shared VPC example variables, to skip creating the associated GKE cluster. + +There are several services that need to be enabled in the GP projects. In the host project, make sure `dns.googleapis.com` is enabled. In the service project, use the [RedHat reference](https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-restricted-networks-gcp.html#installation-gcp-enabling-api-services_installing-restricted-networks-gcp) or just enable this list: + +- `cloudapis.googleapis.com` +- `cloudresourcemanager.googleapis.com` +- `compute.googleapis.com` +- `dns.googleapis.com` +- `iamcredentials.googleapis.com` +- `iam.googleapis.com` +- `servicemanagement.googleapis.com` +- `serviceusage.googleapis.com` +- `storage-api.googleapis.com` +- `storage-component.googleapis.com` + +Or if you're lazy, just wait for error messages to pop up during `terraform apply`, follow the link in the error message to enable the missing service, then re-run `apply`. + +### Python environment + +A few Python libraries are needed by the script used to configure the installation files. The simplest option is to create a new virtualenv and install via the provided requirements file: + +```bash +python3 -m venv ~/ocp-venv +. ~/ocp-venv/bin/activate +pip install -r requirements.txt +``` + +You can then check if the provided Python cli works: + +```bash +./prepare.py --help +Usage: prepare.py [OPTIONS] COMMAND [ARGS]... + +[...] + +Options: + --tfdir PATH Terraform folder. + --tfvars TEXT Terraform vars file, relative to Terraform +[...] +``` + +### Install service account + +The OpenShift install requires a privileged service account and the associated key, which is embedded as a secret in the bootstrap files, and used to create the GCP resources directly managed by the OpenShift controllers. + +The secret can be removed from the cluster after bootstrap, as individual service accounts with lesser privileges are created during the bootstrap phase. Refer to the [Mint Mode](https://docs.openshift.com/container-platform/4.7/authentication/managing_cloud_provider_credentials/cco-mode-mint.html#mint-mode-permissions-gcp) and [Cloud Credential](https://docs.openshift.com/container-platform/4.6/operators/operator-reference.html#cloud-credential-operator_red-hat-operators) documentation for more details. + +The simplest way to get this service account credentials with the right permissions is to create it via `gcloud`, and assign it `owner` role on the service project: + +```bash +# adjust with your project id and credentials path +export OCP_SVC_PRJ=my-ocp-svc-project-id +export OCP_DIR=~/ocp + +gcloud config set project $OCP_SVC_PRJ +gcloud iam service-accounts create ocp-installer + +export OCP_SA=$(\ +gcloud iam service-accounts list \ + --filter ocp-installer --format 'value(email)' \ +) + +gcloud projects add-iam-policy-binding $OCP_SVC_PRJ \ + --role roles/owner --member "serviceAccount:$OCP_SA" +gcloud iam service-accounts keys create $OCP_DIR/credentials.json \ + --iam-account $OCP_SA +``` + +If you need more fine-grained control on the service account's permissions instead, refer to the Mint Mode documentation linked above for the individual roles needed. + +## Installation + +As mentioned above, the installation flow is split in two parts: + +- generating the configuration files used to bootstrap the cluster, via the include Python script driving the `openshift-installer` cli +- creating the bootstrap and control place resources on GCP + +Both steps use a common set of variables defined in Terraform, that set the basic attributes of your GCP infrastructure (projects, VPC), configure paths for prerequisite commands and resources (`openshift-install`, pull secret, etc.), define basic cluster attributes (name, domain), and allow enabling optional features (KMS encryption, proxy). + +### Configuring variables + +Variable configuration is best done in a `.tfvars` file, but can also be done directly in the `terraform.tfvars` file if needed. Variables names and descriptions should be self-explanatory, here are a few extra things you might want to be aware of. + +
+
allowed_ranges
+
IP CIDR ranges included in the firewall rules for SSH and API server.
+
domain
+
Domain name for the parent zone, under which a zone matching the cluster_name variable will be created.
+
disk_encryption_key
+
Set to null if you are not using CMEK keys for disk encryption. If you are using it, ensure the GCE robot account has permissions on the key.
+
fs_paths
+
Filesystem paths for the external dependencies. Home path expansion is supported. The config_dir path is where generated ignition files will be created. Ensure it's empty (incuding hidden files) before starting the installation process.
+
host_project
+
If you don't need installing in different subnets, pass the same subnet names for the default, masters, and workers subnets.
+
install_config_params
+
The `machine` range should match addresses used for nodes.
+
post_bootstrap_config
+
Set to `null` until bootstrap completion, then refer to the post-bootstrap instructions below.
+
service_project
+
The vpc_name value is used for the placeholder VPC needed for the service project Cloud DNS zone used by the cluster. Set it to `null` to use an auto-generated name.
+
+ +### Generating ignition files + +Once all variables match your setup, you can generate the ignition config files that will be used to bootstrap the control plane. Make sure the directory in the `fs_paths.config_dir` variable is empty before running the following command, including hidden files. + +```bash +./prepare.py --tfvars my-vars.tfvars + +[output] +``` + +The directory specified in the `fs_paths.config_dir` variable should now contain a set of ignition files, and the credentials you will use to access the cluster. + +If you need to preserve the intermediate files generated by the OpenShift installer (eg `install-config.yaml` or the manifests files), check the Python script's help and run each of its individual subcommands in order. + +### Bringing up the cluster + +Once you have ignition files ready, change to the `tf` folder and apply: + +```bash +cd tf +terraform init +terraform apply +``` + +If you want to preserve state (which is always a good idea), configure a [GCS backend](https://www.terraform.io/docs/language/settings/backends/gcs.html) as you would do for any other Terraform GCP setup. + +### Waiting for bootstrap to complete + +You have two ways of checking the bootstrap process. + +The first one is from the bootstrap instance itself, by looking at its logs. The `bootstrap-ssh` Terraform output shows the command you need to use to log in. Once logged in, execute this to tail logs: + +```bash +journalctl -b -f -u release-image.service -u bootkube.service +``` + +Wait until logs show success: + +```log +May 11 07:16:38 ocp-fabric-1-px44j-b.c.tf-playground-svpc-openshift.internal bootkube.sh[2346]: bootkube.service complete +May 11 07:16:38 ocp-fabric-1-px44j-b.c.tf-playground-svpc-openshift.internal systemd[1]: bootkube.service: Succeeded. +``` + +The second way has less details but is more terse: copy the contents of the `fs_paths.config_dir` folder to your bastion host, then use the `openshift-install` command to wait for folder completion: + +```bash +# edit commands to match your paths and names +gcloud compute scp --recurse + ~/Desktop/dev/openshift/config bastion: +gcloud compute ssh bastion +openshift-install wait-for bootstrap-complete --dir config/ +``` + +The command exits successfully once bootstrap has completed: + +```log +INFO Waiting up to 20m0s for the Kubernetes API at https://api.ocp-fabric-1.ocp.ludomagno.net:6443... +INFO API v1.20.0+c8905da up +INFO Waiting up to 30m0s for bootstrapping to complete... +INFO It is now safe to remove the bootstrap resources +INFO Time elapsed: 0s +``` + +### Post-bootstrap tasks + +Once bootstrap has completed, use the credentials in the OpenShift configuration folder to look for the name of the generated service account for the machine operator: + +```bash +export KUBECONFIG=config/auth/kubeconfig +oc get CredentialsRequest openshift-machine-api-gcp \ + -n openshift-cloud-credential-operator \ + -o jsonpath='{.status.providerStatus.serviceAccountID}{"\n"}' +``` + +Take the resulting name, and put it into the `post_bootstrap_config.machine_op_sa_prefix` Terraform variable, then run `terraform apply`. This will remove the bootstrap resources which are no longer needed, and grant the correct role on the Shared VPC to the service account. + +You're now ready to scale the machinesets and provision workers: + +```bash +export KUBECONFIG=config/auth/kubeconfig +for m in $(oc get machineset -A -o jsonpath='{..metadata.name}'); do + oc scale machineset $m -n openshift-machine-api --replicas 1; +done +``` + +Then check that machines have been provisioned: + +```bash +oc get machine -A +``` + +### Confirming the cluster is ready + +After a little while, all OpenShift operators should finish their configuration. You can confirm it by checking their status: + +```bash +oc get clusteroperators +``` diff --git a/third-party-solutions/openshift/diagram.png b/third-party-solutions/openshift/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..c5274e2b5e7f9c2f52b61c18fba85d0ba7b2c83b GIT binary patch literal 102091 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

m(5iZ*NPmNkh=}4y{%&H$2z~sxu(;Uf^)CFv zDiC=rn+>&}v!cnp^hnt%F2>XGBT9Zd|1sI9Vf?p9TYNS?K^%kcU?Dsg(1*1&XAM7X z-uz^b+7}L}(G?F;)roHXJbNt4%Ahb$ak9qPK!DgYY@gtbrtg@~PdFKCAD|0z#T+4X zO0^BYQ(ZljsF^I7T{yU8TOv82rAi5iXTb-q%XB|(%_+RnoWCU$7_96cW*5aVu&j3Y z9sBiQ2}LCaO&L3(yPGANT>Po2-(bL?s3?$2Kpjr$7GRRaqc_GZ507|V&@xm-uC;t4 zDISpxyw)emzsX6M(5>nY4qg(}V}5&P9K4}niB!!LDUeV{8=V-J_lWdbF659uc*3uLj5h)@dpfG zicxw0H5GtQZzA%f>T{t^+`L@awM`6U zS+k7ErE4PzFJmI=cNn^`S)HkV6(S2RAq_Xc&sUK590j7-?)6m$98 z?|?fUVR&sZ<3_1;XE+JMEN?&B4WeN_qKAxrKR<^Yd_^-tURBi5mE6835 zubJi~p@op&lROY2AXs!>Z9i9th?B9mr@lG8&?}oPuC<#~52Ev&yVT<}aG_^WCgZsl z*42*KD|34ENML?JKD%{WvKB4%?gJ6#9fdMSdN5HO|LuzZznWTmA9o1RRdQ$xDG9M6 zI@yhimC03#!G&Go7&GU$Z%@eLv;4(Gaa`l|N>JsmxX>YW`}MZGcI(fAD3C>tHe`__ zz$N|8yU6cPr!R}t4365IH?bV(8*(q!#Wp?{oTS}FqXyJaT|+>sA{GXO4MjC zKU{J}mw)MZlb(>lnM7Q92eXn4w#-^H6IogLBIses-Bc3dFAH727xU%*VfbLOTU?<>>Tg^!CUf48YTF9H1~g-5GeFpKPC(reD|@m znZ|b9z7_BZ4UJBOjN~hKA-pZ2+GnOC7QWS{XlWGbeAktqQp9cZTSJd2o8!r-qcI-% zPiyz3i(-KAZ1F-mDn6DdfDYR9%FyRp*>0x%E?^pKZ4F8l@;!sj`d*6nrScOVuKk27 zD|2P}p9n`&@u3oS$A!*V^h9bDmD^w9>0ifuS3NsT)$#Tl;dQ_M$Qx((BWskxfRJ#>6>()fGA9y32da>5pbWrNgw4f4 zl3gd&+FFlmG{2;p^r8F{`9n*k9z>lb6r$7QK`n8%PFOaFBl3Md?=>bS{QTem1L0)1 zLQJfEvw>A^Y;9vFt6&8UyGI#W=W3zY`l8;RH|I|h*Y9V~^*CH)<>ZVnz}P=>7R&Ox znY+);-G)2vCx0S~AfMv#Ssg=Ch=b$)GXM(4IA93D=Z{?R&p*xa&#bptd1)@`B!KGPZ$Wh7%kliHxq)_RoxH!pcrRgVU=T{F` z!IxJ!G?7A514FS_>G|j-?i(3GIFu?x-PU0tp~A1&SlunfizWCLja9 zDo+XSA;sjHvfZ?KW}p`|Shh6DrUBPzeb&0+_C#}ZZ_uh!)-}%S&n{aPWMi>vBr}Ol z>@>;#ce(Ws&GM`iUI!|R41WsgEf2f7Iz_1nUkQJzeC1TmR^!bRYV8V-Ozl`;>Qn{U z0^5+Y!LEbjs1Y{s{y+cw{c&TC;og`8TmIHQ;xZ`BC2N#d)g@&!2b>!gN$@Dv;9L@o zRy~yZ*57s>lgX7;K`%`)cAmRkuh&~EqLk9sBPQ(W3C4%D)S!!sxpcQpqV&*~jCF2l zPpahKT%TTAE4NM1OnYf*Xen;N{Aqp;aFLuhe*H|rU4#7wA76bgJCxXb@483{D; z=DW!IzWG1y`wTj~ZHv5I6&|+>h_U9L|IMJ05B~RUi4bm=6v)e;4=#yNRr!tp(7d2NGrx5fOo1rW+>`))=pnP$4gYs5#iI zBch|Xs`-bfr#UwtrF`tRmQVbR?ukLE0Ud^;96SAe$bkXmWV6HmxnYjQZoB`1h1z)H zq~R09CkRAQzR|>x=H!M1W5si6gZlk%iwd;S+OnUU`1B*G&B8d97JX)sf3i#e+qQC&*^E&*;a+E2(BU7e1`S16XX-hUaF~(q z`rN;?_5OXa6!9LQ(2gj@(8!4ct zlK%ZO|M9+u$d5f-mG#NPaziR;Qzxk2`^#DSyEO+%Aj!og#Og#UZ;Xpm?umCCI^)}$U@$m`s^Ya&$mZA&R>%IsY!-BPs{l6oSIS(94NHf1r%4tZYr&1F*k5 zT6jhAzaQ`Z*W+JSLY#p~$Hwkd|mnR(6QBWxvc&2$>>r`_LWi>1*?s>%9Lp>%RS$ zzW>4kARXD90zll?*{K9ZgkDp!7)ezc0ivROKYzN3|Ubvc`RIuu_;c=2Ji?qc0DdMMycK^Tk}$r6zdGWYk;| z()MDNJ#236u7&EHG4l5ulG+Fx(Z9bjq%aWMm#>TZiLnP2795qx@ZP2mu!RjD*UyZ9 zvav#RG9sWNkn;Ca)_~RK@qd|n&M7#c@=p6RPsDnWk;m7ZsSNf(W#q3}7LF_b{*)tB zKyBhS#KiyTQR{+J>xv8Gg9oS{4F>(w6B7}DUglhklanYnH`mzsU09f$5EW~{?Bkj3 z{@)Q_g%wPZr(=Z0BMtI)ewj-%{FiQkX^nUymwx={yZ1)2{Oa?tkWT9ggCf6(? z_Hl7_Uf#oh#LsWGH{aIV-!JQ>O?I-p{PfzwU&4wG$lbwcXt)Dq71w~Z<+-~3($okl z1v`&|7DwsYzj@}7=6H&{AZpcSxBZ7Xl`6;qB)ubnp{lJ@`JBFIE%%SKa+V}S*)-Q*$e;vzn1J{+=7=7SSE2&APR=^~Ih zpn2QRT%S>AkTlXe&kP1W{e4Pyfpnt+YyWe518TrO014TeuA&T&qk_9u+08l@85rbQ4pZEx@@^32(+-5 z>8SQg9b51DXTfOWDo0;@WBLGB4;J;tf+LCws`IhHasjiV=>6Yko;b2I2cE{*j+^|$ zUD_7lRn&Afxj#9JVsYZsq~&$Wn&%ZTGB7l*=tpx651j-v$$W8;qmQWDI4t(IjQRmI zbE~S^=UY%Uqlih0e(hfBdrHF!Mlh*dgO372be|LR76jIIPuZ>F4Nv{&o9K|MX;>N% z@W^Pm^QTeACSlA+Mm`<=HI7Y{i>;`j5HWXee|F|Q+G~@?0N)!47?!}%|4Di?9>nP& z{R0{Gh!2}%O;dxKI#8sirw?jB$F0o_>_SzZwNsHS+0I7guAw9;qFzwrD~m$Q+#Lq= zaqshBB(*DPgYU82l2Cou2-4#rzsoIy`bFZZi~cpiM~}fsL##Cq5AT`wlp3H);}qf6 zm-6@5p#!>KX0p$d@=NXSP2VQH%dM*;|M>Bx5Xi-VC4I-iIrn_{eQ57@^BfJ_ zl&LoxFQBF>O@F>)0f=YBYaDqfZRbyJJJ*7dsp}Ugb-NnB=othsa~E#fW_Lp9+`GbI zq?CSJ{>7w^B@L=GCAO}^vEm;Y!=$AK}x3aNx6^Vt^ zg1AIoVcA3Hpo7$j%Jq0#4;J#lSz-QF^1&sZp?+3zsDO5H2)!RpL@kP*71T~3?zoBs z9Nz0ltr*N<{nfQKv){3qtl9`zAn^R6m?J=yPe$Cp97lV&9{Y;Yz!K+_9AshDbt4go z4+$d4JtIF?i@T!YGv93pLWI56d3SC{f`we$d_nBmiYv&@QB>yFqmser!(#C>4W)kO zsOCMab2^$3(HV-$_S*T{r77jPqAkYfJG;r=;ZeqMk-Nl6bcI~_P=+xz;IXKv8;``! zL?}{Dd`hB&(D4(046JzaVECB%9#8kqr=pU_RuMCIIWGt2$--k@Ny&*P2|`*DlS~vV zEKCL5J&X{O64@0cC$M|Vm=+x~(xX^=Oz2@-Ix8}m)Gqw_X4{A zyV>eC^5LQy)c^&aMnO6B;(6>@?=#uQ7i*DYdM_-MiiU^P%MrNuuy+-4WDOHE#z*N` zM05?`o?qHc=&hD=Tz>2Ee(g2ZvzX=lMP1Dx{11zuDM13{!0MkLCgR+Eh({#_aJJ^* z0F~0<>H@Z5XLSMc=2TGG=)Ac;Pm%DWKpp@ns&Pa_m9-s6Rc#K}z)n=mqbPtWmTbDl z08M1p)|S6dsJ$*G*PMUWly)BsNc4a*W*La3iAYJ8inI)4-!3z4ef!o`k4veh`S+A} zk|5jf>fO#M_f6;h&^3BKKDF-Cx#ZjMygb>_aUV{t=1Qv|U$zRE2;dC}Z~~cF({Iv( z8B#q&(bmY-{5X_u^=No12)XolXO0S+OQf`<;=8-ig*HFOQ_zAwdn@hB_$tUQ$K%J3 z6%-WWjSu}k!4P}oi9CHm)jPhWe-!IaK9)NfscWi*4vS#CU&4fs66Epx{1V$G33i^d zg2Vw`oZ0cg325@6SDHRLZ%OfI)P*n}=rF1j-f}QEPt<^Z#5Y1#h6e|-Q0Y_R`bjcp z(PGW0+UY^}?zv$S2pbz$hvJtjRpTz5DAL6*suID`(T|Wd6i&U>{>+$;@VNPyn%T#i z7A)$|6P~?LM_gUW)VggvCP)l1^E>!txpL|{a6?eNJwboWV^mF~q4CRWJEq2Y>3G~& zO0M3Q?TbzWjbqaxO=-i4o`Uj|{ok2AbIxfc1+dNxQ!IGj#>c9=$V1q;enZVq4;(Di z=|;0!X9!jaiROVI#Kpxc@7#P?o|$bP53Mp(9TY5o7sK*+R4Ze*-~P4WQH6c;?trvs z;ir8F=Svfdl(MqHL{mg%HN@olAQFqJS26-fqymp;DWjq#gw0+m{?8wZw{PFQ*Fk-S zvan#1B5En{V0h%3r4AkPL{;&y)1~YVU}i(r@4p#la4tDNl!-H*C9mKM*daf-trYV< z+HV1ohasmR!QxWq^;*D}7G@n7w_f#&?uM7YJ<4y8y+yuXuO2F8J|M>??ITvcIcx0@ z^1HB3k@Sp}$_S_NrrPR2Tgzr7;h8Z23n?9Z^Zzgru&iQ#0{Y?DjRN^o2QbS=JLe;_ z81mrCwjumFpVNgX<#eX3a1=N?FPDSw^pi=GMtbPx*sovoj<+bF{ROopyg43Hnr)R4gMqTS_jC%c5&Lbp^X9pa$+he=IY(bN^T1GyEZYA`*=YNQkpEOin zd0l3nGOhM{ez{K44ih`+oZJ6d)a+5bJL4D$rh2*ygc%uzx}vLRv1iuWOH2Dc`(%H)-mgkl1i9?)v!2^!^80AU6zGxg8VTT>`k-_S$kY z=VQTU#CqZV^9%=xfLHVQoDD~&%d0yw);i?PF6YVkl$1-VHKI+i(lA#74hUaC@)#b{ zV-4EKmp`@#U+QM(4}x1IN%4A5WRH^PK=z4*5YlR6V}T&>-e@+Pe^kEX z$w1XLHCck-QHoC?8GAzAMD#@TP~pqsF8lh9L9S4>shErv9EGQf*bnhrL5G3OQA0_~ zL!Z8x(`)p#tR}LDs@byyiObjbB)jX6DV>(wv!Wm1KrSbYZew$0Fw8DbG8b7o?tBU1 z3=zHcU6@;xtMD|U;cjqaV^K{rX3u&OpI`LT0$Aj8E&2%Irb-WEZ@O%_$`6%(yb4zN;aPx7`0)8cNN8an z9ja65guul|p%1?(>)s&P9r;L|o{4hx#@#UhW-4<9<)_sqkcpSG$ewJ$Q1 zA6R8lv#@&d9GvVXug~BW9EQ;Fme`q@G3G0$hrQ_t$K>2odGn)jS80RfyB#kHb_#V^ zHonvuHX&*d-4;`D4fm~EXeZNu^6TtDI^?%YQ8DSvG|&>s?r56Vk&*0T02oUq`iiJu z!iSHcy@UDA=FI60dWB^sH@OqpL{$N(MXy>gz@UpxsCcA7xpl7P)jx3eyl8AsoQlvQ zpjC98+|GejM+|5ej1ZHAa+Yw)*rjjXNwXLQ&~BArW{rbe$G|-s;vxA(tCp z+y7;aLLfWtu`_ws!qM5a20g+DI`cxe2a>y~WZ;+C`li~$mAmFg;?&kec_jrKC#SU8 z^Ta@C_ZnRY@7E5_49R;ssdumX@jAR1a5_c6od66$bLB+ok=~fE#mV z$3TVSB{w|N-jE)7y*2QKP;0DCO?yMmAQ2-Zh3!o)Ngstt$KVT&t((w(;g^RYDWEt) zLZ_{z1&IA2N?DK8NWWvxrOMw?*%cqyD|z5_Q=?cp5CJ`^C5<- z#X!QATBZ&d{!gDhmz+|u-yPGOSymHiH8|%@qkW0q_7~)1nvC4&uv3UF58NA^dp|Ba zZa|np7)p0j+E={^YQri1uiSQ#@%3(&wGTf7znw0#!?Vwa79af(U$ZteLTDQnP5E?_@z zu_1^pZYR@#a_ktqL!M8VswbokYBTc!O?=TYWyTwoFeP%k%{|H;0M~w@-vJ^3J}cwu z+WdFP$!41;+kk7N-7>qRb?){;$m{yjHuI&*`BK&C&hKzN$4h(~zcV;5C2`dLUo;)RN8ApiKxKG2<^yDc8_AE_T%WLCEclmnAkk z5cEugW*z(=+y;>kEe32};}fk5skrM0gwQen;$Eh5&&}(`)w(lA7yf;^m(4AA(mE?g zsqgDT<2Mg;7HyMbLb5;nLsjD;;G~?h2D`1kjD7BZ@-JS+vkk$>(-`NsqwK#6HU$uxSBy z@W}l<+sF*1@BKI;!)ve5L)UwfDZ0PM7-u>bO`gK<8WtE2^ML|2n>Q7LTE^>#w-@(r zP9_*<@ZdLlzvqlw1933>`X*%3YwWs;Zhff7{nzwBB@UIWBI3@=^?7w8?*m}6UtX8Cuz(b*ORrsnC_UI0`|wjXGZr5 zelX7m?l8-VdiX%mEiCC-Sn5F$)AI#;J<1BLSV&v2nB#ztE;1;WLoJ5@7ys@em%{4` zhEa4XcGuj(?|x4Yue0vJ{e5m}ho(nE90Lw*@(KzvoqqC*QcYCZlJ1C28|(Crl4Fj%<~+dipNV{%vG`pESOPiE#j=G0{W z+aAfKeR&#@Zv@Lc_pZ)EBw(Y1X~Bc5qH`B4k&#gl#MR!dL)*yh8Ee08Bb3y@t2yx3 z7%XJ67%(7G?ln=Cc96STM6JkcXuO+_1MQ@u$Fn1I^a_R4^Hm8%X2jas&lb>U3{+my z55N1*kY1MB6jcM8wJqd2GDLcJvvng`7^>h@g%2k<>YXoT(zDzS(tmr~o~}%%qaR&b zu3Y_7`ZVpR@{y{Pnnl=xp0qT6z;;sbquJ&Ot^BnstTBMM|j4wQ7gamXKNOLe_rMC+YtARYI;Ob6|Y zN>Pu)PWkNX>WX!0@NRu~(2xKh9UYzfoa~*m(@hg!?L}|S`{w+lVJF9lE7^;60oRl* zb*IV3(f6d|F>a4Ais$ywW_>rQr*ArLJ0cc3n3Lja(s6f90?tP6P9!IXlRs2R_M(#s z$JrsUExo~mGjs$le9|;$(H)w=+2V4cZ#f#>m#D`=tn&|vgOv?)oznnExel$TzJYyi z0n!mE7>Gal!U49*;n(c6Ob|A}6pv_=11X&@1*QiNs04ssAe8HITH0Bio`A+94%R^{ zJ1e51gt2Tw1V~K1)_7vsL%A+%{mOg<+*nyro*o+f7EkFbLmwXBjHLFe$VWvbPtf9J zUBBQ{Gw<2btooIq3XMij24>jed2{7ZexqH#%mBj75B2%FoIL9ko->|b;LgtFcfy~; zVbX)yH(R8O{vBQ-Y~R>G*`od7tuG#}{}JZR{^;Blf=<+9r*mMp9wScdDDbF5$kEO1 zk^kZ4B|a%>IOUgyv5|t`0lw13_}zEU&k|QAYP}!++5E%z^1C^wUcKyB4foY11$rni z!_TlSCJu_UnO-h8OkOdK!I4q~-796c0e%2toc4St4Fi6T5uaZX8o-m-O1MAzXZ(33 z61lqQ_HqH;qNm?L$Tv?)s&|wvF+MGUA-!hv647Y7@=2Y{fQ)}fM) z34*1_tSf0kZ!c6<{_*SM@T7TU9X2D28fez!-l?eOWl>Q6KzZCF{%vYY0gPUKj_+E7 z;QWr!^lN9c?n-O4AqvD`%yNCYLwB6N69)injk?VEH=8ok+-@eGEnT+|*AN)NLimJB zTlOu0>FDuw6=e=+7I!)Z1OOlJhzCF}e(F`v3%1W0+mJMHRtR?kj_^TMJo<~ib6xGFyXM9&tkVLGY=niqk7549 z=eC%%u;&G-JM2(!GN0-bHW4FWSgwa%YjlX`^&_DnA(hx_I@v*);ym%wT*tvC=bv|Y zZ|2HBiLbSm*_>F;Y9~u}H|{eVS+={}%8v;113t;l_b%NV1;+}WyJH_Tp~J$PQhM74 zNN2P2{DHo?SLCVW6&c7U!0x+z>moHd+si!&RXIH3(yIyHm$CRML_LzPlyFoF_U}*T z>Bdwspl37rr<{@e; z^uw~{_t?xHa+|{x;P9gA!WEoDfb&}d=kP1dWWBxUPV=e6YwE@F-ZMTL|M@rqsnmOs z0O0TZJI)^=f4lhY!ezM^f7&xE))8~f`dlN>!JrEP4+sgwE?Iw#7^WfZf zX@U@I2aoyP0kL3vy&A=_WXYRzxR?j$7?jo=Q_701{Zdugo@>@`*RS!WTd(%kvn8om zC{yD?6k-yokC0x{UAVohH~iO1mvs!Gt9-TM2V~%7Ov!&-QlTi=*szD;kh=_bzp*U> zL^K$9R94@@dXgn8!zjhPc&(bo-YC2C(nC>DdXf?o?{8N`soZH=kFiOcXywuBD+#KCx=p$GdTLmdwPV;TcD4Cpz2HClIMBPqd=oF zgQTPpYS>{z@RI72CvPqwtJLIrmVN^npABN$w6d*{D$CRQMQk<~+vrT(Cw_;z0d$0T&Z?!%55j2b|H|z2GDo)MeTj+mW zHNHPOa@%SY_cDCp?lS-QTm%Qt=0WXBFIpORwLtH*9#_Xti|u-MuifCdy&2nPuGZjtA+}?spabWg zb7mLW%m?aR?$8hjHli#O*)V+k=NCYMIbqN9Nul%z_+OnR zk*PyyAb{1O6l4xBS7!T3Ezs}JPvk45GQ`h}cQ-VN%9PPTTUuOPT|s|{QSU^wH)Hfu zMa7Ov>k(c6A5SXZ--WP%=Bsz*YLp!WA;!j_eHYkOo08trY8^23yMt42)-oPBzxQ1m z8y%b9u*~Y(Tj(fbWyJtec4Xo*{A+@=AUa-TCW7BnMUX*G?9KhLUhNq=bc(;`~T%#*x@Y2jR{@EnhjXIn% z%$~9l9XuDf#YIW|F&xSMc1(a#MB>R37COUwnvqRp-1qqT*@@4t*^e=M(@E#^uZnyw z%TAM0L((AOJ?Bc=}h5E`o8eD!d7YbFYm0>v&z6BANHLyyNxjN+I{jhNu_b>v<kav^bIH;~9E z#l0_UH}u;yVs2!fGp@8C_zSLRt|W`@y;_Xo$^BG^S#!IG5fK-4=*g}$z8MG0E5*G3 z#XYzA8%X{e2b5D%9%S@#IMa_>wB^Bp7o!bNrU+1c8jTh`F5|R*&uY~6{pOd~vkCt&<|LtjX}2gCOtq`F~jdZ?&45eV?%TUi&Z--V49I`;~ z!&MV-ilHVg%$pG<)wr6cz_l~--B0z;Dx^j8kFR-|D@V6?H0cc89WgB4V<_f=)z4&QK+qR*(dgg1h5( z&&avOZhes%=Hc(IIH%VaH3~{fZ&S3>1?)+pzJCcgep){3d-g`&`A}JKvGUV%d|Fyo zp2t(0BXRz()-dfoA+OGT98XINU`()IM=8z7A_RBZ5a8~i_nb4t^r~O)LnBLh$Lt7K z$Ua?O&1r5%No0xcN#Xl|PfX4xE6nQorFKAxN>Zg6_ThtwAn{AmCqrhcAAsL93PFXF zjkMCb-Viny8?;tr3ocxj;61^Ab=>%@kJes(;x$Qk!IoG@JATMS9Hm1FWwF*a=$I{c z`DOF^@VVN~lPGEuimNwuyjPP~up+a&>DU|!4D$zipeEH)cTCs$S_Y#d z920&ve>|1$h*<`L?9M%tj=igv+hEpguSzrrB4W}XdZz{rovD&) z7_9Jr&u{{`GT0YpcEN1tqumjM=rh#msLz5=>1cQmTL`LqY=| z_a>wadX;o`#MTa&o*s*qFATGshixg_xerxZnBZTPB9!uVZMn3)76^GxnFp# zawBPdpdJ?;vM^EYI2Su^6mZR?ZFUrNx&hh~ew+yjv~Pk&`N#TxQxudXOhN+E^sx`) z32>m&H6)0%nmK>HonZT)lqD`gmJcoZ;&&X5v^*tQG@v^huX!EYU z@tN!SsF)10_^!qkzxTT-9Zim>s?MgsF<@{yjXcZI`bhy#nr6SCHh&VG12~q|bb3}2mlC$&e&)uD# z;DDQ8srPygP0IS(BkIprM5nZ}8-L@OXNiJF$oSa8f}Rs%WM+6sNmE1PLvya7Ln|ie zGo3UzOZrecdnhc(eucV>-gW8f?+lCHbLGke5(zNZDbN~p-eakJW^DT~EwlC4p-j1P zG<7bE>0Jy$#Iui}qVYuSN5DC68-|pHLu5=;*KE_}GFQUsUCz4-4bsD$Eq97`MhO06@UEBEeGP*AS;!vikHj?eOK$9KdJTDzsC z+62yxlj31>E-D`kH=7U0(ymBINKC+<&b79dzc7f@uq}jP2X%(XlFQo$c)Aw#dT#F^ z=%$P9W*c=@lSK+FEg0FEu}$zLe5Zm@;7)x8eN=bbKI>y-8BKCpyUBmlb3QMIDbbjd zp7I4?uZ^t|2Y1db9i^?;+rAfsgQ#m-$9j%-$uuxJnOVBzqJF$-X)onw)?%oK+s^smts^V7sSdWuk zr4wJ}-s#0*DJkexD=#r>q1kP|EVbMF&3@%TUsgAdz&5voNQY?2>A$V+mh;SJR!r&@ z!zkK&+yQ*25XsvGPtcCQr77gQ;WXga(tYn{*JAo~bFQitGi&7RH?`zBzW+&7!<(ce z_MG&dePQ0)|EImL3XAJm_C*GnV8h_S1`ig7!7VVj3`8J7gC;;iaCaCq!Gi}+fP^5y z-CaY1y99T4yDR^__qq4H-ly|$=4pNX^{nnyRbAa(@~Z`=>pwp;Ql03KzfS2TKymw5 zsZVUze!e_klTL>w^4j8?Rz7YSYMplh9D4b2V_SWHZw!59`DOw+0QWT=m>~Gi^p1v@E`Oj_jJoEuFAA?u(7a~M`*1e0oNYjWaBs-V-wQznse#vNE3yhj2T!ZO8DEe22MK9?C<~DbGcMG}-E7>{pKtMQ zs#h~m0DlA$5<;6KFGX!?Z^i%SlcZuGsrNP)l^EOjcCR^o+^!zCZw63C&FO}mw=SGA z&fB-z8wf}@T`{=#Ah}Vs)>4=4^Xfv&lh=Q3Gb@v$CCSf# zcskA+EHtbp=;3l=EecPu&{~p`x?+SaMYdA{ek92-(EKfSC+Sy9^S4#h?|!7!yBE9(HMTLd^S#bNPt~LI}Ws9HEEH49;K5$I9kz zc!@cdr%a7U>P-_3SX^RR&AbvQfFxrz9@6~xI?T7H0HmFg{~Q)_T~%odPY*w}w-C+$ zXq~GiLTJ`x9np&Md8Z~EzOb%qkw#Ihukd%jSKPv@y)3u5Y4*!JS{qDU_(4a@+i&Q7 z@|>KKvQk2Lvss6Bc7wQ09l`51=e86P`^S@-JBiYPjutz8h&g%}UNAp5K^+=bBtSsP zBg&bNQYyMv6k{~k#q;`o^`Fu{2-LLe3t$Cmm22D2=?Jsy8r*BG`f-a($&(|*fLb)PQUI6`srv0m_bDe=ZGdCls`@2Qq&GW{E_E1$m? zkqTcO$iPh&Nkbo_pbwA&tDV6fT7Yl3C>Pg76!O--<*mKiHhHdv;0$=fL4J$4u}0u} z=Atrzq`9CkMB5Cz86!9+Cx?Sj;aQx1AwCh2oED15)O0B(=~M7ArmnKepnP~C%|>1i z{Ri-~XFILCSX8QSXENF1CjX?-cNXyDnS~m_J0c6@gJ9;)d%c$pIOr#2ouRNNubNSY zgL6SPAlE|P9UmAgc7E^3zf8t;^{Jn_qo>sp~372h4`yYar zsv_2{QDTm(-3_s3OAQ}&9j5MCs$sMrH-$4HoYsH&b&bslCB&w1b56fWYw4clFX7km zI8gW)6oc7J)t?g5`<-iKbhNkO>X45wmYT`^ibT2A)raHPfS@D~-O*~g|MoWOdY!{H z9fRw9_b)o*X}QK$6ayda3*uPhV#~%>-fIcNlxJrC5)&f?Y3tIEcTwdjEklq_`8R*# zg=A0hme@X9;!zGSRW)AttSYkp!sWYF4!7p=X}t5;bm#9BAbFnbcWpq*()G31q@6?T zz`zl3BkXt?m@F4=6-P-4;s|7SidYot6Q-7lk2W6p_7uqFatayh#szM%M&&vt_jUE6 zXzWqcW@t0B`;%j4OXpzqb#{5X=7XW_hWRacAB&@MIZSO4q|l(m9%7ML#B+#h6PV>bny z4nV+Yv<|&=-4X{hF!knrRG1P_O`<@Aq3fK1Ehne6^P9xp{Ppy%lo&KI45RBoc%O6i z0{+ay!OOyL1pfBQJs0N&Srl{o5Pjp}lkdNW}0k+6??`NAXn>M3*b>m;=~B%nHP_D) zLMdV3Pm@rBO!Vs?L#OQKzEKH%r6p`3zM3AJu#9LCV}UH{=tiC7DOq4dYrfpMz#~bA z6qoPK(_GVL$wKa&hV=dkoALJaadu+=Cq$bEmYD>b+l99cO9Ew=K@-mHWZd$AF-dHl zq@w0x()Al;>~G4IQnP9HHz6MWNSwbYk97uEJhHqR@;Ye?RCHOvuFVp+X|{Lunvd=q zw|(nsqTxrL3lIA-UW-UFLwoWzpd_qfymAH(pUwU(JL+ZYY^la1d?eQb+z|gUO&c4~ z<30kyI~ZU1&iljYz#>=m=|okeXcC*-X~(ODlg`@Ut@~S~kp1~cjA#H;t+LDWUYEs; zHh_6`+Sz&8NfITp8Rndv?5Azb6Jsn3F`E;`Q|#nNll)9M^B%3ErAy==90`b?Oj?VK z9N2#T1Cnjs0r;z4s^~TG{k6mK{rn!u2?Hc8DYo!S0*sTN53`!-rl5JX%dlOBkC`U7wknrKbjx*hzL(RC<&L8PKoV{;)h`ASHcz=r>5s#7lC9 z#1G%xA#U3W7*{c1Rj*+xf;Sp;)eb?ktBTZZej0jgu=Us=tpa71_?&h(_w<2kr{!RTK_HdM?ImjF!N5Lwr{ZH8rXTU>SFtlqU$Zj?4Yld^=bJvVbqWtN35X_o;>Nj~!Ieumx-t2=Usi1L$h>>C^RZsP+vYwmpWVv??u%T}9w#&d~CzC8soR_>p^q;fx#=4Fr!6*3BYqBtb- zSY~mM=lg@|A4-sA!NFgMP5e5#u z+O}P;K>k3HaYN6-cfB6^8$J9;d^~)dB*RY!tmk}Y#?+8N+P9Rg#BI;fR!mY-Fa21M z7`|&29OJ%*3}Zzvxl`WSl-FFCdylE9sm1yVAP}gI6nt`Y@gAX1uU{eI{oXZPEbLpg zg|-1Ug07Bps%;M6>WjhN-j5jlxEQbk<5ddtuq+KazO4g>$K7&7ijj1jPufwGsWMFX zEjVit3$5pgW-Duji}Cz`jXf3nC#~{Wcr8^ zkTxSVe`N2VzNR=j`^T&% zjdO~8CH&rlqQ}N1jIP?9N###l6KOmknshp?93o@F|3PE($usl_82k)eKO*)wT9kNrn|CoS*P137tualm`=^^5{LT)x%BHzkDp1V|ud z)$D9{cB0H8sz4VPLx_h}Lr56=6hdt`OG}e4`l7ODn;X@ozEf`&K&=O-<`Gz@&a?C` zd=5Z)QZm%Lh}qT`;TP8Py8iuF@c#Wj^`wWtNr0pct9LZ0qGXwX|tw9AQW#4pf`noKcCN!*G<* z6%PEA2@~dq&|(ILoL7}t+1>(o5h8F>4<3(A?zvrei0PwHKHXAzhUQ0i*N*PhOL8zq z){9O%02CD37eJEq_42B2uaWx=S+Yy?@8cTIB*8b(7lnFkT|i_eVP7I&A`#}D#(9f~ zjimWvYtUh4zxpa*jpd=mzTIa^vE@l_SO8$4C}Dsb;+kU9PQQ*24HwpkZ6jmpA_Psk zHNU^Jwp28XQNzkhmlgw=mA=buX2hmY3emr=O#0)oJhdz&0~?yFT~>@v0l+FfA0xbq z8ANJlyzCvP=Y)pmfReFSdl`q~3(hrw*P=%8JZ;d*Z@l+);!Ae+>vxIc?Fv*cN4Q1g zrHBAnlx_>2+Gv_QBd|1KK8f!SQL|XoInuZGv{oaFBsg8R!YNIy> zmIjwR&ojP@f8Era|K4U{S0jy15nS$*`>@LUnEL`5&QcVEr@RXkD29hcZ>C}NBof#Y zB5vxBe)h2ry2QG@X2oAolz`dF=j`tlZ&b~=trfqEZE#T*kxh5_B2_dG3`TTdR4U__J~YGf53F)GRM~ z^*b094>Ex_XY3flp39wj*h4bq+6u(@cI}CwnhCJiR#=|{X?8g7oW<(TdQ^oU)UTB$ z??0Bder*&_a}uRNj-3a9KT4+jHtT$^K|v44)eO88D3()v619#Rs#(dw8$oL*_yFlfiAl{~ddRKgrJ8#ahOo zc|c#~dy5QjmtmLYn`$a-ZQR$AgOgQ@$rkBgk$Epib{ZqM#h`)#1BV{ANf>A4e6|W? ziW7VGFBT>lWq_gpLMCS$f)ffUQaUC+W8uque*F-ESo*yUDM9 z6azD8IxAatzclg3NAB<6PCc}xT}gQWSg5_=Gj?r6y5#qy*%==Q%#GsfF*CVG4Xs|- z&gHl-vy$O;ORzFHuLJg@!MQB{29CZ*O>wZ-@7=vVfO1vQ#+7|jJLCkjlGYlq40oFG zXVlMfK^J$2NWWsig^D{JaFgf+@s5p%fv*qu_0r={CO)UfiLVt;2mrV{Hn zigBiag>~e8;3=Ji=6fF{aEJ!okwR!Til*f0W=x08 z`FWYk`PrRKh+LP%qLSy;mA9O>ZO?e3WUJp`n6Ew4F3=iR-MhHf2vxcvjl)xlYc8E8bX@ zSQrXkQXl%7p0G9l{OLoY|zKV(76boP_hJVe(E`!8aWzp>q1gJQ;o<+ z@|)%OdQu_>K$^|nzZ5mzUT*2H5>fs&%vaLhON@cJvr=BL}yh>wx$Qk{(?_R@MsmhaA+sbXyBed#B3oJ2Nd|_-p zT=bSjS?E2c{-k7-IhV+#r??#9RF06`<87`kfv1I> zs8v=vZv>K>61=}qOo(4sOkS4% zs7_bHcuB#0^o`~_k6ypfP~KCAYGC^)^QT!G9!nC7wC{8sV#WWAq0&6m5Mh2DHnJ2E ziF`QkG_0OHVmx>+n?~R+<;Ejwels0doP$0I{LtUc?$2*@cID{VZSfgCUsv|hs+pdD z`t5@L8V*7Zu@r^M=OJvngAFE%y<#7P4=htidPePq?5$<^rYraqYZDCK&&K`u-j&y3 zt)z>gk2&!CPCk%Hnv`9VHmTjXhl?5aDHhr0({P9K|Kc4cn{scC)!b*+=u*(d@{voW z{n8&*e$t_LPGt?!V}qswM-BZJ&Jqg2&P>~%z>ADOtpgp2kEL0DVr&9F_I_~}C&i=L z%MG`Nqn{GGc$>qIr3YBSiwzf1H&6&`KfFZPqpZmy?E+BGbi0jLyFy0!3wOi*W(&$| zr#Xp+m%Cw<@;oaUm#@+U0_0jFbx&pG?@5bG7$hE-F6U+>SK13_zlnqSip~(-U2M6W z-z;iaQcXyP_&pH5Q#gK|yEN@MF>ySiDw3*mFQ`aPEW8;Q9T}x)DdQdW$-C-F( z5FcP&0h|z@z$!nD=Epj92m9tcp=|goVW2z*xcY3C4}71)A#_Dk|2=wciQ^^uSd|FG zG`OL~ijReMh3?UQHmLltkd^8Y#WzhUnl7xNMc}Iw83bqf6ZTr{TH;xpNBfbV1@Rz0 zh1kFXf;X3L9M`K)MCe`;W_7>1XqwlYh8)>y)1KnthqE zr+FKZ<^$jx5)va@P@-k1enln?ofK5JK$_qw|HSs*#zL8G7jM-}DZ5?(jV!L6G6bkt z#fn`c`8nXEKkx_vdj&r~9)4^=dv7RaDZRgy6x{mkzIolHuA|gXMWG1j@&Z-bK3_%w z?rn`;GrC_;j&jHTv{kRCtC6|sE|=`~EMJ4t|M~Knh=oXNyze-n#qJMV+Sl>p>IoM7 zq0 zwkG;_A!InxH$4{OgV!#TYI|$qBpK_$^WK`*WS@^DtQLDSgxVetZW^KM0pFzV8ngz< z@gA-xZs7D8LhK~E;l`ie{*Iwc+39mM%Y}tjv_muo2GNO%JW}fojhFT5*}F{DuJE7Q zw&l24y-o{B6@TWz1G_t!CGElR<7t~Z-a5wC0QEU)<5Dr0Q3=yXvZJyRLkm<*E?E(L zm@i$~R>%0m{HdlwLOTo@x&Rx>J0tt096isl85m$hnFtGQ&G#MaPe`>sdox>a^X$ys z#`*;xDd{z}aWP%6aA2l!E+yx)N_7}c)myv~8gn0-1*RKFl>D2LuUJ@)WtEg_DX_Fy zcBGpyvlppGfDPT0K5;i9;qqyuJi`Ic^xGQsy#xJti{usfOw?e^(a8_4vv?LR{}mdZ zy39u`HJ(jgWshzex@_aC5YRwZii`+0=;vOCrH0tYxXqDgLoY~W8@dv$L*l>)3d~^q z#fFgOWvOoVe)+6rGO8NLpUEI{lP{CN;YUEfv)2*r#QZqPsXIge^)Ol6foe3hYL6gs ziXTu|C$OOrqzVNV3Y`09<@c~BCHV#K!w@^K_e*tFjR`Tnov}HH28UN=R+aa^TJ*+n zDV#zk*TtixZ`~?2lKjc(mBbC5D%X>kdDjh|x;Tv-RSaJHimzO-f7ic}Dtj7AA#AZl z)b<*t#F0vWc5!yn1=Bbu=c5`h@y}QXf(=QA~@~*Bo3hNq95LC8t zY}hKdD&9HXzi8$Q2HuUa3t!$foPC)q19J;BW`_x@ev?qMn+OYRn*LU(uA0X8F%mc{ z>@O$FO+x)&j7HT4%;eDJaZ7qu)%xrmJXeA2b~WV3fwCh!K437}?oUU){M#CB5>I=v z>CJa&GxdGOBWmyKE-YHdrL8WS@#-29(JJtPudTS|p|$i~iR+6-@+cKt5W}9uP$=rJ zZ}oH7C!RKS@U!5Au3S|a@W;13pMF?^vw4sc`!UiAzD61A=8M<5%gTc)SLRlWT?7^I zDzmHTn`=Q~uWfC=<&=!lE)3|aXJ0rcQa{otm{%lqn8u1G9Q?}Lx_PVg^fuORMo<;f z8C@RBj1`EGz?oAdpc<`$-vhCL4ZtADVhy05Wh@!D?8USUsC-Gh*MKUta2DUY2Nnrg}4=S zE}TYQediM=1NhnL9D_6iFj`ABHxhSgZPK3X3A+8IPj2IVpz*D!2L)J5Q3m{srnmQD zJ0U4k1#l%ebyd^ns&fmXGVLTVkyD17H1M>UaLV+T>8VDWQUtEYL{Kt8?o70l_=EZB zb3il&<#DipgQM+g@B`$ZhtirxM8GyLMAz^E#Kn>c$|KC7QuPD&7k^nnf_Pwb!eA{h zf?9eo5?D;EyN3cNqmUd|3Acm z#DXS=Hv<`W=V{IL3ZLYUa+xgzu||^_7awj(dr|1sodqSQ)!iE{qpxsDDy+ihtN!kj zKkFenW)rM2QJd}>{6}k}^i|lePm}zD;%aJNWv`Zh z+jTb1n)nPFeB(F{*G3Z-PPr}O0}Nz-z#1#+1aLHq9w`*ndL5!*H zHkJe1bocSux_|v5K4}dL3tI$g37HJ0OPP!pnf`J620r{lrX&MK*ejT#J7s_e-1WI< zSo)-)3TPfazMvd3CAq?BPC*IVJhefpXaic4&ZSxBzrH$qtr#Hj*V+;V-R*0e|zW`;J&RaYBq0>lrfz);`k^EG}+|q>+l&EX{Iu< zbu=Ev8TZ+pIu6(+o}knD{6B!(nUU0~mD<{JRnvJ%eu=TodRQkOQX{nTv=sUdLK@)G z(o#B0+FMpQ3hP8qiVUVOEie5>*HG_XUsogX)tXCSO*`{Fw?PK+A)C%DR9%|TIpF4rg$=F&M&zMTiJ)GnW!3cJ`J!A_T`l?j6OYMZj>~hGIx`$2jp3fZDT|iWPNj+2xx)wXqB;Ig2_34b(qxf{17ePkaS4u+sKXmRulOi z&^oz8h(b*yz%`1FckCg`z=M3Xp&k}A+7HAE!vxD~$bg~ln4hkX0Wlup)^rrq1H}0E z;i=7kdMLx#ftDoIzH$v5U|cQovSd1D-~qDe@dm6c^RG8S0PUr`jAMB?41BE057fYK zOJHt_ebaR+|Ly_vPXb0T<>DO{rvNCV`Ly5MAryF!3*5?K119p%n`D-OmKdtcp2tzZ z)Ef{&N?+Ij;_=$9JQqd=u(sde@YJ%6wtJ!MTrUj`Xt~@F@Wxs zH}qqd23Qb%+&e-SkpRePd3{65KOg;ftkD0l#0lm=Zfdp+qEwA@=fGtZE(R^!A9mv%NpW=~ql1{uj@hSxH5ugbZGK{eZsus5)LQuVPFMWmSc+|6d{d zXRghG08>zIn921$9GRfxYXl0cknL9MEL_r6vP5_5Qc` z|C@@e5IjJB=~iBGaU=nPf+pz7{x7-x7pd2ofXRvf@8tgHQBDSZ%^2 +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| cluster_name | Name used for the cluster and DNS zone. | string | ✓ | | +| disk_encryption_key | Optional CMEK for disk encryption. | object({...}) | ✓ | | +| domain | Domain name used to derive the DNS zone. | string | ✓ | | +| fs_paths | Filesystem paths for commands and data, supports home path expansion. | object({...}) | ✓ | | +| host_project | Shared VPC project and network configuration. | object({...}) | ✓ | | +| service_project | Service project configuration. | object({...}) | ✓ | | +| *allowed_ranges* | Ranges that can SSH to the boostrap VM and API endpoint. | list(any) | | ["10.0.0.0/8"] | +| *install_config_params* | OpenShift cluster configuration. | object({...}) | | ... | +| *post_bootstrap_config* | Name of the service account for the machine operator. Removes bootstrap resources when set. | object({...}) | | null | +| *region* | Region where resources will be created. | string | | europe-west1 | +| *rhcos_gcp_image* | RHCOS image used. | string | | projects/rhcos-cloud/global/images/rhcos-47-83-202102090044-0-gcp-x86-64 | +| *tags* | Additional tags for instances. | list(string) | | ["ssh"] | +| *zones* | Zones used for instances. | list(string) | | ["b", "c", "d"] | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| backend-health | Command to monitor API internal backend health. | | +| bootstrap-ssh | Command to SSH to the bootstrap instance. | | +| masters-ssh | Command to SSH to the master instances. | | + diff --git a/third-party-solutions/openshift/tf/bootstrap.tf b/third-party-solutions/openshift/tf/bootstrap.tf new file mode 100644 index 000000000..f9d4a5763 --- /dev/null +++ b/third-party-solutions/openshift/tf/bootstrap.tf @@ -0,0 +1,94 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_storage_bucket" "bootstrap-ignition" { + project = var.service_project.project_id + name = local.infra_id + location = var.region + force_destroy = true +} + +resource "google_storage_bucket_object" "bootstrap-ignition" { + count = local.bootstrapping ? 1 : 0 + bucket = google_storage_bucket.bootstrap-ignition.name + name = "bootstrap.ign" + source = "${local.fs_paths.config_dir}/bootstrap.ign" +} + +data "google_storage_object_signed_url" "bootstrap-ignition" { + count = local.bootstrapping ? 1 : 0 + bucket = google_storage_bucket.bootstrap-ignition.name + path = google_storage_bucket_object.bootstrap-ignition.0.name + credentials = file(local.fs_paths.credentials) +} + +resource "google_compute_instance" "bootstrap" { + count = local.bootstrapping ? 1 : 0 + project = var.service_project.project_id + name = "${local.infra_id}-b" + machine_type = "n1-standard-4" + zone = "${var.region}-${element(var.zones, 0)}" + network_interface { + subnetwork = var.host_project.masters_subnet_name + subnetwork_project = var.host_project.project_id + } + boot_disk { + initialize_params { + image = var.rhcos_gcp_image + size = 16 + type = "pd-balanced" + } + kms_key_self_link = local.disk_encryption_key + } + service_account { + email = google_service_account.default["m"].email + scopes = ["cloud-platform", "userinfo-email"] + } + tags = concat( + [local.tags.bootstrap, local.tags.master, "ocp-master"], + var.tags == null ? [] : var.tags + ) + metadata = { + user-data = jsonencode({ + ignition = { + config = { + replace = !local.bootstrapping ? {} : { + source = data.google_storage_object_signed_url.bootstrap-ignition.0.signed_url + } + } + version = "3.1.0" + } + }) + VmDnsSetting = "GlobalDefault" + } +} + +resource "google_compute_instance_group" "bootstrap" { + project = var.service_project.project_id + network = data.google_compute_network.default.self_link + zone = "${var.region}-${var.zones[0]}" + name = "${local.infra_id}-bootstrap" + description = "Openshift bootstrap group for ${local.infra_id}." + instances = [for i in google_compute_instance.bootstrap : i.self_link] + named_port { + name = "https" + port = 6443 + } + named_port { + name = "ignition" + port = 22623 + } +} diff --git a/third-party-solutions/openshift/tf/dns.tf b/third-party-solutions/openshift/tf/dns.tf new file mode 100644 index 000000000..42ac43c12 --- /dev/null +++ b/third-party-solutions/openshift/tf/dns.tf @@ -0,0 +1,68 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_dns_managed_zone" "peering" { + project = var.host_project.project_id + name = "${local.infra_id}-peering-zone" + description = "Openshift peering zone for ${local.infra_id}." + dns_name = "${local.subdomain}." + visibility = "private" + private_visibility_config { + networks { + network_url = data.google_compute_network.default.id + } + } + peering_config { + target_network { + network_url = local.dummy_network + } + } +} + +resource "google_dns_managed_zone" "internal" { + project = var.service_project.project_id + name = "${local.infra_id}-private-zone" + description = "Openshift internal zone for ${local.infra_id}." + dns_name = "${local.subdomain}." + visibility = "private" + private_visibility_config { + networks { + network_url = local.dummy_network + } + } +} + +resource "google_dns_record_set" "dns" { + for_each = toset(["api", "api-int"]) + project = var.service_project.project_id + name = "${each.key}.${local.subdomain}." + managed_zone = google_dns_managed_zone.internal.name + type = "A" + ttl = 60 + rrdatas = [google_compute_address.api.address] +} + +/* +resource "google_dns_record_set" "apps" { + count = local.router_address == null ? 0 : 1 + project = var.service_project.project_id + name = "*.apps.${var.cluster_name}.${var.domain}." + managed_zone = google_dns_managed_zone.internal.name + type = "A" + ttl = 60 + rrdatas = [local.router_address] +} +*/ diff --git a/third-party-solutions/openshift/tf/firewall.tf b/third-party-solutions/openshift/tf/firewall.tf new file mode 100644 index 000000000..bfc3060d0 --- /dev/null +++ b/third-party-solutions/openshift/tf/firewall.tf @@ -0,0 +1,137 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_compute_firewall" "bootstrap-ssh" { + name = "${local.infra_id}-bootstrap-ssh" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = var.allowed_ranges + target_tags = [local.tags.bootstrap] + allow { + protocol = "tcp" + ports = [22] + } +} + +resource "google_compute_firewall" "api" { + name = "${local.infra_id}-api" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = var.allowed_ranges + target_tags = [local.tags.master] + allow { + protocol = "tcp" + ports = [6443] + } +} + +resource "google_compute_firewall" "hc" { + name = "${local.infra_id}-hc" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = [ + "35.191.0.0/16", "130.211.0.0/22", "209.85.152.0/22", "209.85.204.0/22" + ] + target_tags = [local.tags.master] + allow { + protocol = "tcp" + ports = [6080, 6443, 22624] + } +} + +resource "google_compute_firewall" "etcd" { + name = "${local.infra_id}-etcd" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_tags = [local.tags.master] + target_tags = [local.tags.master] + allow { + protocol = "tcp" + ports = [2379, 2380] + } +} + +resource "google_compute_firewall" "ctrl-plane" { + name = "${local.infra_id}-ctrl-plane" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_tags = [local.tags.master, local.tags.worker] + target_tags = [local.tags.master] + allow { + protocol = "tcp" + ports = [10257, 10259, 22623] + } +} + +resource "google_compute_firewall" "internal-net" { + name = "${local.infra_id}-internal-net" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = [var.install_config_params.network.machine] + target_tags = [local.tags.master, local.tags.worker] + allow { + protocol = "icmp" + } + allow { + protocol = "tcp" + ports = [22] + } +} + +resource "google_compute_firewall" "internal-cluster" { + name = "${local.infra_id}-internal-cluster" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_tags = [local.tags.master, local.tags.worker] + target_tags = [local.tags.master, local.tags.worker] + allow { + protocol = "esp" + } + allow { + protocol = "tcp" + ports = ["9000-9999", 10250, "30000-32767"] + } + allow { + protocol = "udp" + ports = [500, 4500, 4789, 6081, "9000-9999", "30000-32767"] + } +} + +resource "google_compute_firewall" "apps-hc" { + name = "${local.infra_id}-apps-hc" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = [ + "35.191.0.0/16", "130.211.0.0/22", "209.85.152.0/22", "209.85.204.0/22" + ] + target_tags = [local.tags.worker] + allow { + protocol = "tcp" + ports = ["30000-32767"] + } +} + +resource "google_compute_firewall" "apps" { + name = "${local.infra_id}-apps" + project = var.host_project.project_id + network = var.host_project.vpc_name + source_ranges = var.allowed_ranges + target_tags = [local.tags.worker] + allow { + protocol = "tcp" + ports = [80, 443] + } +} diff --git a/third-party-solutions/openshift/tf/iam.tf b/third-party-solutions/openshift/tf/iam.tf new file mode 100644 index 000000000..2bddb2e85 --- /dev/null +++ b/third-party-solutions/openshift/tf/iam.tf @@ -0,0 +1,76 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_service_account" "default" { + for_each = { m = "master", w = "worker" } + project = var.service_project.project_id + account_id = "${local.infra_id}-${each.key}" + display_name = "Openshift ${each.value} for ${local.infra_id}." +} + +# https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-gcp-user-infra-vpc.html#installation-creating-gcp-iam-shared-vpc_installing-gcp-user-infra-vpc + +resource "google_project_iam_member" "host-master" { + for_each = toset([ + "roles/compute.networkUser", + "roles/compute.networkViewer" + ]) + project = var.host_project.project_id + role = each.key + member = "serviceAccount:${google_service_account.default["m"].email}" +} + +resource "google_project_iam_member" "host-worker" { + for_each = toset([ + "roles/compute.networkUser" + ]) + project = var.host_project.project_id + role = each.key + member = "serviceAccount:${google_service_account.default["w"].email}" +} + +# This on the other hand seems excessive +# https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-restricted-networks-gcp.html#installation-creating-gcp-iam-shared-vpc_installing-restricted-networks-gcp + +resource "google_project_iam_member" "service-master" { + for_each = toset([ + "roles/compute.instanceAdmin", + "roles/compute.networkAdmin", + "roles/compute.securityAdmin", + "roles/iam.serviceAccountUser", + "roles/storage.admin" + ]) + project = var.service_project.project_id + role = each.key + member = "serviceAccount:${google_service_account.default["m"].email}" +} + +resource "google_project_iam_member" "service-worker" { + for_each = toset([ + "roles/compute.viewer", + "roles/storage.admin" + ]) + project = var.service_project.project_id + role = each.key + member = "serviceAccount:${google_service_account.default["w"].email}" +} + +resource "google_project_iam_member" "machineset-operator" { + count = local.machine_sa == null ? 0 : 1 + project = var.host_project.project_id + role = "roles/compute.networkUser" + member = "serviceAccount:${local.machine_sa}@${var.service_project.project_id}.iam.gserviceaccount.com" +} diff --git a/third-party-solutions/openshift/tf/ilb.tf b/third-party-solutions/openshift/tf/ilb.tf new file mode 100644 index 000000000..cfa4c2fe9 --- /dev/null +++ b/third-party-solutions/openshift/tf/ilb.tf @@ -0,0 +1,68 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_compute_address" "api" { + project = var.service_project.project_id + name = "${local.infra_id}-ilb" + address_type = "INTERNAL" + region = var.region + subnetwork = data.google_compute_subnetwork.default["default"].self_link +} + +resource "google_compute_health_check" "api" { + project = var.service_project.project_id + name = "${local.infra_id}-api" + https_health_check { + port = 6443 + request_path = "/readyz" + } +} + +resource "google_compute_region_backend_service" "api" { + project = var.service_project.project_id + name = "${local.infra_id}-api" + load_balancing_scheme = "INTERNAL" + region = var.region + network = data.google_compute_network.default.self_link + health_checks = [google_compute_health_check.api.self_link] + protocol = "TCP" + dynamic "backend" { + for_each = google_compute_instance_group.master + content { + group = backend.value.self_link + } + } + dynamic "backend" { + for_each = toset(local.bootstrapping ? [""] : []) + content { + group = google_compute_instance_group.bootstrap.self_link + } + } +} + +resource "google_compute_forwarding_rule" "api" { + project = var.service_project.project_id + name = "${local.infra_id}-api" + load_balancing_scheme = "INTERNAL" + region = var.region + network = data.google_compute_network.default.self_link + subnetwork = data.google_compute_subnetwork.default["default"].self_link + ip_address = google_compute_address.api.address + ip_protocol = "TCP" + ports = [6443, 22623] + allow_global_access = true + backend_service = google_compute_region_backend_service.api.self_link +} diff --git a/third-party-solutions/openshift/tf/main.tf b/third-party-solutions/openshift/tf/main.tf new file mode 100644 index 000000000..92cadb8a1 --- /dev/null +++ b/third-party-solutions/openshift/tf/main.tf @@ -0,0 +1,79 @@ +/** + * Copyright 2021 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 { + bootstrapping = var.post_bootstrap_config == null + cluster_name = local.install_metadata["clusterName"] + disk_encryption_key = ( + var.disk_encryption_key == null + ? null + : data.google_kms_crypto_key.default.0.id + ) + dummy_network = ( + var.service_project.vpc_name != null + ? data.google_compute_network.dummy.0.id + : google_compute_network.dummy.0.id + ) + fs_paths = { for k, v in var.fs_paths : k => pathexpand(v) } + infra_id = local.install_metadata["infraID"] + install_metadata = jsondecode(file( + "${local.fs_paths.config_dir}/metadata.json" + )) + machine_sa = try(var.post_bootstrap_config.machine_op_sa_prefix, null) + router_address = try(var.post_bootstrap_config.router_address, null) + subdomain = "${var.cluster_name}.${var.domain}" + tags = { + for n in ["bootstrap", "master", "worker"] : n => "${local.infra_id}-${n}" + } +} + +data "google_compute_network" "default" { + project = var.host_project.project_id + name = var.host_project.vpc_name +} + +data "google_compute_subnetwork" "default" { + for_each = toset(["default", "masters", "workers"]) + project = var.host_project.project_id + region = var.region + name = var.host_project["${each.key}_subnet_name"] +} + +resource "google_compute_network" "dummy" { + count = var.service_project.vpc_name == null ? 1 : 0 + project = var.service_project.project_id + name = "${local.infra_id}-dns" + auto_create_subnetworks = false +} + +data "google_compute_network" "dummy" { + count = var.service_project.vpc_name == null ? 0 : 1 + project = var.service_project.project_id + name = var.service_project.vpc_name +} + +data "google_kms_key_ring" "default" { + count = var.disk_encryption_key == null ? 0 : 1 + project = var.disk_encryption_key.project_id + location = var.disk_encryption_key.location + name = var.disk_encryption_key.keyring +} + +data "google_kms_crypto_key" "default" { + count = var.disk_encryption_key == null ? 0 : 1 + key_ring = data.google_kms_key_ring.default.0.self_link + name = var.disk_encryption_key.name +} diff --git a/third-party-solutions/openshift/tf/masters.tf b/third-party-solutions/openshift/tf/masters.tf new file mode 100644 index 000000000..9533648d7 --- /dev/null +++ b/third-party-solutions/openshift/tf/masters.tf @@ -0,0 +1,65 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_compute_instance" "master" { + for_each = toset(var.zones) + project = var.service_project.project_id + name = "${local.infra_id}-m-${each.key}" + machine_type = "n1-standard-4" + zone = "${var.region}-${each.key}" + network_interface { + subnetwork = var.host_project.masters_subnet_name + subnetwork_project = var.host_project.project_id + } + boot_disk { + initialize_params { + image = var.rhcos_gcp_image + size = var.install_config_params.disk_size + type = "pd-ssd" + } + kms_key_self_link = local.disk_encryption_key + } + service_account { + email = google_service_account.default["m"].email + scopes = ["cloud-platform", "userinfo-email"] + } + tags = concat( + [local.tags.master, "ocp-master"], + var.tags == null ? [] : var.tags + ) + metadata = { + user-data = file("${local.fs_paths.config_dir}/master.ign"), + VmDnsSetting = "GlobalDefault" + } +} + +resource "google_compute_instance_group" "master" { + for_each = toset(var.zones) + project = var.service_project.project_id + network = data.google_compute_network.default.self_link + zone = "${var.region}-${each.key}" + name = "${local.infra_id}-master-${each.key}" + description = "Openshift master group for ${local.infra_id} in zone ${each.key}." + instances = [google_compute_instance.master[each.key].self_link] + named_port { + name = "https" + port = 6443 + } + named_port { + name = "ignition" + port = 22623 + } +} diff --git a/third-party-solutions/openshift/tf/outputs.tf b/third-party-solutions/openshift/tf/outputs.tf new file mode 100644 index 000000000..9ff82b600 --- /dev/null +++ b/third-party-solutions/openshift/tf/outputs.tf @@ -0,0 +1,47 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "backend-health" { + description = "Command to monitor API internal backend health." + value = < < Date: Wed, 12 May 2021 10:34:35 +0200 Subject: [PATCH 06/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea81cdb22..397a625ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. - added support for `CORS` to the `gcs` module - make cluster creation optional in the Shared VPC example - make service account creation optional in `iam-service-account` module +- new `third-party-solutions` top-level folder with initial `openshift` example ## [4.7.0] - 2021-04-21 From 36d253f1d3283c7c3734a632958f78015b18e650 Mon Sep 17 00:00:00 2001 From: sruffilli Date: Wed, 12 May 2021 15:02:27 +0200 Subject: [PATCH 07/17] DNS Policies in net-vpc module (#238) --- modules/net-vpc/README.md | 28 ++++++++++++++++++++++++++ modules/net-vpc/main.tf | 32 ++++++++++++++++++++++++++++++ modules/net-vpc/variables.tf | 38 ++++++++++++++++++++++++------------ 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 61e1b678a..fb6a6a5d4 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -141,6 +141,33 @@ module "vpc" { # tftest:modules=1:resources=4 ``` +### DNS Policies + +```hcl +module "vpc" { + source = "./modules/net-vpc" + project_id = "my-project" + name = "my-network" + dns_policy = { + inbound = true + logging = false + outbound = { + private_ns = ["10.0.0.1"] + public_ns = ["8.8.8.8"] + } + } + subnets = [ + { + ip_cidr_range = "10.0.0.0/24" + name = "production" + region = "europe-west1" + secondary_ip_range = {} + } + ] +} +# tftest:modules=1:resources=3 +``` + ## Variables @@ -151,6 +178,7 @@ module "vpc" { | *auto_create_subnetworks* | Set to true to create an auto mode subnet, defaults to custom mode. | bool | | false | | *delete_default_routes_on_create* | Set to true to delete the default routes at creation time. | bool | | false | | *description* | An optional description of this resource (triggers recreation on change). | string | | Terraform-managed. | +| *dns_policy* | None | object({...}) | | null | | *iam* | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | | *log_config_defaults* | Default configuration for flow logs when enabled. | object({...}) | | ... | | *log_configs* | Map keyed by subnet 'region/name' of optional configurations for flow logs when enabled. | map(map(string)) | | {} | diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf index 0d41b9164..01998f4e7 100644 --- a/modules/net-vpc/main.tf +++ b/modules/net-vpc/main.tf @@ -239,6 +239,38 @@ resource "google_compute_global_address" "psn_range" { network = local.network.id } +resource "google_dns_policy" "dns_policy" { + count = var.dns_policy == null ? 0 : 1 + enable_inbound_forwarding = var.dns_policy.inbound + enable_logging = var.dns_policy.logging + name = "${var.name}-inbound-policy" + project = var.project_id + networks { + network_url = local.network.id + } + + dynamic "alternative_name_server_config" { + for_each = var.dns_policy.outbound == null ? [] : [1] + content { + dynamic "target_name_servers" { + for_each = toset(var.dns_policy.outbound.private_ns) + iterator = ns + content { + ipv4_address = ns.key + forwarding_path = "private" + } + } + dynamic "target_name_servers" { + for_each = toset(var.dns_policy.outbound.public_ns) + iterator = ns + content { + ipv4_address = ns.key + } + } + } + } +} + resource "google_service_networking_connection" "psn_connection" { count = var.private_service_networking_range == null ? 0 : 1 network = local.network.id diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index b98f324cb..6422ac172 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -32,6 +32,18 @@ variable "description" { default = "Terraform-managed." } +variable "dns_policy" { + type = object({ + inbound = bool + logging = bool + outbound = object({ + private_ns = list(string) + public_ns = list(string) + }) + }) + default = null +} + variable "iam" { description = "Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format." type = map(map(list(string))) @@ -84,6 +96,19 @@ variable "peering_create_remote_end" { default = true } +variable "private_service_networking_range" { + description = "RFC1919 CIDR range used for Google services that support private service networking." + type = string + default = null + validation { + condition = ( + var.private_service_networking_range == null || + can(cidrnetmask(var.private_service_networking_range)) + ) + error_message = "Specify a valid RFC1918 CIDR range for private service networking." + } +} + variable "project_id" { description = "The ID of the project where this VPC will be created" type = string @@ -159,16 +184,3 @@ variable "vpc_create" { type = bool default = true } - -variable "private_service_networking_range" { - description = "RFC1919 CIDR range used for Google services that support private service networking." - type = string - default = null - validation { - condition = ( - var.private_service_networking_range == null || - can(cidrnetmask(var.private_service_networking_range)) - ) - error_message = "Specify a valid RFC1918 CIDR range for private service networking." - } -} From c2f84a4143762f836e12973d575dfda822cef04b Mon Sep 17 00:00:00 2001 From: sruffilli Date: Wed, 12 May 2021 15:04:02 +0200 Subject: [PATCH 08/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397a625ca..b9786b728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - make cluster creation optional in the Shared VPC example - make service account creation optional in `iam-service-account` module - new `third-party-solutions` top-level folder with initial `openshift` example +- added support for DNS Policies to the `net-vpc` module ## [4.7.0] - 2021-04-21 From c60037bc1a002348e5fd8f2e69d66bf0b8eded2f Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 12 May 2021 15:05:34 +0200 Subject: [PATCH 09/17] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9786b728..862201c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [4.8.0] - 2021-05-12 + - added support for `CORS` to the `gcs` module - make cluster creation optional in the Shared VPC example - make service account creation optional in `iam-service-account` module @@ -305,7 +307,8 @@ All notable changes to this project will be documented in this file. - merge development branch with suite of new modules and end-to-end examples -[Unreleased]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.7.0...HEAD +[Unreleased]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.8.0...HEAD +[4.8.0]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.7.0...v4.8.0 [4.7.0]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.6.1...v4.7.0 [4.6.1]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.6.0...v4.6.1 [4.6.0]: https://github.com/terraform-google-modules/cloud-foundation-fabric/compare/v4.5.1...v4.6.0 From 654e171b32c892c762562557fe0e1e01c8174580 Mon Sep 17 00:00:00 2001 From: sruffilli Date: Wed, 12 May 2021 16:40:56 +0200 Subject: [PATCH 10/17] Update resource name of google_dns_policy on net-vpc --- modules/net-vpc/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf index 01998f4e7..4b2610d73 100644 --- a/modules/net-vpc/main.tf +++ b/modules/net-vpc/main.tf @@ -239,11 +239,11 @@ resource "google_compute_global_address" "psn_range" { network = local.network.id } -resource "google_dns_policy" "dns_policy" { +resource "google_dns_policy" "default" { count = var.dns_policy == null ? 0 : 1 enable_inbound_forwarding = var.dns_policy.inbound enable_logging = var.dns_policy.logging - name = "${var.name}-inbound-policy" + name = var.name project = var.project_id networks { network_url = local.network.id From 6906bda4f6c23521a3bfa3771420e198b354deab Mon Sep 17 00:00:00 2001 From: sruffilli Date: Wed, 12 May 2021 16:42:29 +0200 Subject: [PATCH 11/17] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 862201c05..0331ab581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- **incompatible change** updated resource name for `google_dns_policy` on the `net-vpc` module + ## [4.8.0] - 2021-05-12 - added support for `CORS` to the `gcs` module From f1bf30ba574ca5b1818d17fa8febb41c38c529ec Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Thu, 13 May 2021 20:08:17 +0200 Subject: [PATCH 12/17] [#239] Pin Google provider version to 3.65.0 for OCP sample code example (#240) * [#225] Add Tinyproxy to shared-vpc-gke example * Update README.md * [#239] Pin Google provider to version 3.65.0 for OCP sample code * Update providers.tf Co-authored-by: Ludovico Magnocavallo --- .../openshift/tf/providers.tf | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 third-party-solutions/openshift/tf/providers.tf diff --git a/third-party-solutions/openshift/tf/providers.tf b/third-party-solutions/openshift/tf/providers.tf new file mode 100644 index 000000000..11735b9f7 --- /dev/null +++ b/third-party-solutions/openshift/tf/providers.tf @@ -0,0 +1,26 @@ +/** + * Copyright 2021 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. + */ + +# pinning to avoid some weird issues we had with the following version + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "3.65.0" + } + } +} From be7b1a37887d800383421b16dcb4c66b4205cbbc Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 14 May 2021 10:18:41 +0200 Subject: [PATCH 13/17] Set false as default oslogin value in peering example --- networking/hub-and-spoke-peering/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networking/hub-and-spoke-peering/main.tf b/networking/hub-and-spoke-peering/main.tf index ccdb146ac..ca536b860 100644 --- a/networking/hub-and-spoke-peering/main.tf +++ b/networking/hub-and-spoke-peering/main.tf @@ -33,7 +33,7 @@ module "project" { source = "../../modules/project" project_create = var.project_create != null billing_account = try(var.project_create.billing_account, null) - oslogin = try(var.project_create.oslogin, null) + oslogin = try(var.project_create.oslogin, false) parent = try(var.project_create.parent, null) name = var.project_id services = [ From 80b1d0d3ec42bfa00d507c43ad61a60c93c0b3ce Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 14 May 2021 14:11:28 +0200 Subject: [PATCH 14/17] Update README.md --- networking/hub-and-spoke-peering/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/networking/hub-and-spoke-peering/README.md b/networking/hub-and-spoke-peering/README.md index 058063f9b..37347c991 100644 --- a/networking/hub-and-spoke-peering/README.md +++ b/networking/hub-and-spoke-peering/README.md @@ -43,7 +43,7 @@ kubectl get all The example configures the peering with the GKE master VPC to export routes for you, so that VPN routes are passed through the peering. You can diable by hand in the console or by editing the `peering_config' variable in the cluster module, to test non-working configurations or switch to using the [GKE proxy](https://cloud.google.com/solutions/creating-kubernetes-engine-private-clusters-with-net-proxies). -### Export routes via Terraform +### Export routes via Terraform (recommended) Change the GKE cluster module and add a new variable after `private_cluster_config`: @@ -56,9 +56,9 @@ Change the GKE cluster module and add a new variable after `private_cluster_conf If you added the variable after applying, simply apply Terraform again. -### Export routes via gcloud +### Export routes via gcloud (alternative) -The peering has a name like `gke-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-peer`, you can edit it in the Cloud Console from the *VPC network peering* page or using `gcloud`: +If you prefer to use `gcloud` to export routes on the peering, firs identify the peering (it has a name like `gke-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-peer`) in the Cloud Console from the *VPC network peering* page, or using `gcloud`, then configure it to export routes: ``` gcloud compute networks peerings list @@ -67,6 +67,8 @@ gcloud compute networks peerings update [peering name from above] \ --network spoke-2 --export-custom-routes ``` +### Test routes + Then connect via SSH to the spoke 1 instance and run the same commands you ran on the spoke 2 instance above, you should be able to run `kubectl` commands against the cluster. To test the default situation with no supporting VPN, just comment out the two VPN modules in `main.tf` and run `terraform apply` to bring down the VPN gateways and tunnels. GKE should only become accessible from spoke 2. ## Operational considerations From e275f17a4858616b14171c3912a597279d937dd4 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 14 May 2021 14:25:23 +0200 Subject: [PATCH 15/17] Update README.md fix typo --- networking/hub-and-spoke-peering/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networking/hub-and-spoke-peering/README.md b/networking/hub-and-spoke-peering/README.md index 37347c991..a732b70a0 100644 --- a/networking/hub-and-spoke-peering/README.md +++ b/networking/hub-and-spoke-peering/README.md @@ -58,7 +58,7 @@ If you added the variable after applying, simply apply Terraform again. ### Export routes via gcloud (alternative) -If you prefer to use `gcloud` to export routes on the peering, firs identify the peering (it has a name like `gke-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-peer`) in the Cloud Console from the *VPC network peering* page, or using `gcloud`, then configure it to export routes: +If you prefer to use `gcloud` to export routes on the peering, first identify the peering (it has a name like `gke-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-peer`) in the Cloud Console from the *VPC network peering* page, or using `gcloud`, then configure it to export routes: ``` gcloud compute networks peerings list From 0a647df4dc4d067e461c8fec9cd8ec082ef83af7 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sun, 16 May 2021 10:28:51 +0200 Subject: [PATCH 16/17] add logging and monitoring roles to openshift SAs --- third-party-solutions/openshift/tf/iam.tf | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/third-party-solutions/openshift/tf/iam.tf b/third-party-solutions/openshift/tf/iam.tf index 2bddb2e85..7b11f0fd3 100644 --- a/third-party-solutions/openshift/tf/iam.tf +++ b/third-party-solutions/openshift/tf/iam.tf @@ -14,6 +14,13 @@ * limitations under the License. */ +locals { + minimal_sa_roles = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter" + ] +} + resource "google_service_account" "default" { for_each = { m = "master", w = "worker" } project = var.service_project.project_id @@ -46,23 +53,23 @@ resource "google_project_iam_member" "host-worker" { # https://docs.openshift.com/container-platform/4.7/installing/installing_gcp/installing-restricted-networks-gcp.html#installation-creating-gcp-iam-shared-vpc_installing-restricted-networks-gcp resource "google_project_iam_member" "service-master" { - for_each = toset([ + for_each = toset(concat(local.minimal_sa_roles, [ "roles/compute.instanceAdmin", "roles/compute.networkAdmin", "roles/compute.securityAdmin", "roles/iam.serviceAccountUser", "roles/storage.admin" - ]) + ])) project = var.service_project.project_id role = each.key member = "serviceAccount:${google_service_account.default["m"].email}" } resource "google_project_iam_member" "service-worker" { - for_each = toset([ + for_each = toset(concat(local.minimal_sa_roles, [ "roles/compute.viewer", "roles/storage.admin" - ]) + ])) project = var.service_project.project_id role = each.key member = "serviceAccount:${google_service_account.default["w"].email}" From 744143d793ade41f28af11be0cb0ab018ef35ab3 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 17 May 2021 13:43:26 +0200 Subject: [PATCH 17/17] Openshift changes (#241) * set custom hostname in bootstrap and master * make the service DNS zone visible to the shared VPC network * remove unused vpc name attribute from service project variable --- third-party-solutions/openshift/README.md | 2 -- third-party-solutions/openshift/tf/README.md | 4 +-- .../openshift/tf/bootstrap.tf | 1 + third-party-solutions/openshift/tf/dns.tf | 32 +------------------ third-party-solutions/openshift/tf/main.tf | 18 ----------- third-party-solutions/openshift/tf/masters.tf | 3 +- .../openshift/tf/variables.tf | 1 - 7 files changed, 6 insertions(+), 55 deletions(-) diff --git a/third-party-solutions/openshift/README.md b/third-party-solutions/openshift/README.md index 333fac45c..d24ddfd4c 100644 --- a/third-party-solutions/openshift/README.md +++ b/third-party-solutions/openshift/README.md @@ -135,8 +135,6 @@ Variable configuration is best done in a `.tfvars` file, but can also be done di
The `machine` range should match addresses used for nodes.
post_bootstrap_config
Set to `null` until bootstrap completion, then refer to the post-bootstrap instructions below.
-
service_project
-
The vpc_name value is used for the placeholder VPC needed for the service project Cloud DNS zone used by the cluster. Set it to `null` to use an auto-generated name.
### Generating ignition files diff --git a/third-party-solutions/openshift/tf/README.md b/third-party-solutions/openshift/tf/README.md index f8ae711f9..18ef57583 100644 --- a/third-party-solutions/openshift/tf/README.md +++ b/third-party-solutions/openshift/tf/README.md @@ -8,12 +8,12 @@ This example is a companion setup to the Python script in the parent folder, and | name | description | type | required | default | |---|---|:---: |:---:|:---:| | cluster_name | Name used for the cluster and DNS zone. | string | ✓ | | -| disk_encryption_key | Optional CMEK for disk encryption. | object({...}) | ✓ | | | domain | Domain name used to derive the DNS zone. | string | ✓ | | | fs_paths | Filesystem paths for commands and data, supports home path expansion. | object({...}) | ✓ | | | host_project | Shared VPC project and network configuration. | object({...}) | ✓ | | -| service_project | Service project configuration. | object({...}) | ✓ | | +| service_project | Service project configuration. | object({...}) | ✓ | | | *allowed_ranges* | Ranges that can SSH to the boostrap VM and API endpoint. | list(any) | | ["10.0.0.0/8"] | +| *disk_encryption_key* | Optional CMEK for disk encryption. | object({...}) | | null | | *install_config_params* | OpenShift cluster configuration. | object({...}) | | ... | | *post_bootstrap_config* | Name of the service account for the machine operator. Removes bootstrap resources when set. | object({...}) | | null | | *region* | Region where resources will be created. | string | | europe-west1 | diff --git a/third-party-solutions/openshift/tf/bootstrap.tf b/third-party-solutions/openshift/tf/bootstrap.tf index f9d4a5763..70f0c3cef 100644 --- a/third-party-solutions/openshift/tf/bootstrap.tf +++ b/third-party-solutions/openshift/tf/bootstrap.tf @@ -39,6 +39,7 @@ resource "google_compute_instance" "bootstrap" { count = local.bootstrapping ? 1 : 0 project = var.service_project.project_id name = "${local.infra_id}-b" + hostname = "${local.infra_id}-bootstrap.${local.subdomain}" machine_type = "n1-standard-4" zone = "${var.region}-${element(var.zones, 0)}" network_interface { diff --git a/third-party-solutions/openshift/tf/dns.tf b/third-party-solutions/openshift/tf/dns.tf index 42ac43c12..12b818bfe 100644 --- a/third-party-solutions/openshift/tf/dns.tf +++ b/third-party-solutions/openshift/tf/dns.tf @@ -14,24 +14,6 @@ * limitations under the License. */ -resource "google_dns_managed_zone" "peering" { - project = var.host_project.project_id - name = "${local.infra_id}-peering-zone" - description = "Openshift peering zone for ${local.infra_id}." - dns_name = "${local.subdomain}." - visibility = "private" - private_visibility_config { - networks { - network_url = data.google_compute_network.default.id - } - } - peering_config { - target_network { - network_url = local.dummy_network - } - } -} - resource "google_dns_managed_zone" "internal" { project = var.service_project.project_id name = "${local.infra_id}-private-zone" @@ -40,7 +22,7 @@ resource "google_dns_managed_zone" "internal" { visibility = "private" private_visibility_config { networks { - network_url = local.dummy_network + network_url = data.google_compute_network.default.id } } } @@ -54,15 +36,3 @@ resource "google_dns_record_set" "dns" { ttl = 60 rrdatas = [google_compute_address.api.address] } - -/* -resource "google_dns_record_set" "apps" { - count = local.router_address == null ? 0 : 1 - project = var.service_project.project_id - name = "*.apps.${var.cluster_name}.${var.domain}." - managed_zone = google_dns_managed_zone.internal.name - type = "A" - ttl = 60 - rrdatas = [local.router_address] -} -*/ diff --git a/third-party-solutions/openshift/tf/main.tf b/third-party-solutions/openshift/tf/main.tf index 92cadb8a1..7bfb88350 100644 --- a/third-party-solutions/openshift/tf/main.tf +++ b/third-party-solutions/openshift/tf/main.tf @@ -22,11 +22,6 @@ locals { ? null : data.google_kms_crypto_key.default.0.id ) - dummy_network = ( - var.service_project.vpc_name != null - ? data.google_compute_network.dummy.0.id - : google_compute_network.dummy.0.id - ) fs_paths = { for k, v in var.fs_paths : k => pathexpand(v) } infra_id = local.install_metadata["infraID"] install_metadata = jsondecode(file( @@ -52,19 +47,6 @@ data "google_compute_subnetwork" "default" { name = var.host_project["${each.key}_subnet_name"] } -resource "google_compute_network" "dummy" { - count = var.service_project.vpc_name == null ? 1 : 0 - project = var.service_project.project_id - name = "${local.infra_id}-dns" - auto_create_subnetworks = false -} - -data "google_compute_network" "dummy" { - count = var.service_project.vpc_name == null ? 0 : 1 - project = var.service_project.project_id - name = var.service_project.vpc_name -} - data "google_kms_key_ring" "default" { count = var.disk_encryption_key == null ? 0 : 1 project = var.disk_encryption_key.project_id diff --git a/third-party-solutions/openshift/tf/masters.tf b/third-party-solutions/openshift/tf/masters.tf index 9533648d7..7e0172e4f 100644 --- a/third-party-solutions/openshift/tf/masters.tf +++ b/third-party-solutions/openshift/tf/masters.tf @@ -17,7 +17,8 @@ resource "google_compute_instance" "master" { for_each = toset(var.zones) project = var.service_project.project_id - name = "${local.infra_id}-m-${each.key}" + name = "${local.infra_id}-master-${each.key}" + hostname = "${local.infra_id}-master-${each.key}.${local.subdomain}" machine_type = "n1-standard-4" zone = "${var.region}-${each.key}" network_interface { diff --git a/third-party-solutions/openshift/tf/variables.tf b/third-party-solutions/openshift/tf/variables.tf index bcef70328..ce18ae3d6 100644 --- a/third-party-solutions/openshift/tf/variables.tf +++ b/third-party-solutions/openshift/tf/variables.tf @@ -121,7 +121,6 @@ variable "service_project" { description = "Service project configuration." type = object({ project_id = string - vpc_name = string }) }