How to configure a Cloud Run service to access an internal Cloud Run service using direct VPC egress
About this codelab
1. Introduction
Overview
To secure network traffic for their services and applications, many organizations use a Virtual Private Cloud (VPC) network on Google Cloud with perimeter controls to prevent data exfiltration. A VPC network is a virtual version of a physical network that is implemented inside of Google's production network. A VPC network provides connectivity for your Compute Engine virtual machine (VM) instances, offers native internal passthrough Network Load Balancers and proxy systems for internal Application Load Balancers, connects to on-premises networks by using Cloud VPN tunnels and VLAN attachments for Cloud Interconnect, and distributes traffic from Google Cloud external load balancers to backends.
Unlike VMs, Cloud Run services are not associated with any particular VPC network by default. This codelab demonstrates how to change ingress (inbound connections) settings such that only traffic coming from a VPC can access a Cloud Run service (e.g. a backend service). In addition, this codelab shows you how to have a second service (e.g. a frontend service) access the backend Cloud Run service through a VPC.
In this example, the backend Cloud Run service returns hello world. The frontend Cloud Run service provides an input field in the UI to collect a URL. Then the frontend service makes a GET request to that URL (e.g. the backend service), hence making this a service to service request (instead of a browser to service request). When the frontend service can successfully reach the backend, the message hello world is displayed in the browser.
What you'll learn
- How to allow only traffic from a VPC to your Cloud Run service
- How to configure egress on a Cloud Run service to communicate with an internal-ingress only Cloud Run service
2. Setup and Requirements
Prerequisites
- You are logged into the Cloud Console.
- You have previously deployed a Cloud Run service. For example, you can follow the deploy a web service from source code quickstart to get started.
Activate Cloud Shell
- From the Cloud Console, click Activate Cloud Shell
.
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.
It should only take a few moments to provision and connect to Cloud Shell.
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.
- 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`
- 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 Cloud Run services
Setup environment variables
You can set environment variables that will be used throughout this codelab.
REGION=<YOUR_REGION, e.g. us-central1> FRONTEND=frontend BACKEND=backend
Create the backend Cloud Run service
First, create a directory for the source code and cd into that directory.
mkdir -p internal-codelab/frontend internal-codelab/backend && cd internal-codelab/backend
Then, create a package.json
file with the following content:
{ "name": "backend-service", "version": "1.0.0", "description": "", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.18.1" } }
Next, create an index.js
source file with the content below. This file contains the entry point for the service and contains the main logic for the app.
const express = require('express'); const app = express(); app.use(express.urlencoded({ extended: true })); app.get('/', function (req, res) { res.send("hello world"); }); const port = parseInt(process.env.PORT) || 8080; app.listen(port, () => { console.log(`helloworld: listening on port ${port}`); });
Lastly, deploy the Cloud Run service running the following command.
gcloud run deploy $BACKEND --source . --allow-unauthenticated --region $REGION
Create the frontend Cloud Run service
Navigate to the frontend directory.
cd ../frontend
Then, create a package.json
file with the following content:
{ "name": "frontend", "version": "1.0.0", "description": "", "scripts": { "start": "node index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "axios": "^1.6.6", "express": "^4.18.2" } }
Next, create an index.js
source file with the content below. This file contains the entry point for the service and contains the main logic for the app.
const express = require("express"); const app = express(); const port = 8080; const path = require('path'); const axios = require('axios'); // serve static content (index.html) using // built-in middleware function in Express app.use(express.static('public')); app.use(express.urlencoded({ extended: true })); // this endpoint receives a URL in the post body // and then makes a get request to that URL // results are sent back to the caller app.post('/callService', async (req, res) => { const url = req.body.url; let message = ""; try { console.log("url: ", url); const response = await axios.get(url); message = response.data; } catch (error) { message = error.message; console.error(error.message); } res.send(` ${message} <p> </p> `); }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); });
Create a public directory for the index.html file
mkdir public touch public/index.html
And update the index.html to contain the following:
<html> <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous" ></script> <body> <div style="margin-top: 100px; margin-left: 100px"> <h1>I'm the Frontend service on the Internet</h1> <form hx-trigger="submit" hx-post="/callService" hx-target="#message"> <label for="url"> URL:</label> <input style="width: 308px" type="text" id="url" name="url" placeholder="The backend service URL" required /> <button hx-indicator="#loading" type="submit">Submit</button> <p></p> <span class="htmx-indicator" id="loading"> Loading... </span> <div id="message" style="white-space: pre-wrap"></div> <p></p> </form> </div> </body> </html>
Lastly, deploy the Cloud Run service running the following command.
gcloud run deploy $FRONTEND --source . --allow-unauthenticated --region $REGION
Call the Backend Service
Verify that you have successfully deployed two Cloud Run services.
Open the URL of the frontend service in your web browser.
In the textbox, enter the URL for the backend service. Note that this request is routed from the front end Cloud Run instance to the backend Cloud Run service, instead of being routed from your browser.
You will see "hello world".
4. Set the Backend Service for internal ingress only
Run the following gcloud command to only allow traffic from within your VPC network to access your backend service.
gcloud run services update $BACKEND --ingress internal --region $REGION
To confirm that your backend service can only receive traffic from your VPC, try again to call your backend service from your frontend service.
This time you will see "Request failed with status code 404"
You received this error because the frontend Cloud Run service outbound request (i.e. egress) goes out to the Internet first, so Google Cloud does not know the origin of the request.
In the next section, you'll configure the frontend service to access the VPC, so Google Cloud will know the request came from the VPC, which is recognized as an internal source.
5. Configure the Frontend Service to access the VPC
In this section, you will configure your frontend Cloud Run service to communicate with your backend service through a VPC.
To do this, you will need to add direct VPC egress to your frontend Cloud Run instances to give your service an internal IP to use within the VPC. Then, you'll configure egress such that all outbound connections from the frontend service will go to the VPC.
First, run this command to enable direct VPC egress:
gcloud beta run services update $FRONTEND \ --network=default \ --subnet=default \ --vpc-egress=all-traffic \ --region=$REGION
You can now confirm that your frontend service has access to the VPC:
gcloud beta run services describe $FRONTEND \ --region=$REGION
You should see output similar to
VPC access: Network: default Subnet: default Egress: all-traffic
Now try again to call your backend service from your frontend service.
This time you will see "hello world".
Note: Your frontend service will not have Internet access since all egress has been routed to the VPC. For example, your frontend service will timeout if it tries to access https://curlmyip.org/.
6. Congratulations!
Congratulations for completing the codelab!
We recommend reviewing the documentation Cloud Run and how to configure private networking for Cloud Run services.
What we've covered
- How to allow only traffic from a VPC to your Cloud Run service
- How to configure egress on a Cloud Run service to communicate with an internal-ingress only Cloud Run service
7. Clean up
To avoid inadvertent charges, (for example, if the Cloud Run services are inadvertently invoked more times than your monthly Cloud Run invokement allocation in the free tier), you can either delete the Cloud Run or delete the project you created in Step 2.
To delete the Cloud Run service, go to the Cloud Run Cloud Console at https://console.cloud.google.com/run and delete the $FRONTEND and $BACKEND services.
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
.