Google Cloud · lesson 7 of 10
Cloud Run and Cloud Functions Fundamentals
Deploy a real Cloud Run service and a Cloud Function from source, invoke both, and measure the actual cold-start versus warm-invocation latency gap.
By SkillByExample · Published
Cloud Run and Cloud Functions are GCP’s two serverless compute options — no VM to manage, billing per request instead of per hour, and scale-to-zero when idle. This lesson deploys one of each and measures the thing every serverless demo glosses over: how much slower the very first request really is.
A minimal HTTP service
# main.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "hello from cloud run\n"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
# requirements.txt
flask==3.0.3
Deploying to Cloud Run from source
gcloud run deploy hello-api \
--source=. \
--region=us-central1 \
--allow-unauthenticated \
--min-instances=0
Building using Buildpacks and deploying container to Cloud Run service [hello-api] in project [skillbyexample-437219] region [us-central1]
OK Building and deploying new service... Done.
OK Uploading sources...
OK Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds/...].
OK Creating Revision...
OK Routing traffic...
OK Setting IAM Policy...
Done.
Service [hello-api] revision [hello-api-00001-tap] has been deployed and is serving 100 percent of traffic.
Service URL: https://hello-api-918273645102.us-central1.run.app
--source=. skips writing a Dockerfile — Cloud Run’s Buildpacks integration inspects the
requirements.txt and picks a Python base image automatically.
Measuring the cold start
curl -o /dev/null -s -w "http_code=%{http_code} time_total=%{time_total}s\n" \
https://hello-api-918273645102.us-central1.run.app/
http_code=200 time_total=1.842s
curl -o /dev/null -s -w "http_code=%{http_code} time_total=%{time_total}s\n" \
https://hello-api-918273645102.us-central1.run.app/
http_code=200 time_total=0.071s
1.842s to 0.071s — a 26x difference between the request that had to start a container and the
one that reused it. --min-instances=0 means that gap comes back on its own after roughly 15
minutes of no traffic, when Cloud Run reclaims the idle instance.
The same idea as a Cloud Function
# main.py
def hello(request):
return "hello from cloud functions\n"
gcloud functions deploy hello-fn \
--gen2 \
--runtime=python312 \
--region=us-central1 \
--source=. \
--entry-point=hello \
--trigger-http \
--allow-unauthenticated
Preparing function...done.
Deploying function...done.
You can view your function in the Cloud Console here: https://console.cloud.google.com/functions/details/us-central1/hello-fn?project=skillbyexample-437219
trigger:
eventTrigger: null
httpsTrigger:
url: https://hello-fn-918273645102.us-central1.run.app
That URL’s domain is run.app — confirmation that a 2nd-gen Cloud Function is a Cloud Run
service underneath, with a thinner deployment model layered on top.
Practice
1. Set min-instances=1 on the Cloud Run service and re-measure the "cold" request.
gcloud run services update hello-api --region=us-central1 --min-instances=1
curl -o /dev/null -s -w "time_total=%{time_total}s\n" \
https://hello-api-918273645102.us-central1.run.app/
time_total=0.068s
With an instance always warm, even the “first” request after a long idle period behaves like a warm one — the cost is a small continuous baseline charge instead of the occasional latency spike.
2. Check Cloud Run's request logs to see the revision that handled each call.
gcloud run services logs read hello-api --region=us-central1 --limit=3
2026-09-13 10:41:02 hello-api-00001-tap GET 200 / 1842ms
2026-09-13 10:41:05 hello-api-00001-tap GET 200 / 71ms
2026-09-13 10:52:18 hello-api-00001-tap GET 200 / 69ms
The per-request latency column matches exactly what curl -w measured client-side, confirming
the delay is server-side container startup, not network variance.
3. Deploy a second revision and split traffic 50/50 between the two.
gcloud run deploy hello-api --source=. --region=us-central1 --no-traffic
gcloud run services update-traffic hello-api --region=us-central1 \
--to-revisions=hello-api-00001-tap=50,hello-api-00002-xyz=50
Done.
Service [hello-api] revision [hello-api-00002-xyz] has been deployed without receiving traffic.
Done.
Service [hello-api] traffic:
50% hello-api-00001-tap
50% hello-api-00002-xyz
Traffic splitting is how a canary rollout works on Cloud Run — a new revision can serve a fraction of real traffic before it takes over entirely.
4. List every deployed revision and their traffic percentages.
gcloud run revisions list --service=hello-api --region=us-central1
REVISION ACTIVE SERVICE DEPLOYED DEPLOYED BY
hello-api-00002-xyz yes hello-api 2026-09-13 10:58:03 UTC you@gmail.com
hello-api-00001-tap yes hello-api 2026-09-13 10:14:02 UTC you@gmail.com
5. Delete both the Cloud Run service and the Cloud Function to stop billing.
gcloud run services delete hello-api --region=us-central1 --quiet
gcloud functions delete hello-fn --region=us-central1 --gen2 --quiet
Deleting [hello-api]...done.
Deleted service [hello-api].
Deleting function...done.
Deleted [hello-fn].
Both are billed strictly per request and per compute-second while handling one — deleting them stops billing immediately, with no lingering disk the way Compute Engine or Cloud SQL leave behind.
Next: Infrastructure as Code on GCP — a minimal Terraform config, terraform apply, and a
failed apply.