Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add keepalived to Anvil #17

Merged
merged 45 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6314099
add keepalived
SK1Y101 Jun 13, 2024
187a861
switch String to STRING
SK1Y101 Jun 13, 2024
80933d5
spell haproxy correctly
SK1Y101 Jun 13, 2024
bba1eb8
provider name?
SK1Y101 Jun 17, 2024
a3f30b4
shouldn't need .name
SK1Y101 Jun 17, 2024
bb6de59
move keepalive to juju step, not distinct terraform
SK1Y101 Jun 18, 2024
e5dac64
fix unterminated string
SK1Y101 Jun 19, 2024
03c8730
None is an allowed vip value
SK1Y101 Jun 19, 2024
98a59d2
formatting
SK1Y101 Jun 19, 2024
1f7bc82
count not counts
SK1Y101 Jun 19, 2024
d8cc0e3
try this
SK1Y101 Jun 21, 2024
93228e9
don't double set provider
SK1Y101 Jun 21, 2024
59d0fda
provider passing
SK1Y101 Jun 21, 2024
606de0d
try adding a haproxy provider?
SK1Y101 Jun 25, 2024
dae4c92
providor maybe
SK1Y101 Jun 25, 2024
7b7b647
maybe this works?
SK1Y101 Jun 25, 2024
ae926cc
try using applications instead of provider
SK1Y101 Jun 25, 2024
df493ab
correct config location
SK1Y101 Jun 25, 2024
2e298cf
try qit that
SK1Y101 Jun 26, 2024
2efc604
correct config location
SK1Y101 Jun 26, 2024
a08ca1f
use variables correctly# --------------------------------------------…
SK1Y101 Jun 26, 2024
cf66d8f
type ignore remove
SK1Y101 Jun 26, 2024
e56cfc8
integration to use the endpoints
SK1Y101 Jun 27, 2024
05ba12c
use max
SK1Y101 Jun 27, 2024
5134127
move validation
SK1Y101 Jun 27, 2024
8b1663c
default is an empty string, not None
SK1Y101 Jun 27, 2024
3d87ec0
add newlines
SK1Y101 Jun 27, 2024
ebc6113
empty vip
SK1Y101 Jun 27, 2024
5a6d056
try accessing all resources
SK1Y101 Jun 27, 2024
da6aa94
format and rebase
SK1Y101 Jun 27, 2024
cf45c55
remove wildcard
SK1Y101 Jul 1, 2024
2eaa068
update readme
SK1Y101 Jul 1, 2024
d07e2cc
readme about accepting defaults
SK1Y101 Jul 1, 2024
1e57bd7
quick readme changes
SK1Y101 Jul 1, 2024
84216b6
load previous questions from DB
SK1Y101 Jul 1, 2024
d20574d
re-add config variable
SK1Y101 Jul 2, 2024
aaa5d6e
Merge branch 'main' into add-vip
SK1Y101 Jul 3, 2024
0410006
transition to new
SK1Y101 Jul 3, 2024
be019a3
linting
SK1Y101 Jul 3, 2024
e33d8ed
more linting
SK1Y101 Jul 3, 2024
5250b97
remove extra formatting
SK1Y101 Jul 3, 2024
df0253b
use the correct config name
SK1Y101 Jul 3, 2024
ac95076
add other configuration options
SK1Y101 Jul 3, 2024
39f984a
Introduce improvements to VIP mechanism
skatsaounis Jul 8, 2024
edf1534
Merge branch 'main' into add-vip
skatsaounis Jul 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ ubuntu@infra1:~$ maas-anvil cluster bootstrap \
--accept-defaults
```

Note: You will be asked for a `virtual_ip` during installation of the HAProxy charm, if `accept-defaults` is omitted.
Pass an empty value to disable it, or any valid IP to enable; the Keepalived charm will be installed to enable connecting to HA MAAS using the VIP.

### Add new nodes to the MAAS cluster

```bash
Expand Down Expand Up @@ -74,14 +77,6 @@ ubuntu@infra1:~$ maas-anvil cluster list
ubuntu@infra1:~$ juju run maas-region/0 create-admin username=admin password=pass email=admin@maas.io ssh-import=lp:maasadmin
```

### (Optional) Add VIP functionality through Keepalived charm

```bash
ubuntu@infra1:~$ juju deploy containers/keepalived
ubuntu@infra1:~$ juju relate haproxy:juju-info keepalived:juju-info
ubuntu@infra1:~$ juju config keepalived virtual_ip=<ip_address>
```

### Charm documentation

- MAAS Region: <https://charmhub.io/maas-region>
Expand Down
88 changes: 80 additions & 8 deletions anvil-python/anvil/commands/haproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,69 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import List
import ipaddress
import logging
from typing import Any, List

from rich.console import Console
from sunbeam.clusterd.client import Client
from sunbeam.commands.terraform import TerraformInitStep
from sunbeam.jobs import questions
from sunbeam.jobs.common import BaseStep
from sunbeam.jobs.juju import JujuHelper
from sunbeam.jobs.manifest import BaseStep
from sunbeam.jobs.steps import (
AddMachineUnitsStep,
DeployMachineApplicationStep,
RemoveMachineUnitStep,
)

from anvil.jobs.manifest import Manifest
from anvil.provider.local.deployment import LocalDeployment

LOG = logging.getLogger(__name__)

APPLICATION = "haproxy"
CONFIG_KEY = "TerraformVarsHaproxyPlan"
HAPROXY_CONFIG_KEY = "TerraformVarsHaproxy"
KEEPALIVED_CONFIG_KEY = "TerraformVarsKeepalived"
HAPROXY_APP_TIMEOUT = 180 # 3 minutes, managing the application should be fast
HAPROXY_UNIT_TIMEOUT = (
1200 # 15 minutes, adding / removing units can take a long time
)


def keepalived_questions() -> dict[str, questions.PromptQuestion]:
return {
"virtual_ip": questions.PromptQuestion(
"Virtual IP to use for the Cluster in HA",
default_value="",
validation_function=validate_virtual_ip,
),
}


def validate_virtual_ip(value: str) -> str:
"""We allow passing an empty IP for virtual_ip"""
if value == "":
return ""
try:
return ipaddress.ip_address(value).exploded
except ValueError as e:
raise ValueError(f"{value} is not a valid IP address: {e}")


class DeployHAProxyApplicationStep(DeployMachineApplicationStep):
"""Deploy HAProxy application using Terraform"""

_KEEPALIVED_CONFIG = KEEPALIVED_CONFIG_KEY

def __init__(
self,
client: Client,
manifest: Manifest,
jhelper: JujuHelper,
model: str,
deployment_preseed: dict[Any, Any] | None = None,
accept_defaults: bool = False,
refresh: bool = False,
):
super().__init__(
Expand All @@ -59,10 +90,46 @@ def __init__(
"Deploying HAProxy",
refresh,
)
self.preseed = deployment_preseed or {}
self.accept_defaults = accept_defaults

def get_application_timeout(self) -> int:
return HAPROXY_APP_TIMEOUT

def has_prompts(self) -> bool:
return True

def prompt(self, console: Console | None = None) -> None:
variables = questions.load_answers(
self.client, self._KEEPALIVED_CONFIG
)
variables.setdefault("virtual_ip", "")

# Set defaults
self.preseed.setdefault("virtual_ip", "")

keepalived_config_bank = questions.QuestionBank(
questions=keepalived_questions(),
console=console,
preseed=self.preseed.get("haproxy"),
previous_answers=variables,
accept_defaults=self.accept_defaults,
)

variables["virtual_ip"] = keepalived_config_bank.virtual_ip.ask()

LOG.debug(variables)
questions.write_answers(
self.client, self._KEEPALIVED_CONFIG, variables
)

def extra_tfvars(self) -> dict[str, Any]:
variables: dict[str, Any] = questions.load_answers(
self.client, self._KEEPALIVED_CONFIG
)
variables["haproxy_port"] = 80
return variables


class AddHAProxyUnitsStep(AddMachineUnitsStep):
"""Add HAProxy Unit."""
Expand Down Expand Up @@ -114,15 +181,20 @@ def haproxy_install_steps(
client: Client,
manifest: Manifest,
jhelper: JujuHelper,
deployment: LocalDeployment,
model: str,
fqdn: str,
accept_defaults: bool,
preseed: dict[Any, Any],
) -> List[BaseStep]:
return [
TerraformInitStep(manifest.get_tfhelper("haproxy-plan")),
DeployHAProxyApplicationStep(
client, manifest, jhelper, deployment.infrastructure_model
),
AddHAProxyUnitsStep(
client, fqdn, jhelper, deployment.infrastructure_model
client,
manifest,
jhelper,
model,
accept_defaults=accept_defaults,
deployment_preseed=preseed,
),
AddHAProxyUnitsStep(client, fqdn, jhelper, model),
]
2 changes: 1 addition & 1 deletion anvil-python/anvil/jobs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Role(enum.Enum):
"""The role that the current node will play

This determines if the role will be a region node, a rack/agent node,
r database node or a haproxy node. The role will help determine which
database node, or a haproxy node. The role will help determine which
particular services need to be configured and installed on the system.
"""

Expand Down
19 changes: 17 additions & 2 deletions anvil-python/anvil/provider/local/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,13 @@ def bootstrap(
if is_haproxy_node:
plan4.extend(
haproxy_install_steps(
client, manifest_obj, jhelper, deployment, fqdn
client,
manifest_obj,
jhelper,
deployment.infrastructure_model,
fqdn,
accept_defaults,
preseed,
)
)
if is_region_node:
Expand Down Expand Up @@ -348,6 +354,7 @@ def _print_output(token: str) -> None:


@click.command()
@click.option("-a", "--accept-defaults", help="Accept all defaults.", is_flag=True)
@click.option("--token", type=str, help="Join token")
@click.option(
"--role",
Expand All @@ -365,6 +372,7 @@ def join(
ctx: click.Context,
token: str,
roles: List[Role],
accept_defaults: bool = False,
) -> None:
"""Join node to the cluster.

Expand Down Expand Up @@ -413,6 +421,7 @@ def join(
manifest_obj = Manifest.load_latest_from_clusterdb(
deployment, include_defaults=True
)
preseed = manifest_obj.deployment_config

machine_id = -1
machine_id_result = get_step_message(plan1_results, AddJujuMachineStep)
Expand All @@ -431,7 +440,13 @@ def join(
if is_haproxy_node:
plan2.extend(
haproxy_install_steps(
client, manifest_obj, jhelper, deployment, name
client,
manifest_obj,
jhelper,
deployment.infrastructure_model,
name,
accept_defaults,
preseed,
)
)
if is_region_node:
Expand Down
16 changes: 16 additions & 0 deletions anvil-python/anvil/provider/local/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
LocalDeployment as SunbeamLocalDeployment,
)

from anvil.commands.haproxy import KEEPALIVED_CONFIG_KEY, keepalived_questions

LOG = logging.getLogger(__name__)
LOCAL_TYPE = "local"

Expand All @@ -51,5 +53,19 @@ def generate_preseed(self, console: Console) -> str:
show_questions(bootstrap_bank, section="bootstrap")
)

# HAProxy questions
try:
variables = load_answers(client, KEEPALIVED_CONFIG_KEY)
except ClusterServiceUnavailableException:
variables = {}
keepalived_config_bank = QuestionBank(
questions=keepalived_questions(),
console=console,
previous_answers=variables,
)
preseed_content.extend(
show_questions(keepalived_config_bank, section="haproxy")
)

preseed_content_final = "\n".join(preseed_content)
return preseed_content_final
9 changes: 8 additions & 1 deletion anvil-python/anvil/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
POSTGRESQL_CHANNEL = "14/candidate"
PGBOUNCER_CHANNEL = "1/candidate"
HAPROXY_CHANNEL = "latest/stable"
KEEPALIVED_CHANNEL = "latest/stable"

MACHINE_CHARMS = {
"maas-region": MAAS_REGION_CHANNEL,
"maas-agent": MAAS_AGENT_CHANNEL,
"haproxy": HAPROXY_CHANNEL,
"postgresql": POSTGRESQL_CHANNEL,
"pgbouncer": PGBOUNCER_CHANNEL,
"keepalived": KEEPALIVED_CHANNEL,
}
K8S_CHARMS: dict[str, str] = {}

Expand Down Expand Up @@ -70,7 +72,12 @@
"channel": "charm_haproxy_channel",
"revision": "charm_haproxy_revision",
"config": "charm_haproxy_config",
}
},
"keepalived": {
"channel": "charm_keepalived_channel",
"revision": "charm_keepalived_revision",
"config": "charm_keepalived_config",
},
}
}

Expand Down
38 changes: 38 additions & 0 deletions cloud/etc/deploy-haproxy/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ data "juju_model" "machine_model" {
name = var.machine_model
}

locals {
virtual_ip = var.virtual_ip != "" ? { virtual_ip = var.virtual_ip } : {}
}

resource "juju_application" "haproxy" {
name = "haproxy"
model = data.juju_model.machine_model.name
Expand All @@ -44,3 +48,37 @@ resource "juju_application" "haproxy" {

config = var.charm_haproxy_config
}

resource "juju_application" "keepalived" {
count = min(length(var.virtual_ip), 1)
name = "keepalived"
model = data.juju_model.machine_model.name

charm {
name = "keepalived"
channel = var.charm_keepalived_channel
revision = var.charm_keepalived_revision
base = "ubuntu@22.04"
}

config = merge(
{ port = var.haproxy_port },
local.virtual_ip,
var.charm_keepalived_config,
)
}

resource "juju_integration" "maas-region-haproxy" {
count = min(length(var.virtual_ip), 1)
model = data.juju_model.machine_model.name

application {
name = juju_application.haproxy.name
endpoint = "juju-info"
}

application {
name = juju_application.keepalived[0].name
endpoint = "juju-info"
}
}
30 changes: 30 additions & 0 deletions cloud/etc/deploy-haproxy/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,33 @@ variable "machine_model" {
description = "Model to deploy to"
type = string
}

variable "charm_keepalived_channel" {
description = "Operator channel for Keepalived"
type = string
default = "latest/stable"
}

variable "charm_keepalived_revision" {
description = "Operator channel revision for Keepalived"
type = number
default = null
}

variable "virtual_ip" {
description = "Virtual IP Address for Keepalived"
type = string
default = ""
}

variable "haproxy_port" {
description = "The port that HAProxy listens on"
type = string
default = 80
}

variable "charm_keepalived_config" {
description = "Operator config for Keepalived deployment"
type = map(string)
default = {}
}