Create a Cloud Run service with a sidecar

1. Overview

Introduction

In this codelab, you'll learn how to deploy a Cloud Run service that uses multiple containers. You'll create a node.js app that will be used as the Cloud Run ingress container and an additional node.js app that will be used as a sidecar.

Technical overview

When using multiple containers within a Cloud Run instance, one container is used as the main container for web ingress. The one or more additional containers are referred to as sidecars.

There are two ways for multiple containers to communicate among one another:

  1. The containers share the localhost network interface, so all containers can listen to a port, e.g. localhost:port.
  2. You can also use in-memory volumes and mount them on the containers to share files.

Use cases

Since all containers within the Cloud Run instance share the localhost network interface, you can use a sidecar in front of your main container to proxy requests. Such proxies can provide an additional layer of abstraction for a more efficient flow of traffic to the application between client and servers by intercepting requests and forwarding them to the appropriate endpoint. As an example, you can use the official Nginx image from DockerHub (as shown here).

Since multiple containers can communicate by sharing files via shared volumes, you add various sidecar applications to your service. For example, you can instrument your Cloud Run service to use custom agents like OpenTelemetry to export logs, metrics and traces (OpenTelemetry example). Another example is to use a sidecar connect to a Cloud Spanner PostgreSQL database (Cloud Spanner Postgress example).

Examples in this codelab

In this codelab, you'll first deploy a Cloud Run service where its ingress container communicates with a sidecar via a localhost port. Then you'll update the ingress container and the sidecar to share a file via a volume mount.

What you'll learn

  • How to create a container that uses a sidecar
  • How an ingress container can communicate with a sidecar using localhost
  • How an ingress container and a sidecar can both share a file via a mounted volume

2. Setup and Requirements

Prerequisites

Activate Cloud Shell

  1. From the Cloud Console, click Activate Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

If this is your first time starting Cloud Shell, you're presented with an intermediate screen describing what it is. If you were presented with an intermediate screen, click Continue.

d95252b003979716.png

It should only take a few moments to provision and connect to Cloud Shell.

7833d5e1c5d18f54.png

This virtual machine is loaded with all the development tools needed. It offers a persistent 5 GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with a browser.

Once connected to Cloud Shell, you should see that you are authenticated and that the project is set to your project ID.

  1. Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list

Command output

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If it is not, you can set it with this command:

gcloud config set project <PROJECT_ID>

Command output

Updated property [core/project].

3. Create the ingress app

Set Environment Variables

In this codelab, you will create a few environment variables to improve the readability of the gcloud commands used in this codelab.

REGION=<YOUR-REGION>
PROJECT_ID=<YOUR-PROJECT-ID>

SERVICE_NAME=sidecar-codelab
REPO_NAME=sidecar-codelab

Create an ArtifactRegistry repo to hold your container images

You can create a repo in Artifact Registry to store your container images for this codelab.

gcloud artifacts repositories create $REPO_NAME --repository-format=docker \
--location=$REGION --description="sidecar codelab"

Then, create a package.json file with the following content:

{
  "name": "sidecar-codelab",
  "version": "1.0.0",
  "private": true,
  "description": "demonstrates how to use sidecars in cloud run",
  "main": "index.js",
  "author": "Google LLC",
  "license": "Apache-2.0",
  "scripts": {
    "start": "node ingress.js"
  },
  "dependencies": {
    "axios": "^1.6.2",
    "express": "^4.18.2"
  }
}

Now create a file called ingress.js with the following contents:

const express = require('express');
const app = express();
const axios = require("axios");

app.get('/', async (req, res) => {

    let response = await axios.get("http://localhost:5000");

    res.send("The sidecar says: " + response.data);
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
    console.log(`Ingress container listening on port ${port}`);
});

Create a dockerfile for the ingress container

FROM node:20.10.0-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production

# Copy local code to the container image.
COPY . .

# Run the web service on container startup.
ENV PORT=8080
CMD [ "npm", "start" ]

And create a ``.dockerignore` file for the ingress container.

# Exclude locally installed dependencies
node_modules/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.gitignore

Now you can build the image for your ingress container by running the following command:

gcloud builds submit --tag $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/ingress:latest

4. Create the sidecar app

In this section, you'll create a second node.js app that will be used as the sidecar in the Cloud Run service.

Navigate to the sidecar directory.

cd ../sidecar

Create a package.json file with the following content:

{
  "name": "sidecar-codelab",
  "version": "1.0.0",
  "private": true,
  "description": "demonstrates how to use sidecars in cloud run",
  "main": "index.js",
  "author": "Google LLC",
  "license": "Apache-2.0",
  "scripts": {
    "start": "node sidecar.js"
  },
  "dependencies": {
    "axios": "^1.6.2",
    "express": "^4.18.2"
  }
}

Now create a file called sidecar.js with the following contents:

const express = require('express');
const app = express();

app.get('/', async (req, res) => {
    res.send("Hello ingress container! I'm the sidecar.");
});

const port = parseInt(process.env.PORT || 5000);
app.listen(port, () => {
    console.log(`Sidecar container listening on port ${port}`);
});

Create a Dockerfile for the sidecar container

FROM node:20.10.0-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production

# Copy local code to the container image.
COPY . .

# Run the web service on container startup.
ENV PORT=5000
CMD [ "npm", "start" ]

And create a ``.dockerignore` file for the sidecar container.

# Exclude locally installed dependencies
node_modules/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.gitignore

Now you can build the image for your ingress container by running the following command:

gcloud builds submit --tag $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/sidecar:latest

Deploy the Cloud Run service

You'll deploy the Cloud Run service using a yaml file.

Navigate to the parent directory.

cd ..

Create a file called sidecar-codelab.yaml with the following contents:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
  name: sidecar-codelab
  labels:
    cloud.googleapis.com/location: "<YOUR_REGION>"
spec:
  template:
    spec:
      containers:
        - image: "<YOUR_REGION>-docker.pkg.dev/<YOUR_PROJECT_ID>/sidecar-codelab/ingress:latest"
          ports:
            - containerPort: 8080
        - image: "<YOUR_REGION>-docker.pkg.dev/<YOUR_PROJECT_ID>/sidecar-codelab/sidecar:latest"
          env:
            - name: PORT
              value: "5000"

Then deploy the service using the following command. You need to use gcloud beta because volume mounts is in public preview.

gcloud beta run services replace sidecar-codelab.yaml

Once deployed, save the service url in an environment variable.

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --platform managed --region $REGION --format 'value(status.url)') 

5. Call the Cloud Run service

Now you can call your service by providing your identity token.

curl -X GET -H "Authorization: Bearer $(gcloud auth print-identity-token)" ${SERVICE_URL}

Your results should look similar to the example output below:

The sidecar says: Hello ingress container! I'm the sidecar.

6. Share a file via a volume mount

In this section, you'll update the containers to share a file via a volume mount. In this example, the ingress container will write to a file on a shared volume. The sidecar will read the file and return its contents back to the ingress container.

First you'll update the ingress container code. Navigate to the ingress directory.

cd ../ingress

and then replace the contents of the ingress.js file with the following:

const express = require('express');
const app = express();
const fs = require('fs');
const axios = require("axios");

const filename = "test.txt"

let path = "/my-volume-mount";
app.use(path, express.static(path));

try {
    fs.writeFileSync(`${path}/${filename}`, "The ingress container created this file.");
} catch (err) {
    console.error(err);
}

app.get('/', async (req, res) => {

    let response = await axios.get("http://localhost:5000");

    res.send("The sidecar says: " + response.data);
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
    console.log(`Ingress container listening on port ${port}`);
});

And build the new image for your ingress container by running the following command:

gcloud builds submit --tag $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/ingress:latest

Now navigate to the sidecar directory:

cd ../sidecar

And update sidecar.js with the following contents:

const express = require('express');
const app = express();
const fs = require('fs');

const filename = "test.txt"

let path = "/my-volume-mount";
app.use(path, express.static(path));

async function readFile() {
    try {
        return await fs.readFileSync(`${path}/${filename}`, { encoding: 'utf8' });
    } catch (err) {
        console.log(err);
    }
}

app.get('/', async (req, res) => {
    let contents = await readFile();
    res.send(contents);
});

const port = parseInt(process.env.PORT || 5000);
app.listen(port, () => {
    console.log(`Sidecar container listening on port ${port}`);
});

And build the new image for your sidecar container by running the following command:

gcloud builds submit --tag $REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/sidecar:latest

Update the sidecar-codelab.yaml with the following to share a volume:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
  name: sidecar-codelab
  labels:
    cloud.googleapis.com/location: "<YOUR_REGION>"
spec:
  template:
    spec:
      containers:
        - image: "<YOUR_REGION>-docker.pkg.dev/<YOUR_PROJECT_ID>/sidecar-codelab/ingress:latest"
          ports:
            - containerPort: 8080
          volumeMounts:
            - mountPath: /my-volume-mount
              name: in-memory-1
        - image: "<YOUR_REGION>-docker.pkg.dev/<YOUR_PROJECT_ID>/sidecar-codelab/sidecar:latest"
          env:
            - name: PORT
              value: "5000"
          volumeMounts:
            - mountPath: /my-volume-mount
              name: in-memory-1
      volumes:
        - emptyDir:
            medium: Memory
          name: in-memory-1

Deploy the updated sidecar-codelab.yaml file

gcloud beta run services replace sidecar-codelab.yaml

Now you can call your service by providing your identity token.

curl -X GET -H "Authorization: Bearer $(gcloud auth print-identity-token)" ${SERVICE_URL}

Your results should look similar to the example output below:

The sidecar says: the ingress container created this file.

7. Congratulations!

Congratulations for completing the codelab!

We recommend reviewing the documentation on Cloud Run, specifically deploying multicontainers and using in-memory volume mounts.

What we've covered

  • How to create a container that uses a sidecar
  • How an ingress container can communicate with a sidecar using localhost
  • How an ingress container and a sidecar can both share a mounted volume

8. Clean up

To avoid inadvertent charges, (for example, if this Cloud Function is inadvertently invoked more times than your monthly Cloud Run invokement allocation in the free tier), you can either delete the Cloud Run service or delete the project you created in Step 2.

To delete the Cloud Function, go to the Cloud Function Cloud Console at https://console.cloud.google.com/run/ and delete the sidecar-codelab service (or the $SERVICE_NAME in case you used a different name).

If you choose to delete the entire project, you can go to https://console.cloud.google.com/cloud-resource-manager, select the project you created in Step 2, and choose Delete. If you delete the project, you'll need to change projects in your Cloud SDK. You can view the list of all available projects by running gcloud projects list.