This project automates the collection and storage of weather data using Google Cloud services. The pipeline fetches weather data from an external API, stores it in Cloud Storage, and loads it into BigQuery for analysis. The process is orchestrated using Cloud Scheduler, Cloud Functions, and Cloud Run jobs.
The approach leverages a serverless architecture to automate weather data collection and storage. Data is fetched from an external API by a Cloud Run job (get-data-job), temporarily stored in Cloud Storage, and then loaded into BigQuery by another Cloud Run job (load-data-job). A Cloud Function (trigger-cloud-run-job) acts as a bridge, triggered by Cloud Scheduler to initiate the jobs hourly, ensuring minimal maintenance and scalability.
- Cloud Scheduler: Triggers the pipeline hourly.
- Cloud Functions: Receives Pub/Sub messages from Cloud Scheduler and triggers Cloud Run jobs.
- Cloud Run Jobs:
get-data-job: Fetches weather data from an API (e.g., OpenWeatherMap) and stores it in Cloud Storage.load-data-job: Loads data from Cloud Storage into BigQuery.
- Cloud Storage: Stores the fetched weather data temporarily.
- BigQuery: Stores the weather data for querying and analysis.
Before starting, ensure you have:
- A Google Cloud account with billing enabled.
- The
gcloudCLI installed and authenticated (gcloud auth login). - Docker installed for building and pushing container images.
- A weather API key (e.g., from OpenWeatherMap).
Create a Google Cloud Project:
gcloud projects create vibrant-map-454012-h9 --name="Weather Pipeline"(Note: If using a different project, adjust accordingly.)
Enable required APIs:
gcloud services enable cloudfunctions.googleapis.com run.googleapis.com cloudscheduler.googleapis.com bigquery.googleapis.com storage.googleapis.com pubsub.googleapis.comCreate service accounts:
Create a service account for Cloud Run jobs:
gcloud iam service-accounts create cloud-run-sa --display-name="Cloud Run Service Account" --project vibrant-map-454012-h9Create a service account for Cloud Functions:
gcloud iam service-accounts create cloud-functions-sa --display-name="Cloud Functions Service Account" --project vibrant-map-454012-h9Create a bucket:
gsutil mb gs://weather_data_lakeThis bucket will store the raw weather data fetched by the get-data-job.
Create a dataset:
bq mk --dataset vibrant-map-454012-h9:weather_dataCreate a table with the schema:
bq mk --table vibrant-map-454012-h9:weather_data.weather_records location:STRUCT<city:STRING,localtime:TIMESTAMP>,temperature:FLOATPurpose: Fetches weather data from an API and stores it in Cloud Storage.
PythonScript:
from google.cloud import storage
import requests
def fetch_weather():
api_key = open("/etc/secrets/weather-api-key").read().strip()
url = f"http://api.openweathermap.org/data/2.5/weather?q=London&appid={api_key}"
response = requests.get(url)
data = response.json()
client = storage.Client()
bucket = client.get_bucket("weather_data_lake")
bucket.blob("weather_data.json").upload_from_string(str(data))Dockerfile:
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY get_data.py .
CMD ["python", "get_data.py"]Requirements:
google-cloud-storage==2.7.0
requests==2.28.1
This job reads from Cloud Storage and writes to BigQuery.
from google.cloud import storage
from google.cloud import bigquery
def load_to_bigquery():
client = bigquery.Client()
dataset_id = "weather_data"
table_id = "weather_records"
bucket_name = "weather_data_lake"
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.get_blob("weather_data.json")
data = blob.download_as_text()
table = client.get_table(f"{client.project}.{dataset_id}.{table_id}")
client.insert_rows_json(table, [eval(data)]) # Adjust parsing based on your data formatDockerfile:
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY load_data.py .
CMD ["python", "load_data.py"]Requirements:
google-cloud-storage==2.7.0
google-cloud-bigquery==3.10.0
Build and Push Docker Images:
cd load-data-job
docker build -t us-central1-docker.pkg.dev/vibrant-map-454012-h9/weather-automation-repo/load-data .
docker push us-central1-docker.pkg.dev/vibrant-map-454012-h9/weather-automation-repo/load-dataDeploy:
gcloud run jobs create load-data-job \
--image us-central1-docker.pkg.dev/vibrant-map-454012-h9/weather-automation-repo/load-data \
--region us-central1 \
--service-account cloud-run-sa@vibrant-map-454012-h9.iam.gserviceaccount.com \
--set-env-vars BUCKET_NAME=weather_data_lake,PROJECT_ID=vibrant-map-454012-h9,DATASET_ID=weather_data,TABLE_ID=weather_recordstrigger-cloud-run-job Purpose: Triggers Cloud Run jobs based on Pub/Sub messages.
Python Script:
from google.cloud import run_v2
import base64
def trigger_cloud_run_job(event, context):
"""Cloud Function triggered by Pub/Sub to execute a Cloud Run Job."""
message = base64.b64decode(event['data']).decode('utf-8')
print(f"Received message: {message}")
client = run_v2.JobsClient()
project_id = "vibrant-map-454012-h9"
region = "us-central1"
if message == "run-get-data":
job_name = f"projects/{project_id}/locations/{region}/jobs/get-data-job"
elif message == "run-load-data":
job_name = f"projects/{project_id}/locations/{region}/jobs/load-data-job"
else:
print(f"Unknown message: {message}")
return
request = run_v2.RunJobRequest(name=job_name)
try:
operation = client.run_job(request=request)
print(f"Triggered job: {job_name}")
except Exception as e:
print(f"Failed to trigger job {job_name}: {e}")Requirements:
google-cloud-run==0.10.0
google-cloud-pubsub==2.18.0
Deploy:
cd cloud-function-trigger
gcloud functions deploy trigger-cloud-run-job \
--runtime python310 \
--region us-central1 \
--trigger-topic trigger-weather-jobs \
--project vibrant-map-454012-h9 \
--no-gen2 \
--entry-point trigger_cloud_run_job \
--service-account cloud-functions-sa@vibrant-map-454012-h9.iam.gserviceaccount.comTrigger get-data-job hourly:
gcloud scheduler jobs create pubsub trigger-get-data-hourly \
--schedule="0 * * * *" \
--topic trigger-weather-jobs \
--message-body="run-get-data" \
--location us-central1 \
--project vibrant-map-454012-h9 \
--time-zone="America/Los_Angeles"Trigger load-data-job 5 minutes later:
gcloud scheduler jobs create pubsub trigger-load-data-hourly \
--schedule="5 * * * *" \
--topic trigger-weather-jobs \
--message-body="run-load-data" \
--location us-central1 \
--project vibrant-map-454012-h9 \
--time-zone="America/Los_Angeles"Grant roles/run.invoker:
gcloud projects add-iam-policy-binding vibrant-map-454012-h9 \
--member="serviceAccount:cloud-functions-sa@vibrant-map-454012-h9.iam.gserviceaccount.com" \
--role="roles/run.invoker"Grant roles/bigquery.dataEditor and roles/storage.objectAdmin:
gcloud projects add-iam-policy-binding vibrant-map-454012-h9 \
--member="serviceAccount:cloud-run-sa@vibrant-map-454012-h9.iam.gserviceaccount.com" \
--role="roles/bigquery.dataEditor"
gcloud projects add-iam-policy-binding vibrant-map-454012-h9 \
--member="serviceAccount:cloud-run-sa@vibrant-map-454012-h9.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"Export current dataset configuration:
bq show --format=prettyjson vibrant-map-454012-h9:weather_data > dataset.jsonEdit dataset.json to add:
{
"role": "WRITER",
"userByEmail": "627559420469-compute@developer.gserviceaccount.com"
}Update the dataset:
bq update --source dataset.json vibrant-map-454012-h9:weather_data- Verify all components are set up: Cloud Storage bucket exists (gs://weather_data_lake). BigQuery dataset and table are created (vibrant-map-454012-h9.weather_data.weather_records). Cloud Run jobs are deployed. Cloud Function is deployed. Cloud Scheduler jobs are created.
- Test the pipeline (see).
Manually Trigger Scheduler Jobs:
gcloud scheduler jobs run trigger-get-data-hourly --location us-central1 --project vibrant-map-454012-h9
gcloud scheduler jobs run trigger-load-data-hourly --location us-central1 --project vibrant-map-454012-h9Cloud Run jobs:
gcloud run jobs executions describe $(gcloud run jobs executions list --job=get-data-job --region=us-central1 --project=vibrant-map-454012-h9 --format="value(metadata.name)" --limit=1) --region=us-central1 --project=vibrant-map-454012-h9
gcloud run jobs executions describe $(gcloud run jobs executions list --job=load-data-job --region=us-central1 --project=vibrant-map-454012-h9 --format="value(metadata.name)" --limit=1) --region=us-central1 --project=vibrant-map-454012-h9Cloud Function:
gcloud functions logs read trigger-cloud-run-job --region us-central1 --project vibrant-map-454012-h9 --limit=50SELECT * FROM `vibrant-map-454012-h9.weather_data.weather_records` ORDER BY location.localtime DESC LIMIT 10;Ensure service accounts have the correct roles (see ). Verify dataset access (adjust dataset.json if needed).
Verify main.py is in the cloud-function-trigger directory and the entry point matches trigger_cloud_run_job.
Check gs://weather_data_lake for files:
gsutil ls gs://weather_data_lakeEnsure the data schema matches the BigQuery table:
bq show --schema vibrant-map-454012-h9:weather_data.weather_recordsTo check for analytics copy the link:
https://lookerstudio.google.com/reporting/93903903-59f1-4f92-9308-b4208c53950a