Skip to content

Commit 1572dd4

Browse files
committed
docs: Cloud Run deployment guide for sam-one
Verified end to end: single-port wss enrollment, a relayed service call between two NAT-hidden nodes through the Cloud Run router, and the admin CLI against the public URL. Documents the load-bearing flags (singleton, no CPU throttling, request timeout), the reserved /healthz path, ephemeral state, and rollout overlap behavior.
1 parent f7ac1ae commit 1572dd4

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
---
2+
title: "Cloud Run Deployment"
3+
linkTitle: "Cloud Run Deployment"
4+
weight: 6
5+
---
6+
7+
# Deploying a SAM Mesh on Cloud Run
8+
9+
`sam-one` is the all-in-one SAM distribution: control plane, libp2p router,
10+
web console and storage in a single binary serving a single public port.
11+
Because everything — REST API, console and mesh (WebSocket) traffic — is
12+
multiplexed on one HTTP port, it runs on Cloud Run and any platform that
13+
forwards HTTP/WebSockets to a container.
14+
15+
This guide was validated end to end on Cloud Run: node enrollment over
16+
`wss`, a relayed service call between two NAT-hidden nodes through the
17+
Cloud Run router, and the admin CLI against the public URL.
18+
19+
## 1. Build and push the image
20+
21+
```bash
22+
# Pick your project/region and an Artifact Registry docker repository.
23+
PROJECT=my-project
24+
REGION=us-central1
25+
IMG=${REGION}-docker.pkg.dev/${PROJECT}/sam-mesh/sam-one:latest
26+
27+
docker build -f Dockerfile.sam-one -t "$IMG" .
28+
gcloud auth configure-docker ${REGION}-docker.pkg.dev
29+
docker push "$IMG"
30+
```
31+
32+
## 2. Deploy the service
33+
34+
Cloud Run has no persistent disk, so pass fixed join/admin tokens as
35+
environment variables — otherwise fresh ones are generated on every
36+
instance start:
37+
38+
```bash
39+
JOIN_TOKEN="sam_tok_$(openssl rand -hex 16)"
40+
ADMIN_TOKEN="sam_adm_$(openssl rand -hex 16)"
41+
42+
gcloud run deploy sam-one \
43+
--project "$PROJECT" --region "$REGION" \
44+
--image "$IMG" \
45+
--allow-unauthenticated \
46+
--min-instances 1 --max-instances 1 \
47+
--port 8080 \
48+
--no-cpu-throttling \
49+
--timeout 3600 \
50+
--set-env-vars "SAM_TOKEN=${JOIN_TOKEN},SAM_ADMIN_TOKEN=${ADMIN_TOKEN}"
51+
```
52+
53+
Flag notes, all load-bearing:
54+
55+
* **`--min-instances 1 --max-instances 1`** — standalone mode is a
56+
singleton: the router's DHT and relay state live in the one process.
57+
* **`--no-cpu-throttling`** — the router runs background loops (leases,
58+
key sync, DHT); request-based CPU throttling stalls them between
59+
requests.
60+
* **`--timeout 3600`** — Cloud Run caps streaming requests; WebSocket mesh
61+
connections live inside that budget. Nodes reconnect automatically when
62+
the cap severs a connection, but a low timeout means needless churn.
63+
* **`--port 8080`** matches the image's default `--port 8080` argument
64+
(see the `CMD` in `Dockerfile.sam-one`).
65+
66+
Then tell the router its public URL so it advertises a dialable `wss`
67+
multiaddr (the URL is only known after the first deploy):
68+
69+
```bash
70+
URL=$(gcloud run services describe sam-one --project "$PROJECT" \
71+
--region "$REGION" --format='value(status.url)')
72+
73+
gcloud run services update sam-one \
74+
--project "$PROJECT" --region "$REGION" \
75+
--update-env-vars "SAM_EXTERNAL_URL=${URL}"
76+
```
77+
78+
## 3. Verify
79+
80+
```bash
81+
curl -s "$URL/readyz" # 200
82+
curl -s "$URL/info" | head -c 200 # advertises /dns4/<host>/tcp/443/wss/p2p/<peer-id>
83+
```
84+
85+
The web console is served from the same URL at `${URL}/console`.
86+
87+
> [!NOTE]
88+
> `/healthz` returns a Google frontend 404 on `run.app` domains — the
89+
> path is reserved by the platform and never reaches the container. Use
90+
> `/readyz` for probes.
91+
92+
## 4. Join nodes from anywhere
93+
94+
```bash
95+
sam-node run --control-plane "$URL" --bootstrap-token "$JOIN_TOKEN"
96+
```
97+
98+
The node enrolls over HTTPS, discovers the router's `wss` multiaddr from
99+
`/info`, and connects through the same public port. Nodes behind NAT are
100+
reachable by other nodes via relay circuits through the Cloud Run router —
101+
no inbound connectivity required on either side.
102+
103+
## 5. Example: share a service across the mesh
104+
105+
This walkthrough was run verbatim against a Cloud Run deployment: two
106+
nodes on different networks, neither reachable from the other
107+
(`--announce-private=false` withholds their private addresses, forcing all
108+
traffic through the Cloud Run relay).
109+
110+
On the **provider** machine, run a node and any local HTTP backend:
111+
112+
```bash
113+
sam-node run --control-plane "$URL" --bootstrap-token "$JOIN_TOKEN" \
114+
--data-dir ~/provider --announce-private=false &
115+
116+
mkdir -p /tmp/www && echo "hello from provider" > /tmp/www/hello.txt
117+
python3 -m http.server 9000 --bind 127.0.0.1 --directory /tmp/www &
118+
```
119+
120+
Register the backend as a mesh service through the node's local sidecar
121+
socket (owner-only permissions replace the API token) and note the node's
122+
PeerID from its startup output:
123+
124+
```bash
125+
curl --unix-socket ~/provider/sam.sock -X POST \
126+
-H "Content-Type: application/json" \
127+
-d '{"service":{"type":"SERVICE_TYPE_MCP","name":"hello","description":"example"},
128+
"targetUrl":"http://127.0.0.1:9000"}' \
129+
http://localhost/sam/service/register
130+
# -> Service registered
131+
```
132+
133+
On the **consumer** machine, run a node the same way, then call the
134+
service by peer and name through the local egress proxy:
135+
136+
```bash
137+
sam-node run --control-plane "$URL" --bootstrap-token "$JOIN_TOKEN" \
138+
--data-dir ~/consumer --announce-private=false &
139+
140+
curl --unix-socket ~/consumer/sam.sock \
141+
"http://localhost/sam/<provider-peer-id>/mcp/hello/hello.txt"
142+
# -> hello from provider
143+
```
144+
145+
The request crosses consumer → Cloud Run router (relay circuit) →
146+
provider → backend, with mutual Biscuit authentication between the nodes
147+
and policy enforced on the service name. Allow a few seconds after
148+
registration for propagation on first call.
149+
150+
## 6. Operate with the CLI
151+
152+
The `sam-one` binary doubles as an admin client for the running service:
153+
154+
```bash
155+
export SAM_ADMIN_TOKEN="$ADMIN_TOKEN"
156+
157+
# Mint a scoped, single-use enrollment token
158+
sam-one token create --server "$URL" --role sam:role:node --max-usages 1
159+
160+
# List tokens and their usage
161+
sam-one token list --server "$URL"
162+
163+
# Ban a peer from the mesh
164+
sam-one admin ban --server "$URL" <peer-id>
165+
```
166+
167+
## Operational notes
168+
169+
* **State is ephemeral.** The SQLite database lives in the container's
170+
in-memory filesystem: enrolled nodes and minted tokens are lost on
171+
instance restart, and the router's peer identity rotates (nodes
172+
re-discover it via `/info` and re-enroll with the join token). Keep
173+
`SAM_TOKEN`/`SAM_ADMIN_TOKEN` pinned via env vars. For durable state,
174+
run `sam-one` on a VM with a disk, or point `--db-driver postgres` at a
175+
managed database.
176+
* **Rollouts briefly overlap revisions.** During a deploy, `/info` may
177+
advertise the new instance while some WebSocket upgrades still land on
178+
the draining one; nodes refuse the peer-ID mismatch and retry. Joins
179+
succeed once the old revision drains.
180+
* **Proxied traffic shares source IPs.** All traffic reaches the
181+
container from a handful of frontend proxy IPs. `sam-one` already
182+
raises libp2p's per-source-IP connection and rate budgets for this
183+
(`ConnsPerSourceIP`); if you front a discrete `sam-router` with a proxy
184+
yourself, set `--conns-per-source-ip` accordingly.
185+
186+
## Anywhere else
187+
188+
The same single-port binary runs on any host without flags:
189+
190+
```bash
191+
sam-one --data-dir /var/lib/sam-one
192+
```
193+
194+
A free port is picked and published in the startup banner together with
195+
the generated tokens; pass `--port 8080` (and optionally
196+
`--bind-address`) for a fixed one, and `--external-url https://mesh.example.com`
197+
when fronted by a reverse proxy or DNS name.

0 commit comments

Comments
 (0)