Dmitry Daw
Posted on August 3, 2023
Here we'll deploy Anycable wih Kamal(MRSK before).
We need our Rails code for anycable-rpc, so anycable-rpc will clearly be a role.
The Anycable websocket server (in our case, anycable-go) could be installed as an accessory, or as a role.
To simplify the setup, we'll utilize --server-command option of anycable
.
This will run the specified command after anycable is launched.
In our case - it'll initiate anycable-go.
bundle exec anycable --server-command "anycable-go"
To run anycable-go, we need to install its binary directly in our Rails app's Docker container:
# Dockerfile
# ...
RUN curl -fsSL https://github.com/anycable/anycable-go/releases/download/v1.4.1/anycable-go-linux-amd64 -o anycable-go \
&& chmod +x anycable-go
&& cp anycable-go /usr/local/bin/anycable-go
# ...
MRSK config
We have websockets on the same host as our main web app. Therefore, we need to set up Traefik so that the /cable path will be handled by the anycable-go server, not the web.
To do this, we can utilize this config:
# role's options in config/deploy.yml
labels:
traefik.enable: true
traefik.http.routers.my_service-anycable.rule: 'PathPrefix(`/cable`)'
traefik.http.routers.my_service-anycable.entrypoints: 'web'
traefik.http.services.my_service-anycable.loadbalancer.server.port: 8080
In my_service-anycable
, my_service
is the service name in MRSK, and anycable
is the role name in MRSK.
This could also contain a destination, if you're using it.
This name should be the same as the role's traefik_service name.
traefik.http.routers.my_service-anycable.rule: 'PathPrefix(
/cable
)'
adds a rule that we only want requests from the/cable
path.traefik.http.routers.my_service-anycable.entrypoints: 'web'
sets the entrypoint to the defaultweb
entrypoint at port80
traefik.http.services.my_service-anycable.loadbalancer.server.port: 8080
sets the port to which Traefik should send requests to our container.
Then, we can add the necessary Anycable environment variables and set up the MRSK deploy config:
# config/deploy.yml
service: my_service
servers:
web:
- 10.0.0.155
# anycable-rpc together with anycable-go
anycable:
hosts:
- 10.0.0.155
cmd: bundle exec anycable --server-command "anycable-go"
env:
clear:
ANYCABLE_RPC_HOST: localhost:50051
ANYCABLE_HOST: "0.0.0.0"
ANYCABLE_PORT: 8080
ANYCABLE_REDIS_URL: "redis://:<%= ENV['REDIS_PASSWORD'] %>@<%= ENV['REDIS_HOST'] %>:6379/0"
ANYCABLE_RPC_HOST: "localhost:50051"
labels:
traefik.enable: true
traefik.http.routers.my_service-anycable.rule: 'PathPrefix(`/cable`)'
traefik.http.routers.my_service-anycable.entrypoints: 'web'
traefik.http.services.my_service-anycable.loadbalancer.server.port: 8080
env:
clear:
ANYCABLE_REDIS_URL: "redis://:<%= ENV['REDIS_PASSWORD'] %>@<%= ENV['REDIS_HOST'] %>:6379/0"
REDIS_URL: "redis://:<%= ENV['REDIS_PASSWORD'] %>@<%= ENV['REDIS_HOST'] %>:6379/1"
ANYCABLE_URL: "wss://<%= ENV['ACTIONCABLE_HOST'] %>/cable"
That's all! This sets up anycable-rpc with anycable-go on your server.
Other deploy variants
If you want to separate anycable-rpc and anycable-go, you have several options to do so.
Separate hosts
You could deploy anycable-go as an accessory on another host, then you could use a different Docker image and not install it in the Rails app's Docker image.
But in this setup, you need to publish ports for both anycable-rpc and anycable-go.
service: my_service
servers:
web:
- 10.0.0.155
anycable-rpc:
hosts:
- 10.0.0.155
cmd: bundle exec anycable
port: 50051
env:
clear:
ANYCABLE_RPC_HOST: 0.0.0.0:50051
labels:
traefik.tcp.routers.my_service-anycable-rpc.rule: 'HostSNI(`*`)'
traefik.tcp.routers.my_service-anycable-rpc.entrypoints: rpc
traefik.tcp.services.my_service-anycable-rpc.loadbalancer.server.port: 50051
traefik:
options:
publish:
- 50051:50051
args:
entrypoints.web.address: ":80"
entrypoints.rpc.address: ":50051"
accesslog: true
accessories:
ws:
image: anycable/anycable-go:1.4
host: 10.0.0.166
port: "80:8080"
env:
clear:
ANYCABLE_HOST: "0.0.0.0"
ANYCABLE_PORT: 8080
ANYCABLE_REDIS_URL: "redis://:<%= ENV['REDIS_PASSWORD'] %>@<%= ENV['REDIS_HOST'] %>:6379/0"
ANYCABLE_RPC_HOST: "10.0.0.155:50051"
ANYCABLE_DEBUG: true
Setup anycable-go as accessory on the same host
You (most likely) could use anycable-go as an accessory, and write Traefik configs in the labels of the accessory.
But after doing this, you'll need to use the host's IP address or the Docker internal network (as mentioned below in other options).
Use the host's IP address
Just publish the ports to the outside and use the host's IP address from inside the container.
Use the host's network
Add the internal host IP as the Docker internal host.
servers:
web:
hosts:
- 10.0.0.155
anycable-rpc:
hosts:
- 10.0.0.155
options:
"add-host": host.docker.internal:host-gateway
# ... env, cmd, etc
anycable-go:
hosts:
- 10.0.0.155
options:
"add-host": host.docker.internal:host-gateway
# ... env, cmd, etc
(source)
Then you could use host.docker.internal
as host in anycable-rpc and anycable-go containers
Create docker internal network by hands and then use it
# Create the network first by ssh'ing into machine or using something like Ansible
servers:
web:
hosts:
- 10.0.1.183
options:
"network": "my-network"
traefik:
options:
network: "my-network"
accessories:
db:
image: postgres:14
host: 10.0.1.183
... etc
options:
network: "my-network"
(source)
And then use hosts from this network
Posted on August 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.