Barnacle
Self-healing clusters for Gleam applications on the BEAM!
Connect to other BEAM nodes, and automatically reconnect when they go down.
Features
- Discover nodes using different strategies. Built-in strategies:
- Local EPMD
- Remote EPMD
- DNS
- Automatically reconnect to nodes when they come back online
- Supply your own strategies
- Listen to events
- Trigger refreshes manually
Getting Started
gleam add barnacle
You’ll also need to start your Gleam process with a node name, and set the cookie.
You can do this using the ERL_FLAGS
environment variable.
There’s a Fly.io example coming soon.
ERL_FLAGS="-name my_app@127.0.0.1 -setcookie my_cookie" gleam run
Start Barnacle under a Supervisor (recommended)
import barnacle
import gleam/erlang/process
import gleam/otp/supervisor
pub fn main() {
// Configure your Barnacle
let barnacle =
barnacle.local_epmd()
|> barnacle.with_poll_interval(15_000)
|> barnacle.with_name("my_barnacle")
// Create a process to receive the child process later
let self = process.new_subject()
// Start the child process under a supervisor
let barnacle_worker = barnacle.child_spec(barnacle, None)
let assert Ok(_) = supervisor.start(supervisor.add(_, barnacle_worker))
// Get a subject to send messages to the child process
let assert Ok(barnacle_subject) = process.receive(self, 10_000)
// Continue your setup...
process.sleep_forever()
}
Start Barnacle as a standalone actor
This is not recommended as your barnacle won’t be restarted if it crashes for any reason.
import barnacle
import gleam/erlang/process
pub fn main() {
// Configure your Barnacle
let barnacle =
barnacle.local_epmd()
|> barnacle.with_poll_interval(15_000)
|> barnacle.with_name("my_barnacle")
// Start the actor
let barnacle_subject = barnacle.start(barnacle)
// Continue your setup...
process.sleep_forever()
}
Run the refresh function once
If you don’t need any sort of polling, or want to manage your the refresh lifecycle
yourself, you can call the run_once
function to run the refresh function a single
time.
In this case, most of the configuration options will be ignored.
import barnacle
pub fn main() {
// Configure your Barnacle
let barnacle =
barnacle.local_epmd()
// Run the refresh function once
barnacle.run_once(barnacle)
// Continue your program...
}
Strategies
Barnacle ships with a few built-in strategies, but you can also supply your own by providing a single callback function.
Local EPMD
Discover nodes using the local EPMD. This will automatically attempt to connect to nodes that are running on the same machine.
import barnacle
pub fn main() {
barnacle.local_epmd()
|> barnacle.start
}
Remote EPMD
Connect to nodes using a known list.
import barnacle
import gleam/erlang/atom
pub fn main() {
barnacle.epmd(
["node1@192.168.1.1", "node2@192.168.1.2"]
|> list.map(atom.create_from_string),
)
|> barnacle.start
}
DNS
Discover nodes using DNS.
The first argument is the basename of the node. Currently, Barnacle only supports connecting to nodes with the same basename.
Barnacle provides a helper function to get the basename of the current node.
import barnacle
pub fn main() {
let assert Ok(basename) = barnacle.get_node_basename(node.self())
barnacle.dns(
basename,
// The hostname to query against
"my_app.example.com",
)
|> barnacle.start
}
Creating a custom strategy
You can create your own strategy using the new_strategy
function.
import barnacle
pub fn main() {
barnacle.new_strategy(
// Discover nodes
fn() {
Ok([])
},
)
|> barnacle.custom
|> barnacle.start
}
You can also supply your own functions for connecting, disconnecting, and listing nodes. If these are not supplied, the built-in functions will be used.
This may be useful if you want to use an alternative to Distributed Erlang, such as Partisan.
import barnacle
pub fn main() {
barnacle.new_strategy(my_discover_function)
|> barnacle.with_connect_nodes_function(my_connect_function)
|> barnacle.with_disconnect_nodes_function(my_disconnect_function)
|> barnacle.with_list_nodes_function(my_list_function)
|> barnacle.custom
|> barnacle.start
}
You can find more information about creating custom strategies in the docs.
Events
You can provide a custom subject to receive events from your barnacle. This will be notified whenever the barnacle refreshes, is paused, or is shutdown.
import barnacle
import gleam/erlang/process
pub fn main() {
let self = process.new_subject()
// Configure your Barnacle
let barnacle =
barnacle.local_epmd()
|> barnacle.with_poll_interval(15_000)
|> barnacle.with_name("my_barnacle")
|> barnacle.with_listener(self)
// Start the actor
barnacle.start(barnacle)
// Wait for the barnacle to refresh
process.sleep(10_000)
let assert Ok(barnacle.RefreshResponse(Ok(new_nodes))) = process.receive(self, 10_000)
}
Manual interactions
You can interact with your barnacle manually by sending messages to it.
import barnacle
import gleam/erlang/process
import gleam/otp/actor
pub fn main() {
// Start a barnacle
let barnacle_subject =
barnacle.local_epmd()
|> barnacle.start
let assert Ok(_) = barnacle.pause(barnacle_subject, 1000)
let assert Ok(_) = barnacle.refresh(barnacle_subject, 10_000)
let assert Ok(_) = barnacle.shutdown(barnacle_subject, 1000)
}
Further documentation can be found at https://hexdocs.pm/barnacle.
Development
gleam run # Run the project
gleam test # Run the tests
If you would like to contribute, please open an issue or a PR. New strategies are welcome, though try to keep dependencies to a minimum.
TODO
- Tests!
-
Add new strategies
- Kubernetes
- Kubernetes with DNS
- Multicast UDP gossip
-
.hosts.erlang
file
- Add a Fly.io example
With thanks
A lot of inspiration came from the following projects:
Thanks!