Architecture¶
ray-vast runs a Ray cluster with the head on AWS EC2 and the workers on vast.ai containers. The nodes talk over the public internet, across NAT, with TLS and a token. There is no VPN and no overlay network.
flowchart TB
subgraph laptop["Your laptop"]
cli["ray-vast CLI"]
rayjob["ray job submit"]
end
subgraph ec2["AWS EC2 head (Docker, host network)"]
rayhead["Ray head: GCS, dashboard"]
autoscaler["Autoscaler v2 + ray-vast provider"]
server["Head server: port A 7443 (TLS), port B 7444 (mTLS)"]
secrets["CA key, cluster token, vast key"]
end
subgraph vast["vast.ai hosts"]
subgraph w1["Worker container"]
boot1["Bootstrap = PID 1 = watchdog"]
ray1["Ray worker node"]
end
subgraph w2["Worker container"]
boot2["Bootstrap = PID 1 = watchdog"]
ray2["Ray worker node"]
end
end
cli -- "SSH" --> ec2
rayjob -- "SSH tunnel to 8265" --> rayhead
autoscaler -- "rent, destroy (vast API)" --> vast
boot1 -- "enroll, heartbeat" --> server
boot2 -- "enroll, heartbeat" --> server
ray1 <-- "Ray (TLS + token)" --> rayhead
ray2 <-- "Ray (TLS + token)" --> rayhead
ray1 <-- "Ray (TLS + token)" --> ray2
The head¶
The head is one EC2 instance in a region that you choose. It runs one Docker container, ray-vast-head, with the host network.
- Why EC2. A vast instance can stop at any time, and on many hosts its IP can change. If the head stops, the cluster is lost. EC2 gives a stable head.
- Security groups. The head's security groups allow SSH from your IP only. They allow the Ray and head server ports from worker IPs only. So a DDoS on the internet can stop single workers, but not the cluster.
- One image. The head runs your image plus the ray-vast package (a derived image that
upbuilds on the head). So Python and Ray are the same on all nodes. - One process does the work. The provider runs in the Ray monitor process, next to the autoscaler. The head server is a thread in the provider. The provider keeps all its state in memory.
- No CPU for tasks. The head declares
CPU: 0. Tasks and actors with resources run on the workers.
The provider¶
The provider is a Ray autoscaler v2 node provider (ray_vast.provider.VastNodeProvider).
- When Ray needs a node, the autoscaler asks the provider. The provider makes a slot at once, and rents candidates for it.
- A reconcile pass runs every 10 seconds. It lists the vast instances, adds security group rules for new IPs, ends candidates that pass a time limit, and destroys instances that it does not know (the orphan sweep).
- When the provider starts, it destroys every instance with the cluster label (the start-up sweep). A new head has a new Ray GCS, so old workers cannot join it again.
The workers¶
A worker is a vast container that has enrolled and runs Ray.
- The container command is a short bootloader. It downloads the bootstrap from the head, checks its SHA-256, and starts it.
- The bootstrap runs as PID 1. It checks the host, enrolls, starts Ray, and then acts as the watchdog.
- The workers need no sshd. The head never connects to a worker over SSH.
Read Enrollment and the join token and Worker life cycle and the watchdog.
NAT and the 64 identity ports¶
A vast container is behind NAT. vast maps some host ports into the container.
Each worker asks vast for 64 ports: the identity ports 70000 to 70063. vast gives a host port P for each, and maps P to the same P in the container. Ray advertises the public IP of the host and the ports P. So other nodes reach the worker directly.
| Identity port | Use |
|---|---|
| 70000 | Ray node manager |
| 70001 | Ray object manager |
| 70002 | Metrics export |
| 70003 | Dashboard agent gRPC |
| 70004 | Dashboard agent HTTP (job port, 127.0.0.1 only) |
| 70005 | Runtime-env agent (127.0.0.1 only) |
| 70006–70063 | up to 58 Ray worker processes (fewer with a process limit) |
The provider adds direct_port_count >= 64 to every offer query, so every host can give the 64 ports.
Before a candidate enrolls, the head connects to all 64 ports from outside (the port check).
The networking shim¶
Ray binds and connects to the address that it advertises. In a container, that address is the host's public IP, which the container does not own.
Without help, the bind fails (Errno 99), and the Ray node stops.
ray-vast loads a small LD_PRELOAD library, the shim, into all Ray processes on the head and on the workers:
- A bind to the public IP becomes a bind to the private IP.
- A connect to the node's own public IP becomes a connect to the private IP. So the node does not need hairpin NAT.
- The job port and the runtime-env agent port bind to 127.0.0.1 only. So the token never goes over the network for these ports.
The head sends the shim to each worker when the worker enrolls.
Head ports¶
| Port | Use | Open to |
|---|---|---|
| 22 | SSH | Your IP |
| 6379 | Ray GCS | Worker IPs |
| 6380–6383 | Ray node manager, object manager, metrics, agent gRPC | Worker IPs |
| 6384, 6385 | Job port, runtime-env agent | 127.0.0.1 only |
| 8265 | Ray dashboard | 127.0.0.1 only (use ray-vast dashboard) |
| 10002–10100 | Ray worker processes | Worker IPs |
| 7443 | Head server: bootstrap, CA, enrollment (TLS) | Worker IPs |
| 7444 | Head server: heartbeat, reports (mTLS) | Worker IPs |
The provider adds one security group rule (all TCP) for each worker IP, before the worker enrolls. It removes the rule when no worker or candidate uses the IP any more.
The full design¶
The full specification, with the Ray facts and the worker and head protocol, is in design.md on GitHub.