slouchpie

slouchpie

Using :dns_cluster with docker-compose locally (it can be done)

Warmest greetings, comrades.

I recently started using :dns_cluster (GitHub - phoenixframework/dns_cluster: Simple DNS clustering for distributed Elixir nodes) in a couple of production projects which are deployed on Fly. These all suffer from various “distributed” problems (not because of dns_cluster, just because of normal distributed problems). I have been trying to emulate the setup locally with minimal setup for new devs. I did not want to use a different clustering lib in dev (e.g. libcluster) because I think these kind of differences between prod and dev make debugging almost impossible.

So I have a working project using docker-compose where I have 2 instances of the same Phoenix application with static IP addresses on a custom docker network and with node long-names of form app_name@ip_address. This mimics the standard long-names seen on fly.io machines.

I thought about writing a blog post about it but time is very precious these days. I am already overloaded. So I am posting this here, hopefully with adequate tags and keywords to be found by someone searching.

Broad strokes what to do:

2 services in docker-compose.override.yaml

name: myapp
services:
  myapp1: &myapp_mapping
    ports:
      - "4000:4000"
    env_file: ../docker/myapp/dev.env
    environment:
      IP_V4_ADDRESS: 192.0.1.11
    volumes:
      - ../assets:/app/assets
      - ../config:/app/config:ro
      - ../lib:/app/lib:ro
      - ../priv:/app/priv
      - ../seeds:/app/seeds:ro
      - ../test:/app/test:ro
      - ../mix.exs:/app/mix.exs:ro
      - ../mix.lock:/app/mix.lock:ro
      - ../.iex.exs:/app/.iex.exs:ro
    networks:
      erlcluster:
        ipv4_address: 192.0.1.11

  myapp2:
    <<: *myapp_mapping
    ports:
      - "4001:4000"
    environment:
      IP_V4_ADDRESS: 192.0.1.12
    networks:
      erlcluster:
        ipv4_address: 192.0.1.12

networks:
  erlcluster:
    ipam:
      driver: default
      config:
        - subnet: "192.0.1.0/24"

Note a custom network is created using IP range reserved for documentation and testing purposes. The volumes is a trick to get hot-reloading.

Use the ipv4_address in your docker entrypoint, e.g.

iex --name myapp@$1 --cookie $2 -S mix phx.server

the cookie is in the dev.env file.

Note both nodes have same shortname (“myapp”) but different hostnames (the ipv4 address).

So that is it in broad strokes.

The next trick is to write a custom DNS resolver module for DNSCluster, like this:

defmodule MyApp.DevDNSClusterResolver do
  @moduledoc false

  require Record

  Record.defrecord(:hostent, Record.extract(:hostent, from_lib: "kernel/include/inet.hrl"))

  def basename(node_name) when is_atom(node_name) do
    [basename, _] = String.split(to_string(node_name), "@")
    basename
  end

  def connect_node(node_name) when is_atom(node_name), do: Node.connect(node_name)

  def list_nodes, do: Node.list(:visible)

  def lookup(query, type) when is_binary(query) and type in [:a, :aaaa] do
    query
    |> String.split()
    |> Enum.reduce([], fn query, acc ->
      case :inet_res.getbyname(~c"#{query}", type) do
        {:ok, hostent(h_addr_list: addr_list)} -> addr_list ++ acc
        {:error, _} -> acc
      end
    end)
  end
end

This is almost identical to the normal DNSCluster.Resolver module except for the lookup function which does String.split. This hack allows us to specify multiple hosts to check for IP addresses. Note we differ from fly.io deployment here because we don’t have an overlay network (they have, e.g. myapp.internal or something like that). That’s why we need multiple calls to :inet_res.getbyname.

This is actually a good thing because we can choose which server will serve our requests since they map to different ports. Server 1 is at localhost:4000, server 2 is at localhost:4001.

Finally, you need to have this env var in, e.g dev.env:

DNS_CLUSTER_QUERY="myapp1 myapp2"

As mentioned, this will be split by our custom resolver and both IP v4 addresses are obtained.

So that’s how I got it to work. I am testing using :pogo for global singletons, regional singletons, etc. and it is nice to have 2 clustered apps by default.
It is especially comforting to my mind that they are clustering using the same lib as prod. Like I said, I can choose which server to manually test on (ports 4000 vs 4001) and I can attach to whatever running server I want.

This is not a neat write-up but there is enough info for anyone else who wants this kind of local setup for dns_cluster clustering with docker-compose. I am happy to provide more info.

Most Liked

mxgrn

mxgrn

From my further digging into it (there’s a part in a talk by Chris McCord on dns_cluster, btw), dns_cluster is a simpler dependency, but both solve the same problem, so, I don’t see why use both. The libcluster lib appears more extensible. E.g., someone wrote Postgres node discovery strategy for it, which is pretty cool.

LostKobrakai

LostKobrakai

No you don’t. Libcluster has a strategy for a list of static nodes matching what OTP does iirc.

epmd does port mapping. It has nothing to do with hostname resolution. It just deal with multiple erlang nodes on a single host and mapping them to ports as well as informing other nodes what the selected ports are.

It does. OTP added the ability to disable epmd in favor of a hardcoded port, but that’s not a default. You need to opt into that.

Where Next?

Popular in Guides/Tuts Top

sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
zachallaun
Hey friends, wanted to share a tiny shell script I’ve been using to start Livebook with easy access to either a running production server...
New
sergio
https://sergiotapia.me/generate-images-with-name-initials-using-elixir-and-imagemagick-374eca4d14ff Hope this saves you guys some time!...
New
cheerfulstoic
I spent quite a bit of time trying to find a good configuration to build / test my Elixir app in CircleCI and then push an image to Docke...
New
jshprentz
Geoffrey Lessel’s 2019 book, Phoenix in Action, was written for Phoenix 1.4. I found that the book’s code examples did not match the cur...
New
bluegene
Hi guys, I’ve been on a personal journey to learn Elixir for the past two years. During this journey I’ve been using the spaced repetiti...
New
f0rest8
Hi everyone, Just wanted to say that the new Self-referencing many to many guide is now up on the official Hex docs (at least I just not...
New
zookzook
If you want to use the MongoDB in your next Killer-App-Project, but you did not dare ask because otherwise many would advise you to use P...
New
anuragg
We finally have a Mix clustering guide to go with Phoenix deployment with Mix Releases. Deploy an Elixir Cluster with Mix Releases and l...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

Other popular topics Top

William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement