Continuous deployment to Cloud Run works out-of-the-box when you own the application code. However, you don’t always own the application code. You might use suppliers that publish container images to your Artifact Registry. Therefore, this blog shows how to configure a continuous deployment pipeline for container images.
Target implementation
Suppliers publish container images to trigger application deployments. To track changes, applications are deployed using Terraform tracked by a Git repository. The process is shown below:
Get the example code on GitHub.
Artifact Registry notifications
Artifact Registry (and Container Registry) publish notifications to the gcr Pub/Sub topic. These notifications include the action
(insert/delete) and the digest
/tag
of the container image. We are interested in the insert-events and the image digests to update the application infrastructure.
Cloud Build triggers
Pub/Sub easily integrates with Cloud Build. Simply configuring a trigger for Pub/Sub events suffices. The next trigger is configured to act on Artifact Registry insert
-events for specific application images:
resource "google_cloudbuild_trigger" "gitops_application_update" {
...
pubsub_config {
topic = google_pubsub_topic.gcr.id
}
substitutions = {
_ACTION = "$(body.message.data.action)"
_IMAGE_DIGEST = "$(body.message.data.digest)"
}
filter = "_ACTION.matches('INSERT') && _IMAGE_DIGEST.startsWith('xxx-docker.pkg.dev/my-project/my-repo/my-app@')"
}
Application deployment
The application (and associated infrastructure) is deployed using Terraform. The Terraform configuration is tracked by Git to ensure that the Terraform state doesn’t drift. To keep the Terraform configuration up to date, the deployment pipeline commits the new application version. To keep the application infrastructure up to date, the deployment pipeline applies the Terraform configuration.
resource "google_cloudbuild_trigger" "gitops_application_update" {
...
build {
source {
repo_source {
project_id = var.project_id
repo_name = google_sourcerepo_repository.gitops.name
branch_name = "main"
}
}
step {
name = "gcr.io/cloud-builders/git"
entrypoint = "bash"
args = [
"-c",
<<-EOT
...
echo "${each.value.variable_name} = \"$${_IMAGE_DIGEST}\"" > variables.${each.key}.auto.tfvars
git add variables.${each.key}.auto.tfvars
git commit -m "Set ${each.key} version to $${_IMAGE_DIGEST}"
git push
EOT
]
}
step {
name = "hashicorp/terraform:1.2.9"
args = ["init", "-no-color"]
}
step {
name = "hashicorp/terraform:1.2.9"
args = ["apply", "-no-color", "-lock-timeout=300s", "-auto-approve"]
}
}
Discussion
While continuous deployment is great, checks and balances are necessary. This example blindly releases the application to production. In reality, this will break your production environment. To comfortably release to production, extend the deployment pipeline with quality gates that test the container image.
This example uses Cloud Build to automate the deployment process. The demonstrated process has two outputs: updated GitOps repository and updated application infrastructure. For production use case, it would make sense to split the two outputs to separate processes. To govern the process, the process could be implemented using Cloud Workflows.
Conclusion
Continuous deployment for container images is enabled by Artifact Registry notifications. By subscribing Cloud Build triggers to these notifications, you release application updates automatically. While this blog uses a Terraform-based GitOps deployment, you can implement any deployment process with Cloud Build.