Add Erlang clustering via libcluster and expose LiveDashboard

- libcluster with Kubernetes IP strategy for pod discovery
- RBAC for pod list/get, POD_IP from downward API, shared cookie
- LiveDashboard at /dashboard for all environments
This commit is contained in:
Graham McIntire 2026-04-06 09:46:35 -05:00
parent f118452035
commit 2de94e2318
9 changed files with 72 additions and 10 deletions

View file

@ -129,6 +129,20 @@ if config_env() == :prod do
]}
]
config :libcluster,
topologies: [
k8s: [
strategy: Cluster.Strategy.Kubernetes,
config: [
mode: :ip,
kubernetes_ip_lookup_mode: :pods,
kubernetes_node_basename: "microwaveprop",
kubernetes_selector: "app=prop",
kubernetes_namespace: "prop"
]
]
]
config :microwaveprop, :email_from, {"NTMS Propagation", "graham@w5isp.com"}
config :microwaveprop, hrrr_base_url: System.get_env("HRRR_BASE_URL", "http://skippy.w5isp.com:8080")
config :microwaveprop, srtm_tiles_dir: "/srtm"

View file

@ -30,6 +30,7 @@ spec:
matchLabels:
app: prop
topologyKey: topology.kubernetes.io/physical-host
serviceAccountName: prop
imagePullSecrets:
- name: forgejo-registry
securityContext:
@ -43,6 +44,10 @@ spec:
image: git.mcintire.me/graham/prop:main-1775486348-7654146 # {"$imagepolicy": "flux-system:prop"}
imagePullPolicy: IfNotPresent
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: PHX_SERVER
value: "true"
- name: PHX_HOST

View file

@ -3,5 +3,6 @@ kind: Kustomization
resources:
- namespace.yaml
- secret.yaml
- rbac.yaml
- deployment.yaml
- service.yaml

29
k8s/rbac.yaml Normal file
View file

@ -0,0 +1,29 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: prop
namespace: prop
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prop-pod-reader
namespace: prop
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: prop-pod-reader
namespace: prop
subjects:
- kind: ServiceAccount
name: prop
namespace: prop
roleRef:
kind: Role
name: prop-pod-reader
apiGroup: rbac.authorization.k8s.io

View file

@ -7,9 +7,12 @@ defmodule Microwaveprop.Application do
@impl true
def start(_type, _args) do
topologies = Application.get_env(:libcluster, :topologies, [])
children = [
MicrowavepropWeb.Telemetry,
Microwaveprop.Repo,
{Cluster.Supervisor, [topologies, [name: Microwaveprop.ClusterSupervisor]]},
{Phoenix.PubSub, name: Microwaveprop.PubSub},
{Oban, Application.fetch_env!(:microwaveprop, Oban)},
Microwaveprop.RepoListener,

View file

@ -47,19 +47,19 @@ defmodule MicrowavepropWeb.Router do
# pipe_through :api
# end
# Enable LiveDashboard and Swoosh mailbox preview in development
if Application.compile_env(:microwaveprop, :dev_routes) do
# If you want to use the LiveDashboard in production, you should put
# it behind authentication and allow only admins to access it.
# If your application does not have an admins-only section yet,
# you can use Plug.BasicAuth to set up some basic authentication
# as long as you are also using SSL (which you should anyway).
import Phoenix.LiveDashboard.Router
scope "/dev" do
scope "/" do
pipe_through :browser
live_dashboard "/dashboard", metrics: MicrowavepropWeb.Telemetry
end
# Enable Swoosh mailbox preview in development
if Application.compile_env(:microwaveprop, :dev_routes) do
scope "/dev" do
pipe_through :browser
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end

View file

@ -70,7 +70,8 @@ defmodule Microwaveprop.MixProject do
{:axon, "~> 0.7", only: [:dev, :test]},
{:exla, "~> 0.9", only: [:dev, :test]},
{:polaris, "~> 0.1", only: [:dev, :test]},
{:tidewave, "~> 0.5", only: :dev}
{:tidewave, "~> 0.5", only: :dev},
{:libcluster, "~> 3.4"}
]
end

View file

@ -23,6 +23,7 @@
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"lazy_html": {:hex, :lazy_html, "0.1.10", "ffe42a0b4e70859cf21a33e12a251e0c76c1dff76391609bd56702a0ef5bc429", [: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", "50f67e5faa09d45a99c1ddf3fac004f051997877dc8974c5797bb5ccd8e27058"},
"libcluster": {:hex, :libcluster, "3.5.0", "5ee4cfde4bdf32b2fef271e33ce3241e89509f4344f6c6a8d4069937484866ba", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebf6561fcedd765a4cd43b4b8c04b1c87f4177b5fb3cbdfe40a780499d72f743"},
"live_stash": {:hex, :live_stash, "0.1.1", "d6a715708d0d281608de62a15ee8aca63427046daaa6333a235aa9e55f2cccb2", [:mix], [{:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:uniq, "~> 0.6", [hex: :uniq, repo: "hexpm", optional: false]}], "hexpm", "96341ebd35513f6e71b4e268ef4d413b1e21048a3d759cafe0cd8378ad8df908"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},

8
rel/env.sh.eex Normal file
View file

@ -0,0 +1,8 @@
#!/bin/sh
# Set the release node name from the pod IP for Erlang clustering
if [ -n "$POD_IP" ]; then
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE=microwaveprop@${POD_IP}
export RELEASE_COOKIE="${ERLANG_COOKIE:-microwaveprop_cookie}"
fi