├── .gitignore ├── Dockerfile ├── README.md ├── lib ├── hop_testapp.ex └── hop_testapp │ └── http │ └── base_router.ex ├── mix.exs ├── mix.lock └── rel ├── env.bat.eex ├── env.sh.eex ├── remote.vm.args.eex └── vm.args.eex /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | /doc 5 | /.fetch 6 | erl_crash.dump 7 | *.ez 8 | *.beam 9 | /config/*.secret.exs 10 | .elixir_ls/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.13.4-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apk update && apk add git 6 | 7 | COPY mix.exs . 8 | COPY mix.lock . 9 | 10 | RUN mix local.hex --force 11 | RUN mix local.rebar --force 12 | RUN mix deps.get 13 | 14 | COPY lib/ ./lib/ 15 | 16 | ENV MIX_ENV=prod 17 | RUN mix release 18 | 19 | FROM elixir:1.13.4-alpine 20 | 21 | WORKDIR /app 22 | 23 | COPY --from=builder /app/_build/prod/rel/prod/ ./_build/prod/rel/prod/ 24 | 25 | ENV MIX_ENV=prod 26 | ENV RELX_REPLACE_OS_VARS=true 27 | ENV RELEASE_DISTRIBUTION=none 28 | CMD ["_build/prod/rel/prod/bin/prod", "start"] 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elixir Cluster on Hop 2 | 3 | Using [libcluster](https://github.com/bitwalker/libcluster) with the [libcluster_hop](https://github.com/hiett/libcluster_hop) strategy, you can easily create a clustered Elixir application on Hop. All nodes (containers) will be connected to each other. 4 | 5 | This repo contains an example application which utilizes `libcluster_hop`. 6 | 7 | ## Run this on Hop 8 | 9 | Follow the [Elixir Cluster](https://docs.hop.io/getting-started/quickstarts/elixir-cluster) quickstart guide. 10 | 11 | ## Demo 12 | 13 | You can find this app running here: https://libcluster-hop.hop.sh 14 | Refresh to see the load balancing in action. 15 | 16 | > Special thanks to community member [Scott Hiett](https://github.com/hiett) for creating the libcluster_hop strategy. 17 | -------------------------------------------------------------------------------- /lib/hop_testapp.ex: -------------------------------------------------------------------------------- 1 | defmodule HopTestapp do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | IO.puts("Starting test app") 6 | 7 | topologies = [ 8 | hop: [ 9 | strategy: ClusterHop.Strategy.Deployment, 10 | config: [ 11 | hop_token: System.get_env("HOP_TOKEN") 12 | ] 13 | ] 14 | ] 15 | 16 | children = [ 17 | {Cluster.Supervisor, [topologies, [name: HopTestapp.ClusterSupervisor]]}, 18 | { 19 | Plug.Cowboy, 20 | scheme: :http, 21 | plug: HopTestapp.Http.BaseRouter, 22 | options: [ 23 | port: 8080 24 | ] 25 | } 26 | ] 27 | 28 | opts = [strategy: :one_for_one, name: HopTestapp.Supervisor] 29 | 30 | Supervisor.start_link(children, opts) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/hop_testapp/http/base_router.ex: -------------------------------------------------------------------------------- 1 | defmodule HopTestapp.Http.BaseRouter do 2 | use Plug.Router 3 | 4 | plug(:match) 5 | plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason) 6 | plug(:dispatch) 7 | 8 | get "/" do 9 | data = %{ 10 | sent_from: "Hop libcluster Deployment Strategy Test App", 11 | node: node(), 12 | nodes: Node.list() 13 | } 14 | 15 | conn 16 | |> put_resp_header("content-type", "application/json") 17 | |> send_resp(200, Jason.encode!(data)) 18 | end 19 | 20 | match _ do 21 | data = %{ 22 | error: true, 23 | message: "path not found" 24 | } 25 | 26 | conn 27 | |> put_resp_header("content-type", "application/json") 28 | |> send_resp(404, Jason.encode!(data)) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HopTestapp.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :libcluster_hop_testapp, 7 | version: "0.1.0", 8 | elixir: "~> 1.13", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps(), 11 | releases: [ 12 | prod: [ 13 | include_executables_for: [:unix], 14 | steps: [:assemble, :tar] 15 | ] 16 | ] 17 | ] 18 | end 19 | 20 | # Run "mix help compile.app" to learn about applications. 21 | def application do 22 | [ 23 | extra_applications: [:logger], 24 | mod: {HopTestapp, []} 25 | ] 26 | end 27 | 28 | # Run "mix help deps" to learn about dependencies. 29 | defp deps do 30 | [ 31 | {:plug, "~> 1.13"}, 32 | {:cowboy, "~> 2.9"}, 33 | {:plug_cowboy, "~> 2.5"}, 34 | {:jason, "~> 1.4"}, 35 | {:libcluster, "~> 3.3"}, 36 | {:libcluster_hop, 37 | github: "hiett/libcluster_hop", ref: "6e3a00d2e8e8ea0388135d8c000b999536021909"} 38 | ] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, 3 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, 4 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, 5 | "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, 6 | "libcluster": {:hex, :libcluster, "3.3.1", "e7a4875cd1290cee7a693d6bd46076863e9e433708b01339783de6eff5b7f0aa", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b575ca63c1cd84e01f3fa0fc45e6eb945c1ee7ae8d441d33def999075e9e5398"}, 7 | "libcluster_hop": {:git, "https://github.com/hiett/libcluster_hop.git", "6e3a00d2e8e8ea0388135d8c000b999536021909", [ref: "6e3a00d2e8e8ea0388135d8c000b999536021909"]}, 8 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 9 | "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, 10 | "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, 11 | "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, 12 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 13 | "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, 14 | "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, 15 | } 16 | -------------------------------------------------------------------------------- /rel/env.bat.eex: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Set the release to work across nodes. 3 | rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". 4 | rem set RELEASE_DISTRIBUTION=name 5 | rem set RELEASE_NODE=<%= @release.name %> 6 | -------------------------------------------------------------------------------- /rel/env.sh.eex: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Sets and enables heart (recommended only in daemon mode) 4 | # case $RELEASE_COMMAND in 5 | # daemon*) 6 | # HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND" 7 | # export HEART_COMMAND 8 | # export ELIXIR_ERL_OPTIONS="-heart" 9 | # ;; 10 | # *) 11 | # ;; 12 | # esac 13 | 14 | # Set the release to work across nodes. 15 | # RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". 16 | # export RELEASE_DISTRIBUTION=name 17 | # export RELEASE_NODE=<%= @release.name %> 18 | -------------------------------------------------------------------------------- /rel/remote.vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: https://erlang.org/doc/man/erl.html 2 | ## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here 3 | 4 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 5 | ##+SDio 5 6 | 7 | ## Increase number of concurrent ports/sockets 8 | ##+Q 65536 9 | 10 | ## Tweak GC to run more often 11 | ##-env ERL_FULLSWEEP_AFTER 10 12 | -------------------------------------------------------------------------------- /rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: https://erlang.org/doc/man/erl.html 2 | ## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here 3 | 4 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 5 | ##+SDio 5 6 | 7 | ## Increase number of concurrent ports/sockets 8 | ##+Q 65536 9 | 10 | ## Tweak GC to run more often 11 | ##-env ERL_FULLSWEEP_AFTER 10 --------------------------------------------------------------------------------