System Architect

Product Manager

Software Engineer

Deploying containers on Cloud Run using YAML manifests

When you are working with an architecture that includes Kubernetes clusters you might still find useful to use Cloud Run for certain applications, like a web frontend, API gateway and more. At the same time, you may find confusing how to integrate it seamlessly in your CI/CD workflow.

gcloud CLI can deploy Cloud Run service, but it doesn’t have all possible deployment options, and in this case you can’t store configurations in a repository like a pre-configured manifest, or use the same Config Map with environment variables for both Kubernetes and Cloud Run applications. Luckily, it’s possible to deploy Cloud Run applications in a similar way as Kubernetes and I’m going to show how to do it.

Creating Cloud Run service

Cloud Run uses knative and you can find there a description for a base manifest structure. At the same time, I wasn’t be able to find a complete manifest reference for Cloud Run. But it isn’t a problem, because we can easily get a manifest preset from a Cloud Run service created with Console (a web interface):

  1. Go to Cloud Run Console
  2. Click “Create Service”.
  3. Set your preferable settings.
  4. Click “Create”.

Or from Cloud Run service created by gcloud CLI:

gcloud run deploy

Exporting Cloud Run YAML manifest

Our Cloud Run application is deployed, now we need to obtain YAML manifest. An easy way to do it by using gcloud CLI:

gcloud run services describe <cloudrun-service-name> --format=export > cloudrun-preset.yaml

This is how your exported Cloud Run YAML may look like:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
    client.knative.dev/user-image: <region>-docker.pkg.dev/<project-name>/<container-name>@sha256:<hash>
    run.googleapis.com/ingress: all
    run.googleapis.com/ingress-status: all
  labels:
    cloud.googleapis.com/location: <region>
  name: <app-name>
  namespace: '<namespace>'
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '3'
        run.googleapis.com/client-name: cloud-console
        run.googleapis.com/cpu-throttling: 'true'
        run.googleapis.com/execution-environment: gen1
        run.googleapis.com/startup-cpu-boost: 'true'
      name: <app-name>-<revision-code>
    spec:
      containerConcurrency: 80
      containers:
      - env:
        - name: LISTEN_ADDR
          value: 0.0.0.0
        - name: LISTEN_PORT
          value: '80'
        - name: DB_HOST
          value: 198.51.100.1
        - name: DB_PORT
          value: '5432'
        - name: DB_NAME
          value: db_default
        - name: DB_SECRET
          value: default-user
        image: <region>-docker.pkg.dev/<project-name>/<container-name>@sha256:<hash>
        ports:
        - containerPort: 80
          name: http1
        resources:
          limits:
            cpu: 1000m
            memory: 512Mi
      serviceAccountName: compute@developer.gserviceaccount.com
      timeoutSeconds: 300
  traffic:
  - latestRevision: true
    percent: 100

Now we can adjust the preset by changing or removing certain fields and values. Then, the manifest can be committed to a git repository or put to any other place where you store your Kubernetes YAML manifests.

Deploying Cloud Run using YAML manifest

One of the ways to deploy Cloud Run instance from a manifest preset is by using yq and gcloud. Let’s say we have configmap.yaml with environment variables:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  LISTEN_ADDR: 0.0.0.0
  LISTEN_PORT: '80'
  DB_HOST: 198.51.100.1
  DB_PORT: '5432'
  DB_NAME: db_default
  DB_SECRET: default-user

A new container that we want to deploy is ${_DOCKER_IMAGE_URL} with a digest ${_DOCKER_IMAGE_DIGEST}

The Bash deployment code may look like:

yq e ".spec.template.spec.containers[0].env = $(yq e -o=j -I=0 '.data | \
to_entries | (.[] |= with_entries( (select(.key=="key") | .key) = "name" \
) )' configmap.yaml)" cloudrun-preset.yaml > deployment.yaml;

artifact="${_DOCKER_IMAGE_URL}${_DOCKER_IMAGE_DIGEST}";

artifact=$artifact yq e -i \
'.metadata.annotations."client.knative.dev/user-image"=strenv(artifact)' \
deployment.yaml;

artifact=$artifact yq e -i \
'.spec.template.spec.containers[0].image=strenv(artifact)' \
deployment.yaml;

gcloud run services replace deployment.yaml;

Optionally, after running this script you can check if the deployment was successful, then you can commit deployment.yaml to the repository where successful deployments are stored.

Now you can deploy the same application to Kubernetes and Cloud Run seamlessly inside the single CI/CD workflow.