Sunday, June 23, 2024
No menu items!
HomeCloud ComputingDeploy to Cloud Run with GitHub Actions

Deploy to Cloud Run with GitHub Actions

Google Cloud customers rely on a mixture of Google Cloud services and third party software. In this blog, we discuss how to deploy Google Cloud Run from GitHub Actions using code from example workflows built by Google. We focus on deploying Cloud Run with a declarative service YAML to multiple environments.

Guidance on how to build images and deploy Google Cloud Run with buildpacks, from source, and from Docker can be found in this Google example workflows repository.The example in this blog will show a more detailed view of how to use one of these workflows while also being set up to deploy to multiple projects. This blog is intended for users who are not using Cloud Deploy, if you are using Cloud Deploy it is recommended that you see the following blog on how to use GitHub Actions with Cloud Deploy.

Typical workflow

A typical GitHub Actions workflow uses the following steps to deploy to Cloud Run.

Continuous Integration

Artifact build stage: use language-specific tooling such as maven, gradle, sbt, npm to build an application artifactPackaging & Containerization stage: collect all of the necessary components and dependencies for the application artifact, containerize and push that image to an existing Artifact Registry (Docker, Buildpacks)

Continuous Deployment

Release approval: Request and obtain approval to release new container artifact to the existing target environment. Use reusable deployment workflows, along with GitHub environments to manage the release process. Third party tools can be used for release management as well. Google Cloud Deploy handles releases seamlessly, although we have found that some customers want to use different tools for such deployments.Deploy stage: Deploy the new code from the release to the target environmentRollout stages: progress through a series of Cloud Run target environments with gradual rollouts or canary deployments

Prerequisites

Workload Identity Federation must be set up with the pool and connected to the service account saved as a GitHub secret. A workload identity federation pool and connected service account are needed to authenticate to Google Cloud. Please see this blog for more information on setting this up.The service account has required roles to be able to push an image, impersonate the Cloud Run service account and deploy Cloud Run. The following roles are required.roles/artifactregistry.writerroles/iam.serviceAccountUserroles/run.adminroles/iam.workloadIdentityUser

GitHub Actions workflows

Caller workflow for environment based deployments

The workflow below is designed to be a caller workflow that reuses a base workflow; allowing different variables per environment. This can be used to deploy to separate environments even if the environments are split between Google Cloud projects.

This workflow builds a container image, pushes it to Artifact Registry, and deploys it to Cloud Run. This workflow is triggered on a push to the repository then deploys to the develop, stage, and main environments located in separate Google Cloud projects. Other triggers can be used, such as on a schedule or on a manual trigger. You can also define your variables using environments.

Here is a more detailed breakdown of the workflow:

On push to develop, stage, or main, the workflow is triggeredBuilds the container image using a DockerfilePushes the container image to Artifact RegistryDeploys the container image to Cloud Run in the corresponding Google Cloud project

Optional steps:

Conduct code scanning and unit testing of code on pull requestsDefine variables using environmentsTrigger on a schedule or manually

The caller workflow can help with deployments to multiple environments. Here, the user can define different attributes per environment which will use the same base workflow to deploy. The caller workflow calls the workflow that is referenced within the uses block.

code_block<ListValue: [StructValue([(‘code’, “on:rn push:rn branches: [develop, stage, main]rnrnjobs: rn dev:rn name: deploy cloudrun to dev environmentrn if: ${{ github.ref == ‘refs/heads/develop’ || github.base_ref == ‘develop’ }}rn uses: ./.github/workflows/_deployment.yamlrn permissions: rn id-token: writern contents: readrn with: rn environment: devrn ref: ${{ github.sha }}rn secrets: inheritrn stage:rn if: ${{ github.ref == ‘refs/heads/stage’ || github.base_ref == ‘stage’ }}rn uses: ./.github/workflows/_deployment.yamlrn permissions: rn id-token: writern contents: readrn with: rn environment: stagern ref: ${{ github.sha }}rn secrets: inheritrn prod: rn if: ${{ github.ref == ‘refs/heads/main’ || github.base_ref == ‘main’ }}rn uses: ./.github/workflows/_deployment.yamlrn permissions: rn id-token: writern contents: readrn with:rn environment: prodrn ref: ${{ github.sha }}rn secrets: inherit”), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c066486d0>)])]>

Reusable workflow

The reusable workflow defines inputs for the caller workflow. This allows the user to pass variables to the reusable workflow. The workflow_call attribute allows this workflow to be called from another workflow.

code_block<ListValue: [StructValue([(‘code’, ‘on:rn workflow_call:rn inputs:rn environment:rn type: stringrn required: truern description: Name of the target environment. rn ref: rn type: stringrn required: truern description: The tag or SHA to checkout.’), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c06648460>)])]>

This workflow is also calling environment based secrets and variables. In this example, the following variables are defined.

Continuous Integration

Artifact build and packaging stages

The following workflow will show how to authenticate to Google Cloud and push an image to Artifact Registry before the artifact build & packing stages.

The first step of the workflow is to use the checkout and auth actions. These steps checkout the repository and authenticate to Google Cloud. Workload Identity Federation is used to utilize Open Authorization (OAuth) for adhering to zero trust.

code_block<ListValue: [StructValue([(‘code’, “steps:rn – name: Checkoutrn uses: actions/checkout@v3rn – name: Google Authrn id: authrn uses: ‘google-github-actions/auth@v0’rn with:rn token_format: ‘access_token’rn workload_identity_provider: ‘${{ secrets.WIF_PROVIDER }}’rn service_account: ‘${{ secrets.WIF_SERVICE_ACCOUNT }}'”), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c06648a90>)])]>

Authenticate to Docker using the OAuth token that was generated in the previous step.

code_block<ListValue: [StructValue([(‘code’, “- name: Docker Authrn id: docker-authrn uses: ‘docker/login-action@v1’rn with:rn username: ‘oauth2accesstoken’rn password: ‘${{ steps.auth.outputs.access_token }}’rn registry: ‘${{ vars.REGION }}-docker.pkg.dev'”), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c06648040>)])]>

In the case of Docker builds, the Dockerfile will build the artifact then package that artifact into an image. A sample Dockerfile for packaging a Java application into a container image can be found in the Google Cloud Platform’s Java code samples.

The following workflow step will build the container image and tag it with the GitHub reference commit hash. This is to keep track of the latest image while also keeping commit history and images aligned. It is important to note the context attribute in this step tells the action where to look for the source code relative to the current working directory. Please see this repository for more information on the docker build push action.

code_block<ListValue: [StructValue([(‘code’, ‘- name: Build, tag and push containerrn id: build-imagern uses: docker/build-push-action@v3rn with:rn context: ${{ vars.code_directory }}rn push: true rn tags: |rn ${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}’), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c066480d0>)])]>

Continuous Deployment

Release approval stage

In any application deployment, it is common to have controls on who can deploy to production and when. There are many different tools to handle change management. We will show how to do so with GitHub environments. Below we have defined three environments for our Cloud Run deployment.

There are many branch protection rules, available to enforce deployment standards on each environment. It is helpful to integrate branch protection rules to manage releases and prevent accidental deployments to your production environment. Below, we have defined a rule to require approvals before deployment.

Deploy stage

The next stage after approval is the stage to deploy to Cloud Run. This step will export the environment variables so they are defined locally within the workflow. The envsubst command will tokenize and replace the values in your service template YAML file. You can see a sample service template YAML in the Google example workflows repository. For a full list of attributes for the service YAML, please see the Google Cloud Run YAML Reference.

code_block<ListValue: [StructValue([(‘code’, ‘- name: Create Service declaration rn run: |-rn export CONTAINER_IMAGE=”${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}”rn export SERVICE_NAME=”${{ vars.SERVICE_NAME }}”rn export PROJECT_ID=”${{ vars.GCP_PROJECT_ID }}”rn export REVISION_TAG=”${{ inputs.ref }}”rn export CLOUD_RUN_SA=”${{ vars.CLOUD_RUN_SA }}”rn export ENVIRONMENT=”${{ inputs.environment }}”rn envsubst < ./service-yaml/container.yaml > container-${{ inputs.environment }}.yamlrn cat container-${{ inputs.environment }}.yaml’), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c06648640>)])]>

The next step uses Google’s Cloud Run GitHub Action to perform the Cloud Run deployment. Tokenizing the file name ensures the developer can split the traffic in production environments while it is not needed in lower environments.

code_block<ListValue: [StructValue([(‘code’, ‘- name: Deploy to Cloud Runrn id: deployrn uses: google-github-actions/deploy-cloudrun@v0rn with:rn service: ${{ vars.service_name }}rn region: ${{ vars.region }}rn metadata: container-${{ inputs.environment }}.yaml’), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3e2c066487c0>)])]>

Gradual rollout stage

Gradual rollouts can be utilized to partially deploy your new release to the target environment. To do this, you can split the traffic within your service YAML file. Traffic can also be changed in the console, please see the documentation on rollbacks, gradual rollouts and traffic migration in Google Cloud for more information on managing traffic.

You have now deployed a Cloud Run instance using GitHub Actions! You can find the Cloud Run GitHub Action with documentation here, and other GitHub Actions examples here. Don’t wait, test this deployment today!

Cloud BlogRead More

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments