| img.png | ||
| img_1.png | ||
| img_2.png | ||
| img_3.png | ||
| img_4.png | ||
| img_5.png | ||
| img_6.png | ||
| img_7.png | ||
| img_8.png | ||
| img_9.png | ||
| img_10.png | ||
| img_11.png | ||
| img_12.png | ||
| img_13.png | ||
| img_14.png | ||
| img_15.png | ||
| img_16.png | ||
| img_17.png | ||
| img_18.png | ||
| img_19.png | ||
| img_20.png | ||
| img_21.png | ||
| img_22.png | ||
| img_23.png | ||
| img_24.png | ||
| img_25.png | ||
| img_26.png | ||
| img_27.png | ||
| img_28.png | ||
| img_29.png | ||
| img_30.png | ||
| img_31.png | ||
| img_32.png | ||
| img_33.png | ||
| img_34.png | ||
| img_35.png | ||
| img_36.png | ||
| img_37.png | ||
| img_38.png | ||
| img_39.png | ||
| README.md | ||
Lab 5
Task 1
Ensuring project ID is set so commands run in the right project (for billing and such):

Enabling APIs so we can access the resources (Cloud Resource Manager, Artifact Registry, Cloud Run, and Cloud Build):

Ensuring we have necessary ART_REG environment variable:
Ensuring Artifact Registry is accessible and working by listing our previous images from other labs:

Making and entering the lab 5 directory (using my new favorite Bash shortcuts!):

Cloning the repo, finding out Cloud shell doesn't come with tree (?) and verifying clone success:

Creating and verifying .venv creation (as to not install packages globally:

Entering and confirming .venv environment:

Installing gRPC tools:
Creating restore (with minor environment changes) script and sourcing it:

Task 2
Verifying proto contract that both services will use and generating gRPC stubs:

Ensuring the stubs got copied into both services directory as they both use it:

Verifying that the engine implements Convert (what actually runs the conversion on the request) and that the API that validates input so the engine can focus on conversion:
Task 3
Reviewing Dockerfile:
Building and pushing with Cloud Build to make it available in our Artifact Registry (for Cloud Run):

Deploying to serverless Cloud Run:

Saving the URL of the service for later use:

Calling the service by the saved URL (but getting rejected by the Cloud Run edge auth checker):

Task 4
Reviewing the Dockerfile:
Building and pushing with Cloud Build so we can again use it in Cloud Run:
Deploying to Cloud Run:
Saving URL and ensuring Cloud Run container is up and available:

Task 5
Getting the converter API service and modifying it so it has invoker rights to call the engine:
Ensuring the converter API can access the engine:
Task 6
Health checking to ensure converter API is up:
Listing supported units:
Performing a length conversion (which will have the API trigger a gRPC call):
Performing temperature conversion:
Multiple tests:
Test rejection if query isn't valid:
Trying to observe cold start. This was after manually setting the service's scaling to 0 and then back to auto 0 -> N so I know this is actualy boot up time (about a 10th of a second difference - impressive):
Ensuring the converter is publicly accessible:
Ensuring conversion engine requires authentication:
Checking converter API logs. Note that this confirms out .1s difference was coldstart:
Checking engine logs. Same thing with the coldstart. Note it autoscaled at 16:02:19.138 and responded to the call at 16:02:19.138:
Checking Artifact Registry images:
Setting engine's tag to v1:
Cleaning up by removing the Cloud Run services:
Reflection
- Unit conversion is stateless as its output requires solely on its input. It requires no auth checks, no DB queries, nothing. This makes it well suited to an auto-scaled service as it means any instance can handle any request without worrying about state or handling multiple backends. No state synchronization is needed.
-
- The REST interface is defined by the API handler in server.py. It parses incoming requests and issues a gRPC call via an internal service account to an engine.
- That gRPC interface itself is defined in the proto/ folder, which has the agreed-upon structure of requests. When generated, it is copied to each service's folder so they can both access and use it.
- The intended audience for the REST API is anyone in the public. The gRPC interface is only for authenticated internal traffic and cannot be used by anyone except the converter API.
- It's useful to separate these interfaces as they can be independently deployed, updated and scaled without taking the entire system down.
- The API validates before calling the engine so that they can each have only one responsibility. This means the engine isn't being bogged down with erroneous requests, and ensures they are seperated in function. The gRPC request, once sent, should be authoritative and error free else it could cause undefined behavior (the engine may not be designed to withstand errors).
- The API proves it's identity by requesting a short-lived token from Google's metadata servers. This token represents trust, and it will be checked by the engine to ensure it can be trusted. Before forwarding the call, Cloud Run checks it's source, ensuring it has proper roles and permissions. This is considered zero trust as every thing is checked every step of the way. There is no caching of tokens. Every request, the requester must prove their identity authoritatively.
- Cold starts refer to the first request when a service is scaled to zero during idle time. This system needs two cold starts, first the API, then the engine. Cold start time is largely determined by the app. A webserver will take a lot longer than this tiny API and engine. Turning the containers off reduces usage in the downtime. Serverless containers are also able to attach to arbitrary VPCs to reduce DB latency, which isn't touched on in this lab but is another plus to justify scaling to 0. Additionally, the scaling can be turned off if completely undesirable, and a service can be kept to a minimum of 1 container.
- The generate files are not stored next to the stubs as the stubs are generated files for use by each service. They each need to be placed in the service's directory.
- This would be fairly easy to decide. Validation (more checks) would be solely API side. The proto contract should only be modified if new data needs to be sent between the API and engine. The engine will only be updated with new features and conversions. While a large change may require touching all of these, the usage of them ensures single responsibilities and distinct service boundaries.
Diagram
The diagram shows how the client queries the API. The API sends a gRPC conversion request via a shared contract to the conversion engine (after authentication, not pictured for brevity) and how the engine returns the result to the API and then the client. The Cloud Run services themselves are inherently scalable without any configuration, so the frontend and backend can both scale or be updated independently. They both pull images fro the Artifact Repository.
@startuml
title Lab 5 Architecture
actor Client
cloud "Google Cloud Run Platform" {
component "Converter API" as API
component "Conversion Engine" as ENGINE
}
database "Artifact Registry" as AR
Client --> API : HTTPS /convert
API --> ENGINE : ConversionRequest
ENGINE --> API : ConversionResult
API --> Client : JSON
AR --> API : converter-api:v1
AR --> ENGINE : conversion-engine:v1
@enduml
























