├── apps ├── app_a │ ├── config │ │ ├── config.exs │ │ └── runtime.exs │ ├── test │ │ ├── test_helper.exs │ │ └── app_a_test.exs │ ├── .formatter.exs │ ├── Dockerfile │ ├── lib │ │ ├── app_a.ex │ │ └── app_a │ │ │ ├── router.ex │ │ │ └── application.ex │ ├── README.md │ ├── .gitignore │ ├── mix.exs │ └── mix.lock ├── app_b │ ├── config │ │ ├── config.exs │ │ └── runtime.exs │ ├── test │ │ ├── test_helper.exs │ │ └── app_b_test.exs │ ├── .formatter.exs │ ├── Dockerfile │ ├── lib │ │ ├── app_b.ex │ │ └── app_b │ │ │ ├── router.ex │ │ │ └── application.ex │ ├── README.md │ ├── .gitignore │ ├── mix.exs │ └── mix.lock └── cluster_tests │ ├── lib │ └── cluster_tests.ex │ ├── config │ └── config.exs │ ├── .formatter.exs │ ├── test │ ├── test_helper.exs │ └── cluster_tests_test.exs │ ├── README.md │ ├── .gitignore │ ├── mix.exs │ └── mix.lock ├── .formatter.exs ├── k8s_gossip_statefulset ├── service-beam-cluster.yaml ├── livebook.yaml ├── app-b.yaml └── app-a.yaml ├── k8s_dns_deployment ├── service-beam-cluster.yaml ├── livebook.yaml ├── app-a.yaml └── app-b.yaml ├── k8s_statefulset ├── service-beam-cluster.yaml ├── roles.yaml ├── livebook.yaml ├── app-a.yaml └── app-b.yaml ├── mix.exs ├── .gitignore ├── config └── config.exs ├── k8s_gossip_deployment ├── livebook.yaml ├── app-a.yaml └── app-b.yaml ├── README.md ├── docker-compose.yml └── mix.lock /apps/app_a/config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | -------------------------------------------------------------------------------- /apps/app_b/config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | -------------------------------------------------------------------------------- /apps/app_a/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/app_b/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/cluster_tests/lib/cluster_tests.ex: -------------------------------------------------------------------------------- 1 | defmodule ClusterTests do 2 | end 3 | -------------------------------------------------------------------------------- /apps/cluster_tests/config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :libcluster, 4 | topologies: [] 5 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "config/*.exs"], 4 | subdirectories: ["apps/*"] 5 | ] 6 | -------------------------------------------------------------------------------- /apps/app_a/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/app_b/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/cluster_tests/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/cluster_tests/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | :ok = LocalCluster.start() 2 | 3 | Application.ensure_all_started(:cluster_tests) 4 | 5 | ExUnit.start() 6 | -------------------------------------------------------------------------------- /apps/app_a/test/app_a_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppATest do 2 | use ExUnit.Case 3 | doctest AppA 4 | 5 | test "greets the world" do 6 | assert AppA.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /apps/app_b/test/app_b_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppBTest do 2 | use ExUnit.Case 3 | doctest AppB 4 | 5 | test "greets the world" do 6 | assert AppB.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /apps/app_a/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14.3 as build 2 | 3 | COPY . . 4 | 5 | RUN mix local.hex --force && mix deps.get && mix compile 6 | 7 | CMD elixir --sname app -S mix run --no-halt 8 | 9 | EXPOSE 4001 10 | -------------------------------------------------------------------------------- /apps/app_b/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14.3 as build 2 | 3 | COPY . . 4 | 5 | RUN mix local.hex --force && mix deps.get && mix compile 6 | 7 | CMD elixir --sname b -S mix run --no-halt 8 | 9 | EXPOSE 4001 10 | -------------------------------------------------------------------------------- /k8s_gossip_statefulset/service-beam-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | 4 | metadata: 5 | name: beam-cluster 6 | labels: 7 | cluster: beam 8 | spec: 9 | clusterIP: None 10 | selector: 11 | cluster: beam 12 | -------------------------------------------------------------------------------- /k8s_dns_deployment/service-beam-cluster.yaml: -------------------------------------------------------------------------------- 1 | # use for Cluster.Strategy.Kubernetes.DNS 2 | apiVersion: v1 3 | kind: Service 4 | 5 | metadata: 6 | name: beam-cluster 7 | namespace: default 8 | spec: 9 | clusterIP: None 10 | selector: 11 | cluster: beam 12 | -------------------------------------------------------------------------------- /k8s_statefulset/service-beam-cluster.yaml: -------------------------------------------------------------------------------- 1 | # use for Cluster.Strategy.Kubernetes.DNS 2 | apiVersion: v1 3 | kind: Service 4 | 5 | metadata: 6 | name: beam-cluster 7 | labels: 8 | cluster: beam 9 | spec: 10 | clusterIP: None 11 | selector: 12 | cluster: beam 13 | -------------------------------------------------------------------------------- /apps/app_a/lib/app_a.ex: -------------------------------------------------------------------------------- 1 | defmodule AppA do 2 | @moduledoc """ 3 | Documentation for `AppA`. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> AppA.hello() 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /apps/app_b/lib/app_b.ex: -------------------------------------------------------------------------------- 1 | defmodule AppB do 2 | @moduledoc """ 3 | Documentation for `AppB`. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> AppB.hello() 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Cluster.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | apps_path: "apps", 7 | version: "0.1.0", 8 | start_permanent: Mix.env() == :prod, 9 | deps: deps() 10 | ] 11 | end 12 | 13 | # Dependencies listed here are available only for this 14 | # project and cannot be accessed from applications inside 15 | # the apps folder. 16 | # 17 | # Run "mix help deps" for examples and options. 18 | defp deps do 19 | [] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/cluster_tests/test/cluster_tests_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ClusterTestsTest do 2 | use ExUnit.Case 3 | 4 | test "greets the world" do 5 | [node_a1, node_a2, node_a3] = LocalCluster.start_nodes("app_a", 3, applications: [:app_a]) 6 | 7 | [node_b1, node_b2, node_b3] = LocalCluster.start_nodes("app_b", 3, applications: [:app_b]) 8 | 9 | IO.inspect(:rpc.call(node_a1, Node, :list, [])) 10 | IO.inspect(:rpc.call(node_a1, NodeRegistry, :all, [])) 11 | IO.inspect(:rpc.call(node_a2, NodeRegistry, :all, [])) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /apps/app_a/README.md: -------------------------------------------------------------------------------- 1 | # AppA 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `app_a` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:app_a, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at . 21 | 22 | -------------------------------------------------------------------------------- /apps/app_b/README.md: -------------------------------------------------------------------------------- 1 | # AppB 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `app_b` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:app_b, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at . 21 | 22 | -------------------------------------------------------------------------------- /apps/cluster_tests/README.md: -------------------------------------------------------------------------------- 1 | # ClusterTests 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `cluster_tests` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:cluster_tests, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at . 21 | 22 | -------------------------------------------------------------------------------- /apps/app_a/lib/app_a/router.ex: -------------------------------------------------------------------------------- 1 | defmodule AppA.Router do 2 | use Plug.Router 3 | 4 | plug(Plug.Logger, log: :debug) 5 | plug(:match) 6 | plug(:dispatch) 7 | 8 | get "/" do 9 | list = Node.list() 10 | send_resp(conn, 200, "Hey from AppA #{inspect(Node.self())}! Node.list: #{inspect(list)}") 11 | end 12 | 13 | get "/registry" do 14 | list = NodeRegistry.all() 15 | send_resp(conn, 200, inspect(list)) 16 | end 17 | 18 | get "/favicon.ico" do 19 | send_resp(conn, 200, "sorry, no icon") 20 | end 21 | 22 | match _ do 23 | send_resp(conn, 404, "NOT FOUND") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /apps/app_b/lib/app_b/router.ex: -------------------------------------------------------------------------------- 1 | defmodule AppB.Router do 2 | use Plug.Router 3 | 4 | plug(Plug.Logger, log: :debug) 5 | plug(:match) 6 | plug(:dispatch) 7 | 8 | get "/" do 9 | list = Node.list() 10 | send_resp(conn, 200, "Hey from AppB #{inspect(Node.self())}! Node.list: #{inspect(list)}") 11 | end 12 | 13 | get "/registry" do 14 | list = NodeRegistry.all() 15 | send_resp(conn, 200, inspect(list)) 16 | end 17 | 18 | get "/favicon.ico" do 19 | send_resp(conn, 200, "sorry, no icon") 20 | end 21 | 22 | match _ do 23 | send_resp(conn, 404, "NOT FOUND") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.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 third-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 | # Temporary files, for example, from tests. 23 | /tmp/ 24 | -------------------------------------------------------------------------------- /k8s_statefulset/roles.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | creationTimestamp: null 5 | name: read-pods-role 6 | namespace: default 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - pods 12 | verbs: 13 | - get 14 | - list 15 | 16 | --- 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | kind: RoleBinding 19 | metadata: 20 | creationTimestamp: null 21 | name: role-binding 22 | namespace: default 23 | roleRef: 24 | apiGroup: rbac.authorization.k8s.io 25 | kind: Role 26 | name: read-pods-role 27 | subjects: 28 | - kind: ServiceAccount 29 | name: default 30 | namespace: default 31 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your umbrella 2 | # and **all applications** and their dependencies with the 3 | # help of the Config module. 4 | # 5 | # Note that all applications in your umbrella share the 6 | # same configuration and dependencies, which is why they 7 | # all use the same configuration file. If you want different 8 | # configurations or dependencies per app, it is best to 9 | # move said applications out of the umbrella. 10 | import Config 11 | 12 | # Sample configuration: 13 | # 14 | # config :logger, :console, 15 | # level: :info, 16 | # format: "$date $time [$level] $metadata$message\n", 17 | # metadata: [:user_id] 18 | # 19 | -------------------------------------------------------------------------------- /apps/app_a/.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 third-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 | app_a-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /apps/app_b/.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 third-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 | app_b-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /apps/cluster_tests/.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 third-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 | cluster_tests-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /apps/app_b/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule AppB.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :app_b, 7 | version: "0.1.0", 8 | build_path: "_build", 9 | config_path: "config/config.exs", 10 | deps_path: "deps", 11 | lockfile: "mix.lock", 12 | elixir: "~> 1.14", 13 | start_permanent: Mix.env() == :prod, 14 | deps: deps() 15 | ] 16 | end 17 | 18 | # Run "mix help compile.app" to learn about applications. 19 | def application do 20 | [ 21 | extra_applications: [:logger, :runtime_tools], 22 | mod: {AppB.Application, []} 23 | ] 24 | end 25 | 26 | # Run "mix help deps" to learn about dependencies. 27 | defp deps do 28 | [ 29 | {:plug_cowboy, "~> 2.5"}, 30 | {:jason, "~> 1.4"}, 31 | {:libcluster, "~> 3.3"}, 32 | {:node_registry, "~> 0.1"} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /apps/app_a/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule AppA.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :app_a, 7 | version: "0.1.0", 8 | build_path: "_build", 9 | config_path: "config/config.exs", 10 | deps_path: "deps", 11 | lockfile: "mix.lock", 12 | elixir: "~> 1.14", 13 | start_permanent: Mix.env() == :prod, 14 | deps: deps() 15 | ] 16 | end 17 | 18 | # Run "mix help compile.app" to learn about applications. 19 | def application do 20 | [ 21 | extra_applications: [:logger, :runtime_tools], 22 | mod: {AppA.Application, []} 23 | ] 24 | end 25 | 26 | # Run "mix help deps" to learn about dependencies. 27 | defp deps do 28 | [ 29 | {:plug_cowboy, "~> 2.5"}, 30 | {:jason, "~> 1.4"}, 31 | {:libcluster, "~> 3.3"}, 32 | {:node_registry, "~> 0.1.1"} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /apps/cluster_tests/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ClusterTests.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :cluster_tests, 7 | version: "0.1.0", 8 | build_path: "_build", 9 | config_path: "config/config.exs", 10 | deps_path: "deps", 11 | lockfile: "mix.lock", 12 | elixir: "~> 1.14", 13 | start_permanent: Mix.env() == :prod, 14 | deps: deps(), 15 | aliases: aliases() 16 | ] 17 | end 18 | 19 | # Run "mix help compile.app" to learn about applications. 20 | def application do 21 | [ 22 | extra_applications: [:logger] 23 | ] 24 | end 25 | 26 | # Run "mix help deps" to learn about dependencies. 27 | defp deps do 28 | [ 29 | {:app_a, path: "../app_a", runtime: false}, 30 | {:app_b, path: "../app_b", runtime: false} 31 | ] 32 | end 33 | 34 | defp aliases do 35 | [ 36 | test: "test --no-start" 37 | ] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /apps/app_a/lib/app_a/application.ex: -------------------------------------------------------------------------------- 1 | defmodule AppA.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | require Logger 8 | 9 | @impl true 10 | def start(_type, _args) do 11 | topologies = Application.get_env(:libcluster, :topologies) 12 | Logger.info("topologies: #{inspect(topologies)}") 13 | 14 | children = [ 15 | {Cluster.Supervisor, [topologies, [name: AppA.ClusterSupervisor]]}, 16 | {NodeRegistry, "app_a_#{postfix()}"} 17 | ] 18 | 19 | children = 20 | if start_server?() do 21 | [{Plug.Cowboy, scheme: :http, plug: AppA.Router, options: [port: port()]} | children] 22 | else 23 | children 24 | end 25 | 26 | opts = [strategy: :one_for_one, name: AppA.Supervisor] 27 | Supervisor.start_link(children, opts) 28 | end 29 | 30 | defp port do 31 | (System.get_env("PORT") || "4001") 32 | |> String.to_integer() 33 | end 34 | 35 | defp start_server? do 36 | System.get_env("START_SERVER") == "true" 37 | end 38 | 39 | defp postfix do 40 | System.get_env("POSTFIX") || random_string() 41 | end 42 | 43 | defp random_string do 44 | :crypto.strong_rand_bytes(5) |> Base.encode32() 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /apps/app_b/lib/app_b/application.ex: -------------------------------------------------------------------------------- 1 | defmodule AppB.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | require Logger 8 | 9 | @impl true 10 | def start(_type, _args) do 11 | topologies = Application.get_env(:libcluster, :topologies) 12 | Logger.info("topologies: #{inspect(topologies)}") 13 | 14 | children = [ 15 | {Cluster.Supervisor, [topologies, [name: AppB.ClusterSupervisor]]}, 16 | {NodeRegistry, "app_b_#{postfix()}"} 17 | ] 18 | 19 | children = 20 | if start_server?() do 21 | [{Plug.Cowboy, scheme: :http, plug: AppB.Router, options: [port: port()]} | children] 22 | else 23 | children 24 | end 25 | 26 | opts = [strategy: :one_for_one, name: AppB.Supervisor] 27 | Supervisor.start_link(children, opts) 28 | end 29 | 30 | defp port do 31 | (System.get_env("PORT") || "5001") 32 | |> String.to_integer() 33 | end 34 | 35 | defp start_server? do 36 | System.get_env("START_SERVER") == "true" 37 | end 38 | 39 | defp postfix do 40 | System.get_env("POSTFIX") || random_string() 41 | end 42 | 43 | defp random_string do 44 | :crypto.strong_rand_bytes(5) |> Base.encode32() 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /apps/app_a/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | cond do 4 | System.get_env("LIBCLUSTER_STRATEGY") == "local_epmd" -> 5 | config :libcluster, 6 | topologies: [ 7 | local_epmd: [ 8 | strategy: Cluster.Strategy.LocalEpmd 9 | ] 10 | ] 11 | 12 | System.get_env("LIBCLUSTER_STRATEGY") == "gossip" -> 13 | config :libcluster, 14 | topologies: [ 15 | gossip: [ 16 | strategy: Cluster.Strategy.Gossip 17 | ] 18 | ] 19 | 20 | System.get_env("LIBCLUSTER_STRATEGY") == "kubernetes" -> 21 | config :libcluster, 22 | topologies: [ 23 | k8s: [ 24 | strategy: Cluster.Strategy.Kubernetes, 25 | config: [ 26 | mode: :hostname, 27 | kubernetes_ip_lookup_mode: :pods, 28 | kubernetes_service_name: "beam-cluster", 29 | kubernetes_node_basename: "cluster-app", 30 | kubernetes_selector: "cluster=beam" 31 | ] 32 | ] 33 | ] 34 | 35 | System.get_env("LIBCLUSTER_STRATEGY") == "kubernetes.dns" -> 36 | config :libcluster, 37 | topologies: [ 38 | k8s_dns: [ 39 | strategy: Cluster.Strategy.Kubernetes.DNS, 40 | config: [ 41 | service: "beam-cluster", 42 | application_name: "cluster-app" 43 | ] 44 | ] 45 | ] 46 | 47 | true -> 48 | config :libcluster, topologies: [] 49 | end 50 | -------------------------------------------------------------------------------- /k8s_statefulset/livebook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: livebook 5 | labels: 6 | component: livebook 7 | spec: 8 | containers: 9 | - name: livebook 10 | image: livebook/livebook:edge 11 | ports: 12 | - containerPort: 8080 13 | - containerPort: 8081 14 | env: 15 | - name: LIVEBOOK_PASSWORD 16 | value: "securesecret" 17 | - name: LIVEBOOK_COOKIE 18 | value: "secret" 19 | - name: ERLANG_COOKIE 20 | value: "secret" 21 | - name: LIVEBOOK_DISTRIBUTION 22 | value: "name" 23 | - name: LIVEBOOK_PORT 24 | value: "8080" 25 | - name: POD_IP 26 | valueFrom: 27 | fieldRef: 28 | fieldPath: status.podIP 29 | - name: POD_NAME 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: metadata.name 33 | - name: LIVEBOOK_NODE 34 | value: "cluster-app@$(POD_NAME)" 35 | - name: LIVEBOOK_DEFAULT_RUNTIME 36 | value: "attached:$(LIVEBOOK_NODE):$(LIVEBOOK_COOKIE)" 37 | 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | name: livebook 43 | spec: 44 | type: LoadBalancer 45 | ports: 46 | - name: http 47 | port: 8080 48 | targetPort: 8080 49 | # - name: web-socket 50 | # port: 8081 51 | # targetPort: 8081 52 | selector: 53 | component: livebook 54 | -------------------------------------------------------------------------------- /apps/app_b/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | cond do 4 | System.get_env("LIBCLUSTER_STRATEGY") == "local_epmd" -> 5 | config :libcluster, 6 | topologies: [ 7 | local_epmd: [ 8 | strategy: Cluster.Strategy.LocalEpmd 9 | ] 10 | ] 11 | 12 | System.get_env("LIBCLUSTER_STRATEGY") == "gossip" -> 13 | config :libcluster, 14 | topologies: [ 15 | gossip: [ 16 | strategy: Cluster.Strategy.Gossip 17 | ] 18 | ] 19 | 20 | System.get_env("LIBCLUSTER_STRATEGY") == "kubernetes" -> 21 | config :libcluster, 22 | topologies: [ 23 | k8s: [ 24 | strategy: Cluster.Strategy.Kubernetes, 25 | config: [ 26 | mode: :hostname, 27 | kubernetes_ip_lookup_mode: :pods, 28 | kubernetes_service_name: "beam-cluster", 29 | kubernetes_node_basename: "cluster-app", 30 | kubernetes_selector: "cluster=beam", 31 | kubernetes_namespace: "default" 32 | ] 33 | ] 34 | ] 35 | 36 | System.get_env("LIBCLUSTER_STRATEGY") == "kubernetes.dns" -> 37 | config :libcluster, 38 | topologies: [ 39 | k8s_dns: [ 40 | strategy: Cluster.Strategy.Kubernetes.DNS, 41 | config: [ 42 | service: "beam-cluster", 43 | application_name: "cluster-app" 44 | ] 45 | ] 46 | ] 47 | 48 | true -> 49 | config :libcluster, topologies: [] 50 | end 51 | -------------------------------------------------------------------------------- /k8s_gossip_deployment/livebook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: livebook 5 | labels: 6 | component: livebook 7 | spec: 8 | containers: 9 | - name: livebook 10 | image: livebook/livebook:edge 11 | imagePullPolicy: Never 12 | ports: 13 | - containerPort: 8080 14 | - containerPort: 8081 15 | env: 16 | - name: LIVEBOOK_PASSWORD 17 | value: "securesecret" 18 | - name: LIVEBOOK_COOKIE 19 | value: "secret" 20 | - name: ERLANG_COOKIE 21 | value: "secret" 22 | - name: LIVEBOOK_DISTRIBUTION 23 | value: "name" 24 | - name: LIVEBOOK_PORT 25 | value: "8080" 26 | - name: POD_IP 27 | valueFrom: 28 | fieldRef: 29 | fieldPath: status.podIP 30 | - name: POD_NAME 31 | valueFrom: 32 | fieldRef: 33 | fieldPath: metadata.name 34 | - name: LIVEBOOK_NODE 35 | value: "livebook@livebook" 36 | - name: LIVEBOOK_DEFAULT_RUNTIME 37 | value: "attached:$(LIVEBOOK_NODE):$(LIVEBOOK_COOKIE)" 38 | 39 | --- 40 | apiVersion: v1 41 | kind: Service 42 | metadata: 43 | name: livebook 44 | spec: 45 | type: LoadBalancer 46 | ports: 47 | - name: http 48 | port: 8080 49 | targetPort: 8080 50 | # - name: web-socket 51 | # port: 8081 52 | # targetPort: 8081 53 | selector: 54 | component: livebook 55 | 56 | -------------------------------------------------------------------------------- /k8s_dns_deployment/livebook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: livebook 5 | labels: 6 | component: livebook 7 | cluster: beam 8 | spec: 9 | containers: 10 | - name: livebook 11 | image: livebook/livebook:edge 12 | imagePullPolicy: Never 13 | ports: 14 | - containerPort: 8080 15 | - containerPort: 8081 16 | env: 17 | - name: LIVEBOOK_PASSWORD 18 | value: "securesecret" 19 | - name: LIVEBOOK_COOKIE 20 | value: "secret" 21 | - name: ERLANG_COOKIE 22 | value: "secret" 23 | - name: LIVEBOOK_DISTRIBUTION 24 | value: "name" 25 | - name: LIVEBOOK_PORT 26 | value: "8080" 27 | - name: POD_IP 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: status.podIP 31 | - name: POD_NAME 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.name 35 | - name: LIVEBOOK_NODE 36 | value: "livebook@$(POD_IP)" 37 | - name: LIVEBOOK_DEFAULT_RUNTIME 38 | value: "attached:$(LIVEBOOK_NODE):$(LIVEBOOK_COOKIE)" 39 | 40 | --- 41 | apiVersion: v1 42 | kind: Service 43 | metadata: 44 | name: livebook 45 | spec: 46 | type: LoadBalancer 47 | ports: 48 | - name: http 49 | port: 8080 50 | targetPort: 8080 51 | # - name: web-socket 52 | # port: 8081 53 | # targetPort: 8081 54 | selector: 55 | component: livebook 56 | 57 | -------------------------------------------------------------------------------- /k8s_gossip_statefulset/livebook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: livebook 5 | labels: 6 | component: livebook 7 | cluster: beam 8 | spec: 9 | hostname: livebook 10 | subdomain: beam-cluster 11 | containers: 12 | - name: livebook 13 | image: livebook/livebook:edge 14 | ports: 15 | - containerPort: 8080 16 | - containerPort: 8081 17 | env: 18 | - name: LIVEBOOK_PASSWORD 19 | value: "securesecret" 20 | - name: LIVEBOOK_COOKIE 21 | value: "secret" 22 | - name: ERLANG_COOKIE 23 | value: "secret" 24 | - name: LIVEBOOK_DISTRIBUTION 25 | value: "name" 26 | - name: LIVEBOOK_PORT 27 | value: "8080" 28 | - name: POD_IP 29 | valueFrom: 30 | fieldRef: 31 | fieldPath: status.podIP 32 | - name: POD_NAME 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.name 36 | - name: LIVEBOOK_NODE 37 | value: "livebook@$(POD_NAME).beam-cluster.default.svc.cluster.local" 38 | - name: LIVEBOOK_DEFAULT_RUNTIME 39 | value: "attached:$(LIVEBOOK_NODE):$(LIVEBOOK_COOKIE)" 40 | 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: livebook 46 | spec: 47 | type: LoadBalancer 48 | ports: 49 | - name: http 50 | port: 8080 51 | targetPort: 8080 52 | # - name: web-socket 53 | # port: 8081 54 | # targetPort: 8081 55 | selector: 56 | component: livebook 57 | -------------------------------------------------------------------------------- /k8s_dns_deployment/app-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: app-a 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | component: app-a 10 | template: 11 | metadata: 12 | labels: 13 | component: app-a 14 | cluster: beam 15 | spec: 16 | containers: 17 | - name: app-a 18 | image: app-a 19 | imagePullPolicy: Never 20 | ports: 21 | - containerPort: 4001 22 | - containerPort: 9000 23 | env: 24 | - name: LIBCLUSTER_STRATEGY 25 | value: "kubernetes.dns" 26 | - name: PORT 27 | value: "4001" 28 | - name: START_SERVER 29 | value: "true" 30 | - name: ERLANG_COOKIE 31 | value: "secret" 32 | - name: POD_IP 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: status.podIP 36 | command: [ "elixir" ] 37 | args: [ 38 | "--name", 39 | "cluster-app@$(POD_IP)", 40 | "--cookie","$(ERLANG_COOKIE)", 41 | "--erl", 42 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 43 | "--no-halt", 44 | "-S","mix", 45 | "run" 46 | ] 47 | 48 | --- 49 | apiVersion: v1 50 | kind: Service 51 | metadata: 52 | name: app-a 53 | spec: 54 | type: LoadBalancer 55 | ports: 56 | - port: 4001 57 | targetPort: 4001 58 | selector: 59 | component: app-a 60 | 61 | -------------------------------------------------------------------------------- /k8s_dns_deployment/app-b.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: app-b-deployment 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | component: app-b 10 | template: 11 | metadata: 12 | labels: 13 | component: app-b 14 | cluster: beam 15 | spec: 16 | containers: 17 | - name: app-b 18 | image: app-b 19 | imagePullPolicy: Never 20 | ports: 21 | - containerPort: 4002 22 | - containerPort: 9000 23 | env: 24 | - name: LIBCLUSTER_STRATEGY 25 | value: "kubernetes.dns" 26 | - name: PORT 27 | value: "4002" 28 | - name: START_SERVER 29 | value: "true" 30 | - name: ERLANG_COOKIE 31 | value: "secret" 32 | - name: POD_IP 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: status.podIP 36 | command: [ "elixir" ] 37 | args: [ 38 | "--name", 39 | "cluster-app@$(POD_IP)", 40 | "--cookie","$(ERLANG_COOKIE)", 41 | "--erl", 42 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 43 | "--no-halt", 44 | "-S","mix", 45 | "run" 46 | ] 47 | 48 | --- 49 | apiVersion: v1 50 | kind: Service 51 | metadata: 52 | name: app-b 53 | spec: 54 | type: LoadBalancer 55 | ports: 56 | - port: 4002 57 | targetPort: 4002 58 | selector: 59 | component: app-b 60 | 61 | -------------------------------------------------------------------------------- /k8s_gossip_deployment/app-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: app-a 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | component: app-a 10 | template: 11 | metadata: 12 | labels: 13 | component: app-a 14 | cluster: beam 15 | spec: 16 | containers: 17 | - name: app-a 18 | image: app-a 19 | imagePullPolicy: Never 20 | ports: 21 | - containerPort: 4001 22 | - containerPort: 9000 23 | env: 24 | - name: LIBCLUSTER_STRATEGY 25 | value: "gossip" 26 | - name: PORT 27 | value: "4001" 28 | - name: START_SERVER 29 | value: "true" 30 | - name: ERLANG_COOKIE 31 | value: "secret" 32 | - name: POD_IP 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: status.podIP 36 | - name: POD_NAME 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.name 40 | command: [ "elixir" ] 41 | args: [ 42 | "--name", 43 | "app-a@$(POD_IP)", 44 | "--cookie","$(ERLANG_COOKIE)", 45 | "--erl", 46 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 47 | "--no-halt", 48 | "-S","mix", 49 | "run" 50 | ] 51 | 52 | --- 53 | apiVersion: v1 54 | kind: Service 55 | metadata: 56 | name: app-a 57 | spec: 58 | type: LoadBalancer 59 | ports: 60 | - port: 4001 61 | targetPort: 4001 62 | selector: 63 | component: app-a 64 | 65 | -------------------------------------------------------------------------------- /k8s_gossip_deployment/app-b.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: app-b 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | component: app-b 10 | template: 11 | metadata: 12 | labels: 13 | component: app-b 14 | cluster: beam 15 | spec: 16 | containers: 17 | - name: app-b 18 | image: app-b 19 | imagePullPolicy: Never 20 | ports: 21 | - containerPort: 4002 22 | - containerPort: 9000 23 | env: 24 | - name: LIBCLUSTER_STRATEGY 25 | value: "gossip" 26 | - name: PORT 27 | value: "4002" 28 | - name: START_SERVER 29 | value: "true" 30 | - name: ERLANG_COOKIE 31 | value: "secret" 32 | - name: POD_IP 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: status.podIP 36 | - name: POD_NAME 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.name 40 | command: [ "elixir" ] 41 | args: [ 42 | "--name", 43 | "app-b@$(POD_IP)", 44 | "--cookie","$(ERLANG_COOKIE)", 45 | "--erl", 46 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 47 | "--no-halt", 48 | "-S","mix", 49 | "run" 50 | ] 51 | 52 | --- 53 | apiVersion: v1 54 | kind: Service 55 | metadata: 56 | name: app-b 57 | spec: 58 | type: LoadBalancer 59 | ports: 60 | - port: 4002 61 | targetPort: 4002 62 | selector: 63 | component: app-b 64 | 65 | -------------------------------------------------------------------------------- /k8s_gossip_statefulset/app-b.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: app-b 5 | spec: 6 | serviceName: "beam-cluster" 7 | replicas: 3 8 | selector: 9 | matchLabels: 10 | component: app-b 11 | template: 12 | metadata: 13 | labels: 14 | component: app-b 15 | cluster: beam 16 | spec: 17 | containers: 18 | - name: app-b 19 | image: app-b 20 | imagePullPolicy: Never 21 | ports: 22 | - containerPort: 4002 23 | - containerPort: 9000 24 | env: 25 | - name: LIBCLUSTER_STRATEGY 26 | value: "gossip" 27 | - name: PORT 28 | value: "4002" 29 | - name: START_SERVER 30 | value: "true" 31 | - name: ERLANG_COOKIE 32 | value: "secret" 33 | - name: POD_IP 34 | valueFrom: 35 | fieldRef: 36 | fieldPath: status.podIP 37 | - name: POD_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: metadata.name 41 | command: [ "elixir" ] 42 | args: [ 43 | "--name", 44 | "app-b@$(POD_NAME).beam-cluster.default.svc.cluster.local", 45 | "--cookie","$(ERLANG_COOKIE)", 46 | "--erl", 47 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 48 | "--no-halt", 49 | "-S","mix", 50 | "run" 51 | ] 52 | 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: app-b 58 | spec: 59 | type: LoadBalancer 60 | ports: 61 | - port: 4002 62 | targetPort: 4002 63 | selector: 64 | component: app-b 65 | -------------------------------------------------------------------------------- /k8s_statefulset/app-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: app-a 5 | spec: 6 | serviceName: "beam-cluster" 7 | replicas: 3 8 | selector: 9 | matchLabels: 10 | cluster: beam 11 | template: 12 | metadata: 13 | labels: 14 | component: app-a 15 | cluster: beam 16 | spec: 17 | containers: 18 | - name: app-a 19 | image: app-a 20 | imagePullPolicy: Never 21 | ports: 22 | - containerPort: 4001 23 | - containerPort: 9000 24 | env: 25 | - name: LIBCLUSTER_STRATEGY 26 | value: "kubernetes" 27 | - name: PORT 28 | value: "4001" 29 | - name: START_SERVER 30 | value: "true" 31 | - name: ERLANG_COOKIE 32 | value: "secret" 33 | - name: POD_IP 34 | valueFrom: 35 | fieldRef: 36 | fieldPath: status.podIP 37 | - name: POD_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: metadata.name 41 | command: [ "elixir" ] 42 | args: [ 43 | "--name", 44 | "cluster-app@$(POD_NAME).beam-cluster.default.svc.cluster.local", 45 | "--cookie","$(ERLANG_COOKIE)", 46 | "--erl", 47 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 48 | "--no-halt", 49 | "-S","mix", 50 | "run" 51 | ] 52 | 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: app-a 58 | spec: 59 | type: LoadBalancer 60 | ports: 61 | - port: 4001 62 | targetPort: 4001 63 | selector: 64 | component: app-a 65 | -------------------------------------------------------------------------------- /k8s_statefulset/app-b.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: app-b 5 | spec: 6 | serviceName: "beam-cluster" 7 | replicas: 3 8 | selector: 9 | matchLabels: 10 | cluster: beam 11 | template: 12 | metadata: 13 | labels: 14 | component: app-b 15 | cluster: beam 16 | spec: 17 | containers: 18 | - name: app-b 19 | image: app-b 20 | imagePullPolicy: Never 21 | ports: 22 | - containerPort: 4002 23 | - containerPort: 9000 24 | env: 25 | - name: LIBCLUSTER_STRATEGY 26 | value: "kubernetes" 27 | - name: PORT 28 | value: "4002" 29 | - name: START_SERVER 30 | value: "true" 31 | - name: ERLANG_COOKIE 32 | value: "secret" 33 | - name: POD_IP 34 | valueFrom: 35 | fieldRef: 36 | fieldPath: status.podIP 37 | - name: POD_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: metadata.name 41 | command: [ "elixir" ] 42 | args: [ 43 | "--name", 44 | "cluster-app@$(POD_NAME).beam-cluster.default.svc.cluster.local", 45 | "--cookie","$(ERLANG_COOKIE)", 46 | "--erl", 47 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 48 | "--no-halt", 49 | "-S","mix", 50 | "run" 51 | ] 52 | 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: app-b 58 | spec: 59 | type: LoadBalancer 60 | ports: 61 | - port: 4002 62 | targetPort: 4002 63 | selector: 64 | component: app-b 65 | -------------------------------------------------------------------------------- /k8s_gossip_statefulset/app-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: app-a 5 | spec: 6 | serviceName: "beam-cluster" 7 | replicas: 3 8 | selector: 9 | matchLabels: 10 | component: app-a 11 | template: 12 | metadata: 13 | labels: 14 | component: app-a 15 | cluster: beam 16 | spec: 17 | containers: 18 | - name: app-a 19 | image: app-a 20 | imagePullPolicy: Never 21 | ports: 22 | - containerPort: 4001 23 | - containerPort: 9000 24 | env: 25 | - name: LIBCLUSTER_STRATEGY 26 | value: "gossip" 27 | - name: PORT 28 | value: "4001" 29 | - name: START_SERVER 30 | value: "true" 31 | - name: ERLANG_COOKIE 32 | value: "secret" 33 | - name: POD_IP 34 | valueFrom: 35 | fieldRef: 36 | fieldPath: status.podIP 37 | - name: POD_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: metadata.name 41 | command: [ "elixir" ] 42 | args: [ 43 | "--name", 44 | "app-a@$(POD_NAME).beam-cluster.default.svc.cluster.local", 45 | "--cookie","$(ERLANG_COOKIE)", 46 | "--erl", 47 | "-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000", 48 | "--no-halt", 49 | "-S","mix", 50 | "run" 51 | ] 52 | 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: app-a 58 | spec: 59 | type: LoadBalancer 60 | ports: 61 | - port: 4001 62 | targetPort: 4001 63 | selector: 64 | component: app-a 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cluster 2 | 3 | ## Experiment with clustering. Check the [Medium article](https://anton-mishchuk.medium.com/notes-on-clustering-elixir-applications-49707ed53910) 4 | 5 | ### Run locally: 6 | START_SERVER=true iex --name app_a@127.0.0.1 --cookie secret -S mix 7 | 8 | START_SERVER=true iex --name app_b@127.0.0.1 --cookie secret -S mix 9 | 10 | Node.connect(:"app_b@127.0.0.1") 11 | 12 | 13 | #### With Cluster.Strategy.LocalEpmd 14 | LIBCLUSTER_STRATEGY=local_epmd iex --name app_a@127.0.0.1 --cookie secret -S mix 15 | 16 | LIBCLUSTER_STRATEGY=local_epmd iex --name app_b@127.0.0.1 --cookie secret -S mix 17 | 18 | ### docker-compose: 19 | #### From local 20 | Modify /etc/hosts 21 | 22 | 127.0.0.1 app-a1.docker 23 | 24 | 127.0.0.1 app-b1.docker 25 | 26 | iex --name local@app-a1.docker --cookie secret --erl '-dist_listen false -erl_epmd_port 9000' --remsh app-a1@app-a1.docker 27 | 28 | iex --name local@app-b1.docker --cookie secret --erl '-dist_listen false -erl_epmd_port 10000' --remsh app-b1@app-b1.docker 29 | 30 | ### Kubernetes: 31 | check: 32 | 33 | kubectl get service 34 | 35 | minikube service app-a 36 | 37 | restart: 38 | 39 | kubectl get deployment 40 | 41 | kubectl delete deployment app-a-deployment 42 | 43 | kubectl delete deployment app-b-deployment 44 | 45 | kubectl apply -f k8s 46 | 47 | rebuild containers: 48 | 49 | eval $(minikube -p minikube docker-env) 50 | 51 | docker build -t app-a . 52 | 53 | docker build -t app-b . 54 | 55 | Connect to cluster: 56 | 57 | Modify /etc/hosts 58 | 59 | 127.0.0.1 app-a-0.beam-headless.default.svc.cluster.local 60 | 61 | iex --cookie secret --name local@app-a-0.beam-cluster.default.svc.cluster.local --erl '-dist_listen false -erl_epmd_port 9000' --remsh app-a@app-a-0.beam-cluster.default.svc.cluster.local 62 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | networks: 3 | cluster-network: 4 | driver: bridge 5 | 6 | services: 7 | app-a1: 8 | build: ./apps/app_a/ 9 | command: elixir --name "app-a1@app-a1.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 10 | hostname: app-a1.docker 11 | ports: 12 | - "9000:9000" 13 | - "4001:4001" 14 | environment: 15 | - LIBCLUSTER_STRATEGY=gossip 16 | - PORT=4001 17 | - START_SERVER=true 18 | networks: 19 | - cluster-network 20 | app-a2: 21 | build: ./apps/app_a/ 22 | command: elixir --name "app-a2@app-a2.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 23 | hostname: app-a2.docker 24 | environment: 25 | - LIBCLUSTER_STRATEGY=gossip 26 | networks: 27 | - cluster-network 28 | app-a3: 29 | build: ./apps/app_a/ 30 | command: elixir --name "app-a3@app-a3.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 31 | hostname: app-a3.docker 32 | environment: 33 | - LIBCLUSTER_STRATEGY=gossip 34 | networks: 35 | - cluster-network 36 | 37 | app-b1: 38 | build: ./apps/app_b/ 39 | command: elixir --name "app-b1@app-b1.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 40 | hostname: app-b1.docker 41 | ports: 42 | - "10000:9000" 43 | - "5001:5001" 44 | environment: 45 | - PORT=5001 46 | - START_SERVER=true 47 | - LIBCLUSTER_STRATEGY=gossip 48 | networks: 49 | - cluster-network 50 | app-b2: 51 | build: ./apps/app_b/ 52 | command: elixir --name "app-b2@app-b2.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 53 | hostname: app-b2.docker 54 | environment: 55 | - LIBCLUSTER_STRATEGY=gossip 56 | networks: 57 | - cluster-network 58 | app-b3: 59 | build: ./apps/app_b/ 60 | command: elixir --name "app-b3@app-b3.docker" --erl '-kernel inet_dist_listen_min 9000 inet_dist_listen_max 9000' --cookie secret --no-halt -S mix run 61 | hostname: app-b3.docker 62 | environment: 63 | - LIBCLUSTER_STRATEGY=gossip 64 | networks: 65 | - cluster-network 66 | 67 | livebook: 68 | image: livebook/livebook:edge 69 | networks: 70 | - cluster-network 71 | environment: 72 | - LIVEBOOK_PASSWORD=securesecret 73 | - LIVEBOOK_DISTRIBUTION=name 74 | - LIVEBOOK_PORT=8080 75 | - LIVEBOOK_NODE=livebook@livebook.docker 76 | - LIVEBOOK_COOKIE=secret 77 | - LIVEBOOK_DEFAULT_RUNTIME=attached:livebook@livebook.docker:secret 78 | hostname: livebook.docker 79 | ports: 80 | - "8080:8080" 81 | - "8081:8081" 82 | 83 | -------------------------------------------------------------------------------- /apps/app_a/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.2", "84c6ebfdc72a03805955abfb5ff573f71921a3e299279cc3445445d5af619ad1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8b691ce8185670fc8f3fc0b7ed59eff66c6889df890d13411f8f1a0e6871d8a5"}, 7 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 8 | "node_registry": {:hex, :node_registry, "0.1.1", "64e62d2599ca6e1834a594f4b609673fb234e36e06148f735cd0d1c01e62a608", [:mix], [], "hexpm", "e7e4a33d706a3d8345347c81324c0e396c37c910c45c8d32bdfd0af61eafe332"}, 9 | "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, 10 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, 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.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 14 | } 15 | -------------------------------------------------------------------------------- /apps/app_b/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.2", "84c6ebfdc72a03805955abfb5ff573f71921a3e299279cc3445445d5af619ad1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8b691ce8185670fc8f3fc0b7ed59eff66c6889df890d13411f8f1a0e6871d8a5"}, 7 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 8 | "node_registry": {:hex, :node_registry, "0.1.0", "53858831ab9e1c85225e15c240e864ff053e4fb2c9a7c6050d55b96e653168aa", [:mix], [], "hexpm", "cb555f9b17e51f04981f629da4139e9aeca54533bc1421185dc2279790e8640d"}, 9 | "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, 10 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, 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.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 14 | } 15 | -------------------------------------------------------------------------------- /apps/cluster_tests/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.2", "84c6ebfdc72a03805955abfb5ff573f71921a3e299279cc3445445d5af619ad1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8b691ce8185670fc8f3fc0b7ed59eff66c6889df890d13411f8f1a0e6871d8a5"}, 7 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 8 | "node_registry": {:hex, :node_registry, "0.1.1", "64e62d2599ca6e1834a594f4b609673fb234e36e06148f735cd0d1c01e62a608", [:mix], [], "hexpm", "e7e4a33d706a3d8345347c81324c0e396c37c910c45c8d32bdfd0af61eafe332"}, 9 | "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, 10 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, 11 | "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, 12 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 13 | "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 14 | } 15 | -------------------------------------------------------------------------------- /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 | "global_flags": {:hex, :global_flags, "1.0.0", "ee6b864979a1fb38d1fbc67838565644baf632212bce864adca21042df036433", [:rebar3], [], "hexpm", "85d944cecd0f8f96b20ce70b5b16ebccedfcd25e744376b131e89ce61ba93176"}, 6 | "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, 7 | "libcluster": {:hex, :libcluster, "3.3.2", "84c6ebfdc72a03805955abfb5ff573f71921a3e299279cc3445445d5af619ad1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8b691ce8185670fc8f3fc0b7ed59eff66c6889df890d13411f8f1a0e6871d8a5"}, 8 | "local_cluster": {:hex, :local_cluster, "1.2.1", "8eab3b8a387680f0872eacfb1a8bd5a91cb1d4d61256eec6a655b07ac7030c73", [:mix], [{:global_flags, "~> 1.0", [hex: :global_flags, repo: "hexpm", optional: false]}], "hexpm", "aae80c9bc92c911cb0be085fdeea2a9f5b88f81b6bec2ff1fec244bb0acc232c"}, 9 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 10 | "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, 11 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, 12 | "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, 13 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 14 | "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 15 | } 16 | --------------------------------------------------------------------------------