clustering

8c257f3 · gmorell · 2026-04-28 00:43

7 files +66 -7

Files changed

modified app.yml
+49 −0
@@ -43,6 +43,21 @@ spec:
43 43 - name: POOL_SIZE
44 44 value: "10"
45 45
46 + # Cluster glue: every pod becomes diogramos@<pod-ip>, joins
47 + # via DNS query against the headless service below, and
48 + # shares Authority registry state through Horde.
49 + - name: POD_IP
50 + valueFrom:
51 + fieldRef:
52 + fieldPath: status.podIP
53 + - name: DNS_CLUSTER_QUERY
54 + value: diogramos-headless.diogramos.svc.cluster.local
55 + - name: RELEASE_COOKIE
56 + valueFrom:
57 + secretKeyRef:
58 + name: elixir-args
59 + key: erlang_cookie
60 +
46 61 - name: SECRET_KEY_BASE
47 62 valueFrom:
48 63 secretKeyRef:
@@ -104,6 +119,35 @@ spec:
104 119 selector:
105 120 name: diogramos
106 121 ---
122 +# Headless service so dns_cluster can resolve all pod IPs for clustering.
123 +# Pods discover siblings via SRV/A records on this name; epmd-less long-
124 +# name distribution does the actual node connection.
125 +apiVersion: v1
126 +kind: Service
127 +metadata:
128 + name: diogramos-headless
129 + namespace: diogramos
130 +spec:
131 + clusterIP: None
132 + publishNotReadyAddresses: true
133 + ports:
134 + - port: 4000
135 + targetPort: 4000
136 + protocol: TCP
137 + name: http
138 + selector:
139 + name: diogramos
140 +---
141 +apiVersion: traefik.io/v1alpha1
142 +kind: Middleware
143 +metadata:
144 + name: redirect-https
145 + namespace: diogramos
146 +spec:
147 + redirectScheme:
148 + scheme: https
149 + permanent: true
150 +---
107 151 apiVersion: networking.k8s.io/v1
108 152 kind: Ingress
109 153 metadata:
@@ -112,6 +156,11 @@ metadata:
112 156 annotations:
113 157 cert-manager.io/cluster-issuer: letsencrypt-prod
114 158 kubernetes.io/ingress.class: "traefik"
159 + # Listen on both Traefik entrypoints. The middleware below redirects
160 + # http → https for the `web` (port 80) entrypoint, so the app itself
161 + # never has to issue a 301 (which broke the LiveView WS handshake).
162 + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
163 + traefik.ingress.kubernetes.io/router.middlewares: diogramos-redirect-https@kubernetescrd
115 164 spec:
116 165 tls:
117 166 - hosts:
modified lib/diogramos/diagrams/authority.ex
+11 −6
@@ -39,7 +39,7 @@ defmodule Diogramos.Diagrams.Authority do
39 39 """
40 40 @spec for_canvas(integer()) :: {:ok, pid()} | {:error, term()}
41 41 def for_canvas(canvas_id) when is_integer(canvas_id) do
42 − case Registry.lookup(@registry, canvas_id) do
42 + case Horde.Registry.lookup(@registry, canvas_id) do
43 43 [{pid, _}] -> {:ok, pid}
44 44 [] -> start_authority(canvas_id)
45 45 end
@@ -70,7 +70,7 @@ defmodule Diogramos.Diagrams.Authority do
70 70 delete. Persists current state before exit.
71 71 """
72 72 def stop(canvas_id) do
73 − case Registry.lookup(@registry, canvas_id) do
73 + case Horde.Registry.lookup(@registry, canvas_id) do
74 74 [{pid, _}] -> GenServer.stop(pid, :normal)
75 75 [] -> :ok
76 76 end
@@ -89,8 +89,13 @@ defmodule Diogramos.Diagrams.Authority do
89 89 def start_link(_opts \\ []) do
90 90 Supervisor.start_link(
91 91 [
92 − {Registry, keys: :unique, name: @registry},
93 − {DynamicSupervisor, strategy: :one_for_one, name: @supervisor}
92 + {Horde.Registry, keys: :unique, name: @registry, members: :auto},
93 + {Horde.DynamicSupervisor,
94 + strategy: :one_for_one,
95 + name: @supervisor,
96 + distribution_strategy: Horde.UniformDistribution,
97 + members: :auto,
98 + process_redistribution: :active}
94 99 ],
95 100 strategy: :one_for_all,
96 101 name: __MODULE__.Supervisor
@@ -99,7 +104,7 @@ defmodule Diogramos.Diagrams.Authority do
99 104
100 105 defp start_authority(canvas_id) do
101 106 spec = {__MODULE__.Server, canvas_id}
102 − DynamicSupervisor.start_child(@supervisor, spec)
107 + Horde.DynamicSupervisor.start_child(@supervisor, spec)
103 108 end
104 109
105 110 ## Server module ---------------------------------------------------------
@@ -112,7 +117,7 @@ defmodule Diogramos.Diagrams.Authority do
112 117
113 118 def start_link(canvas_id) do
114 119 GenServer.start_link(__MODULE__, canvas_id,
115 − name: {:via, Registry, {Authority.registry(), canvas_id}}
120 + name: {:via, Horde.Registry, {Authority.registry(), canvas_id}}
116 121 )
117 122 end
118 123
modified mix.exs
+2 −1
@@ -68,7 +68,8 @@ defmodule Diogramos.MixProject do
68 68 {:dns_cluster, "~> 0.2.0"},
69 69 {:bandit, "~> 1.5"},
70 70 {:sentry, "~> 10.0"},
71 − {:hackney, "~> 1.20"}
71 + {:hackney, "~> 1.20"},
72 + {:horde, "~> 0.9.0"}
72 73 ]
73 74 end
74 75
modified mix.lock
+4 −0
@@ -6,6 +6,7 @@
6 6 "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
7 7 "db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"},
8 8 "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
9 + "delta_crdt": {:hex, :delta_crdt, "0.6.5", "c7bb8c2c7e60f59e46557ab4e0224f67ba22f04c02826e273738f3dcc4767adc", [:mix], [{:merkle_map, "~> 0.2.0", [hex: :merkle_map, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6ae23a525d30f96494186dd11bf19ed9ae21d9fe2c1f1b217d492a7cc7294ae"},
9 10 "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
10 11 "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
11 12 "ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
@@ -18,10 +19,13 @@
18 19 "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
19 20 "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
20 21 "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
22 + "horde": {:hex, :horde, "0.9.1", "547507dfe1228471d9a31cece7653be81df2cf8f210816f672ce507dc9606128", [:mix], [{:delta_crdt, "~> 0.6.2", [hex: :delta_crdt, repo: "hexpm", optional: false]}, {:libring, "~> 1.7", [hex: :libring, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 0.5.0 or ~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "14e1be4d6a84b066101641628a64f849d6df4f0311e93b7a9c39709e9c0bf4ba"},
21 23 "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
22 24 "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
23 25 "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
24 26 "lazy_html": {:hex, :lazy_html, "0.1.11", "136c8e9cd616b4f4e9c1562daa683880891120b759606dc4c3b6b18058ba5d79", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "3b1be592929c31eca1a21673d25696e5c14cddfe922d9d1a3e3b48be4163883b"},
27 + "libring": {:hex, :libring, "1.7.0", "4f245d2f1476cd7ed8f03740f6431acba815401e40299208c7f5c640e1883bda", [:mix], [], "hexpm", "070e3593cb572e04f2c8470dd0c119bc1817a7a0a7f88229f43cf0345268ec42"},
28 + "merkle_map": {:hex, :merkle_map, "0.2.2", "f36ff730cca1f2658e317a3c73406f50bbf5ac8aff54cf837d7ca2069a6e251c", [:mix], [], "hexpm", "383107f0503f230ac9175e0631647c424efd027e89ea65ab5ea12eeb54257aaf"},
25 29 "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
26 30 "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
27 31 "mimerl": {:hex, :mimerl, "1.5.0", "f35aca6f23242339b3666e0ac0702379e362b469d0aea167f6cc713547e777ed", [:rebar3], [], "hexpm", "db648ce065bae14ea84ca8b5dd123f42f49417cef693541110bf6f9e9be9ecc4"},

Parents: 39209b7