Why profile before you pick
Most bad container platform choices start with someone saying "we're a Kubernetes shop" or "let's just use serverless" before anyone has written down what the workload actually needs. In Level 1 we laid out every Google Cloud container service. From here on, every comparison, every cost sheet and the final selection record all read from one document: the workload profile.
Think of it like a pilot's pre-flight checklist. It doesn't fly the plane. It stops you taking off with something important left blank. The profile has eight dimensions, and every one needs a concrete value or range. "Medium traffic" doesn't count. "40 req/s typical, 900 req/s for 20 minutes at 08:00 on Mondays" does.
The eight dimensions, filled in
Here is a worked profile for a sample workload: a parcel-tracking platform for a mid-sized logistics firm. Keep it in the repo as YAML so it's reviewed like code and its changes show up in history.
# workload-profile.yaml: one file per workload, reviewed in pull requests
workload: parcel-tracking
profiled_on: 2026-10-29 # profiles go stale; date them
services:
count: 6 # api, web, notifier, label-render, ocr, nightly-reconcile
talk_to_each_other: true # 4 call each other over HTTP/gRPC
state:
stateless: [api, web, notifier, label-render, ocr]
stateful: [nightly-reconcile] # keeps a 3 GB working set on local disk while it runs
external_state: "Cloud SQL Postgres, Cloud Storage" # state kept OUTSIDE the containers
traffic:
shape: "business-hours peaks, near zero 22:00-06:00"
typical_rps: 40
peak_rps: 900 # Monday 08:00-08:20, from last quarter's load balancer logs
longest_request: "label-render bulk PDF up to 4 min"
batch: "nightly-reconcile runs about 3 h"
gpus:
needed_now: false
maybe_later: "ocr may move to GPU inference in 2027"
team:
kubernetes_skill: "1 engineer has run production clusters; 4 have not"
on_call: "business hours only, no 24/7 rota"
ops_hours_per_week: 6 # what the team can REALLY give to platform upkeep
portability:
requirement: "must be able to move to another cloud within 12 months if the contract says so"
compliance:
data_residency: "EU only"
standards: ["ISO 27001"]
cost_model:
preference: "pay for use; finance dislikes paying for idle capacity"
budget_ceiling: "£4,000 / month compute"
How each dimension pushes the choice
No single row decides the answer. Each one nudges you towards Kubernetes (GKE), serverless containers (Cloud Run) or a managed PaaS (App Engine).
| Dimension | Pushes towards Kubernetes | Pushes towards serverless containers / PaaS |
|---|---|---|
| Number of services | Dozens of services that call each other a lot and need shared policy | A handful of independent services |
| State | Stateful sets, local disks, custom storage | Stateless; state lives in managed databases |
| Traffic and scaling | Steady and high, or needs fine control of scaling | Spiky, bursty or idle much of the day (scale to zero) |
| GPUs | Many GPU types, large or long training runs | Bursty inference on supported GPUs |
| Skills and operating model | Platform team plus 24/7 on-call | Small team with no rota |
| Portability | Kubernetes manifests carry over to any cloud | Each platform's own config (Cloud Run uses standard container images, so the images still move) |
| Compliance | Needs node-level controls and custom hardening | Satisfied by provider attestations |
| Cost model | Steady use that can fill reserved capacity | Pay per use; idle should cost nothing |
Limits can veto a choice on their own, so check the hard numbers early. As of October 2026, the Cloud Run docs give a maximum request timeout of 60 minutes, up to 1,000 concurrent requests per instance and 8 vCPU and 32 GiB per instance. Cloud Run jobs tasks can run for up to 168 hours (one hour with GPUs). Measure the parcel platform against those numbers. The 4-minute PDF fits a request easily. The 3-hour reconcile wouldn't fit a request, but it fits a job.
Trade-off: the cost-model row interacts with traffic. Cloud Run defaults to request-based billing, which Google recommends for sporadic or spiky traffic. Instance-based billing is recommended for steady, slowly varying traffic. GKE Autopilot bills for the resources your pods request. You can only compare these once you know your traffic numbers, which is why traffic comes before cost.
Lint your profile for gaps
A profile with "TBD" in it is not finished. A few lines of Python catch the obvious holes before the review meeting:
# lint_profile.py: flag blank or vague answers before anyone chooses a platform
import yaml, sys
VAGUE = {"tbd", "unknown", "medium", "normal", "some", "n/a", ""}
REQUIRED = ["services", "state", "traffic", "gpus", "team",
"portability", "compliance", "cost_model"]
profile = yaml.safe_load(open(sys.argv[1]))
problems = [f"missing section: {k}" for k in REQUIRED if k not in profile]
def walk(node, path=""):
if isinstance(node, dict):
for k, v in node.items():
walk(v, f"{path}.{k}" if path else k)
elif str(node).strip().lower() in VAGUE:
problems.append(f"vague value at {path}: {node!r}")
walk(profile)
if "peak_rps" not in profile.get("traffic", {}):
problems.append("traffic.peak_rps missing: averages hide the peak you must survive")
print("\n".join(problems) or "profile OK")
$ python lint_profile.py colleague-profile.yaml
vague value at traffic.shape: 'normal'
traffic.peak_rps missing: averages hide the peak you must survive
vague value at team.on_call: 'TBD'
Spotting the gaps a script can't
A script only finds blanks. A reviewer has to find the claims that are wrong. These are the gaps that most often make a choice unsafe:
- Unstated peaks. "200 req/s average" can hide a 3,000 req/s spike when a marketing email goes out. Ask: what was the worst 15 minutes last year?
- Hidden state. "Stateless" services that write uploads to local disk, keep sessions in memory or depend on sticky connections. Ask: what is lost if this instance dies right now?
- On-call fantasy. A design that needs someone to patch nodes at 03:00 when the team has no rota. Ask: who gets paged, and are they paid for it?
- Long-running work. A report that "usually" takes 50 minutes will one day take 70, which is past a 60-minute request limit.
- Portability stated as a wish. "We might move clouds" with no deadline or contract clause behind it costs you effort and gains you nothing.
Best practice: have someone outside the team review the profile. The people who built a system are the least likely to notice its unstated assumptions.
Key takeaways
- The workload profile has eight dimensions: number of services, state, traffic and scaling, GPUs, skills and operating model, portability, compliance and cost model. Every later decision in this course reads from it.
- Every dimension needs a concrete value or range, a date and a source such as logs or contracts. Words like "medium" or "TBD" don't count.
- Each dimension pushes the choice towards Kubernetes, serverless containers or a managed PaaS. Hard limits, such as Cloud Run's 60-minute request timeout, can rule an option out on their own.
- The operating model you can actually sustain, including on-call cover and ops hours, matters as much as technical fit.
- Review a profile for unstated peaks, hidden state, missing on-call cover and work that runs too long. These are the gaps that make a choice unsafe.