Skip to content

Commit f046047

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

3 files changed

Lines changed: 249 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: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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+
## Deploying AnyCable server
20+
21+
### Using Thruster
22+
23+
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.
24+
25+
Rails' default Dockerfile already uses Thruster as its proxy server, so no additional changes required.
26+
27+
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.
28+
29+
### Deploying AnyCable as an Accessory
30+
31+
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.
32+
33+
One particular benefit AnyCable benefit this approach brings is **zero-disconnect deployments** (WebSocket connections are kept between application restarts).
34+
35+
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.
36+
37+
> See this [demo PR](https://github.com/anycable/anycable_rails_demo/pull/37) for a complete configuration example.
38+
39+
Here is a `config/deploy.yml` example with the AnyCable accessory:
40+
41+
```yaml
42+
# ...
43+
44+
accessories:
45+
# ...
46+
anycable-go:
47+
image: anycable/anycable-go:1.6
48+
host: 192.168.0.1
49+
proxy:
50+
host: ws.demo.anycable.io
51+
ssl: true
52+
app_port: 8080
53+
healthcheck:
54+
path: /health
55+
env:
56+
clear:
57+
ANYCABLE_HOST: "0.0.0.0"
58+
ANYCABLE_PORT: 8080
59+
ANYCABLE_BROADCAST_ADAPTER: http
60+
ANYCABLE_HTTP_BROADCAST_PORT: 8080
61+
secret:
62+
- ANYCABLE_SECRET
63+
```
64+
65+
The important bits are:
66+
67+
- `proxy` configuration for `anycable` accessory; it's required to server incoming traffic via Kamal
68+
69+
- 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.
70+
71+
The example above uses HTTP broadcasting. If you want to use Redis, it will look as follows:
72+
73+
```yaml
74+
# Name of your service defines accessory service names
75+
service: anycable_rails_demo
76+
77+
# ...
78+
79+
accessories:
80+
# ...
81+
redis:
82+
image: redis:7.0
83+
host: 192.168.0.1
84+
directories:
85+
- data:/data
86+
anycable-go:
87+
image: anycable/anycable-go:1.6
88+
host: 192.168.0.1
89+
proxy:
90+
host: ws.demo.anycable.io
91+
ssl: true
92+
app_port: 8080
93+
healthcheck:
94+
path: /health
95+
env:
96+
clear:
97+
ANYCABLE_HOST: "0.0.0.0"
98+
ANYCABLE_PORT: 8080
99+
ANYCABLE_REDIS_URL: "redis://anycable_rails_demo-redis:6379/0"
100+
```
101+
102+
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):
103+
104+
```yaml
105+
accessories:
106+
# ...
107+
redis:
108+
host: <%= ENV.fetch('REDIS_HOST') %>
109+
image: redis:8.0-alpine
110+
port: "6379:6379"
111+
cmd: redis-server --requirepass <%= ENV.fetch("REDIS_PASSWORD") %>
112+
volumes:
113+
- redisdata:/data
114+
anycable-go:
115+
image: anycable/anycable-go:1.6
116+
host: <%= ENV.fetch("ANYCABLE_HOST") %>
117+
proxy:
118+
# ..
119+
env:
120+
clear:
121+
ANYCABLE_HOST: "0.0.0.0"
122+
ANYCABLE_PORT: 8080
123+
ANYCABLE_REDIS_URL: "redis://:<%= ENV.fetch("REDIS_PASSWORD") %>@<%= ENV.fetch("REDIS_HOST") %>:6379/0"
124+
```
125+
126+
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).
127+
128+
#### Using Embedded NATS
129+
130+
AnyCable can run with an embedded NATS server, eliminating the need for Redis:
131+
132+
```yaml
133+
accessories:
134+
# ...
135+
anycable-go:
136+
host: <%= ENV.fetch("ANYCABLE_HOST") %>
137+
image: anycable/anycable-go:1.6.2-alpine
138+
env:
139+
clear:
140+
<<: *default_env
141+
ANYCABLE_HOST: "0.0.0.0"
142+
ANYCABLE_PORT: "8080"
143+
ANYCABLE_EMBED_NATS: "true"
144+
ANYCABLE_PUBSUB: nats
145+
ANYCABLE_BROADCAST_ADAPTER: "http"
146+
ANYCABLE_HTTP_BROADCAST_PORT: 8080
147+
ANYCABLE_ENATS_ADDR: "nats://0.0.0.0:4242"
148+
ANYCABLE_ENATS_CLUSTER: "nats://0.0.0.0:4243"
149+
secret:
150+
- ANYCABLE_SECRET
151+
options:
152+
publish:
153+
- "4242:4242"
154+
- "4243:4243"
155+
proxy:
156+
host: <%= ENV.fetch("WS_PROXY_HOST") %>
157+
ssl: true
158+
app_port: 8080
159+
healthcheck:
160+
path: /health
161+
interval: 1
162+
timeout: 5
163+
```
164+
165+
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).
166+
167+
## Deploying gRPC servers
168+
169+
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:
170+
171+
```yaml
172+
service: anycable_rails_demo
173+
174+
servers:
175+
web:
176+
- 192.168.0.1
177+
178+
anycable-rpc:
179+
hosts:
180+
- 192.168.0.1
181+
cmd: bundle exec anycable
182+
proxy: false
183+
options:
184+
network-alias: anycable_rails_demo-rpc
185+
186+
accessories:
187+
# ...
188+
anycable-go:
189+
# ...
190+
env:
191+
clear:
192+
ANYCABLE_HOST: "0.0.0.0"
193+
ANYCABLE_PORT: 8080
194+
ANYCABLE_RPC_HOST: anycable_rails_demo-rpc:50051
195+
secret:
196+
- ANYCABLE_SECRET
197+
```
198+
199+
The important bits are:
200+
201+
- `proxy: false` is required to skip Kamal Proxy (it doesn't support gRPC)
202+
203+
- `network-alias: anycable_rails_demo-rpc` allows us to use an fixed Docker service name to access the RPC server container from the accessory.
204+
205+
### Scaling gRPC servers horizontally
206+
207+
> See this [demo PR](https://github.com/anycable/anycable_rails_demo/pull/39) for a complete configuration example.
208+
209+
AnyCable-Go 1.6.2+ supports the `grpc-list://` scheme to connect to multiple RPC endpoints. This way, you can spread RPC traffic across machines:
210+
211+
```yaml
212+
# ...
213+
servers:
214+
web:
215+
# ...
216+
217+
rpc:
218+
hosts: <%= ENV.fetch("RPC_HOSTS").split(",") %>
219+
cmd: bundle exec anycable
220+
env:
221+
clear:
222+
<<: *default_env
223+
ANYCABLE_RPC_HOST: "0.0.0.0:50051"
224+
options:
225+
publish:
226+
- "50051:50051"
227+
proxy: false
228+
229+
accessories:
230+
# ...
231+
anycable-go:
232+
host: <%= ENV.fetch("WS_HOSTS") %>
233+
image: anycable/anycable-go:1.6.2-alpine
234+
env:
235+
clear:
236+
<<: *default_env
237+
ANYCABLE_HOST: "0.0.0.0"
238+
ANYCABLE_PORT: "8080"
239+
# Using a fixed list of RPC addresses https://docs.anycable.io/deployment/load_balancing?id=using-a-fixed-list-of-rpc-addresses
240+
ANYCABLE_RPC_HOST: "grpc-list://<%= ENV.fetch("RPC_HOSTS").split(",").map { "#{_1}:50051" }.join(",") %>"
241+
proxy:
242+
# ...
243+
```
244+
245+
**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)).
246+
247+
Alternatively, you may consider adding a standalone load balancer with gRPC support (this is out of scope of this guide).

forspell.dict

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ Stackblitz
5858
Laravel
5959
observability
6060
Kamal
61+
Thruster

0 commit comments

Comments
 (0)