Deploy Anycable with Kamal(formely MRSK)

haukot

Dmitry Daw

Posted on August 3, 2023

Deploy Anycable with Kamal(formely MRSK)

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"
Enter fullscreen mode Exit fullscreen mode

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
# ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 default web entrypoint at port 80

  • 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"

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

(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"
Enter fullscreen mode Exit fullscreen mode

(source)

And then use hosts from this network

💖 💪 🙅 🚩
haukot
Dmitry Daw

Posted on August 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related