Study Guide 2023+

gcp

Warning: These notes are partial, ongoing, incomplete, and may contain typos/inaccuracies. (They are kept factually accurate, time permitting.)

They are being united from many disparate notes created in the past and the layout/organization will gradually improve with time!

Please view them on a computer as they are not optimized for mobile (although you can still view them on Mobile along with the Flashcards at your own risk)!

Topics and code examples are lazy-loaded and may require two-clicks from the TOC to correctly calculate the updated x,y coordinates (after rendering). Thanks!

GCP: Overview

College Credits

Google learning and cloud content divides into at least the following kinds:

  1. Google Certification
    • Proctored examinations through Pearson.
    • These are not ACE College Credit eligible in-of-themselves (although they are more valuable professionally).
  2. Google Certificates
    • Through skills.google/
    • Not all Google Courses, Labs, etc. result in a Certificate.
    • In fact only a handful do - and of these only the those described here are ACE College Credit eligible.
  3. Coursera Certificates
    • These are offered through Coursera but don't automatically confer a Google Certificate nor a Google Certification.
    • These don't appear to be ACE College Credit eligible.

Idioms

Ways that Google defines/does things:

Some Quick Comparison with AWS

Cost Summary

Studies, models, cost comparisons:

  1. https://www.qovery.com/blog/google-cloud-vs-aws-cost-breakdown-by-service
  2. https://spot.rackspace.com/blog/cloud-computing-cost
  3. https://www.usage.ai/blogs/finops/multi-cloud/cloud-pricing-comparison/
  1. https://docs.aws.amazon.com/whitepapers/latest/real-time-communication-on-aws/cross-region-dns-based-load-balancing-and-failover.html
  2. https://www.acenet.edu/National-Guide/Pages/Organization.aspx?oid=3fdb1492-e04f-e911-a97b-000d3a363c81
  3. https://www.qovery.com/blog/google-cloud-vs-aws-cost-breakdown-by-service
  4. https://spot.rackspace.com/blog/cloud-computing-cost
  5. https://www.usage.ai/blogs/finops/multi-cloud/cloud-pricing-comparison/

GCP: Google Cloud Computing Foundations Certificate

Cloud Computing Fundamentals

Notes.

Best ways to interact with Google Cloud Resources:

Useful Google Cloud Shell Commands

Used in many Google Skill Labs and Challenges.

Note that the early Skill Labs and Challenges tend to be Imperative (emphasizing sequences of CLI commands for new users to gain familiarity).

Google Cloud Shell (gcloud through the interactive terminal):

gcloud config set compute/region asia-southeast1
gcloud config get-value compute/region
gcloud config set compute/zone asia-southeast1-a
gcloud config get-value compute/zone

gcloud compute project-info describe --project $(gcloud config get-value project)

# Define an Environment Variable
export PROJECT_ID=$(gcloud config get-value project)
export ZONE=$(gcloud config get-value compute/zone)
export REGION=$(gcloud config get-value compute/region)
echo -e "PROJECT ID: $PROJECT_ID\nZONE: $ZONE\nZONE: $REGION"

Virtual Machines

# Create VM
## Used in several skill labs
gcloud compute instances create www1 --machine-type e2-medium --zone $ZONE
gcloud compute instances create www2 \
    --zone=asia-southeast1-b \
    --tags=network-lb-tag \
    --machine-type=e2-small \
    --image-family=debian-12 \
    --image-project=debian-cloud \
    --metadata=startup-script='#!/bin/bash
      apt-get update
      apt-get install apache2 -y
      service apache2 restart
      echo "<h3>Web Server: www1</h3>" | tee /var/www/html/index.html'
## Then list and filter them
gcloud compute instances list
gcloud compute instances list --filter="name=('www1')"

# SSH
gcloud compute ssh www2 --zone $ZONE
# Deploy contents of current directory to App Engine
gcloud app deploy
gcloud app browse

Load Balancers

gcloud config set compute/region asia-southeast1
gcloud config set compute/zone asia-southeast1-b
export ZONE=$(gcloud config get-value compute/zone)
export REGION=$(gcloud config get-value compute/region)

Network Load Balancer:

gcloud compute instances create web1 \
  --zone=$ZONE\
  --tags=network-lb-tag \
  --machine-type=e2-small \
  --image-family=debian-12 \
  --image-project=debian-cloud \
  --metadata=startup-script='#!/bin/bash
      apt-get update
      apt-get install apache2 -y
      service apache2 restart
      echo "<h3>Web Server: web1</h3>" | tee /var/www/html/index.html'

gcloud compute firewall-rules create www-firewall-network-lb \
  --target-tags network-lb-tag --allow tcp:80
gcloud compute addresses create network-lb-ip-1 \
  --region $REGION
gcloud compute http-health-checks create basic-check
gcloud compute target-pools create www-pool \
  --region $REGION --http-health-check basic-check
gcloud compute target-pools add-instances www-pool \
    --instances web1,web2,web3
gcloud compute forwarding-rules create www-rule \
    --region $REGION \
    --ports 80 \
    --address network-lb-ip-1 \
    --target-pool www-pool

Application Load Balancer:

gcloud compute instance-templates create lb-backend-template \
   --region=$REGION \
   --network=default \
   --subnet=default \
   --tags=allow-health-check \
   --machine-type=e2-medium \
   --image-family=debian-12\
   --image-project=debian-cloud \
   --metadata=startup-script='#!/bin/bash
     apt-get update
     apt-get install apache2 -y
     a2ensite default-ssl
     a2enmod ssl
     vm_hostname="$(curl -H "Metadata-Flavor:Google" \
     http://169.254.169.254/computeMetadata/v1/instance/name)"
     echo "Page served from: $vm_hostname" | \
     tee /var/www/html/index.html
     systemctl restart apache2'

# Managed Instance Group
## Automatically creates and associates VM Instances 
## using the Template created above.
gcloud compute instance-groups managed create lb-backend-group \
  --template=lb-backend-template --size=2 --zone=$ZONE

# Health Checks, Firewall Rules
## Make sure the Tags match!
gcloud compute firewall-rules create fw-allow-health-check \
  --network=default \
  --action=allow \
  --direction=ingress \
  --source-ranges=130.211.0.0/22,35.191.0.0/16 \
  --target-tags=allow-health-check \
  --rules=tcp:80
gcloud compute addresses create lb-ipv4-1 \
  --ip-version=IPV4 \
  --global
gcloud compute health-checks create http http-basic-check \
  --port 80

# Create the Backend Service...
gcloud compute backend-services create web-backend-service \
  --protocol=HTTP \
  --port-name=http \
  --health-checks=http-basic-check \
  --global
## ...and Associate the Managed Instance Group
gcloud compute backend-services add-backend web-backend-service \
  --instance-group=lb-backend-group \
  --instance-group-zone=asia-southeast1-b \
  --global

# Expose the Application Load Balancer
gcloud compute url-maps create web-map-http \
  --default-service web-backend-service
## And define listeners
gcloud compute target-http-proxies create http-lb-proxy \
  --url-map web-map-http
gcloud compute forwarding-rules create http-content-rule \
  --address=lb-ipv4-1\
  --global \
  --target-http-proxy=http-lb-proxy \
  --ports=80

Big Query Dataflow

gcloud services disable dataflow.googleapis.com --project qwiklabs-gcp-02-3087b0d6e7e5 --force
gcloud services enable dataflow.googleapis.com --project qwiklabs-gcp-02-3087b0d6e7e5

bq mk lab_737
## Create Table in the UI
## Create Bucket in the UI
## Create Dataflow from batch template "Text Files on Cloud Storage to BigQuery"

Managed Apache Spark

gcloud dataproc clusters create example-cluster \
  --region=us-central1 \
  --master-machine-type=n2d-standard-2 \
  --master-boot-disk-type=pd-standard \
  --master-boot-disk-size=100GB \
  --worker-machine-type=n2d-standard-2 \
  --worker-boot-disk-type=pd-standard \
  --worker-boot-disk-size=100GB \
  --num-workers=2
## Create a Job through the UI

Google Cloud Speech-to-Text

## Create an API Key
export API_KEY=<YOUR_API_KEY>
## SSH into a Linux VM
touch request.json

nano request.json
## Copy contents into request.json
### {
###   "config": {
###       "encoding":"FLAC",
###       "languageCode": "en-US" 
###   },
###   "audio": {
###       "uri":"gs://cloud-samples-tests/speech/brooklyn.flac"
###   }
### }

curl -s -X POST -H "Content-Type: application/json" --data-binary @request.json \
"https://speech.googleapis.com/v1/speech:recognize?key=${API_KEY}"

curl -s -X POST -H "Content-Type: application/json" --data-binary @request.json \
"https://speech.googleapis.com/v1/speech:recognize?key=${API_KEY}" > result.json

## Upload and rename result.json to and within the relevant bucket

Cloud Natural Language API

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value core/project)

gcloud iam service-accounts create my-natlang-sa \
  --display-name "my natural language service account"

gcloud iam service-accounts keys create ~/key.json \
  --iam-account my-natlang-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com

export GOOGLE_APPLICATION_CREDENTIALS="/home/USER/key.json"

## SSH into a VM
gcloud ml language analyze-entities --content="Old Norse texts portray Odin as one-eyed and long-bearded, frequently wielding a spear named Gungnir and wearing a cloak and a broad hat." > result.json
## Upload and rename result.json to and within the relevant bucket

Secure a Bastion

export ZONE="asia-south1-b"
export REGION="asia-south1"
export TAG_ONE=accept-ssh-iap-ingress-ql-609
export TAG_TWO=accept-http-ingress-ql-609
export TAG_THREE=accept-ssh-internal-ingress-ql-609

gcloud compute firewall-rules delete open-access

gcloud compute instances start bastion --zone=$ZONE

gcloud compute instances add-tags bastion \
    --zone=$ZONE \
    --tags=$TAG_ONE

gcloud compute firewall-rules create allow-ssh-iap-ingress \
    --network acme-vpc \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:22 \
    --source-ranges=35.235.240.0/20 \
    --target-tags=$TAG_ONE

gcloud compute instances add-tags juice-shop \
    --zone=$ZONE \
    --tags=$TAG_TWO

gcloud compute firewall-rules create allow-http-juice-shop \
    --network acme-vpc \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:80 \
    --source-ranges=0.0.0.0/0 \
    --target-tags=$TAG_TWO

gcloud compute instances add-tags juice-shop \
    --zone=$ZONE \
    --tags=$TAG_THREE

## Subnet must be represented by exact IP Range
### Fetch it
export MGMT_SUBNET_RANGE=$(gcloud compute networks subnets describe acme-mgmt-subnet \
    --region=$REGION \
    --format="value(ipCidrRange)")

gcloud compute firewall-rules create allow-ssh-internal-ingress \
    --network=acme-vpc \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:22 \
    --source-ranges=$MGMT_SUBNET_RANGE \
    --target-tags=$TAG_THREE

## SSH in then run
gcloud compute ssh juice-shop --internal-ip

Compute Services Comparison

Service Description Workload Type
GCP Compute Engine Configurable Virtual Machines. VM's IaaS
GCP App Engine Quickly deploying Applications from Source Code. Managed Serverless Applications PaaS
GCP Cloud Run Deploy Fully Managed Containerized Applications. Managed Serverless Containers PaaS
GCP Cloud Run Functions Serverless Function execution environment. Serverless Functions FaaS (PaaS)
GCP Google Kubernetes Engine Managed Kubernetes Service. Serverless Kubernetes, Containers/Pods PaaS

GCP Databases

Service Description Type
GCP Spanner Globally distributed, strongly consistent SQL relational database for structured data. Managed, SQL
GCP Firestore Serverless NoSQL document database for semi-structured and unstructured data. Managed, NoSQL
GCP Bigtable Scalable NoSQL wide-column database for large-scale, low-latency structured and semi-structured data. Managed, NoSQL
GCP Cloud Storage Scalable object storage for unstructured data such as files, images, videos, and backups. Managed, Blob
GCP Cloud SQL Managed relational database service for MySQL, PostgreSQL, and SQL Server with structured data. Managed, SQL
  1. https://www.skills.google/paths/36
  2. https://docs.cloud.google.com/compute/docs/tutorials/high-scalability-autoscaling

GCP: Associate Cloud Engineer Certification

https://cloud.google.com/learn/certification/cloud-engineer/

XLA

Accelerated Linear Algebra (XLA) integrates with existing, widely used, ML libraries while offering deep compiler-level optimizations:

  1. PyTorch/XLA
  2. JAX/XLA

Google Cloud Dynamic Workload Scheduler

Consumption option Best for Capacity assurance Lifespan Preemptible Quota Discounts
Flex-start Short-duration workloads up to seven days that can wait for capacity. Best-effort Up to seven days No Preemptible quota Discounted (up to 53%) on supported series
Spot VMs Fault-tolerant, short-duration general GPU workloads. Best-effort Preemptible Yes Preemptible quota Deeply discounted (up to 91%)
Standard Reservations Critical general GPU workloads that require a very high level of assurance for capacity. Very high Very high User-defined No No quota consumed
Future Reservations Large-scale, long-running training on clustered GPU. Very high Unlimited within the reservation period. No Automatically increased Discounted (up to 53%) with CUDs
Future Reservations in Calendar Mode Clustered GPU workloads up to 90 days that require reserved capacity. Very high Up to 90 days No No quota consumed Discounted (up to 53%)
On-Demand VMs General GPU workloads with no specific duration. Best-effort Unlimited No On-demand quota None (pay-as-you-go)

https://cloud.google.com/blog/products/compute/introducing-dynamic-workload-scheduler

GKE TPU

Kubernetes added DRA in 2025:

  1. ResourceClaims were added then as well.
  2. Kubernetes workloads are often used to manage AI workloads in the cloud (GKE, TPU, DRA).

See:

  1. https://www.skills.google/paths/11/course_templates/1403
  2. https://medium.com/google-cloud/part-i-gke-managed-dranet-with-tpus-ece22b31e4d9
  3. https://github.com/GoogleCloudPlatform/cluster-toolkit/tree/main/examples/gke-tpu-v6e

GKE

Inference Gateway:

  1. https://docs.cloud.google.com/kubernetes-engine/docs/concepts/about-gke-inference-gateway
  1. https://cloud.google.com/blog/products/compute/introducing-dynamic-workload-scheduler
  2. https://www.skills.google/paths/11/course_templates/1403
  3. https://medium.com/google-cloud/part-i-gke-managed-dranet-with-tpus-ece22b31e4d9
  4. https://github.com/GoogleCloudPlatform/cluster-toolkit/tree/main/examples/gke-tpu-v6e
  5. https://docs.cloud.google.com/kubernetes-engine/docs/concepts/about-gke-inference-gateway

GCP: Professional Cloud Architect Certification

https://cloud.google.com/learn/certification/cloud-architect/

GKE

Problem Solution
Need more pods Horizontal Pod Autoscaler (HPA)
Need more nodes Cluster Autoscaler
Pods need different resource requests Vertical Pod Autoscaler (VPA)
Pods are Pending because the cluster lacks capacity Cluster Autoscaler

Recollect that Kubernetes Secrets are akin to Kubernetes Config Maps but are specifically intended to store Secrets.

Useful Google Cloud Shell Commands

Used in many Google Skill Labs and Challenges.

GKE

export PROJECT_ID="qwiklabs-gcp-01-00ab3bd88327"
export REGION="us-central1"
export ZONE="us-central1-a"

export CLUSTER_NAME="hello-world-uuyh"
export NAMESPACE_NAME="gmp-t3gp"
export REPO_NAME="demo-repo"
export SERVICE_NAME="helloweb-service-5t69"

export DEFAULT_CLUSTER_VER=$(gcloud container get-server-config)

## Create Cluster with specified settings
gcloud container clusters create $CLUSTER_NAME \
  --zone=$ZONE \
  --release-channel=regular \
  --cluster-version=$DEFAULT_CLUSTER_VER \
  --enable-autoscaling \
  --num-nodes 3 \
  --min-nodes 2 \
  --max-nodes 6

## This has to be run for credit
gcloud container clusters update $CLUSTER_NAME \
  --zone=$ZONE \
  --enable-managed-prometheus

## Create Namespace
kubectl create ns $NAMESPACE_NAME

## Download yaml
gcloud storage cp gs://spls/gsp510/prometheus-app.yaml .
### Edit
nano prometheus-app.yaml

## Deploy into the Namespace
kubectl -n $NAMESPACE_NAME apply -f prometheus-app.yaml

## Download yaml
gcloud storage cp gs://spls/gsp510/pod-monitoring.yaml .
### Edit
nano pod-monitoring.yaml
kubectl -n $NAMESPACE_NAME apply -f pod-monitoring.yaml

## Download yaml
gcloud storage cp -r gs://spls/gsp510/hello-app/ .
### Edit 
cd hello-app/manifests
nano helloweb-deployment.yaml
## Deploy
kubectl create -f helloweb-deployment.yaml

## Use this filter in 'Logs explorer'
### "Error: InvalidImageName" OR "Failed to apply default image tag \"<todo>\": couldn't parse image name \"<todo>\": invalid reference format"
### severity="WARNING"
### resource.type="k8s_pod"

## This was unnecessarily convoluted
### The lab says that the repo is already created, it isn't...
### I finally figured out that you have to create it like so
export REPO_NAME=demo-repo
gcloud auth configure-docker $REGION-docker.pkg.dev
gcloud artifacts repositories list
docker build -t $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/hello-app:v2 .
gcloud artifacts repositories create $REPO_NAME \
    --repository-format=docker \
    --location=$REGION \
    --description="Docker repository for lab"
docker push $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/hello-app:v2

## Expose the deployment through an external kubernetes loadbalancer
kubectl expose deployment helloweb --name=helloweb-service-5t69 --type=LoadBalancer --port=8080 --target-port=8080

https://docs.cloud.google.com/sdk/gcloud/reference/container/clusters/create

export REGION=europe-west1
export ZONE=europe-west1-c
export PROJECT=qwiklabs-gcp-01-8dd3c7a798ca

gcloud config set compute/region $REGION
gcloud config set compute/zone $ZONE

source <(gcloud storage cat gs://spls/gsp318/script.sh)
gcloud storage cp gs://spls/gsp318/valkyrie-app.tgz .
tar -xzf valkyrie-app.tgz
cd valkyrie-app
touch Dockerfile
nano Dockerfile
## FROM golang:1.10
## WORKDIR /go/src/app
## COPY source .
## RUN go install -v
## ENTRYPOINT ["app","-single=true","-port=8080"]
cat Dockerfile

docker build -t valkyrie-dev:v0.0.1 .
docker images
# Find ImageID
## export IMAGEID=
docker run -p 8080:8080 $IMAGEID &
docker ps -a

gcloud auth configure-docker $REGION-docker.pkg.dev
docker images
# Retag
export REPONAME=valkyrie-repo
## Use this exact name going forward
export IMAGEID=valkyrie-dev:v0.0.1
docker tag valkyrie-dev:v0.0.1 $REGION-docker.pkg.dev/$PROJECT/$REPONAME/$IMAGEID
gcloud artifacts repositories create $REPONAME \
    --repository-format=docker \
    --location=$REGION \
    --description="Docker repository for lab"
## Verify
docker images
## Push
docker push $REGION-docker.pkg.dev/$PROJECT/$REPONAME/$IMAGEID

# Deployment and Expose 80
## Replace with the above
## e.g. - europe-west1-docker.pkg.dev/qwiklabs-gcp-01-8dd3c7a798ca/valkyrie-repo/valkyrie-dev:v0.0.1
nano k8s/deployment.yaml
cat k8s/deployment.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl expose deployment valkyrie-dev --name=valkyrie-dev --type=LoadBalancer --port=80 --target-port=80

# Info
kubectl get pods
gcloud compute instances list
kubectl get services
## Find CONTAINERID
export CONTAINERID=
docker logs $CONTAINERID

TF

https://www.skills.google/paths/12/course_templates/636/labs

This lab uses "Modules" that are actually "Resource" definitions. This was confusing to me.

You have to install tf in the outer shell (not from within the Open Editor).

Create necessary files:

terraform --version
export REGION=europe-west1
export ZONE=europe-west1-d
export PROJECT=qwiklabs-gcp-02-1ec38e8c48e3

touch main.tf
touch variables.tf

mkdir modules && mkdir modules/instances
touch modules/instances/instances.tf
touch modules/instances/outputs.tf
touch modules/instances/variables.tf

mkdir modules/storage
touch modules/storage/storage.tf
touch modules/storage/outputs.tf
touch modules/storage/variables.tf

Create root variables.tf:

cat << EOF > variables.tf
variable "region" {
  type        = string
  default     = "$REGION"
}
variable "zone" {
  type        = string
  default     = "$ZONE"
}
variable "project_id" {
  type        = string
  default     = "$PROJECT"
}
EOF

Prepare instances.tf:

cat << EOF > modules/instances/instances.tf
resource "google_compute_instance" "tfinstance1" {
  name         = "tf-instance-1"
  machine_type = "e2-micro"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

resource "google_compute_instance" "tfinstance2" {
  name         = "tf-instance-2"
  machine_type = "e2-micro"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}
EOF

Prep main.tf:

cat << EOF > main.tf
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

module "instances" {
  source = "./modules/instances"
}
EOF

So one can import by the instance name: tf-instance-1 rather than the id:

terraform init
terraform fmt
terraform import 'module.instances.google_compute_instance.tfinstance1' projects/$PROJECT/zones/$ZONE/instances/tf-instance-1
terraform import 'module.instances.google_compute_instance.tfinstance2' projects/$PROJECT/zones/$ZONE/instances/tf-instance-2
terraform show
terraform plan
terraform apply --auto-approve
export BUCKET=tf-bucket-963122
cat << EOF > modules/storage/storage.tf
resource "google_storage_bucket" "storage-bucket" {
  name          = "$BUCKET"
  location      = "US"
  force_destroy = true
  uniform_bucket_level_access = true
}
EOF

Prep main.tf:

cat << EOF > main.tf
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

module "instances" {
  source = "./modules/instances"
}

module "storage" {
  source = "./modules/storage"
}
EOF
terraform fmt
terraform init
terraform apply --auto-approve

The Bucket must be created in GCP before switching backends over:

cat << EOF > main.tf
terraform {
  backend "gcs" {
    bucket  = "$BUCKET"
    prefix  = "terraform/state"
  }
}
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

module "instances" {
  source = "./modules/instances"
}

module "storage" {
  source = "./modules/storage"
}
EOF
terraform init -migrate-state

Update instances.tf with the correct tfinstance3.name:

cat << EOF > modules/instances/instances.tf
resource "google_compute_instance" "tfinstance1" {
  name         = "tf-instance-1"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

resource "google_compute_instance" "tfinstance2" {
  name         = "tf-instance-2"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

resource "google_compute_instance" "tfinstance3" {
  name         = "tf-instance-230370"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}
EOF
terraform apply

Update instances.tf:

cat << EOF > modules/instances/instances.tf
resource "google_compute_instance" "tfinstance1" {
  name         = "tf-instance-1"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

resource "google_compute_instance" "tfinstance2" {
  name         = "tf-instance-2"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "default"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}
EOF
terraform apply
export VPC=tf-vpc-803543

Update main.tf with the correct network_name:

cat << EOF > main.tf
terraform {
  backend "gcs" {
    bucket  = "$BUCKET"
    prefix  = "terraform/state"
  }
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 7.46.0"
    }
  }
}
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

module "instances" {
  source = "./modules/instances"
}

module "storage" {
  source = "./modules/storage"
}

module "vpc" {
    source  = "terraform-google-modules/network/google"
    version = "~> 18.2"

    project_id   = "$PROJECT"
    network_name = "$VPC"
    routing_mode = "GLOBAL"

    subnets = [
        {
            subnet_name           = "subnet-01"
            subnet_ip             = "10.10.10.0/24"
            subnet_region         = "$REGION"
        },
        {
            subnet_name           = "subnet-02"
            subnet_ip             = "10.10.20.0/24"
            subnet_region         = "$REGION"
        },
    ]
}
EOF

Note that the exact combination: ~> 18.2 and ~> 7.46.0 were required to finish the lab (the lab itself states that one must use 10.0.0). Otherwise, Provider and version errors will prevent the creation of required resources.

Also note that the lab seemingly requires creating the vpc in two places you will see an error that the Resource already exists but this can be ignored.

To be clearer it requires the student to create the vpc Module in main.tf before referring to it within instances.tf - instances.tf, however, is already referenced by main.tf so it has to be added to instances.tf - this strange convention is apparently what others have done to complete the challenge as well.

rm .terraform.lock.hcl
terraform init
terraform apply

Update instances.tf:

cat << EOF > modules/instances/instances.tf
resource "google_compute_instance" "tfinstance1" {
  name         = "tf-instance-1"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "$VPC"
    subnetwork = "subnet-01"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

resource "google_compute_instance" "tfinstance2" {
  name         = "tf-instance-2"
  machine_type = "e2-standard-2"
  zone         = "$ZONE"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }
  network_interface {
    network = "$VPC"
    subnetwork = "subnet-02"
  }
  metadata_startup_script = <<-EOT
        #!/bin/bash
    EOT
  allow_stopping_for_update = true
}

module "vpc" {
    source  = "terraform-google-modules/network/google"
    version = "~> 18.2"

    project_id   = "$PROJECT"
    network_name = "$VPC"
    routing_mode = "GLOBAL"

    subnets = [
        {
            subnet_name           = "subnet-01"
            subnet_ip             = "10.10.10.0/24"
            subnet_region         = "$REGION"
        },
        {
            subnet_name           = "subnet-02"
            subnet_ip             = "10.10.20.0/24"
            subnet_region         = "$REGION"
        },
    ]
}
EOF
terraform apply

Update main.tf one last time updating the tf-firewall.name:

cat << EOF > main.tf
terraform {
  backend "gcs" {
    bucket  = "$BUCKET"
    prefix  = "terraform/state"
  }
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 7.46.0"
    }
  }
}
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

module "instances" {
  source = "./modules/instances"
}

module "storage" {
  source = "./modules/storage"
}

module "vpc" {
    source  = "terraform-google-modules/network/google"
    version = "~> 18.2"

    project_id   = "$PROJECT"
    network_name = "$VPC"
    routing_mode = "GLOBAL"

    subnets = [
        {
            subnet_name           = "subnet-01"
            subnet_ip             = "10.10.10.0/24"
            subnet_region         = "$REGION"
        },
        {
            subnet_name           = "subnet-02"
            subnet_ip             = "10.10.20.0/24"
            subnet_region         = "$REGION"
        },
    ]
}

resource "google_compute_firewall" "tf-firewall"{
  name    = "tf-firewall"
  network = "projects/$PROJECT/global/networks/$VPC"

  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  source_tags = ["web"]
  source_ranges = ["0.0.0.0/0"]
}
EOF
terraform apply
  1. https://www.skills.google/paths/12
  2. https://docs.cloud.google.com/sdk/gcloud/reference/container/clusters/create
  3. https://registry.terraform.io/providers/hashicorp/google/latest/docs
  4. https://github.com/tariqsheikhsw/GoogleCloudArchitectLabs/blob/main/Solutions/GSP345.sh <- this was helpful to prep for the PCA