This article aims to demystify Cloud Deploy by implementing a practical deployment pipeline for Cloud Run. Since Cloud Deploy was initially released for Kubernetes, learning it solely for Cloud Run can feel overwhelming. This guide focuses on the essentials for building and operating a production-ready Cloud Run deployment pipeline, covering:
Advanced features like automation, deployment hooks, and canary deployments can be readily applied once you grasp these fundamentals, so they won’t be covered here.
This article is geared towards those already using Cloud Run but haven’t adopted Cloud Deploy. While Kubernetes knowledge isn’t mandatory, the following is assumed:
Just a heads up, the recommendations and best practices shared here are based on my own experiences and thoughts through actual deployment adventures. They’re not the official word from Google Cloud. Also, I’ve simplified some explanations to keep things clear and digestible.
Cloud Deploy is a fully managed service for managing and automating deployment pipelines across different environments, such as “development → staging → production”. These pipelines are called Delivery Pipelines in Cloud Deploy terminology.
For a deeper understanding of Cloud Deploy’s capabilities and benefits, check out this video:
Cloud Deploy enables you to implement various Delivery Pipelines, including:
This simple pipeline deploys a single Cloud Run service app sequentially through dev, stg, and prd environments.
This pipeline deploys multiple Cloud Run service apps to each environment sequentially, but deploys multiple apps simultaneously within each environment. For example, you can deploy the same app to multiple regions at once using a single Delivery Pipeline.
Cloud Deploy also supports canary deployments.
To build Delivery Pipelines effectively, it’s crucial to understand Cloud Deploy’s architecture and deployment process.
First, let’s address Skaffold, which often confuses Cloud Deploy newcomers.
Cloud Deploy leverages Skaffold, an open-source tool, for deployments. While Skaffold is a powerful Kubernetes development tool with a steep learning curve, for Cloud Run deployments, you can initially understand it as follows:
Skaffold, guided by the skaffold.yaml
configuration file, performs these tasks:
manifest.yaml
) based on the built imagemanifest.yaml
to the Cloud Run serviceA Cloud Deploy Delivery Pipeline consists of a Delivery Pipeline and multiple associated Targets.
The Delivery Pipeline is the core resource, defining what and in what order things are deployed. The order is defined using the concept of Stages. For example, if you have dev, stg, and prd environments, you define a stage for each. Each Stage is then associated with one or more Targets.
A Target represents the deployment destination. For Cloud Run, it defines the project and location where the service is deployed.
Here’s how Delivery Pipeline and Target are configured in Terraform:
A typical Cloud Deploy deployment involves these main steps:
Cloud Deploy focuses solely on deployment, so tasks like building the container image need to be handled beforehand. Specifically, you need to run the skaffold build
command.
This command instructs Skaffold to build and push the container image according to the skaffold.yaml
configuration, and outputs the results to artifacts.json
:
The artifacts.json
file contains information about the built image. Cloud Deploy treats this information as a deployment unit, ensuring the same artifact is deployed to each environment.
Deployments in Cloud Deploy start by creating a Release resource. This Release associates the pre-built container image with the Delivery Pipeline, enabling its deployment to the Cloud Run service. Besides the image information, the Release also stores the Cloud Storage URI containing the source (skaffold.yaml
and templates of manifest.yaml
) for rendering the manifest.yaml
.
Use gcloud to create a Release like this:
Upon creation, the Release also renders the manifest.yaml
for all Targets and stores them in Cloud Storage. This involves triggering Cloud Build for each Target to execute the skaffold render
command.
After creating the Release, you “Promote” it to actually deploy it to the next Stage.
Interestingly, while “promote” is a commonly used term in Cloud Deploy, there’s no API action called “promote”. The actual process involves creating a Rollout resource.
A Rollout links a Release to a Target. When a Rollout is created, Cloud Deploy triggers Cloud Build to deploy the rendered manifest.yaml
to the Target using the skaffold apply
command.
Therefore, to “promote” generally means “to create a Rollout for the target Release, associated with the Target pointed to by the next Stage”. Here’s how to promote using gcloud:
Creating the Rollout for the first Stage often happens simultaneously with Release creation, which is the behavior you see in the console and with gcloud deploy releases create
. This means that for a dev → stg → prd Delivery Pipeline, deploying to the dev environment occurs automatically upon Release creation.
Technically, there are additional concepts like Phases and Jobs between Rollouts and Cloud Build. While not crucial for simple Delivery Pipelines, understanding Phases becomes important for canary deployments, and Jobs come into play when using deployment hooks.
Promotion essentially boils down to creating a Rollout for a specific
Target. Understanding this reveals that deployments don’t necessarily have
to follow the dev → stg → prd sequence. For example, using the
--disable-initial-rollout
flag with gcloud deploy releases create
allows creating a Release without a dev Rollout. Then, you can directly
deploy to the prd environment by using the --to-target
flag with gcloud deploy releases promote
. This can be useful for hotfixes in production.
Here’s a summary of the deployment flow in a dev → stg → prd pipeline using Cloud Deploy:
skaffold build
artifacts.json
file containing build informationgcloud deploy releases create
command or similarmanifest.yaml
for each Target using skaffold render
and stores them in Cloud Storagemanifest.yaml
to the dev environment using skaffold apply
gcloud deploy releases promote
command or similarmanifest.yaml
to the stg environment using skaffold apply
gcloud deploy releases promote
command or similarmanifest.yaml
to the prd environment using skaffold apply
Overall deployment flow
Now, let’s walk through building a dev → stg → prd deployment pipeline for a Cloud Run service app named hello-app
, exploring Cloud Deploy’s mechanics, recommended setup practices, and design considerations.
Note that the Terraform code snippets in this article are abbreviated. Refer to the sample code for complete and functional examples.
In Google Cloud, it’s best practice to separate projects for different environments. So, where should deployment resources like Delivery Pipelines reside? It’s recommended to create a dedicated pipeline project separate from the service projects.
Let’s consider the various actors (or Principals in Google Cloud terminology) involved in the Delivery Pipeline and their required permissions.
First, let’s identify the relevant Principals. The diagram below summarizes them:
Here’s a brief description of their roles and responsibilities:
skaffold build
(builds and pushes container image)gcloud deploy releases create
(creates Release)gcloud deploy releases promote
gcloud deploy releases promote
skaffold render
and skaffold apply
The releaser performs the following actions. The required roles or permissions for each action are listed alongside:
skaffold build
roles/artifactregistry.writer
on the Artifact Registry repository)gcloud deploy releases create
clouddeploy.releases.create
on the Delivery Pipeline)
source.tgz
to Cloud Storage (roles/storage.objectCreator
and roles/storage.legacyBucketReader
on the Cloud Storage bucket)skaffold render
for each Target (roles/iam.serviceAccountUser
on the Service Account configured for each Target)roles/clouddeploy.releaser
on the Delivery Pipeline)
skaffold apply
for the dev Target (roles/iam.serviceAccountUser
on the Service Account configured for the dev Target)roles/clouddeploy.viewer
on the pipeline project)Read access to each Target is also required when creating a Rollout. In
this case, we need to grant roles/clouddeploy.viewer
to the project to
retrieve Operations, which grants read access to all Targets, so it’s
omitted here.
Since the releaser’s actions are often automated through GitHub Workflows or Cloud Build triggered by source code changes, these permissions are typically assigned to the corresponding Service Account.
Here’s how to configure the permissions in Terraform when using a Service Account as the releaser:
The Terraform configuration for releaser permissions appears complex, but
it’s necessary to prevent the releaser from deploying to stg and prd
environments. While the roles/clouddeploy.releaser
role allows creating
Releases on the Delivery Pipeline, it also includes permission to create
Rollouts. Assigning it unconditionally to the Delivery Pipeline would
allow deployments to all Targets. Therefore, we create a custom role
specifically for Release creation. Additionally, we configure a condition
to restrict roles/clouddeploy.releaser
to only apply to the dev
environment.
The Service Account attached to Cloud Build instances launched by the Delivery Pipeline depends on the associated Target. For example, here’s the Terraform configuration for setting the Service Account for Cloud Build related to the dev Target:
For each Target, Cloud Build is triggered twice: once during Release creation and again during Rollout creation. Here are the actions performed in each case and the required permissions:
skaffold render
(triggered at Release creation)
source.tgz
from Cloud Storage (roles/storage.objectViewer
on the Cloud Storage bucket)manifest.json
to Cloud Storage (roles/storage.objectCreator
on the Cloud Storage bucket)skaffold apply
(triggered at Rollout creation)
manifest.json
from Cloud Storage (roles/storage.objectViewer
on the Cloud Storage bucket)roles/run.developer
on the Cloud Run service and roles/iam.serviceAccountUser
on the Cloud Run service’s Service Account)Additionally, the basic role roles/logging.logWriter
is required for Cloud Build execution.
Here’s the Terraform configuration for setting the necessary permissions for the Cloud Build Service Account associated with the dev Target (similar configuration applies to stg and prd):
By default, Cloud Deploy uses separate Cloud Storage buckets for the
source.tgz
and manifest.json
files. This necessitates permission
configuration for both buckets. If using the same bucket is acceptable for
your use case, it’s recommended to simplify the setup by doing so.
The stg promoter performs the following actions (same applies to the prd promoter):
gcloud deploy releases promote
roles/clouddeploy.releaser
for the Delivery Pipeline)
skaffold apply
for the stg Target (roles/iam.serviceAccountUser
for the Service Account configured for the stg Target)roles/clouddeploy.viewer
for the pipeline project)Promoters are typically SREs or automated systems triggered by events or individuals.
Here’s the Terraform configuration for setting permissions when implementing the stg promoter as a Service Account (similar configuration applies to the prd promoter):
While many official Cloud Deploy documents and articles suggest defining the Delivery Pipeline in YAML, it’s entirely unnecessary. Manage it using your preferred method, such as Terraform.
In Terraform, use the google_clouddeploy_delivery_pipeline resource like this:
The location
is independent of the deployment target location.
Many documents and samples configure profiles
for each stage, but this is not required. Especially for Cloud Run, omitting them leads to a simpler and clearer configuration.
The manifest.yaml
is the Cloud Run service YAML. While not necessary for basic Cloud Run usage, it’s required for Cloud Deploy.
If you’re already using Cloud Run, you can find this YAML in the console or retrieve it using the following command. Copy it and remove unnecessary elements while referring to the reference.
You only need one manifest.yaml
for your Cloud Run service app (no need for separate ones for dev, stg, and prd). Here’s a basic example:
Notice the comments like # from-param: ${service_account_name}
. These are significant. Parameters set with # from-param:
are called deploy parameters. Since deploy parameters can be configured per Target, they’re useful for values that vary across environments.
For instance, with the above YAML, Cloud Deploy will replace the following values before deploying to each environment:
serviceAccountName
: dummy
→ hello-app@hello-app-dev.iam.gserviceaccount.com
env[0].value
: dummy
→ Hello, dev!
serviceAccountName
: dummy
→ hello-app@hello-app-stg.iam.gserviceaccount.com
env[0].value
: dummy
→ Hello, stg!
serviceAccountName
: dummy
→ hello-app@hello-app-prd.iam.gserviceaccount.com
env[0].value
: dummy
→ Hello, prd!
This article recommends deploy parameters for their simplicity, ease of
learning, and manageability with Terraform. However, if your
manifest.yaml
becomes complex, rendering it with just deploy parameters
might become challenging. In such cases, Cloud Deploy supports Helm,
Kustomize, and kpt, so consider utilizing these rendering
tools
.
Cloud Deploy Targets, like Delivery Pipelines, should be built using Terraform or similar tools. In Terraform, use the google_clouddeploy_target resource.
The location
needs to be the same as the Delivery Pipeline location, not the Cloud Run deployment location.
Deploy parameters discussed in the manifest.yaml
section are configured here.
The skaffold.yaml
file defines the configuration for the skaffold build
, skaffold render
, and skaffold apply
commands mentioned earlier.
skaffold build
skaffold render
manifest.yaml
skaffold apply
Here’s an example skaffold.yaml
with these configurations:
For detailed explanations, refer to the skaffold.yaml reference. Here’s a brief overview of the essential elements:
build.tagPolicy
: Defines how container image tags are named. The example uses envTemplate
, which sets the tag based on the value of the APP_VERSION
environment variable during skaffold build
. Other options include gitCommit
, which automatically sets tags based on Git commits. See the documentation for details.build.artifacts[].image
: The name of the container image. In the manifest.yaml
, any image matching this name will be replaced with the actual built container image.build.artifacts[].context
: The context for running docker build
.build.artifacts[].docker
: Configuration for docker build
.manifests.rawYaml
: Specifies the path to the manifest.yaml
file.It’s convenient to keep skaffold.yaml
and manifest.yaml
together in their own directory. This allows you to run commands like skaffold build
and gcloud deploy releases create
from within that directory for smooth operation.
Cloud Deploy may appear complex at first, but don’t let that deter you! By grasping the concepts and processes outlined in this guide, you’ll be well-equipped to build and manage robust, automated deployment pipelines for your Cloud Run applications. With Cloud Deploy in your arsenal, you can streamline your workflow, accelerate deployments, and focus on what matters most: innovating and delivering exceptional applications. So, embrace the power of Cloud Deploy and take your Cloud Run deployments to the next level! 🚀✨