├── .formatter.exs ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── config ├── config.exs └── sys.config ├── docker-compose.yml ├── lib └── exsample.ex ├── mix.exs ├── mix.lock └── test ├── exsample_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | exsample-*.tar 24 | 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM elixir:latest 3 | 4 | # Updating apt repository of the related OS 5 | RUN apt-get clean && \ 6 | apt-get update && \ 7 | apt-get install -y lsof telnet 8 | 9 | 10 | COPY . /exsample 11 | WORKDIR /exsample 12 | 13 | RUN mix deps.clean --all 14 | RUN mix deps.get 15 | RUN mix local.rebar --force 16 | RUN mix deps.compile -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | start: 3 | @ERL_LIBS=_build/dev/lib/ iex --name ${NODE_NAME} \ 4 | --erl "+K true" \ 5 | --erl "-config config/sys.config" \ 6 | --erl "-proto_dist epmdless_proto" \ 7 | --erl "-start_epmd false" \ 8 | --erl "-epmd_module epmdless_client" \ 9 | -S mix 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exsample 2 | 3 | # Elixir in docker cloud sandbox # 4 | 5 | This application demostrates how to setup a connection between elixir nodes 6 | which are running inside containers which are destributed over the internet. 7 | 8 | The application uses: 9 | 1. epmdless - a distribution protocol implementation which allows to run erlang nodes without epmd (https://github.com/oltarasenko/epmdless) 10 | 2. erlang-node-discovery - service which allows to setup dynamic node discovery accross different hosts (for cases when your application can scheduled on different phisical hosts, like in case of Mesos) (https://github.com/oltarasenko/erlang-node-discovery) 11 | 12 | ## Example usage ## 13 | 14 | 1) Build the docker container with the app inside `docker-compose build` 15 | 2) Configure your `/etc/hosts` to add host1.com/host2.com aliases (production hosts will probably have own hostnames) 16 | 3) Run `docker-compose up app1 app2` on host1.com machine 17 | 4) Run `docker-compose up app3` on host2.com machine 18 | 19 | You will start getting: 20 | ``` 21 | app1_1 | iex(app1@host1.local)1> I know these nodes: [:"app2@host1.local"] 22 | app2_1 | iex(app2@host1.local)1> I know these nodes: [:"app1@host1.local"] 23 | ``` 24 | which means that nodes was able to discover each other 25 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :exsample, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:exsample, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /config/sys.config: -------------------------------------------------------------------------------- 1 | [ 2 | { epmdless, [ 3 | {transport, tcp}, 4 | {listen_port, 17012} 5 | ] 6 | }, 7 | { erlang_node_discovery, [ 8 | {db_callback, epmdless_dist}, 9 | {hosts, ["host1.com", "host2.com", "host3.com"]}, 10 | {node_ports, [ 11 | {'app1', 17012}, 12 | {'app2', 17013}, 13 | {'app3', 17014} 14 | ]}, 15 | {cookie, sample_app_cookie} 16 | ]} 17 | ]. 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app1: 4 | build: . 5 | command: make start 6 | container_name: host1.com 7 | environment: 8 | - EPMDLESS_DIST_PORT=17012 9 | - NODE_NAME=app1@host1.com 10 | ports: 11 | - 17012:17012 12 | networks: 13 | - net1 14 | 15 | app2: 16 | build: . 17 | command: make start 18 | container_name: host2.com 19 | environment: 20 | - EPMDLESS_DIST_PORT=17013 21 | - NODE_NAME=app2@host2.com 22 | ports: 23 | - 17013:17013 24 | networks: 25 | - net1 26 | 27 | app3: 28 | build: . 29 | command: make start 30 | container_name: host3.com 31 | environment: 32 | - EPMDLESS_DIST_PORT=17014 33 | - NODE_NAME=app3@host3.com 34 | ports: 35 | - 17014:17014 36 | networks: 37 | - net1 38 | 39 | # We're using custom network setup, as it ships with a DNS 40 | # system which allows containers to communicate by hostnames. 41 | networks: 42 | net1: 43 | driver: bridge -------------------------------------------------------------------------------- /lib/exsample.ex: -------------------------------------------------------------------------------- 1 | defmodule Exsample do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | Supervisor.start_link([{Exsample.Worker, []}], strategy: :one_for_one) 6 | end 7 | end 8 | 9 | defmodule Exsample.Worker do 10 | use GenServer 11 | 12 | def start_link([]) do 13 | GenServer.start_link(__MODULE__, [], name: __MODULE__) 14 | end 15 | 16 | def init([]) do 17 | Process.send_after(self(), :discovered_nodes, 1000) 18 | {:ok, %{}} 19 | end 20 | 21 | def handle_info(:discovered_nodes, state) do 22 | IO.puts "I know these nodes: #{inspect Node.list()}" 23 | Process.send_after(self(), :discovered_nodes, 30000) 24 | {:noreply, state} 25 | end 26 | end -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Exsample.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :exsample, 7 | version: "0.1.0", 8 | elixir: "~> 1.6", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | mod: {Exsample, []}, 18 | applications: [:logger, :epmdless, :erlang_node_discovery] 19 | ] 20 | end 21 | 22 | # Run "mix help deps" to learn about dependencies. 23 | defp deps do 24 | [ 25 | {:epmdless, git: "https://github.com/oltarasenko/epmdless.git", tag: "0.1.0"}, 26 | {:erlang_node_discovery, git: "https://github.com/oltarasenko/erlang-node-discovery.git", tag: "0.1.2"} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "epmdless": {:git, "https://github.com/oltarasenko/epmdless.git", "72819343d8ca718701dddc8df4ad2b6dd128a984", [tag: "0.1.0"]}, 3 | "erlang_node_discovery": {:git, "https://github.com/oltarasenko/erlang-node-discovery.git", "2a97fc7fd50c338a7d4fd230293d1b6965a4b0f6", [tag: "0.1.2"]}, 4 | } 5 | -------------------------------------------------------------------------------- /test/exsample_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExsampleTest do 2 | use ExUnit.Case 3 | doctest Exsample 4 | 5 | test "greets the world" do 6 | assert Exsample.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------