I always recommend to go to the documentation at first place. However, sometimes there are some concepts that are not so clear for everyone. In this case, network concepts for developers.
When we hear Serverless we forgot almost everything about DevOps, networking, memory and so on, just worry about the code and that's ok.
But now we have a requirement: the client API only accepts requests from a whitelisted IP.
This is the schema for a 'traditional' architecture:
Cloud Functions will resolve most of the architecture but as you can see, some resources from a 'traditional' architecture are still necessary to achieve our objetive and that's where I want to help.
Create a Simple HTTP Function
main.py
# This function will return the IP address for egress
import requests
import json
def test_ip(request):
result = requests.get("https://api.ipify.org?format=json")
return json.dumps(result.json())
deploy
gcloud functions deploy testIP \
--runtime python37 \
--entry-point test_ip \
--trigger-http \
--allow-unauthenticated
test
curl https://us-central1-your-project.cloudfunctions.net/testIP
# {"ip": "35.203.245.150"} (ephemeral: changes any time)
Networking
So this is the part that some devs going crazy, we're going to use a VPC (Virtual Private Cloud) that provides networking functionalities to out cloud-based services, in this case our Cloud Function.
VPC networks do not have any IP address ranges associated with them. IP ranges are defined for the subnets.
# Create VPC
gcloud services enable compute.googleapis.com
gcloud compute networks create my-vpc \
--subnet-mode=custom \
--bgp-routing-mode=regional
Then we have to create a Serverless VPC Access connector that allows Cloud functions (an another Serverless resources) to connect with a VPC.
# Create a Serverless VPC Access connectors
gcloud services enable vpcaccess.googleapis.com
gcloud compute networks vpc-access connectors create functions-connector \
--network my-vpc \
--region us-central1 \
--range 10.8.0.0/28
Before we can use our functions-connector we have to grant the appropriate permissions to the Cloud Functions service account, so the Cloud Functions will be able to connect to our functions-connector.
# Grant Permissions
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
export PROJECT_NUMBER=$(gcloud projects list --filter="$PROJECT_ID" --format="value(PROJECT_NUMBER)")
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:service-$PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com \
--role=roles/viewer
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:service-$PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com \
--role=roles/compute.networkUser
Ok, we have the connector and the permissions, let's configure our Cloud Function to use the connector.
# Configurate the connector
gcloud functions deploy testIP \
--runtime python37 \
--entry-point test_ip \
--trigger-http \
--allow-unauthenticated \
--vpc-connector functions-connector \
--egress-settings all
If you make a request to our Cloud Function you will see this message: "Error: could not handle the request" that's because our VPC doesn't have any exit to the internet.
In order to be accessible to the outside world we have to:
-
Reserve a static IP.
-
Configure a Cloud Router to route our network traffic.
-
Create a [Cloud Nat] (https://cloud.google.com/nat) to allow our instances without external IP to send outbound packets to the internet and receive any corresponding established inbound response packets (aka via static IP).
# Reserve static IP
gcloud compute addresses create functions-static-ip \
--region=us-central1
gcloud compute addresses list
# NAME ADDRESS/RANGE TYPE PURPOSE NETWORK REGION SUBNET STATUS
# functions-static-ip 34.72.171.164 EXTERNAL us-central1 RESERVED
We have our static IP! 34.72.171.164
# Creating the Cloud Router
gcloud compute routers create my-router \
--network my-vpc \
--region us-central1
# Creating Cloud Nat
gcloud compute routers nats create my-cloud-nat-config \
--router=my-router \
--nat-external-ip-pool=functions-static-ip \
--nat-all-subnet-ip-ranges \
--enable-logging
Awesome! now let's try our Cloud Functions with a new request
curl https://us-central1-your-project.cloudfunctions.net/testIP
# {"ip": "34.72.171.164"} (our static IP!)
Yay! everything is working :) a little recap:
-
We have deployed a simple Cloud Function (HTTP).
-
Created a VPC to provide networking functionalities to our Cloud Function.
-
Created a Serverless VPC Access connector to allow our Cloud Function to use VPC functionalities (like use IPs for example).
-
Granted permissions to the Cloud Functions Service Account to use network resourcing.
-
Configured the Cloud Function to use the Serverless VPC Access connector and redirect all the outbound request through the VPC
-
Reserved a static IP.
-
Created a Cloud Router to route our network traffic.
-
An finally create a Cloud Nat to communicate with the outside world.
Hope this post helps you and let me know if you have any questions or recommendations.