Skip to content

Commit ad0c88a

Browse files
committed
+ kamal.md
1 parent 9f1158f commit ad0c88a

2 files changed

Lines changed: 246 additions & 0 deletions

File tree

docs/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* Deployment
3434
* [Heroku](/deployment/heroku.md)
3535
* [Fly.io](/deployment/fly.md)
36+
* [Kamal](/deployment/kamal.md)
3637
* [Render](/deployment/render.md)
3738
* <a rel="noopener" href="https://hatchbox.relationkit.io/articles/12-does-hatchbox-support-anycable" target="_blank">Hatchbox 🔗</a>
3839
* [Docker](/deployment/docker.md)

docs/deployment/kamal.md

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# Kamal
2+
3+
[Kamal](https://kamal-deploy.org/) is a deployment tool from Basecamp that makes it easy to deploy Rails applications with Docker. This guide covers different approaches to deploying AnyCable web server and RPC servers (if required) with Kamal 2.
4+
5+
**NOTE:** This guide assumes that the primary application framework is Ruby on Rails. However, most ideas could be applied to other frameworks and stacks.
6+
7+
There is a number of ways you can run AnyCable with Kamal depending on your needs. Here is the table describing recommended setups based on such factors as expected load, the number of servers (machines), whether you need an RPC server or not:
8+
9+
| Setup | Load | Servers | RPC | Recommended Approach |
10+
|-------|------|---------|--------------|---------------------|
11+
| Small | Low | 1 | No | Anycable Thruster |
12+
| Small | Low | 1 | Yes | AnyCable Thruster + Embedded gRPC or HTTP RPC |
13+
| Small | Medium | 1 | Yes | AnyCable Thruster + RPC role |
14+
| Medium | Medium | 1-2 | No | AnyCable accessory (single server) |
15+
| Medium | Medium | 1-2 | Yes | AnyCable accessory (single server) + RPC role (each server) |
16+
| Large | High | 3+ | No | AnyCable accessory (many servers) + Redis/NATS |
17+
| Large | High | 3+ | Yes | AnyCable accessory (many servers) + Redis/NATS + RPC role (each server) |
18+
19+
### Using Thruster
20+
21+
The simplest way to deploy AnyCable with Kamal is using the [anycable-thruster](https://github.com/anycable/thruster) gem, which allows you to run AnyCable alongside your Rails web server in a single container.
22+
23+
Rails' default Dockerfile already uses Thruster as its proxy server, so no additional changes required.
24+
25+
With this setup, we recommend getting started with an [embedded gRPC server](/rails/getting_started?id=embedded-grpc-server) or [HTTP RPC](https://docs.anycable.io/ruby/http_rpc), so you can keep the Kamal configuration untouched.
26+
27+
### Deploying AnyCable as an Accessory
28+
29+
For applications that need more control or better resource isolation, you can deploy AnyCable server separately as a Kamal _accessory_. With this approach, running `kamal setup` should be sufficient to make AnyCable server up and running.
30+
31+
One particular benefit AnyCable benefit this approach brings is **zero-disconnect deployments** (WebSocket connections are kept between application restarts).
32+
33+
However, there is a trade-off of having to use a separate domain name for AnyCable server (e.g., `ws.myapp.whatever`). That might require taking additional care of authentication (e.g., cookie-sharing). We recommend using AnyCable's built-in [JWT authentication](/anycable-go/jwt_identification) to not worry about that.
34+
35+
> See this [demo PR](https://github.com/anycable/anycable_rails_demo/pull/37) for a complete configuration example.
36+
37+
Here is a `config/deploy.yml` example with the AnyCable accessory:
38+
39+
```yaml
40+
# ...
41+
42+
accessories:
43+
# ...
44+
anycable-go:
45+
image: anycable/anycable-go:1.6
46+
host: 192.168.0.1
47+
proxy:
48+
host: ws.demo.anycable.io
49+
ssl: true
50+
app_port: 8080
51+
healthcheck:
52+
path: /health
53+
env:
54+
clear:
55+
ANYCABLE_HOST: "0.0.0.0"
56+
ANYCABLE_PORT: 8080
57+
ANYCABLE_BROADCAST_ADAPTER: http
58+
ANYCABLE_HTTP_BROADCAST_PORT: 8080
59+
secret:
60+
- ANYCABLE_SECRET
61+
```
62+
63+
The important bits are:
64+
65+
- `proxy` configuration for `anycable` accessory; it's required to server incoming traffic via Kamal
66+
67+
- we configure AnyCable to receive broadcast HTTP requests on the same port served by Kamal Proxy to avoid publishing any additional ports; specifying `ANYCABLE_SECRET` is required to ensure your HTTP broadcasting endpoint is secured.
68+
69+
The example above uses HTTP broadcasting. If you want to use Redis, it will look as follows:
70+
71+
```yaml
72+
# Name of your service defines accessory service names
73+
service: anycable_rails_demo
74+
75+
# ...
76+
77+
accessories:
78+
# ...
79+
redis:
80+
image: redis:7.0
81+
host: 192.168.0.1
82+
directories:
83+
- data:/data
84+
anycable-go:
85+
image: anycable/anycable-go:1.6
86+
host: 192.168.0.1
87+
proxy:
88+
host: ws.demo.anycable.io
89+
ssl: true
90+
app_port: 8080
91+
healthcheck:
92+
path: /health
93+
env:
94+
clear:
95+
ANYCABLE_HOST: "0.0.0.0"
96+
ANYCABLE_PORT: 8080
97+
ANYCABLE_REDIS_URL: "redis://anycable_rails_demo-redis:6379/0"
98+
```
99+
100+
Note that if you want to run AnyCable servers on multiple hosts and use Redis for pub/sub, you must provide the same static Redis address for all AnyCable accessories (and better protect it at least via a password):
101+
102+
```yaml
103+
accessories:
104+
# ...
105+
redis:
106+
host: <%= ENV.fetch('REDIS_HOST') %>
107+
image: redis:8.0-alpine
108+
port: "6379:6379"
109+
cmd: redis-server --requirepass <%= ENV.fetch("REDIS_PASSWORD") %>
110+
volumes:
111+
- redisdata:/data
112+
anycable-go:
113+
image: anycable/anycable-go:1.6
114+
host: <%= ENV.fetch("ANYCABLE_HOST") %>
115+
proxy:
116+
# ..
117+
env:
118+
clear:
119+
ANYCABLE_HOST: "0.0.0.0"
120+
ANYCABLE_PORT: 8080
121+
ANYCABLE_REDIS_URL: "redis://:<%= ENV.fetch("REDIS_PASSWORD") %>@<%= ENV.fetch("REDIS_HOST") %>:6379/0"
122+
```
123+
124+
The example above assumes that we store various configuration parameters such as IP addresses in the `.env` file (so, the actual configuration is _parameterized_). See the full example [here](https://github.com/anycable/anycable_rails_demo/pull/39).
125+
126+
#### Using Embedded NATS
127+
128+
AnyCable can run with an embedded NATS server, eliminating the need for Redis:
129+
130+
```yaml
131+
accessories:
132+
# ...
133+
anycable-go:
134+
host: <%= ENV.fetch("ANYCABLE_HOST") %>
135+
image: anycable/anycable-go:1.6.2-alpine
136+
env:
137+
clear:
138+
<<: *default_env
139+
ANYCABLE_HOST: "0.0.0.0"
140+
ANYCABLE_PORT: "8080"
141+
ANYCABLE_EMBED_NATS: "true"
142+
ANYCABLE_PUBSUB: nats
143+
ANYCABLE_BROADCAST_ADAPTER: "http"
144+
ANYCABLE_HTTP_BROADCAST_PORT: 8080
145+
ANYCABLE_ENATS_ADDR: "nats://0.0.0.0:4242"
146+
ANYCABLE_ENATS_CLUSTER: "nats://0.0.0.0:4243"
147+
secret:
148+
- ANYCABLE_SECRET
149+
options:
150+
publish:
151+
- "4242:4242"
152+
- "4243:4243"
153+
proxy:
154+
host: <%= ENV.fetch("WS_PROXY_HOST") %>
155+
ssl: true
156+
app_port: 8080
157+
healthcheck:
158+
path: /health
159+
interval: 1
160+
timeout: 5
161+
```
162+
163+
The complete example of deploying AnyCable with embedded NATS via Kamal can be found in [this PR](https://github.com/anycable/anycasts_demo/pull/19).
164+
165+
## Deploying gRPC servers
166+
167+
AnyCable RPC server using gRPC transport should be deployed as separate _server role_ (not an accessory), since it serves your application. Thus, you must add to the list of servers as follows:
168+
169+
```yaml
170+
service: anycable_rails_demo
171+
172+
servers:
173+
web:
174+
- 192.168.0.1
175+
176+
anycable-rpc:
177+
hosts:
178+
- 192.168.0.1
179+
cmd: bundle exec anycable
180+
proxy: false
181+
options:
182+
network-alias: anycable_rails_demo-rpc
183+
184+
accessories:
185+
# ...
186+
anycable-go:
187+
# ...
188+
env:
189+
clear:
190+
ANYCABLE_HOST: "0.0.0.0"
191+
ANYCABLE_PORT: 8080
192+
ANYCABLE_RPC_HOST: anycable_rails_demo-rpc:50051
193+
secret:
194+
- ANYCABLE_SECRET
195+
```
196+
197+
The important bits are:
198+
199+
- `proxy: false` is required to skip Kamal Proxy (it doesn't support gRPC)
200+
201+
- `network-alias: anycable_rails_demo-rpc` allows us to use an fixed Docker service name to access the RPC server container from the accessory.
202+
203+
### Scaling gRPC servers horizontally
204+
205+
> See this [demo PR](https://github.com/anycable/anycable_rails_demo/pull/39) for a complete configuration example.
206+
207+
AnyCable-Go 1.6.2+ supports the `grpc-list://` scheme to connect to multiple RPC endpoints. This way, you can spread RPC traffic accross machines:
208+
209+
```yaml
210+
# ...
211+
servers:
212+
web:
213+
# ...
214+
215+
rpc:
216+
hosts: <%= ENV.fetch("RPC_HOSTS").split(",") %>
217+
cmd: bundle exec anycable
218+
env:
219+
clear:
220+
<<: *default_env
221+
ANYCABLE_RPC_HOST: "0.0.0.0:50051"
222+
options:
223+
publish:
224+
- "50051:50051"
225+
proxy: false
226+
227+
accessories:
228+
# ...
229+
anycable-go:
230+
host: <%= ENV.fetch("WS_HOSTS") %>
231+
image: anycable/anycable-go:1.6.2-alpine
232+
env:
233+
clear:
234+
<<: *default_env
235+
ANYCABLE_HOST: "0.0.0.0"
236+
ANYCABLE_PORT: "8080"
237+
# Using a fixed list of RPC addresses https://docs.anycable.io/deployment/load_balancing?id=using-a-fixed-list-of-rpc-addresses
238+
ANYCABLE_RPC_HOST: "grpc-list://<%= ENV.fetch("RPC_HOSTS").split(",").map { "#{_1}:50051" }.join(",") %>"
239+
proxy:
240+
# ...
241+
```
242+
243+
**IMPORTANT**: The setup above expose the gRPC server to the public (so it's reachable from other machines). We recommend securing access either by setting up firewall rules / virtual network within the cluster or using TLS with a private certificate for gRPC (see [configuration docs](https://docs.anycable.io/anycable-go/configuration?id=tls)).
244+
245+
Alternatively, you may consider adding a standalone load balancer with gRPC support (this is out of scope of this guide).

0 commit comments

Comments
 (0)