# Reaching FastAPI deployment from outside cluster

**URL:** <https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047>\
**Category:** Ray Serve\
**Created:** [August 2, 2022, 10:24am UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047 "2022-08-02T10:24:33Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lewis\_Bails1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/lewis_bails1/32/2955_2.png) [@Lewis\_Bails1](https://discuss.ray.io/u/Lewis_Bails1)\
**Post date:** [August 2, 2022, 10:24am UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/1 "2022-08-02T10:24:33Z")

</div>

**How severe does this issue affect your experience of using Ray?**

- Medium: It contributes to significant difficulty to complete my task, but I can work around it.

I recently deployed the FastAPI “hello world” example to a minimal GCP cluster.  
I have no trouble reaching the endpoint if I attach to the the cluster first with `ray attach config.yaml` and call the API from there.  
But I can’t manage to reach the endpoint from my local machine after replacing localhost with the head node’s external IP, still on port 8000.  
Is this simply a firewall issue with the GCP cluster, or am I misunderstanding the purpose of the Ray Serve deployment in that it’s not intended to be reached from a local machine?

The config used for creating the cluster:

```auto
# A unique identifier for the head node and workers of this cluster.
cluster_name: minimal

# The maximum number of workers nodes to launch in addition to the head
# node.
max_workers: 2

# The autoscaler will scale up the cluster faster with higher upscaling speed.
# E.g., if the task requires adding more nodes then autoscaler will gradually
# scale up the cluster in chunks of upscaling_speed*currently_running_nodes.
# This number should be > 0.
upscaling_speed: 1.0

# This executes all commands on all nodes in the docker container,
# and opens all the necessary ports to support the Ray cluster.
# Empty string means disabled.
docker:
  image: "rayproject/ray-ml:nightly-py38-cpu"
    # image: rayproject/ray:latest-gpu # use this one if you don't need ML dependencies, it's faster to pull
  container_name: "ray_container"
  # If true, pulls latest version of image. Otherwise, `docker run` will only pull the image
  # if no cached version is present.
  pull_before_run: True
  run_options: # Extra options to pass into "docker run"
    - --ulimit nofile=65536:65536

  # Example of running a GPU head with CPU workers
  # head_image: "rayproject/ray-ml:latest-gpu"
  # Allow Ray to automatically detect GPUs

  # worker_image: "rayproject/ray-ml:latest-cpu"
  # worker_run_options: []

# If a node is idle for this many minutes, it will be removed.
idle_timeout_minutes: 5

# Cloud-provider specific configuration.
provider:
    type: gcp
    region: europe-west2
    availability_zone: europe-west2-a
    project_id: bert-training-test

# How Ray will authenticate with newly launched nodes.
auth:
    ssh_user: ubuntu
# By default Ray creates a new private keypair, but you can also use your own.
# If you do so, make sure to also set "KeyName" in the head and worker node
# configurations below. This requires that you have added the key into the
# project wide meta-data.
# ssh_private_key: /path/to/your/key.pem

# Tell the autoscaler the allowed node types and the resources they provide.
# The key is the name of the node type, which is just for debugging purposes.
# The node config specifies the launch config and physical instance type.
available_node_types:
    ray_head_default:
        # The resources provided by this node type.
        resources: {"CPU": 2}
        # Provider-specific config for the head node, e.g. instance type. By default
        # Ray will auto-configure unspecified fields such as subnets and ssh-keys.
        # For more documentation on available fields, see:
        # https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert
        node_config:
            machineType: n1-standard-2
            disks:
              - boot: true
                autoDelete: true
                type: PERSISTENT
                initializeParams:
                  diskSizeGb: 50
                  # See https://cloud.google.com/compute/docs/images for more images
                  sourceImage: projects/deeplearning-platform-release/global/images/family/common-cpu

            # Additional options can be found in in the compute docs at
            # https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert

            # If the network interface is specified as below in both head and worker
            # nodes, the manual network config is used. Otherwise an existing subnet is
            # used. To use a shared subnet, ask the subnet owner to grant permission
            # for 'compute.subnetworks.use' to the ray autoscaler account...
            # networkInterfaces:
            # - kind: compute#networkInterface
            # subnetwork: path/to/subnet
            # aliasIpRanges: []
    ray_worker_small:
        # The minimum number of worker nodes of this type to launch.
        # This number should be >= 0.
        min_workers: 0
        # The maximum number of worker nodes of this type to launch.
        # This takes precedence over min_workers.
        max_workers: 2
        # The resources provided by this node type.
        resources: {"CPU": 2}
        # Provider-specific config for the head node, e.g. instance type. By default
        # Ray will auto-configure unspecified fields such as subnets and ssh-keys.
        # For more documentation on available fields, see:
        # https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert
        node_config:
            machineType: n1-standard-2
            disks:
              - boot: true
                autoDelete: true
                type: PERSISTENT
                initializeParams:
                  diskSizeGb: 50
                  # See https://cloud.google.com/compute/docs/images for more images
                  sourceImage: projects/deeplearning-platform-release/global/images/family/common-cpu
            # Run workers on preemtible instance by default.
            # Comment this out to use on-demand.
            scheduling:
              - preemptible: true
            # Un-Comment this to launch workers with the Service Account of the Head Node
            # serviceAccounts:
            # - email: ray-autoscaler-sa-v1@<project_id>.iam.gserviceaccount.com
            # scopes:
            # - https://www.googleapis.com/auth/cloud-platform

    # Additional options can be found in in the compute docs at
    # https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert

# Specify the node type of the head node (as configured above).
head_node_type: ray_head_default

# Files or directories to copy to the head and worker nodes. The format is a
# dictionary from REMOTE_PATH: LOCAL_PATH, e.g.
file_mounts: {
# "/path1/on/remote/machine": "/path1/on/local/machine",
# "/path2/on/remote/machine": "/path2/on/local/machine",
}

# Files or directories to copy from the head node to the worker nodes. The format is a
# list of paths. The same path on the head node will be copied to the worker node.
# This behavior is a subset of the file_mounts behavior. In the vast majority of cases
# you should just use file_mounts. Only use this if you know what you're doing!
cluster_synced_files: []

# Whether changes to directories in file_mounts or cluster_synced_files in the head node
# should sync to the worker node continuously
file_mounts_sync_continuously: False

# Patterns for files to exclude when running rsync up or rsync down
rsync_exclude:
    - "**/.git"
    - " **/.git/**"

# Pattern files to use for filtering out files when running rsync up or rsync down. The file is searched for
# in the source directory and recursively through all subdirectories. For example, if .gitignore is provided
# as a value, the behavior will match git's behavior for finding and using .gitignore files.
rsync_filter:
    - ".gitignore"

# List of commands that will be run before `setup_commands`. If docker is
# enabled, these commands will run outside the container and before docker
# is setup.
initialization_commands: []

# List of shell commands to run to set up nodes.
setup_commands: []
    # Note: if you're developing Ray, you probably want to create a Docker image that
    # has your Ray repo pre-cloned. Then, you can replace the pip installs
    # below with a git checkout <your_sha> (and possibly a recompile).
    # To run the nightly version of ray (as opposed to the latest), either use a rayproject docker image
    # that has the "nightly" (e.g. "rayproject/ray-ml:nightly-gpu") or uncomment the following line:
    # - pip install -U "ray[default] @ https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-3.0.0.dev0-cp37-cp37m-manylinux2014_x86_64.whl"

# Custom commands that will be run on the head node after common setup.
head_setup_commands:
  - pip install google-api-python-client==1.7.8

# Custom commands that will be run on worker nodes after common setup.
worker_setup_commands: []

# Command to start ray on the head node. You don't need to change this.
head_start_ray_commands:
    - ray stop
    - >-
      ray start
      --head
      --port=6379
      --object-manager-port=8076
      --autoscaling-config=~/ray_bootstrap_config.yaml

# Command to start ray on worker nodes. You don't need to change this.
worker_start_ray_commands:
    - ray stop
    - >-
      ray start
      --address=$RAY_HEAD_IP:6379
      --object-manager-port=8076

```

The app:

```auto
import ray

from fastapi import FastAPI
from ray import serve
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware as StarletteCORSMiddleware

app = FastAPI()
origins = [
    "*",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

ray.init(address="auto", namespace="example")
serve.start(detached=True)

@serve.deployment(route_prefix="/hello")
@serve.ingress(app)
class MyFastAPIDeployment:
    @app.get("/")
    def root(self):
        return "Hello, world!"

MyFastAPIDeployment.deploy()

```

Pinging the API from inside the cluster:

```auto
>> ray attach config.yaml
>> python -c 'import requests; requests.get("http://localhost:8000/hello").text'
>> Hello World!

```

Pinging from local machine:

```auto
curl -X GET http://34.105.238.180:8000/hello
# Connection refused

```

---

<div class="post-metadata">

**Author:** ![Sihan\_Wang](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/sihan_wang/32/2570_2.png) [@Sihan\_Wang](https://discuss.ray.io/u/Sihan_Wang)\
**Post date:** [August 2, 2022, 4:37pm UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/2 "2022-08-02T16:37:04Z")

</div>

Hi @Lewis_Bails1 , to rule out the firewall issue, are you able to setup a FastAPI application without using ray serve, and can you try to ping from outside?

---

<div class="post-metadata">

**Author:** ![Lewis\_Bails1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/lewis_bails1/32/2955_2.png) [@Lewis\_Bails1](https://discuss.ray.io/u/Lewis_Bails1)\
**Post date:** [August 3, 2022, 8:44am UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/3 "2022-08-03T08:44:54Z")

</div>

Hi @Sihan_Wang , I was able to setup a simple FastAPI application on the cluster head without using ray serve and ping it from my local machine.

I saw one example where `serve.start(detached=True)` was replaced with `serve.start(detached=True, http_options={"host": "0.0.0.0"})`.  
To me this is similar to running a FastAPI app via `uvicorn main:app --host 0.0.0.0` which allows me to reach my apps from my local machine (although I’m not sure why, I’m not very familiar with networking). However, when I use the `http_options` parameter, it seems to tell me that the `host` value was ignored.

```bash
The new client HTTP config differs from the existing one in the following fields: ['host']. The new HTTP config is ignored.

```

---

<div class="post-metadata">

**Author:** ![Sihan\_Wang](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/sihan_wang/32/2570_2.png) [@Sihan\_Wang](https://discuss.ray.io/u/Sihan_Wang)\
**Post date:** [August 3, 2022, 4:40pm UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/4 "2022-08-03T16:40:36Z")

</div>

That is because you have already had serve running. Can you make sure serve is started with `serve.start(detached=True, http_options={"host": "0.0.0.0"})`?

This works for me in my local:  
cli: `ray start --head`  
script run: `serve.start(detached=True, http_options={"host": "0.0.0.0"})`

---

<div class="post-metadata">

**Author:** ![Lewis\_Bails1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/lewis_bails1/32/2955_2.png) [@Lewis\_Bails1](https://discuss.ray.io/u/Lewis_Bails1)\
**Post date:** [August 4, 2022, 8:09am UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/5 "2022-08-04T08:09:24Z")

</div>

Thanks very much, it’s working now. Does the script always have to have `serve.start(...)` or is it better practise to start Serve with the CLI?

---

<div class="post-metadata">

**Author:** ![Sihan\_Wang](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ray.io/sihan_wang/32/2570_2.png) [@Sihan\_Wang](https://discuss.ray.io/u/Sihan_Wang)\
**Post date:** [August 4, 2022, 3:52pm UTC](https://discuss.ray.io/t/reaching-fastapi-deployment-from-outside-cluster/7047/6 "2022-08-04T15:52:31Z")

</div>

Nope. Since you have `detached=True` you will have a long running serve instance. You don’t need to start all the time.  
To be clear, The cli thing I showed up is to bring up the ray cluster and serve.start() is to bring up the serve instance on top of the ray.
