Can't access static external IP for Google Cloud Run instance - followed guides (Router, Subnet, NAT, VPC, etc)

I'm trying to assign an external static IP address to our Cloud Run instance, so that I can use it with websockets (which I've read require a static IP in order to work, versus the self-appointed/load-balanced GCloud app domain name). The regular URL is accessible, but the static IP is just hanging.

I tried following this guide mostly: https://cloud.google.com/run/docs/configuring/static-outbound-ip

I'm not entirely sure how the external IP gets routed to the Cloud Run instance, because it's a self-managed instance (ie. there is no private IP address on it), but assume that the other pars are figuring that out...

What I've done:

  • Created a cloud router:

gcloud compute routers create cloud-run-router --network=default --region=us-central1

router

  • Created external static IP:

gcloud compute addresses create cloud-run --region=us-central1

external ip

  • I also created a subnet on the network:

gcloud compute networks subnets create cloud-run-subnet --range=10.20.0.0/28 --network=default --region=us-central1

subnet

  • Then I created a Serverless VPC Access connector with this subnet:

gcloud beta compute networks vpc-access connectors create cloud-run-sub-conn --subnet-project=project-name --subnet=cloud-run-subnet --region=us-central1

vpc connector

  • Then I created a new Cloud NAT Gateway with this router and subnet:

gcloud compute routers nats create cloud-run-nat --router=cloud-run-router --region=us-central1 --nat-custom-subnet-ip-ranges=cloud-run-subnet --nat-external-ip-pool=cloud-run

nat gateway

  • I also setup some firewall rules, to try to allow everything: firewall

  • I then deployed the Cloud Run instance referring to the VPC egress and connector:

gcloud beta run deploy api-node --image gcr.io/project-name/api-node:latest --platform managed --allow-unauthenticated --set-env-vars REDISHOST='10.0.0.4',REDISPORT=6379,GOOGLE_APPLICATION_CREDENTIALS=credentials.json --set-cloudsql-instances=project-name:us-central1:mysql-db --vpc-egress=all --vpc-connector=cloud-run-sub-conn

Service [api-node] revision [api-node-00035-waw] has been deployed and is serving 100 percent of traffic.
Service URL: https://api-node-ojzumfbnoq-uc.a.run.app

For some reason the Cloud Run instance is still accessible by the self-appointed url: https://api-node-ojzumfbnoq-uc.a.run.app/

...but the external IP doesn't work: http://104.197.97.194/ https://104.197.97.194/ http://104.197.97.194:8080/ https://104.197.97.194:80/

The Cloud Run service yaml file looks like this:

gcloud run services describe api-node --format export > service.yaml

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
    client.knative.dev/user-image: gcr.io/project-name/api-node
    run.googleapis.com/ingress: all
    run.googleapis.com/ingress-status: all
    run.googleapis.com/launch-stage: BETA
  labels:
    cloud.googleapis.com/location: us-central1
  name: api-node
  namespace: '938045200399'
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '1000'
        autoscaling.knative.dev/minScale: '4'
        client.knative.dev/user-image: gcr.io/project-name/api-node
        run.googleapis.com/client-name: gcloud
        run.googleapis.com/client-version: 329.0.0
        run.googleapis.com/cloudsql-instances: project-name:us-central1:mysql-db
        run.googleapis.com/sandbox: gvisor
        run.googleapis.com/vpc-access-connector: cloud-run-sub-conn
        run.googleapis.com/vpc-access-egress: all
      name: api-node-00034-xah
    spec:
      containerConcurrency: 250
      containers:
      - env:
        - name: REDISHOST
          value: 10.0.0.4
        - name: REDISPORT
          value: '6379'
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: credentials.json
        image: gcr.io/project-name/api-node
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: '4'
            memory: 2Gi
      serviceAccountName: [email protected]
      timeoutSeconds: 20
  traffic:
  - latestRevision: true
    percent: 100

Here is my Dockerfile, if it helps:

# Use the official lightweight Node.js 12 image.
# https://hub.docker.com/_/node
FROM node:12-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

ENV REDISHOST='10.0.0.4'
ENV REDISPORT=6379
ENV GOOGLE_APPLICATION_CREDENTIALS=credentials.json
ENV PORT=80

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
# If you add a package-lock.json, speed your build by switching to 'npm ci'.
# RUN npm ci --only=production
RUN npm install --only=production

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

EXPOSE 80/tcp
EXPOSE 8080/tcp
EXPOSE 9001/tcp

# Run the web service on container startup.
CMD [ "node", "build/index.js" ]

Does anyone see anything wrong with the above steps? Would appreciate any help! Been at it for a few days.


Well, the main issue with your approach is that you actually missed the most important part of this doc, the title, which is:

Static outbound IP address

And the reason behind this title is that Cloud NAT does not allow inbound traffic, i.e., you cannot make requests to the Cloud Run Service using the static IP of the NAT using that as an Endpoint, this is why it is "hanging".

So, the best approach you can follow is to create a Load Balancer for Serverless Apps as stated here. You can give it a try and check if this works with websockets.