Introduction
To understand Google Cloud Run, you must peel back the layers and look at the interfaces that the API offers. Understanding the API will provide you with a better understanding of the features and capabilities. If your goal is to develop your own tooling, then understanding the API is mandatory. In this article series, I will review the Go libraries for Cloud Run and the REST API. I have written several programs in Go that demonstrate how to use the API. This part includes two Go programs: one for listing service locations and another for listing deployed services. Additional example programs will be included in future parts.
Google Cloud Run Managed is built upon gVisor, Borg and GFE.
Google Cloud Run GKE is built upon Knative, Istio and Google Cloud Load Balancer (L3/L4).
Both variants of Google Cloud Run provide the Knative interface.
GFE (Google Frontend Service) is a reverse proxy that terminates TCP connections. When a service wants to make itself available on the Internet, it can register itself with an infrastructure service called the Google Front End (GFE). The GFE ensures that all TLS connections are terminated using correct certificates and following best practices such as supporting perfect forward secrecy. The GFE additionally applies protections against Denial of Service attacks (which we will discuss in more detail later). The GFE then forwards requests for the service using the RPC security protocol discussed previously. In effect, any internal service which chooses to publish itself externally uses the GFE as a smart reverse-proxy front end. This front end provides public IP hosting of its public DNS name, Denial of Service (DoS) protection, and TLS termination. Note that GFEs run on the infrastructure like any other service and thus have the ability to scale to match incoming request volumes. (link).
gVisor is a user-space kernel, written in Go, that implements a substantial portion of the Linux system surface. It includes an Open Container Initiative (OCI) runtime called runsc
that provides an isolation boundary between the application and the host kernel. (link).
Borg is a cluster manager that runs hundreds of thousands of jobs, from many thousands of different applications, across a number of clusters each with up to tens of thousands of machines. (link).
Knative provides several features:
- Route: provides a named endpoint and a mechanism for routing traffic to revisions.
- Revision: immutable snapshots of code + configuration.
- Configuration: which acts as a stream of environments for Revisions.
- Service: acts as a top-level container for managing the set of Routes and Configurations which implement a network service.
Istio provides several features:
- Load balancing (Layer 7).
- Fine-grained control of traffic behavior with routing rules, retries, failovers, and fault injection.
- Configurable policy layer supporting access controls, rate limits and quotas.
- Automatic metrics, logs, and traces for all traffic.
- Secure service-to-service communication.
Google Cloud Run Managed does not expose/emulate all Knative features. Google Cloud Run is a manged service whose goal is to simplify management and deployments of containers.
Google Cloud SDK CLI
The Google Cloud SDK CLI provides a wealth of features for interfacing with Cloud Run. Review the CLI documentation. The CLI groups Cloud Run commands into several major areas:
- Configurations
- Deployments
- Domain-mappings
- Revisions
- Routes
- Services
Configurations
A Configuration describes the desired latest Revision state, and creates and tracks the status of Revisions as the desired state is updated. A configuration will reference a container image and associated execution metadata needed by the Revision. On updates to a Configuration’s spec, a new Revision will be created; the Configuration’s controller will track the status of created Revisions and makes the most recently created and most recently ready Revisions available in the status section.
Text source: link
Deployments
The deploy command deploys a container image to Google Cloud Run. This command creates the required service, route, revision, and configuration.
Domain-mappings
Google Cloud Run provides the ability to map a custom domain name (myservice.example.com) to a Cloud Run network endpoint (see Routes) and supports creating custom SSL certificates to provide services over HTTPS.
Note: Google Cloud Run on GKE, only HTTP is available by default. You can install a wildcard SSL certificate to enable SSL for all services mapped to domains included in the wildcard SSL certificate. For more information, see Enabling HTTPS.
Tip: You can map multiple custom domains to the same Cloud Run service.
Revisions
Revision is an immutable snapshot of code and configuration. A revision references a container image. Revisions are created by updates to a Configuration.
Revisions that are not addressable via a Route may be garbage collected and all underlying K8s resources will be deleted. Revisions that are addressable via a Route will have resource utilization proportional to the load they are under.
Text source: link
Routes
A Route provides a network endpoint for a user’s service (which consists of a series of software and configuration Revisions over time). A kubernetes namespace can have multiple routes. The route provides a long-lived, stable, named, HTTP-addressable endpoint that is backed by one or more Revisions. The default configuration is for the route to automatically route traffic to the latest revision created by a Configuration. For more complex scenarios, the API supports splitting traffic on a percentage basis, and CI tools could maintain multiple configurations for a single route (e.g. “golden path” and “experiments”) or reference multiple revisions directly to pin revisions during an incremental rollout and n-way traffic split. The route can optionally assign addressable subdomains to any or all backing revisions.
Text source: link
Services
A Service encapsulates a Route and Configuration which together provide a software component. Service exists to provide a singular abstraction which can be access controlled, reasoned about, and which encapsulates software lifecycle decisions such as rollout policy and team resource ownership. Service acts only as an orchestrator of the underlying Routes and Configurations (much as a kubernetes Deployment orchestrates ReplicaSets), and its usage is optional but recommended.
The Service’s controller will track the statuses of its owned Configuration and Route, reflecting their statuses and conditions as its own.
The owned Configuration’s Ready conditions are surfaced as the Service’s ConfigurationsReady condition. The owned Routes’ Ready conditions are surfaced as the Service’s RoutesReady condition.
Text source: link
Service Description
Let’s start with looking at a typical Google Cloud Run service description. The following output was copied from the Google Cloud Console, edited for security. This output is similar to that provided by the CLI command gcloud beta run services describe SERVICE-NAME --platform managed
.
Documentation: link
I will describe several important sections:
- apiVersion: Line 1. This documents the API version that is being used.
- Google Cloud Run has the following API versions: v1, v1beta1, and v1alpha1.
- For the most part, you will use the v1alpha1 API version as this version has the most useful interfaces and features.
- The CLI uses v1alpha1.
- kind: Line 2. The kind of resource being described.
- Common ones are Service, Route, Revision, and Configuration.
- metadata: Line 3. This describes the service.
- Notice the name, creationTimestamp, and annotations.
- https://godoc.org/google.golang.org/api/run/v1alpha1#ObjectMeta
- spec: Line 19. This holds the desired state of the service.
- status: Line 36. Communicates the observed state of the service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: name: cloudrun namespace: my-project selfLink: /apis/serving.knative.dev/v1alpha1/namespaces/my-project/services/cloudrun uid: e6b0877b-8ae3-4e6b-bd54-c236e31dea2b resourceVersion: AAWHix2qcvg generation: 1 creationTimestamp: '2019-04-27T23:07:18.628Z' labels: cloud.googleapis.com/location: us-central1 annotations: run.googleapis.com/creatorEmail: username@example.com run.googleapis.com/lastModifierEmail: username@example.com serving.knative.dev/creator: username@example.com serving.knative.dev/lastModifier: username@example.com client.knative.dev/user-image: gcr.io/my-project/cloudrun spec: traffic: - percent: 100 latestRevision: true template: metadata: labels: client.knative.dev/nonce: woqloqwazh spec: containers: - image: gcr.io/my-project/cloudrun env: - name: MY_PROJECT value: my-project resources: limits: memory: 128Mi status: conditions: - type: Ready status: 'True' lastTransitionTime: '2019-04-27T23:07:31.472Z' - type: ConfigurationsReady status: 'True' lastTransitionTime: '2019-04-27T23:07:29.224Z' - type: RoutesReady status: 'True' lastTransitionTime: '2019-04-27T23:07:31.472Z' domain: https://test-url.a.run.app observedGeneration: 1 traffic: - revisionName: cloudrun-00001 percent: 100 latestReadyRevisionName: cloudrun-00001 latestCreatedRevisionName: cloudrun-00001 address: hostname: https://test-url.a.run.app url: https://test-url.a.run.app url: https://test-url.a.run.app |
Example Program to List Google Cloud Run Locations
Note: I will be publishing my sample programs to GitHub.
Google Cloud Run supports deploying services to multiple regions. Cloud Run supports the following regions:
- us-central1
- us-east1
- europe-west1
- asia-northeast1
The following program will list the locations that you can deploy services.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
package main import ( "context" "fmt" "google.golang.org/api/option" "google.golang.org/api/run/v1alpha1" ) const ( createDefaultClientFlag = false scopes = run.CloudPlatformScope projectId = "Enter Your Project ID" sa_file = "Enter the full path to your service account JSON key file" ) func createDefaultClient(ctx context.Context) (*run.APIService, error) { return run.NewService(ctx) } func createScopedClient(ctx context.Context) (*run.APIService, error) { return run.NewService(ctx, option.WithCredentialsFile(sa_file), option.WithScopes(scopes)) } func main() { // https://godoc.org/google.golang.org/api/run/v1beta1#NewService var err error = nil var runService *run.APIService = nil ctx := context.Background() if createDefaultClientFlag == true { runService, err = createDefaultClient(ctx) } else { runService, err = createScopedClient(ctx) } if err != nil { fmt.Println("Error:", err) return } // https://godoc.org/google.golang.org/api/run/v1beta1#ProjectsService projects := runService.Projects // https://godoc.org/google.golang.org/api/run/v1beta1#ProjectsService locations := projects.Locations parent := "projects/" + projectId // https://godoc.org/google.golang.org/api/run/v1beta1#ProjectsLocationsService.List call := locations.List(parent) // https://godoc.org/google.golang.org/api/run/v1beta1#ProjectsLocationsListCall.Pages err = call.Pages(ctx, display) if err != nil { fmt.Println(err) return } } func display(response *run.ListLocationsResponse) error { for _, location := range response.Locations { // https://godoc.org/google.golang.org/api/run/v1beta1#Location // debugPrintLocation(location) fmt.Printf("%-20s %s\n", location.LocationId, location.Name) } return nil } func debugPrintLocation(location *run.Location) { fmt.Println("************************************************************") fmt.Println("DisplayName:", location.DisplayName) fmt.Println("Labels:", location.Labels) fmt.Println("LocationId:", location.LocationId) fmt.Println("Metadata:", location.Metadata) fmt.Println("Name:", location.Name) fmt.Println("ForceSendFields:", location.ForceSendFields) fmt.Println("NullFields:", location.NullFields) fmt.Println("************************************************************") } |
The output from the program:
1 2 3 4 |
us-central1 projects/my-project/locations/us-central1 us-east1 projects/my-project/locations/us-east1 europe-west1 projects/my-project/locations/europe-west1 asia-northeast1 projects/my-project/locations/asia-northeast1 |
Example Program to List Google Cloud Run Services
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
package main import ( "context" "fmt" "sort" "google.golang.org/api/option" "google.golang.org/api/run/v1alpha1" ) const ( createDefaultClientFlag = false scopes = run.CloudPlatformScope projectId = "Enter Your Project ID" sa_file = "Enter the full path to your service account JSON key file" // List the Cloud Run services in this location locationsId = "us-central1" ) func createDefaultClient(ctx context.Context) (*run.APIService, error) { return run.NewService(ctx) } func createScopedClient(ctx context.Context) (*run.APIService, error) { return run.NewService(ctx, option.WithCredentialsFile(sa_file), option.WithScopes(scopes)) } func main() { // https://godoc.org/google.golang.org/api/run/v1#NewService var err error = nil var runService *run.APIService = nil ctx := context.Background() if createDefaultClientFlag == true { runService, err = createDefaultClient(ctx) } else { runService, err = createScopedClient(ctx) } if err != nil { fmt.Println("Error:", err) return } // https://godoc.org/google.golang.org/api/run/v1#ProjectsService projects := runService.Projects // https://godoc.org/google.golang.org/api/run/v1#ProjectsService services := projects.Locations.Services // parent := "projects/" + projectId + "/locations/" + locationsId + "/services" parent := "projects/" + projectId + "/locations/" + locationsId // https://godoc.org/google.golang.org/api/run/v1#ProjectsLocationsService.Get call := services.List(parent) response, err := call.Do() if err != nil { fmt.Println(err) return } names := []string{} for _, item := range response.Items { names = append(names, item.Metadata.Name) } sort.Strings(names) for _, name := range names { fmt.Println(name) } } |
I will go much deeper into Google Cloud Run in the next parts to this article series.
Credits
I write free articles about technology. Recently, I learned about Pexels.com which provides free images. The image in this article is courtesy of Darrell Gough at Pexels.
I design software for enterprise-class systems and data centers. My background is 30+ years in storage (SCSI, FC, iSCSI, disk arrays, imaging) virtualization. 20+ years in identity, security, and forensics.
For the past 14+ years, I have been working in the cloud (AWS, Azure, Google, Alibaba, IBM, Oracle) designing hybrid and multi-cloud software solutions. I am an MVP/GDE with several.
Leave a Reply