Add support for DNS clustering (#2578)

This commit is contained in:
Alexandre de Souza 2024-04-23 15:40:18 -03:00 committed by GitHub
parent d6848ed106
commit d4752ebd52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 114 additions and 44 deletions

View file

@ -14,7 +14,7 @@ It automatically sets up a cluster to run on Fly using DNS configuration. Behind
Sets up a cluster using DNS for queries for A/AAAA records to discover new nodes. Additionally, you must additionally set the following env vars:
* `LIVEBOOK_NODE=livebook_server@IP`, where `IP` is the machine IP of each deployed node
* `LIVEBOOK_NODE=livebook_server@MACHINE_IP`, where `MACHINE_IP` is the machine IP of each deployed node
* You must set `LIVEBOOK_SECRET_KEY_BASE` and `LIVEBOOK_COOKIE` to different random values (use `openssl rand -base64 48` to generate said values)

View file

@ -54,7 +54,7 @@ defmodule Livebook.Hubs.Dockerfile do
types = %{
deploy_all: :boolean,
docker_tag: :string,
clustering: Ecto.ParameterizedType.init(Ecto.Enum, values: [:fly_io]),
clustering: Ecto.ParameterizedType.init(Ecto.Enum, values: [:fly_io, :dns]),
zta_provider: Ecto.ParameterizedType.init(Ecto.Enum, values: zta_types),
zta_key: :string
}
@ -151,15 +151,30 @@ defmodule Livebook.Hubs.Dockerfile do
{secret_key_base, cookie} = deterministic_skb_and_cookie(secret_key)
startup =
if config.clustering == :fly_io do
"""
# --- Clustering ---
case to_string(config.clustering) do
"fly_io" ->
"""
# --- Clustering ---
# Set the same Livebook secrets across all nodes
ENV LIVEBOOK_SECRET_KEY_BASE "#{secret_key_base}"
ENV LIVEBOOK_COOKIE "#{cookie}"
ENV LIVEBOOK_CLUSTER "fly"
"""
# Set the same Livebook secrets across all nodes
ENV LIVEBOOK_SECRET_KEY_BASE "#{secret_key_base}"
ENV LIVEBOOK_COOKIE "#{cookie}"
ENV LIVEBOOK_CLUSTER "fly"
"""
"dns" ->
"""
# --- Clustering ---
# Set the same Livebook secrets across all nodes
ENV LIVEBOOK_SECRET_KEY_BASE "#{secret_key_base}"
ENV LIVEBOOK_COOKIE "#{cookie}"
ENV LIVEBOOK_CLUSTER "dns:QUERY"
ENV LIVEBOOK_NODE "livebook_server@MACHINE_IP"
"""
_ ->
nil
end
[
@ -324,17 +339,27 @@ defmodule Livebook.Hubs.Dockerfile do
[]
end
clustering_env =
if config.clustering == :fly_io do
{secret_key_base, cookie} = deterministic_skb_and_cookie(hub.teams_key)
{secret_key_base, cookie} = deterministic_skb_and_cookie(hub.teams_key)
[
{"LIVEBOOK_CLUSTER", "fly"},
{"LIVEBOOK_SECRET_KEY_BASE", secret_key_base},
{"LIVEBOOK_COOKIE", cookie}
]
else
[]
clustering_env =
case to_string(config.clustering) do
"fly_io" ->
[
{"LIVEBOOK_CLUSTER", "fly"},
{"LIVEBOOK_SECRET_KEY_BASE", secret_key_base},
{"LIVEBOOK_COOKIE", cookie}
]
"dns" ->
[
{"LIVEBOOK_NODE", "livebook_server@MACHINE_IP"},
{"LIVEBOOK_CLUSTER", "dns:QUERY"},
{"LIVEBOOK_SECRET_KEY_BASE", secret_key_base},
{"LIVEBOOK_COOKIE", cookie}
]
_ ->
[]
end
%{image: image, env: base_image.env ++ env ++ hub_env ++ clustering_env}

View file

@ -25,7 +25,7 @@ defmodule Livebook.Teams.DeploymentGroup do
field :name, :string
field :mode, Ecto.Enum, values: [:online, :offline], default: :online
field :hub_id, :string
field :clustering, Ecto.Enum, values: [:fly_io]
field :clustering, Ecto.Enum, values: [:fly_io, :dns]
field :zta_provider, Ecto.Enum, values: @zta_providers
field :zta_key, :string

View file

@ -92,28 +92,43 @@ defmodule LivebookWeb.AppComponents do
def deployment_group_form_content(assigns) do
~H"""
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<.select_field
label="Clustering"
help={
~S'''
When running multiple
instances of Livebook,
they need to be connected
into a single cluster.
You must either deploy
it as a single instance
or choose a platform to
enable clustering on.
'''
}
field={@form[:clustering]}
options={[
{"Single instance", ""},
{"Fly.io", "fly_io"}
]}
disabled={@disabled}
/>
<div class="flex flex-col">
<.select_field
label="Clustering"
help={
~S'''
When running multiple
instances of Livebook,
they need to be connected
into a single cluster.
You must either deploy
it as a single instance
or choose a platform to
enable clustering on.
'''
}
field={@form[:clustering]}
options={[
{"Single instance", ""},
{"Fly.io", "fly_io"},
{"DNS", "dns"}
]}
disabled={@disabled}
/>
<div :if={to_string(@form[:clustering].value) == "dns"} class="text-sm mt-1">
See the
<a
class="text-blue-800 hover:text-blue-600"
href="https://hexdocs.pm/livebook/docker.html#clustering"
>
Clustering docs
</a>
for more information.
</div>
</div>
</div>
<%= if Hubs.Provider.type(@hub) == "team" do %>
<div class="flex flex-col">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
@ -131,6 +146,7 @@ defmodule LivebookWeb.AppComponents do
options={zta_options()}
disabled={@disabled}
/>
<.text_field
:if={zta_metadata = zta_metadata(@form[:zta_provider].value)}
field={@form[:zta_key]}
@ -141,6 +157,7 @@ defmodule LivebookWeb.AppComponents do
disabled={@disabled}
/>
</div>
<div :if={zta_metadata = zta_metadata(@form[:zta_provider].value)} class="text-sm mt-1">
See the
<a

View file

@ -207,6 +207,17 @@ defmodule Livebook.Hubs.DockerfileTest do
assert dockerfile =~ ~s/ENV LIVEBOOK_CLUSTER "fly"/
end
test "deploying with dns cluster setup" do
config = dockerfile_config(%{clustering: :dns})
hub = personal_hub()
file = Livebook.FileSystem.File.local(p("/notebook.livemd"))
dockerfile = Dockerfile.airgapped_dockerfile(config, hub, [], [], file, [], %{})
assert dockerfile =~ ~s/ENV LIVEBOOK_NODE "livebook_server@MACHINE_IP"/
assert dockerfile =~ ~s/ENV LIVEBOOK_CLUSTER "dns:QUERY"/
end
end
describe "online_docker_info/3" do
@ -255,6 +266,17 @@ defmodule Livebook.Hubs.DockerfileTest do
assert {"LIVEBOOK_CLUSTER", "fly"} in env
end
test "deploying with dns cluster setup" do
config = dockerfile_config(%{clustering: :dns})
hub = team_hub()
agent_key = Livebook.Factory.build(:agent_key)
%{env: env} = Dockerfile.online_docker_info(config, hub, agent_key)
assert {"LIVEBOOK_NODE", "livebook_server@MACHINE_IP"} in env
assert {"LIVEBOOK_CLUSTER", "dns:QUERY"} in env
end
end
describe "warnings/6" do

View file

@ -78,7 +78,7 @@ defmodule Livebook.Teams.ConnectionTest do
assert_receive :connected
# creates a new deployment group with offline mode
deployment_group = build(:deployment_group, name: "FOO", mode: :offline)
deployment_group = build(:deployment_group, name: "FOO", mode: :offline, clustering: :dns)
assert {:ok, _id} =
Livebook.Teams.create_deployment_group(hub, deployment_group)
@ -88,11 +88,14 @@ defmodule Livebook.Teams.ConnectionTest do
assert deployment_group_created.name == deployment_group.name
assert String.to_existing_atom(deployment_group_created.mode) == deployment_group.mode
assert String.to_existing_atom(deployment_group_created.clustering) ==
deployment_group.clustering
# since the deployment group is with offline mode, the agent key shouldn't exists
assert deployment_group_created.agent_keys == []
# creates a new deployment group with online mode
deployment_group = build(:deployment_group, name: "BAR", mode: :online)
deployment_group = build(:deployment_group, name: "BAR", mode: :online, clustering: :dns)
{:ok, _id} = Livebook.Teams.create_deployment_group(hub, deployment_group)
# deployment_group name and mode are not encrypted
@ -100,6 +103,9 @@ defmodule Livebook.Teams.ConnectionTest do
assert deployment_group_created.name == deployment_group.name
assert String.to_existing_atom(deployment_group_created.mode) == deployment_group.mode
assert String.to_existing_atom(deployment_group_created.clustering) ==
deployment_group.clustering
# receives the built-in agent key
assert [agent_key] = deployment_group_created.agent_keys
assert is_binary(agent_key.key)