Monitor and Manage Google Cloud Resources

Solution for Monitor and Manage Google Cloud Resources. 1 lab: ARC101. Fast copy-paste commands for Google Cloud.

ARC101 — Monitor and Manage Google Cloud Resources: Challenge Lab

Estimated time: 1 hour 15 minutes

# 🚀 Monitor and Manage Google Cloud Resources: Challenge Lab > ⚠️ **Disclaimer:** This is an independent, community-made walkthrough built to help you understand why each step works. Attempt the challenge yourself first. This guide is provided for educational purposes only and is not affiliated with or endorsed by Google Cloud or Google Cloud Skills Boost. Always follow the official Google Cloud and Qwiklabs terms of service, lab instructions, and usage policies. > > **Walkthrough by Orbit of

clear
CYAN='\e[1;36m'
YELLOW='\e[1;33m'
GREEN='\e[1;32m'
MAGENTA='\e[1;35m'
RED='\e[1;31m'
RESET='\e[0m'
BOLD='\e[1m'

echo -e "${MAGENTA}${BOLD}--- Part 1: Infrastructure & IAM Prep ---${RESET}"

# Auto-detect project
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
echo -e "${CYAN}Detected Project ID:${RESET} $PROJECT_ID"

# Ask for the required variables from your lab panel
echo -e "\n${YELLOW}Please enter the required variables from your lab panel:${RESET}"
read -p "Bucket Name: " BUCKET_NAME
read -p "Topic Name: " TOPIC_NAME
read -p "Function Name: " FUNCTION_NAME
read -p "Region: " REGION
read -p "User 2 Email Address: " USER_2
read -p "Alert Email Address: " ALERT_EMAIL

# Export variables so Command 2 can use them in the same tab
export BUCKET_NAME TOPIC_NAME FUNCTION_NAME REGION USER_2 ALERT_EMAIL

echo -e "\n1️⃣ ${BOLD}Task 1 & 2 — Creating Infrastructure...${RESET}"
# Create the required bucket
gcloud storage buckets create gs://$BUCKET_NAME --location=$REGION
# Create the Pub/Sub topic 
gcloud pubsub topics create $TOPIC_NAME

echo -e "\n2️⃣ ${BOLD}Task 1 & 2 — Applying IAM Permissions...${RESET}"
# Grant User 2 access at the project level (Viewer)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="user:$USER_2" \
    --role="roles/viewer" --quiet

# Apply strict Eventarc IAM permissions
# 1. Grant Pub/Sub Publisher role to Cloud Storage service account
GCS_SA=$(gsutil kms serviceaccount -p $PROJECT_ID)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:$GCS_SA" \
    --role="roles/pubsub.publisher" --quiet

# 2. Grant Eventarc Event Receiver role to Compute Engine default service account
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${PROJECT_NUMBER}[email protected]" \
    --role="roles/eventarc.eventReceiver" --quiet

echo -e "\n${GREEN}✅ Part 1 completed successfully!${RESET}"
echo -e "${YELLOW}📌 IMPORTANT: Return to the lab page and click 'Check my progress' for Tasks 1 & 2 before running Command 2 of 2.${RESET}"
clear
CYAN='\e[1;36m'
YELLOW='\e[1;33m'
GREEN='\e[1;32m'
MAGENTA='\e[1;35m'
RED='\e[1;31m'
RESET='\e[0m'
BOLD='\e[1m'

echo -e "\n3️⃣ ${BOLD}Task 3 — Creating & Deploying the Thumbnail Cloud Run Function...${RESET}"
mkdir -p ~/thumbnail_app
cd ~/thumbnail_app

cat > index.js <<EOF
/* globals exports, require */
//jshint strict: false
//jshint esversion: 6
"use strict";
const crc32 = require("fast-crc32c");
const { Storage } = require('@google-cloud/storage');
const gcs = new Storage();
const { PubSub } = require('@google-cloud/pubsub');
const imagemagick = require("imagemagick-stream");

exports.thumbnail = (event, context) => {
  const fileName = event.name;
  const bucketName = event.bucket;
  const size = "64x64"
  const bucket = gcs.bucket(bucketName);
  const topicName = "${TOPIC_NAME}";
  const pubsub = new PubSub();
  if ( fileName.search("64x64_thumbnail") == -1 ){
    var filename_split = fileName.split('.');
    var filename_ext = filename_split[filename_split.length - 1];
    var filename_without_ext = fileName.substring(0, fileName.length - filename_ext.length );
    if (filename_ext.toLowerCase() == 'png' || filename_ext.toLowerCase() == 'jpg'){
      console.log(\`Processing Original: gs://\${bucketName}/\${fileName}\`);
      const gcsObject = bucket.file(fileName);
      let newFilename = filename_without_ext + size + '_thumbnail.' + filename_ext;
      let gcsNewObject = bucket.file(newFilename);
      let srcStream = gcsObject.createReadStream();
      let dstStream = gcsNewObject.createWriteStream();
      let resize = imagemagick().resize(size).quality(90);
      srcStream.pipe(resize).pipe(dstStream);
      return new Promise((resolve, reject) => {
        dstStream
          .on("error", (err) => {
            console.log(\`Error: \${err}\`);
            reject(err);
          })
          .on("finish", () => {
            console.log(\`Success: \${fileName} → \${newFilename}\`);
              gcsNewObject.setMetadata(
              {
                contentType: 'image/'+ filename_ext.toLowerCase()
              }, function(err, apiResponse) {});
              pubsub
                .topic(topicName)
                .publisher()
                .publish(Buffer.from(newFilename))
                .then(messageId => {
                  console.log(\`Message \${messageId} published.\`);
                })
                .catch(err => {
                  console.error('ERROR:', err);
                });
          });
      });
    }
  }
};
EOF

cat > package.json <<EOF
{
  "name": "thumbnails",
  "version": "1.0.0",
  "description": "Create Thumbnail of uploaded image",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "@google-cloud/pubsub": "^2.0.0",
    "@google-cloud/storage": "^5.0.0",
    "fast-crc32c": "1.0.4",
    "imagemagick-stream": "4.1.1"
  },
  "devDependencies": {},
  "engines": {
    "node": ">=4.3.2"
  }
}
EOF

echo -e "${YELLOW}Waiting 60 seconds to guarantee all IAM policies have propagated...${RESET}"
sleep 60

echo -e "${YELLOW}Deploying Cloud Run Function (Restricted to 1 instance)...${RESET}"
deploy_success=false

for i in 1 2 3; do
    echo -e "${CYAN}Deployment Attempt $i of 3...${RESET}"
    if gcloud functions deploy $FUNCTION_NAME \
        --gen2 \
        --runtime=nodejs22 \
        --region=$REGION \
        --source=. \
        --entry-point=thumbnail \
        --trigger-bucket=$BUCKET_NAME \
        --max-instances=1 \
        --quiet; then
        deploy_success=true
        echo -e "${GREEN}✅ Function deployed successfully!${RESET}"
        break
    else
        echo -e "${RED}⚠️ Deployment failed (IAM delay). Waiting 45 seconds before next try...${RESET}"
        sleep 45
    fi
done

echo -e "\n4️⃣ ${BOLD}Task 4 — Testing Infrastructure & Creating Alert Policy...${RESET}"
wget -q https://storage.googleapis.com/cloud-training/arc101/travel.jpg
gcloud storage cp travel.jpg gs://$BUCKET_NAME/ 2>/dev/null

CHANNEL_ID=$(gcloud beta monitoring channels create \
    --display-name="Personal Email" \
    --type=email \
    --channel-labels=email_address=${ALERT_EMAIL} \
    --format="value(name)")

cat > active-instances-policy.json <<EOF
{
  "displayName": "Active Cloud Run Function Instances",
  "combiner": "OR",
  "conditions": [
    {
      "displayName": "Cloud Function Active Instances",
      "conditionThreshold": {
        "filter": "resource.type=\"cloud_function\" AND metric.type=\"cloudfunctions.googleapis.com/function/active_instances\"",
        "aggregations": [
          {
            "alignmentPeriod": "60s",
            "perSeriesAligner": "ALIGN_MAX"
          }
        ],
        "comparison": "COMPARISON_GT",
        "thresholdValue": 0,
        "duration": "0s"
      }
    }
  ],
  "notificationChannels": ["$CHANNEL_ID"]
}
EOF

gcloud alpha monitoring policies create --policy-from-file="active-instances-policy.json" --quiet
echo -e "${GREEN}✅ Alerting Policy created successfully!${RESET}"

echo -e "\n${MAGENTA}${BOLD}╔════════════════════════════════════════════════════════════╗${RESET}"
echo -e "${MAGENTA}${BOLD}║            🎉 AUTOMATION COMPLETED SUCCESSFULLY 🎉           ║${RESET}"
echo -e "${MAGENTA}${BOLD}╚════════════════════════════════════════════════════════════╝${RESET}"