- Published on
Getting started with Cloud Deploy and Cloud Run? Here's how, even if Kubernetes isn't your thing
- Authors
- Name
- Rami Rashid
- @KingRomstar
Google Cloud Run was built as a simpler Container as a Service alternative to Kubernetes. Google Cloud Run enables developers to quickly and easily stand up scalable serverless applications with few drawbacks. Google Cloud Run takes advantage of Skaffold configuration YAML files which are the same config files used by Kubernetes and this enables a smooth transition from Kubernetes to Cloud Run.
Today we're going to cover how to easily integrate Google Cloud Run with Google Cloud Deploy using Skaffold files for non-Kubernetes developers, myself included!
This article assumes you know the basics of Cloud Engineering and Application Development.
Cloud Deploy + Cloud Run = 🔥
- Setting up A Cloud Run Service
- Using Secrets
- Gotcha Skaffold values
- Setting up Cloud Build
- Cloud Deploy For The Win
Setting up A Cloud Run Service
Unless you're an expert with Skaffold then you probably won't know what properties exist in a Skaffold file or how to customize them. The goal here is to avoid spinning our wheels while attempting to integrate these two amazing services as seasoned application developers. And if you've never used Google Cloud Run then you won't know how to set up a basic service, let's start there.
We're going to start out by utilizing Google Cloud's console to set up a new Cloud Run service and from there we'll let Google Cloud Run do the heavy lifting for us so we end up with a mostly complete Skaffold YAML config file for our service.
- Navigate to Google Cloud Run and click on the
Create Service
button. You'll be presented with a screen that looks like this:
- Click on the
Select
button for theHello World
container image. This will take you to the next screen where you'll be able to configure your service. You'll be presented with a screen that looks like this:
- Modify the authentication setting by selecting
Allow unauthenticated invocations
so that we can easily try out our service. Scroll down to the bottom of the page and click on theCreate
button. Your page should look similar to this:
- Once the service is up and running we're presented with the
Service Details
page. On this page we'll want to click on the YAML tab on the far right:
- Once on the YAML tab for the service you can simply click the edit button and copy the YAML file to your clipboard. There are a few values that you will need to remove and the gcloud cli command automatically does this for us but if you prefer to use the console then below you'll find the values. Removing all of these lines isn't required but some of them are and the rest will be auto populated during deployment so you don't need to worry about them.
The YAML file will look similar to this:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
namespace: '151830072274'
- selfLink: /apis/serving.knative.dev/v1/namespaces/151830072274/services/hello
- uid: 41da3741-407c-4115-8126-26d00a085bdf
- resourceVersion: AAYHmfItR+A
- generation: 2
- creationTimestamp: '2023-10-12T21:48:06.103866Z'
labels:
cloud.googleapis.com/location: us-central1
annotations:
- run.googleapis.com/client-name: cloud-console
- serving.knative.dev/creator: test@goidealsoftware.com
- serving.knative.dev/lastModifier: test@goidealsoftware.com
- run.googleapis.com/operation-id: 098192r5-8d76-4d77-b42f-6so97c07d480
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
spec:
template:
metadata:
labels:
client.knative.dev/nonce: 5a07530d-6280-484d-987f-1f5cfd79b696
run.googleapis.com/startupProbeType: Default
annotations:
run.googleapis.com/client-name: cloud-console
autoscaling.knative.dev/maxScale: '100'
run.googleapis.com/startup-cpu-boost: 'true'
spec:
containerConcurrency: 80
timeoutSeconds: 300
serviceAccountName: 151830072274-compute@developer.gserviceaccount.com
containers:
- image: us-docker.pkg.dev/cloudrun/container/hello
ports:
- name: http1
containerPort: 8080
resources:
limits:
cpu: 1000m
memory: 512Mi
startupProbe:
timeoutSeconds: 240
periodSeconds: 240
failureThreshold: 1
tcpSocket:
port: 8080
traffic:
- percent: 100
latestRevision: true
Accessing Secrets
- Once the service is created we can now run this gcloud command to get a copy of the Skaffold YAML file that was created for us. If you manually copied and pasted the YAML above then you can skip this step as it is redundant. Doing this will save us a lot of time and effort. The command below will create a file called
service.yaml
in the current directory. This file will contain all the configuration settings for our service. We'll use this file as our base Skaffold YAML file.
gcloud run services describe hello --format export --region us-central1 --project atribusi > hello-service.yaml
This will create a file called hello-service.yaml
in the current directory. This file will contain all the configuration settings for our service. We'll use this file as our base Skaffold YAML file.
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
annotations:
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
labels:
cloud.googleapis.com/location: us-central1
name: hello
namespace: '151830072274'
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: '100'
run.googleapis.com/client-name: cloud-console
run.googleapis.com/startup-cpu-boost: 'true'
labels:
run.googleapis.com/startupProbeType: Default
spec:
containerConcurrency: 80
containers:
- image: us-docker.pkg.dev/cloudrun/container/hello
ports:
- containerPort: 8080
name: http1
resources:
limits:
cpu: 1000m
memory: 512Mi
startupProbe:
failureThreshold: 1
periodSeconds: 240
tcpSocket:
port: 8080
timeoutSeconds: 240
serviceAccountName: 151837495827-compute@developer.gserviceaccount.com
timeoutSeconds: 300
traffic:
- latestRevision: true
percent: 100
- The file above does not contain secrets or environment variables so lets go ahead and add each of those respectively. The below version contains both an environment variable and a secret. The secret is a reference to a
Secret Manager
key in Google Cloud and it is exposed as an environment variable. The added code is highlighted in pink.
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
annotations:
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
labels:
cloud.googleapis.com/location: us-central1
name: hello
namespace: '151830072274'
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: '100'
run.googleapis.com/client-name: cloud-console
run.googleapis.com/startup-cpu-boost: 'true'
labels:
run.googleapis.com/startupProbeType: Default
spec:
containerConcurrency: 80
containers:
- image: us-docker.pkg.dev/cloudrun/container/hello
ports:
- containerPort: 8080
name: http1
env:
- name: ANIMAL_TYPE_BASIC_ENV_VAR
value: horse
- name: SUPER_DUPER_SECRET_AS_ENV_VAR
valueFrom:
secretKeyRef:
key: latest
name: SUPER_DUPER_SECRET_KEY_IN_SECRET_MANAGER
resources:
limits:
cpu: 1000m
memory: 512Mi
startupProbe:
failureThreshold: 1
periodSeconds: 240
tcpSocket:
port: 8080
timeoutSeconds: 240
serviceAccountName: 151830072274-compute@developer.gserviceaccount.com
timeoutSeconds: 300
traffic:
- latestRevision: true
percent: 100
Gotcha Skaffold Values
Because we aren't building our YAML files from scratch Google Cloud Run automatically adds some values to our YAML file that we don't need and will in fact break our deployments. We'll need to remove these values.
Under the spec in the service yaml definition it must have a "name"
attribute which operates as the revision name. The value must be unique and it must start with the name of the service followed by any unique characters. Google will automatically generate this value for us and that is the best approach in my opinion. Why deal with the hassle when there is very little upside!
If we don't update the name during deployment then our previous revision will be redeployed and that is obviously not what we want!
I personally ran into this issue a couple of times while building Atribusi and it was a pain to debug. Luckily the logs were helpful but they were located under the Cloud Run service logs not the Cloud Deploy logs so it took a few attempts for me to realize this.
The value is highlighted in pink below.
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
annotations:
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
labels:
cloud.googleapis.com/location: us-central1
- name: hello
namespace: '151830072274'
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: '100'
run.googleapis.com/client-name: cloud-console
run.googleapis.com/startup-cpu-boost: 'true'
labels:
run.googleapis.com/startupProbeType: Default
spec:
containerConcurrency: 80
containers:
- image: us-docker.pkg.dev/cloudrun/container/hello
ports:
- containerPort: 8080
name: http1
env:
- name: ANIMAL_TYPE_BASIC_ENV_VAR
value: horse
- name: SUPER_DUPER_SECRET_AS_ENV_VAR
valueFrom:
secretKeyRef:
key: latest
name: SUPER_DUPER_SECRET_KEY_IN_SECRET_MANAGER
resources:
limits:
cpu: 1000m
memory: 512Mi
startupProbe:
failureThreshold: 1
periodSeconds: 240
tcpSocket:
port: 8080
timeoutSeconds: 240
serviceAccountName: 151830072274-compute@developer.gserviceaccount.com
timeoutSeconds: 300
traffic:
- latestRevision: true
percent: 100
Setting Up Cloud build
What good is a Skaffold YAML file if we can't use it? We'll need to set up a Cloud Build pipeline to deploy our service. We'll need to create a cloudbuild.yaml
file in the root of our project.
This file will be used to build our container image and deploy it to Cloud Run. We'll also use this file to set up our Cloud Deploy pipeline. If you need help with the below cloudbuild.yaml file then check out this article.
steps:
- name: 'gcr.io/cloud-builders/git'
secretEnv: ['SSH_KEY']
entrypoint: 'bash'
args:
- -c
- |
echo "$$SSH_KEY" >> /root/.ssh/id_rsa
chmod 400 /root/.ssh/id_rsa
cp known_hosts.github /root/.ssh/known_hosts
volumes:
- name: 'ssh'
path: /root/.ssh
- name: gcr.io/cloud-builders/git
id: 'fetch-git-repo'
entrypoint: bash
args:
- -c
- |
git remote set-url origin 'git@github.com:Go-Ideal-Software-LLC/atribusi'
git fetch --unshallow
volumes:
- name: 'ssh'
path: /root/.ssh
- name: node:18
id: 'npm-install'
entrypoint: npm
args: ['install']
- name: 'gcr.io/cloud-builders/docker'
id: 'build-hello-docker-image'
args: ['build', '-t', 'us-central1-docker.pkg.dev/atribusi/atribusi/hello:$BUILD_ID', '-f', 'Dockerfile.hello', '.']
- name: 'gcr.io/cloud-builders/docker'
id: 'push-hello-docker-image'
args: ['push', 'us-central1-docker.pkg.dev/atribusi/atribusi/hello:$BUILD_ID']
waitFor: ['build-hello-docker-image']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
id: "deploy-hello-to-dev"
entrypoint: 'bash'
args:
- '-c'
- |
gcloud deploy releases create release-$(date '+%Y%m%d-%H%M%S') --skaffold-file skaffold.hello.yaml --delivery-pipeline hello-deployment-pipeline --region us-central1 --images hello_app=us-central1-docker.pkg.dev/atribusi/atribusi/hello:$BUILD_ID
waitFor: ['push-hello-docker-image']
availableSecrets:
secretManager:
- versionName: projects/796283614197/secrets/Github-clone-build-blog/versions/latest
env: 'SSH_KEY'
options:
logging: CLOUD_LOGGING_ONLY
pool:
name: 'projects/go-ideal-software-websites/locations/us-central1/workerPools/go-ideal-software-websites-pp'
Cloud Deploy For The Win
For Cloud Deploy to work properly we must setup a YAML file for our service and this YAML file will reference the service's YAML that we defined above. This works because we will have different service yaml files for each environment. In our contrived example we only have one environment for our hello app but in an enterprise environment you'd likely have at least two, one for dev and one for prod.
If you notice I highlighted line 9 because that is the actual Google Cloud Run service YAML file that we created above. We'll need to create a new file called skaffold.hello.yaml
and add the contents below to it. This file is referenced in the cloudbuild.yaml
on line 42. when pushing the latest build to our lowest environment.
apiVersion: skaffold/v4beta6
kind: Config
metadata:
name: hello
profiles:
- name: dev
manifests:
rawYaml:
- hello-service.yaml
deploy:
cloudrun: {}
Now that we have the YAML file setup for our Cloud Run service we will need to manually setup the deployment pipeline and this only ever needs to occur once so it is best to do it manually IMO. Create the below file and run the shell command below the file to create the pipeline.
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
name: hello-deployment-pipeline
description: hello deployment pipeline
serialPipeline:
stages:
- targetId: hello-dev
profiles: [dev]
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: hello
description: Hello World
run:
location: projects/atribusi/locations/us-central1
executionConfigs:
- defaultPool:
serviceAccount: deployer@atribusi.iam.gserviceaccount.com
usages:
- RENDER
- DEPLOY
gcloud deploy apply --file=hello-delivery-pipeline.yaml --region=us-central1 --project=atribusi
After running the above command we can navigate over to Cloud Deploy to see our newly created pipeline. With the pipeline created we can now use our mouse and keyboard to create new deployments to higher environments on the fly!
The above flow assumes you've set up your IAM policies and service accounts correctly and if you haven't check out reference #2 below.
That is all you need to do to get Cloud Deploy and Cloud Run working together. If you have any questions or comments please feel free to reach out to me on Twitter or LinkedIn.
References: