mirror of
https://github.com/techno-tim/k3s-ansible.git
synced 2026-10-23 12:12:04 +02:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332f36ceec | ||
|
|
c8c3609fde | ||
|
|
ac77e33be0 | ||
|
|
070d7b302b | ||
|
|
1687a3b098 | ||
|
|
4dc333a7d3 | ||
|
|
287d8b7a27 | ||
|
|
56bb912bd3 |
No files matched your search
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel)"
|
||||||
|
prereq_defaults="$repo_root/roles/prereq/defaults/main.yml"
|
||||||
|
prereq_tasks="$repo_root/roles/prereq/tasks/main.yml"
|
||||||
|
|
||||||
|
# #670: k3s recommends swap be disabled on all nodes. The prereq role must expose
|
||||||
|
# a disable_swap toggle (defaulting to true) that turns swap off now and comments
|
||||||
|
# out the /etc/fstab swap entries so swap stays off across reboots.
|
||||||
|
grep -Eq -- '^disable_swap: true' "$prereq_defaults" || {
|
||||||
|
printf 'prereq defaults are missing disable_swap: true\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
grep -Fq -- 'Disable swap on all cluster nodes' "$prereq_tasks" || {
|
||||||
|
printf 'prereq tasks are missing the swap-disable block\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
grep -Fq -- 'swapoff -a' "$prereq_tasks" || {
|
||||||
|
printf 'swap-disable block does not run swapoff -a\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
grep -Fq -- '/etc/fstab' "$prereq_tasks" || {
|
||||||
|
printf 'swap-disable block does not comment out /etc/fstab swap entries\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! grep -Eq -- 'when: disable_swap' "$prereq_tasks"; then
|
||||||
|
printf 'swap-disable block is not gated on the disable_swap toggle\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Swap disable regression test passed\n'
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression test for the kube-vip deploy conditions.
|
||||||
|
|
||||||
|
The control-plane VIP (roles/k3s_server/tasks/vip.yml) and the kube-vip
|
||||||
|
service load balancer (roles/k3s_server/tasks/kube-vip.yml) are both included
|
||||||
|
from roles/k3s_server/tasks/main.yml. Before this switch existed the VIP
|
||||||
|
include had no gate and always ran, so a user who wanted no kube-vip (single
|
||||||
|
node or external LB) could not opt out.
|
||||||
|
|
||||||
|
This test loads the real `when` expressions from the k3s_server task file and
|
||||||
|
evaluates them against representative variable sets, asserting that:
|
||||||
|
|
||||||
|
- the control-plane VIP is deployed only when kube_vip_enabled is true;
|
||||||
|
- the service load balancer is deployed only when kube_vip_enabled is true
|
||||||
|
AND kube_vip_lb_ip_range is defined.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from jinja2 import Environment
|
||||||
|
|
||||||
|
VIP_WHEN = "kube_vip_enabled"
|
||||||
|
KUBE_VIP_WHEN = "kube_vip_enabled and kube_vip_lb_ip_range is defined"
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root():
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "rev-parse", "--show-toplevel"], text=True
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
raise SystemExit("kube-vip deploy condition test failed: " + message)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_when(path, task_name):
|
||||||
|
"""Return the `when:` expression string for the named task."""
|
||||||
|
with open(path, encoding="utf-8") as handle:
|
||||||
|
doc = yaml.safe_load(handle)
|
||||||
|
for task in doc:
|
||||||
|
if task.get("name") == task_name:
|
||||||
|
when = task.get("when")
|
||||||
|
return (when or "").strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate(when, variables):
|
||||||
|
"""Evaluate a `when` expression against variables using Jinja2."""
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
def fake_bool(value):
|
||||||
|
# Minimal stand-in for Ansible's truthiness filter used by `| bool`.
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return value
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
return str(value).lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
env.filters["bool"] = fake_bool
|
||||||
|
template = env.from_string("{{ " + when + " }}")
|
||||||
|
rendered = template.render(**variables)
|
||||||
|
# The expression renders to the literal strings "True"/"False".
|
||||||
|
if rendered == "True":
|
||||||
|
return True
|
||||||
|
if rendered == "False":
|
||||||
|
return False
|
||||||
|
fail("condition did not render to a boolean: {0!r}".format(rendered))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_deployment(when, variables, expected, label):
|
||||||
|
result = evaluate(when, variables)
|
||||||
|
verdict = "deploy" if result else "skip"
|
||||||
|
expected_verdict = "deploy" if expected else "skip"
|
||||||
|
if result != expected:
|
||||||
|
fail(
|
||||||
|
"{0}: expected to {1} but the condition chose to {2} "
|
||||||
|
"(vars: {3})".format(label, expected_verdict, verdict, variables)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def scenarios():
|
||||||
|
"""Yield (variables, expected_vip, expected_service_lb, label) pairs."""
|
||||||
|
yield (
|
||||||
|
# Default inventory: kube-vip enabled, no service LB range.
|
||||||
|
{
|
||||||
|
"kube_vip_enabled": True,
|
||||||
|
},
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
"default: kube-vip VIP only",
|
||||||
|
)
|
||||||
|
yield (
|
||||||
|
# kube-vip owns the service LB range too.
|
||||||
|
{
|
||||||
|
"kube_vip_enabled": True,
|
||||||
|
"kube_vip_lb_ip_range": "192.168.30.80-192.168.30.90",
|
||||||
|
},
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
"kube-vip VIP and service LB",
|
||||||
|
)
|
||||||
|
yield (
|
||||||
|
# Explicitly disabled, no LB range.
|
||||||
|
{
|
||||||
|
"kube_vip_enabled": False,
|
||||||
|
},
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
"kube_vip_enabled: false",
|
||||||
|
)
|
||||||
|
yield (
|
||||||
|
# Explicitly disabled even when the LB range is present.
|
||||||
|
{
|
||||||
|
"kube_vip_enabled": False,
|
||||||
|
"kube_vip_lb_ip_range": "192.168.30.80-192.168.30.90",
|
||||||
|
},
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
"kube_vip_enabled: false despite LB range",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = repo_root()
|
||||||
|
server_tasks = os.path.join(root, "roles", "k3s_server", "tasks", "main.yml")
|
||||||
|
|
||||||
|
vip_when = extract_when(server_tasks, "Deploy vip manifest")
|
||||||
|
kube_vip_when = extract_when(server_tasks, "Deploy kube-vip manifest")
|
||||||
|
|
||||||
|
if vip_when is None:
|
||||||
|
fail("could not find 'Deploy vip manifest' when condition")
|
||||||
|
if kube_vip_when is None:
|
||||||
|
fail("could not find 'Deploy kube-vip manifest' when condition")
|
||||||
|
|
||||||
|
if vip_when != VIP_WHEN:
|
||||||
|
fail(
|
||||||
|
"k3s_server/tasks/main.yml 'Deploy vip manifest' when condition "
|
||||||
|
"changed unexpectedly:\n"
|
||||||
|
" expected: {0}\n got: {1}".format(VIP_WHEN, vip_when)
|
||||||
|
)
|
||||||
|
if kube_vip_when != KUBE_VIP_WHEN:
|
||||||
|
fail(
|
||||||
|
"k3s_server/tasks/main.yml 'Deploy kube-vip manifest' when "
|
||||||
|
"condition changed unexpectedly:\n"
|
||||||
|
" expected: {0}\n got: {1}".format(KUBE_VIP_WHEN, kube_vip_when)
|
||||||
|
)
|
||||||
|
|
||||||
|
for variables, expected_vip, expected_service_lb, label in scenarios():
|
||||||
|
assert_deployment(vip_when, variables, expected_vip, "vip " + label)
|
||||||
|
assert_deployment(
|
||||||
|
kube_vip_when,
|
||||||
|
variables,
|
||||||
|
expected_service_lb,
|
||||||
|
"service_lb " + label,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("kube-vip deploy condition regression test passed for all scenarios")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Render the kube-vip DaemonSet template and assert env key correctness.
|
"""Render the kube-vip DaemonSet template and assert env key correctness.
|
||||||
|
|
||||||
kube-vip v1.2.2 reads `bgp_peers` and `vip_subnet`; it ignores the older
|
kube-vip v1.2.3 reads `bgp_peers` and `vip_subnet`; it ignores the older
|
||||||
`bgppeers` and `vip_cidr` names. This test proves the rendered manifest uses
|
`bgppeers` and `vip_cidr` names. This test proves the rendered manifest uses
|
||||||
the keys the target image actually parses.
|
the keys the target image actually parses.
|
||||||
"""
|
"""
|
||||||
@@ -74,7 +74,7 @@ def render(env, extra_vars):
|
|||||||
{"peer_address": "192.168.30.1", "peer_asn": "64512"},
|
{"peer_address": "192.168.30.1", "peer_asn": "64512"},
|
||||||
{"peer_address": "192.168.30.2", "peer_asn": "64513"},
|
{"peer_address": "192.168.30.2", "peer_asn": "64513"},
|
||||||
],
|
],
|
||||||
"kube_vip_tag_version": "v1.2.2",
|
"kube_vip_tag_version": "v1.2.3",
|
||||||
}
|
}
|
||||||
base_vars.update(extra_vars)
|
base_vars.update(extra_vars)
|
||||||
template = env.get_template("vip.yaml.j2")
|
template = env.get_template("vip.yaml.j2")
|
||||||
@@ -105,8 +105,8 @@ def main():
|
|||||||
fail("rendered manifest still uses the ignored vip_cidr key")
|
fail("rendered manifest still uses the ignored vip_cidr key")
|
||||||
if "192.168.30.1:64512,192.168.30.2:64513" not in output:
|
if "192.168.30.1:64512,192.168.30.2:64513" not in output:
|
||||||
fail("bgp_peers value is not comma-separated address:ASN entries")
|
fail("bgp_peers value is not comma-separated address:ASN entries")
|
||||||
if "ghcr.io/kube-vip/kube-vip:v1.2.2" not in output:
|
if "ghcr.io/kube-vip/kube-vip:v1.2.3" not in output:
|
||||||
fail("kube-vip image tag is not v1.2.2")
|
fail("kube-vip image tag is not v1.2.3")
|
||||||
|
|
||||||
# BGP enabled with no merged peers: single-peer fallback vars, no bgp_peers.
|
# BGP enabled with no merged peers: single-peer fallback vars, no bgp_peers.
|
||||||
output = render(
|
output = render(
|
||||||
@@ -123,6 +123,38 @@ def main():
|
|||||||
if "name: bgp_peers" in output:
|
if "name: bgp_peers" in output:
|
||||||
fail("bgp_peers present even though the peer list is empty")
|
fail("bgp_peers present even though the peer list is empty")
|
||||||
|
|
||||||
|
# kube_vip_endpoint defaults to null (defined in role defaults): the
|
||||||
|
# address and subnet must fall back to the apiserver endpoint. default()
|
||||||
|
# without a truthy flag does NOT fall back on null, only on undefined, so
|
||||||
|
# this case pins the null runtime condition to prevent that regression.
|
||||||
|
output = render(
|
||||||
|
env,
|
||||||
|
{
|
||||||
|
"_kube_vip_bgp_peers": [],
|
||||||
|
"kube_vip_endpoint": None,
|
||||||
|
"kube_vip_arp": True,
|
||||||
|
"kube_vip_bgp": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if "value: 192.168.30.222" not in output:
|
||||||
|
fail("null kube_vip_endpoint does not fall back to apiserver_endpoint")
|
||||||
|
|
||||||
|
# kube_vip_endpoint set: overrides the internal listening address AND the
|
||||||
|
# subnet derivation while the advertised apiserver_endpoint stays separate.
|
||||||
|
output = render(
|
||||||
|
env,
|
||||||
|
{
|
||||||
|
"_kube_vip_bgp_peers": [],
|
||||||
|
"kube_vip_endpoint": "10.66.1.5",
|
||||||
|
"kube_vip_arp": True,
|
||||||
|
"kube_vip_bgp": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if "value: 10.66.1.5" not in output:
|
||||||
|
fail("kube_vip_endpoint did not override the address")
|
||||||
|
if "value: 192.168.30.222" in output:
|
||||||
|
fail("apiserver_endpoint leaked into address when kube_vip_endpoint set")
|
||||||
|
|
||||||
print("kube-vip manifest regression test passed")
|
print("kube-vip manifest regression test passed")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ scenario.
|
|||||||
|
|
||||||
This test loads the real `when` expressions from both task files and evaluates
|
This test loads the real `when` expressions from both task files and evaluates
|
||||||
them against representative variable sets, asserting MetalLB is deployed in
|
them against representative variable sets, asserting MetalLB is deployed in
|
||||||
every topology except when kube-vip owns the VIP range or Cilium BGP is enabled.
|
every topology except when MetalLB is explicitly disabled, kube-vip owns the
|
||||||
|
VIP range, or Cilium BGP is enabled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
@@ -23,6 +24,7 @@ import yaml
|
|||||||
from jinja2 import Environment
|
from jinja2 import Environment
|
||||||
|
|
||||||
METALLB_WHEN = (
|
METALLB_WHEN = (
|
||||||
|
"metal_lb_enabled and "
|
||||||
"kube_vip_lb_ip_range is not defined and "
|
"kube_vip_lb_ip_range is not defined and "
|
||||||
"not (cilium_bgp | default(false) | bool)"
|
"not (cilium_bgp | default(false) | bool)"
|
||||||
)
|
)
|
||||||
@@ -88,6 +90,7 @@ def scenarios():
|
|||||||
yield (
|
yield (
|
||||||
# Default Flannel inventory (all.yml sets cilium_bgp: false).
|
# Default Flannel inventory (all.yml sets cilium_bgp: false).
|
||||||
{
|
{
|
||||||
|
"metal_lb_enabled": True,
|
||||||
"cilium_bgp": False,
|
"cilium_bgp": False,
|
||||||
"cilium_iface": None,
|
"cilium_iface": None,
|
||||||
},
|
},
|
||||||
@@ -98,6 +101,7 @@ def scenarios():
|
|||||||
# Calico CNI with no Cilium variable in scope (issue #644): cilium_bgp
|
# Calico CNI with no Cilium variable in scope (issue #644): cilium_bgp
|
||||||
# is genuinely undefined, so `default(false)` must keep MetalLB on.
|
# is genuinely undefined, so `default(false)` must keep MetalLB on.
|
||||||
{
|
{
|
||||||
|
"metal_lb_enabled": True,
|
||||||
"calico_iface": "eth1",
|
"calico_iface": "eth1",
|
||||||
},
|
},
|
||||||
True,
|
True,
|
||||||
@@ -106,6 +110,7 @@ def scenarios():
|
|||||||
yield (
|
yield (
|
||||||
# Cilium CNI with BGP disabled: MetalLB must still be deployed.
|
# Cilium CNI with BGP disabled: MetalLB must still be deployed.
|
||||||
{
|
{
|
||||||
|
"metal_lb_enabled": True,
|
||||||
"cilium_bgp": False,
|
"cilium_bgp": False,
|
||||||
"cilium_iface": "eth1",
|
"cilium_iface": "eth1",
|
||||||
},
|
},
|
||||||
@@ -115,6 +120,7 @@ def scenarios():
|
|||||||
yield (
|
yield (
|
||||||
# Cilium CNI with BGP enabled: Cilium provides the LB, skip MetalLB.
|
# Cilium CNI with BGP enabled: Cilium provides the LB, skip MetalLB.
|
||||||
{
|
{
|
||||||
|
"metal_lb_enabled": True,
|
||||||
"cilium_bgp": True,
|
"cilium_bgp": True,
|
||||||
"cilium_iface": "eth1",
|
"cilium_iface": "eth1",
|
||||||
},
|
},
|
||||||
@@ -124,12 +130,21 @@ def scenarios():
|
|||||||
yield (
|
yield (
|
||||||
# kube-vip is the load balancer provider: skip MetalLB.
|
# kube-vip is the load balancer provider: skip MetalLB.
|
||||||
{
|
{
|
||||||
|
"metal_lb_enabled": True,
|
||||||
"kube_vip_lb_ip_range": "192.168.30.80-192.168.30.90",
|
"kube_vip_lb_ip_range": "192.168.30.80-192.168.30.90",
|
||||||
"cilium_bgp": False,
|
"cilium_bgp": False,
|
||||||
},
|
},
|
||||||
False,
|
False,
|
||||||
"kube-vip owns the VIP range",
|
"kube-vip owns the VIP range",
|
||||||
)
|
)
|
||||||
|
yield (
|
||||||
|
# MetalLB explicitly disabled (external LB / single node): skip.
|
||||||
|
{
|
||||||
|
"metal_lb_enabled": False,
|
||||||
|
},
|
||||||
|
False,
|
||||||
|
"metal_lb_enabled: false (external LB)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression test for the MetalLB converge checks.
|
||||||
|
|
||||||
|
The MetalLB tasks in roles/k3s_server_post/tasks/metallb.yml must actually
|
||||||
|
verify resources through an explicit kubectl get, and must retry on a
|
||||||
|
transient kube API error while MetalLB converges.
|
||||||
|
|
||||||
|
The "Test metallb-system namespace" task previously ran `k3s kubectl -n
|
||||||
|
metallb-system` with no subcommand, which only printed a usage page and always
|
||||||
|
exited 0, so it always succeeded even when the namespace did not exist (issue
|
||||||
|
#350). It must instead run an explicit `get namespace metallb-system`, which
|
||||||
|
returns non-zero when the namespace is absent.
|
||||||
|
|
||||||
|
An explicit get actually contacts the API server, so these tasks need the same
|
||||||
|
retry wiring as their siblings (register, until rc == 0, retries, delay). A
|
||||||
|
bare get with no retry would otherwise abort the converge play on a transient
|
||||||
|
kube API error while MetalLB converges.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root():
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "rev-parse", "--show-toplevel"], text=True
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
raise SystemExit("MetalLB namespace test failed: " + message)
|
||||||
|
|
||||||
|
|
||||||
|
def find_task(tasks, name):
|
||||||
|
for entry in tasks:
|
||||||
|
if entry.get("name") == name:
|
||||||
|
return entry
|
||||||
|
fail("could not find the '{0}' task".format(name))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def command_text(task):
|
||||||
|
cmd = task.get("ansible.builtin.command")
|
||||||
|
if not cmd:
|
||||||
|
cmd = task.get("command")
|
||||||
|
if not cmd:
|
||||||
|
fail("task does not use ansible.builtin.command")
|
||||||
|
return cmd if isinstance(cmd, str) else " ".join(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def check_explicit_get(task, name, needle):
|
||||||
|
text = command_text(task)
|
||||||
|
if needle not in text:
|
||||||
|
fail(
|
||||||
|
"command does not run '{0}'; the task would only print usage and "
|
||||||
|
"never verify the resource (got: {1!r})".format(needle, text)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_retry_wiring(task, name):
|
||||||
|
# The sibling k3s_server_post metallb tasks retry kubectl because the kube
|
||||||
|
# API can briefly be unavailable while MetalLB converges. Without the same
|
||||||
|
# retry, a transient API error aborts the whole converge play.
|
||||||
|
if not task.get("register"):
|
||||||
|
fail(
|
||||||
|
"{0} does not register a result; without retry wiring a transient "
|
||||||
|
"kube API error aborts the converge play".format(name)
|
||||||
|
)
|
||||||
|
if not isinstance(task.get("until"), str) or "rc == 0" not in task["until"]:
|
||||||
|
fail(
|
||||||
|
"{0} does not retry on rc == 0; the kube API can transiently fail "
|
||||||
|
"while MetalLB converges and abort the play".format(name)
|
||||||
|
)
|
||||||
|
if task.get("retries") is None:
|
||||||
|
fail("{0} is missing retries".format(name))
|
||||||
|
if task.get("delay") is None:
|
||||||
|
fail("{0} is missing delay".format(name))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
task_file = os.path.join(
|
||||||
|
repo_root(), "roles", "k3s_server_post", "tasks", "metallb.yml"
|
||||||
|
)
|
||||||
|
with open(task_file, encoding="utf-8") as handle:
|
||||||
|
tasks = yaml.safe_load(handle)
|
||||||
|
|
||||||
|
namespace_task = find_task(tasks, "Test metallb-system namespace")
|
||||||
|
# A bare `-n metallb-system` with no subcommand prints kubectl usage and
|
||||||
|
# always exits 0, so it never proves the namespace exists. The fix must
|
||||||
|
# use an explicit get.
|
||||||
|
check_explicit_get(namespace_task, "Test metallb-system namespace",
|
||||||
|
"get namespace metallb-system")
|
||||||
|
check_retry_wiring(namespace_task, "Test metallb-system namespace")
|
||||||
|
|
||||||
|
webhook_task = find_task(tasks, "Test metallb-system webhook-service endpoint")
|
||||||
|
check_explicit_get(webhook_task, "Test metallb-system webhook-service endpoint",
|
||||||
|
"get endpoints")
|
||||||
|
check_retry_wiring(webhook_task, "Test metallb-system webhook-service endpoint")
|
||||||
|
|
||||||
|
print("MetalLB namespace check regression test passed")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression test for the verify_from_outside nginx HTTP assert.
|
||||||
|
|
||||||
|
The "Assert that the nginx welcome page is available" task in
|
||||||
|
molecule/resources/verify_from_outside/tasks/test/deploy-example.yml performs
|
||||||
|
a plain HTTP GET (ansible.builtin.uri) against the MetalLB VIP that the load
|
||||||
|
balancer pool assigned, reaching it from the CI runner over the VirtualBox
|
||||||
|
host-only network.
|
||||||
|
|
||||||
|
The MetalLB speaker announces the VIP on the host-only network, and the runner
|
||||||
|
may briefly not see it in its ARP table yet. That manifests as a single-shot
|
||||||
|
"status -1 / No route to host" failure that fails the whole verify play even
|
||||||
|
though the cluster is healthy. Like the sibling load-balancer address wait and
|
||||||
|
the k3s_server_post metallb waits, the assert must retry until it observes
|
||||||
|
HTTP 200 with the welcome page.
|
||||||
|
|
||||||
|
A bare assert with no retry would otherwise fail an otherwise-green cluster on
|
||||||
|
a transient announcement miss.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root():
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "rev-parse", "--show-toplevel"], text=True
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
raise SystemExit("verify nginx assert test failed: " + message)
|
||||||
|
|
||||||
|
|
||||||
|
def find_task(play, name):
|
||||||
|
block = play.get("block")
|
||||||
|
if not isinstance(block, list):
|
||||||
|
fail("expected a block list of tasks")
|
||||||
|
for entry in block:
|
||||||
|
if entry.get("name") == name:
|
||||||
|
return entry
|
||||||
|
fail("could not find the '{0}' task".format(name))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_retry_wiring(task, name):
|
||||||
|
if not task.get("register"):
|
||||||
|
fail("{0} does not register a result; without retry wiring a transient "
|
||||||
|
"MetalLB VIP announcement miss fails the verify play".format(name))
|
||||||
|
until = task.get("until")
|
||||||
|
if not until:
|
||||||
|
fail("{0} does not retry; a transient MetalLB VIP announcement miss "
|
||||||
|
"on the host-only network would fail the verify play".format(name))
|
||||||
|
if isinstance(until, str):
|
||||||
|
until = [until]
|
||||||
|
joined = " | ".join(str(expr) for expr in until)
|
||||||
|
if "result.status == 200" not in joined:
|
||||||
|
fail("{0} does not retry until the HTTP status is 200".format(name))
|
||||||
|
if "Welcome to nginx!" not in joined:
|
||||||
|
fail("{0} does not verify the welcome page content".format(name))
|
||||||
|
if task.get("retries") is None:
|
||||||
|
fail("{0} is missing retries".format(name))
|
||||||
|
if task.get("delay") is None:
|
||||||
|
fail("{0} is missing delay".format(name))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
task_file = os.path.join(
|
||||||
|
repo_root(),
|
||||||
|
"molecule", "resources", "verify_from_outside", "tasks", "test",
|
||||||
|
"deploy-example.yml",
|
||||||
|
)
|
||||||
|
with open(task_file, encoding="utf-8") as handle:
|
||||||
|
plays = yaml.safe_load(handle)
|
||||||
|
|
||||||
|
play = plays[0]
|
||||||
|
task = find_task(play, "Assert that the nginx welcome page is available")
|
||||||
|
|
||||||
|
if not task.get("ansible.builtin.uri"):
|
||||||
|
fail("task does not use ansible.builtin.uri to reach the VIP")
|
||||||
|
|
||||||
|
check_retry_wiring(task, "Assert that the nginx welcome page is available")
|
||||||
|
|
||||||
|
print("verify nginx assert regression test passed")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 7.0.1
|
||||||
- name: Ensure SHA pinned actions
|
- name: Ensure SHA pinned actions
|
||||||
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@46cfe808a5f1588656ef299eedd0ce2fd7ec0dcc # 5.0.6
|
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@c5fc58bd0be7a4b94b73ce40250322d5b838a108 # 5.0.7
|
||||||
with:
|
with:
|
||||||
allowlist: |
|
allowlist: |
|
||||||
aws-actions/
|
aws-actions/
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
trap stop_monitor EXIT
|
trap stop_monitor EXIT
|
||||||
/usr/bin/time -v -o "$timing_file" \
|
/usr/bin/time -v -o "$timing_file" \
|
||||||
molecule test --scenario-name ${{ matrix.scenario }}
|
molecule test --scenario-name ${{ matrix.scenario }}
|
||||||
timeout-minutes: 150
|
timeout-minutes: 180
|
||||||
env:
|
env:
|
||||||
ANSIBLE_K3S_LOG_DIR: ${{ runner.temp }}/logs/k3s-ansible/${{ matrix.scenario }}
|
ANSIBLE_K3S_LOG_DIR: ${{ runner.temp }}/logs/k3s-ansible/${{ matrix.scenario }}
|
||||||
ANSIBLE_SSH_RETRIES: 4
|
ANSIBLE_SSH_RETRIES: 4
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^site\.yml$|^\.github/scripts/test-unique-hostname-precheck\.sh$
|
files: ^site\.yml$|^\.github/scripts/test-unique-hostname-precheck\.sh$
|
||||||
|
- id: disable-swap-test
|
||||||
|
name: Disable swap test
|
||||||
|
entry: .github/scripts/test-disable-swap.sh
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^roles/prereq/(tasks/main|defaults/main)\.yml$|^\.github/scripts/test-disable-swap\.sh$
|
||||||
- id: cilium-bgp-manifest-test
|
- id: cilium-bgp-manifest-test
|
||||||
name: Cilium BGP manifest test
|
name: Cilium BGP manifest test
|
||||||
entry: python3 .github/scripts/test-cilium-bgp-manifest.py
|
entry: python3 .github/scripts/test-cilium-bgp-manifest.py
|
||||||
@@ -107,6 +113,14 @@ repos:
|
|||||||
- Jinja2>=3.1
|
- Jinja2>=3.1
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^roles/k3s_server_post/templates/metallb\.crs\.j2$|^\.github/scripts/test-metallb-interfaces\.py$
|
files: ^roles/k3s_server_post/templates/metallb\.crs\.j2$|^\.github/scripts/test-metallb-interfaces\.py$
|
||||||
|
- id: metallb-namespace-test
|
||||||
|
name: MetalLB namespace test
|
||||||
|
entry: python3 .github/scripts/test-metallb-namespace.py
|
||||||
|
language: python
|
||||||
|
additional_dependencies:
|
||||||
|
- PyYAML
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^roles/k3s_server_post/tasks/metallb\.yml$|^\.github/scripts/test-metallb-namespace\.py$
|
||||||
- id: metallb-deploy-condition-test
|
- id: metallb-deploy-condition-test
|
||||||
name: MetalLB deploy condition test
|
name: MetalLB deploy condition test
|
||||||
entry: python3 .github/scripts/test-metallb-deploy-condition.py
|
entry: python3 .github/scripts/test-metallb-deploy-condition.py
|
||||||
@@ -116,6 +130,15 @@ repos:
|
|||||||
- PyYAML
|
- PyYAML
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^roles/k3s_server/tasks/main\.yml$|^roles/k3s_server_post/tasks/main\.yml$|^\.github/scripts/test-metallb-deploy-condition\.py$ # noqa yaml[line-length]
|
files: ^roles/k3s_server/tasks/main\.yml$|^roles/k3s_server_post/tasks/main\.yml$|^\.github/scripts/test-metallb-deploy-condition\.py$ # noqa yaml[line-length]
|
||||||
|
- id: kube-vip-deploy-condition-test
|
||||||
|
name: kube-vip deploy condition test
|
||||||
|
entry: python3 .github/scripts/test-kube-vip-deploy-condition.py
|
||||||
|
language: python
|
||||||
|
additional_dependencies:
|
||||||
|
- Jinja2>=3.1
|
||||||
|
- PyYAML
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^roles/k3s_server/tasks/main\.yml$|^\.github/scripts/test-kube-vip-deploy-condition\.py$ # noqa yaml[line-length]
|
||||||
- id: default-interface-test
|
- id: default-interface-test
|
||||||
name: default interface test
|
name: default interface test
|
||||||
entry: python3 .github/scripts/test-default-interface.py
|
entry: python3 .github/scripts/test-default-interface.py
|
||||||
@@ -124,3 +147,11 @@ repos:
|
|||||||
- Jinja2>=3.1
|
- Jinja2>=3.1
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^inventory/sample/group_vars/all\.yml$|^\.github/scripts/test-default-interface\.py$
|
files: ^inventory/sample/group_vars/all\.yml$|^\.github/scripts/test-default-interface\.py$
|
||||||
|
- id: verify-nginx-assert-test
|
||||||
|
name: verify nginx assert test
|
||||||
|
entry: python3 .github/scripts/test-verify-nginx-assert.py
|
||||||
|
language: python
|
||||||
|
additional_dependencies:
|
||||||
|
- PyYAML
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^molecule/resources/verify_from_outside/tasks/test/deploy-example\.yml$|^\.github/scripts/test-verify-nginx-assert\.py$ # noqa yaml[line-length]
|
||||||
@@ -219,9 +219,12 @@ See the commands [here](https://technotim.com/posts/k3s-etcd-ansible/#testing-yo
|
|||||||
| `k3s_server` | `kube_vip_bgp_peers` | list | `[]` | Not required | List of BGP peer ASN & address pairs |
|
| `k3s_server` | `kube_vip_bgp_peers` | list | `[]` | Not required | List of BGP peer ASN & address pairs |
|
||||||
| `k3s_server` | `kube_vip_bgp_peers_groups` | list | `['k3s_master']` | Not required | Inventory group in which to search for additional `kube_vip_bgp_peers` parameters to merge. |
|
| `k3s_server` | `kube_vip_bgp_peers_groups` | list | `['k3s_master']` | Not required | Inventory group in which to search for additional `kube_vip_bgp_peers` parameters to merge. |
|
||||||
| `k3s_server` | `kube_vip_iface` | string | `~` | Not required | Explicitly define an interface that ALL control nodes should use to propagate the VIP, define it here. Otherwise, kube-vip will determine the right interface automatically at runtime. |
|
| `k3s_server` | `kube_vip_iface` | string | `~` | Not required | Explicitly define an interface that ALL control nodes should use to propagate the VIP, define it here. Otherwise, kube-vip will determine the right interface automatically at runtime. |
|
||||||
| `k3s_server` | `kube_vip_tag_version` | string | `v1.2.2` | Not required | Image tag for kube-vip |
|
| `k3s_server` | `kube_vip_endpoint` | string | `~` | Not required | Overrides the internal address kube-vip binds/listens on, which can differ from the announced apiserver_endpoint for complex routing/tunnels. Defaults to apiserver_endpoint. |
|
||||||
|
| `k3s_server` | `kube_vip_tag_version` | string | `v1.2.3` | Not required | Image tag for kube-vip |
|
||||||
| `k3s_server` | `kube_vip_cloud_provider_tag_version` | string | `v0.0.12` | Not required | Tag for kube-vip-cloud-provider manifest when enable |
|
| `k3s_server` | `kube_vip_cloud_provider_tag_version` | string | `v0.0.12` | Not required | Tag for kube-vip-cloud-provider manifest when enable |
|
||||||
|
| `k3s_server` | `kube_vip_enabled` | bool | `true` | Not required | Enable kube-vip install, covering both the control-plane VIP and the service load balancer. Set false to skip kube-vip entirely (single node or external LB). |
|
||||||
| `k3s_server`, `k3_server_post` | `kube_vip_lb_ip_range` | string | `~` | Not required | IP range for kube-vip load balancer |
|
| `k3s_server`, `k3_server_post` | `kube_vip_lb_ip_range` | string | `~` | Not required | IP range for kube-vip load balancer |
|
||||||
|
| `k3s_server`, `k3s_server_post` | `metal_lb_enabled` | bool | `true` | Not required | Enable MetalLB install for service load balancing. Set false to skip MetalLB (external LB). |
|
||||||
| `k3s_server`, `k3s_server_post` | `metal_lb_controller_tag_version` | string | `v0.16.0` | Not required | Image tag for MetalLB |
|
| `k3s_server`, `k3s_server_post` | `metal_lb_controller_tag_version` | string | `v0.16.0` | Not required | Image tag for MetalLB |
|
||||||
| `k3s_server` | `metal_lb_speaker_tag_version` | string | `v0.16.0` | Not required | Image tag for MetalLB |
|
| `k3s_server` | `metal_lb_speaker_tag_version` | string | `v0.16.0` | Not required | Image tag for MetalLB |
|
||||||
| `k3s_server` | `metal_lb_type` | string | `native` | Not required | Use FRR mode or native. Valid values are `frr` and `native` |
|
| `k3s_server` | `metal_lb_type` | string | `native` | Not required | Use FRR mode or native. Valid values are `frr` and `native` |
|
||||||
@@ -261,6 +264,7 @@ See the commands [here](https://technotim.com/posts/k3s-etcd-ansible/#testing-yo
|
|||||||
| `reboot` (playbook) | `concurrent_reboots` | int/string | `100%` | Not required | Number (or percentage) of nodes to reboot at a time for a staggered reboot |
|
| `reboot` (playbook) | `concurrent_reboots` | int/string | `100%` | Not required | Number (or percentage) of nodes to reboot at a time for a staggered reboot |
|
||||||
| `reboot` (playbook) | `wait_seconds_after_reboot` | int | `0` | Not required | Pause in seconds between staggered reboot batches |
|
| `reboot` (playbook) | `wait_seconds_after_reboot` | int | `0` | Not required | Pause in seconds between staggered reboot batches |
|
||||||
| `prereq` | `system_timezone` | string | `null` | Not required | Timezone to be set on all nodes |
|
| `prereq` | `system_timezone` | string | `null` | Not required | Timezone to be set on all nodes |
|
||||||
|
| `prereq` | `disable_swap` | bool | `true` | Not required | Disable swap on all cluster nodes (swapoff + comment out /etc/fstab swap entries), all-or-nothing |
|
||||||
| `proxmox_lxc`, `reset_proxmox_lxc` | `proxmox_lxc_ct_ids` | list | ❌ | Required | Proxmox container ID list |
|
| `proxmox_lxc`, `reset_proxmox_lxc` | `proxmox_lxc_ct_ids` | list | ❌ | Required | Proxmox container ID list |
|
||||||
| `raspberrypi` | `state` | string | `present` | Not required | Indicates whether the k3s prerequisites for Raspberry Pi should be set up (possible values are `present` and `absent`) |
|
| `raspberrypi` | `state` | string | `present` | Not required | Indicates whether the k3s prerequisites for Raspberry Pi should be set up (possible values are `present` and `absent`) |
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
k3s_version: v1.36.2+k3s1
|
k3s_version: v1.36.3+k3s1
|
||||||
# this is the user that has ssh access to these machines
|
# this is the user that has ssh access to these machines
|
||||||
ansible_user: ansibleuser
|
ansible_user: ansibleuser
|
||||||
systemd_dir: /etc/systemd/system
|
systemd_dir: /etc/systemd/system
|
||||||
@@ -7,6 +7,10 @@ systemd_dir: /etc/systemd/system
|
|||||||
# Set your timezone
|
# Set your timezone
|
||||||
system_timezone: Your/Timezone
|
system_timezone: Your/Timezone
|
||||||
|
|
||||||
|
# k3s recommends swap be disabled on every cluster node. Applied uniformly to all
|
||||||
|
# nodes (all-or-nothing) in the prereq role. Set to false to leave swap enabled.
|
||||||
|
disable_swap: true
|
||||||
|
|
||||||
# interface which will be used for flannel
|
# interface which will be used for flannel
|
||||||
# Defaults to each host's default IPv4 interface (e.g. eth0, enp1s0, ens3)
|
# Defaults to each host's default IPv4 interface (e.g. eth0, enp1s0, ens3)
|
||||||
# so KVM/cloud hosts without eth0 work out of the box. Override per-host if needed.
|
# so KVM/cloud hosts without eth0 work out of the box. Override per-host if needed.
|
||||||
@@ -44,6 +48,11 @@ cilium_bgp_lb_cidr: 192.168.31.0/24 # cidr for cilium loadbalancer ipam
|
|||||||
# enable kube-vip ARP broadcasts
|
# enable kube-vip ARP broadcasts
|
||||||
kube_vip_arp: true
|
kube_vip_arp: true
|
||||||
|
|
||||||
|
# (optional) overrides the address kube-vip binds/listens on internally, which
|
||||||
|
# can differ from the announced apiserver_endpoint for complex routing/tunnels.
|
||||||
|
# Defaults to apiserver_endpoint. Also used to derive the kube-vip subnet.
|
||||||
|
# kube_vip_endpoint: 10.66.1.5
|
||||||
|
|
||||||
# enable kube-vip BGP peering
|
# enable kube-vip BGP peering
|
||||||
kube_vip_bgp: false
|
kube_vip_bgp: false
|
||||||
|
|
||||||
@@ -93,7 +102,11 @@ extra_agent_args: >-
|
|||||||
{{ extra_args }}
|
{{ extra_args }}
|
||||||
|
|
||||||
# image tag for kube-vip
|
# image tag for kube-vip
|
||||||
kube_vip_tag_version: v1.2.2
|
kube_vip_tag_version: v1.2.3
|
||||||
|
|
||||||
|
# enable kube-vip (covers both the control-plane VIP and service load balancing)
|
||||||
|
# set false for a single node or when an external load balancer is used
|
||||||
|
kube_vip_enabled: true
|
||||||
|
|
||||||
# tag for kube-vip-cloud-provider manifest
|
# tag for kube-vip-cloud-provider manifest
|
||||||
# kube_vip_cloud_provider_tag_version: "v0.0.12"
|
# kube_vip_cloud_provider_tag_version: "v0.0.12"
|
||||||
@@ -102,6 +115,10 @@ kube_vip_tag_version: v1.2.2
|
|||||||
# (uncomment to use kube-vip for services instead of MetalLB)
|
# (uncomment to use kube-vip for services instead of MetalLB)
|
||||||
# kube_vip_lb_ip_range: "192.168.30.80-192.168.30.90"
|
# kube_vip_lb_ip_range: "192.168.30.80-192.168.30.90"
|
||||||
|
|
||||||
|
# enable MetalLB for service load balancing
|
||||||
|
# set false when using an external load balancer
|
||||||
|
metal_lb_enabled: true
|
||||||
|
|
||||||
# metallb type frr or native
|
# metallb type frr or native
|
||||||
metal_lb_type: native
|
metal_lb_type: native
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,16 @@
|
|||||||
url: http://{{ nginx_lb_ip | ansible.utils.ipwrap }}:{{ port_ }}/
|
url: http://{{ nginx_lb_ip | ansible.utils.ipwrap }}:{{ port_ }}/
|
||||||
return_content: true
|
return_content: true
|
||||||
register: result
|
register: result
|
||||||
failed_when: "'Welcome to nginx!' not in result.content"
|
# The MetalLB speaker announces the VIP on the host-only network, and the
|
||||||
|
# runner may briefly not see it in its ARP table yet. Retry so a transient
|
||||||
|
# announcement miss does not fail an otherwise healthy cluster, matching
|
||||||
|
# the load-balancer address wait above and the k3s_server_post metallb waits.
|
||||||
|
until:
|
||||||
|
- result.status is defined
|
||||||
|
- result.status == 200
|
||||||
|
- "'Welcome to nginx!' in result.content"
|
||||||
|
retries: 30
|
||||||
|
delay: 5
|
||||||
vars:
|
vars:
|
||||||
port_: >-
|
port_: >-
|
||||||
{{ nginx_services.resources[0].spec.ports[0].port }}
|
{{ nginx_services.resources[0].spec.ports[0].port }}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
ansible-core>=2.19.11
|
ansible-core>=2.19.12
|
||||||
jmespath>=1.1.0
|
jmespath>=1.1.0
|
||||||
jsonpatch>=1.33
|
jsonpatch>=1.33
|
||||||
kubernetes>=29.0.0
|
kubernetes>=29.0.0
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
ansible-compat==4.1.11
|
ansible-compat==4.1.11
|
||||||
# via molecule
|
# via molecule
|
||||||
ansible-core==2.19.11
|
ansible-core==2.19.12
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# ansible-compat
|
# ansible-compat
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ group_name_master: master
|
|||||||
|
|
||||||
kube_vip_arp: true
|
kube_vip_arp: true
|
||||||
kube_vip_iface:
|
kube_vip_iface:
|
||||||
|
kube_vip_endpoint:
|
||||||
kube_vip_cloud_provider_tag_version: v0.0.12
|
kube_vip_cloud_provider_tag_version: v0.0.12
|
||||||
kube_vip_tag_version: v1.2.2
|
kube_vip_tag_version: v1.2.3
|
||||||
|
|
||||||
kube_vip_bgp: false
|
kube_vip_bgp: false
|
||||||
kube_vip_bgp_routerid: 127.0.0.1
|
kube_vip_bgp_routerid: 127.0.0.1
|
||||||
@@ -19,6 +20,9 @@ kube_vip_bgp_peeras: "64512"
|
|||||||
kube_vip_bgp_peers: []
|
kube_vip_bgp_peers: []
|
||||||
kube_vip_bgp_peers_groups: ['k3s_master']
|
kube_vip_bgp_peers_groups: ['k3s_master']
|
||||||
|
|
||||||
|
kube_vip_enabled: true
|
||||||
|
|
||||||
|
metal_lb_enabled: true
|
||||||
metal_lb_controller_tag_version: v0.16.0
|
metal_lb_controller_tag_version: v0.16.0
|
||||||
metal_lb_speaker_tag_version: v0.16.0
|
metal_lb_speaker_tag_version: v0.16.0
|
||||||
metal_lb_type: native
|
metal_lb_type: native
|
||||||
|
|||||||
@@ -78,18 +78,42 @@ argument_specs:
|
|||||||
- automatically at runtime.
|
- automatically at runtime.
|
||||||
default: ~
|
default: ~
|
||||||
|
|
||||||
|
kube_vip_endpoint:
|
||||||
|
description:
|
||||||
|
- Overrides the address kube-vip binds/listens on internally, which
|
||||||
|
- can differ from the announced apiserver_endpoint for complex
|
||||||
|
- routing and site-to-site tunnels.
|
||||||
|
- Defaults to apiserver_endpoint and is used to derive the kube-vip
|
||||||
|
- subnet.
|
||||||
|
default: ~
|
||||||
|
|
||||||
kube_vip_tag_version:
|
kube_vip_tag_version:
|
||||||
description: Image tag for kube-vip
|
description: Image tag for kube-vip
|
||||||
default: v1.2.2
|
default: v1.2.3
|
||||||
|
|
||||||
kube_vip_cloud_provider_tag_version:
|
kube_vip_cloud_provider_tag_version:
|
||||||
description: Tag for kube-vip-cloud-provider manifest when enabled
|
description: Tag for kube-vip-cloud-provider manifest when enabled
|
||||||
default: v0.0.12
|
default: v0.0.12
|
||||||
|
|
||||||
|
kube_vip_enabled:
|
||||||
|
description:
|
||||||
|
- Enable installing kube-vip.
|
||||||
|
- Covers both the control-plane VIP and the service load balancer.
|
||||||
|
- Set false to skip kube-vip entirely, for example on a single node or when an external load balancer is used.
|
||||||
|
default: true
|
||||||
|
type: bool
|
||||||
|
|
||||||
kube_vip_lb_ip_range:
|
kube_vip_lb_ip_range:
|
||||||
description: IP range for kube-vip load balancer
|
description: IP range for kube-vip load balancer
|
||||||
default: ~
|
default: ~
|
||||||
|
|
||||||
|
metal_lb_enabled:
|
||||||
|
description:
|
||||||
|
- Enable installing MetalLB for service load balancing.
|
||||||
|
- Set false to skip MetalLB, for example when an external load balancer is used.
|
||||||
|
default: true
|
||||||
|
type: bool
|
||||||
|
|
||||||
metal_lb_controller_tag_version:
|
metal_lb_controller_tag_version:
|
||||||
description: Image tag for MetalLB
|
description: Image tag for MetalLB
|
||||||
default: v0.16.0
|
default: v0.16.0
|
||||||
|
|||||||
@@ -24,19 +24,21 @@
|
|||||||
|
|
||||||
- name: Deploy vip manifest
|
- name: Deploy vip manifest
|
||||||
ansible.builtin.include_tasks: vip.yml
|
ansible.builtin.include_tasks: vip.yml
|
||||||
|
when: kube_vip_enabled
|
||||||
- name: Deploy metallb manifest
|
- name: Deploy metallb manifest
|
||||||
ansible.builtin.include_tasks: metallb.yml
|
ansible.builtin.include_tasks: metallb.yml
|
||||||
tags: metallb
|
tags: metallb
|
||||||
# Deploy MetalLB unless kube-vip owns the load balancer IP range, or Cilium
|
# Deploy MetalLB unless explicitly disabled, kube-vip owns the load balancer
|
||||||
# BGP is enabled (Cilium then provides its own load balancing). The cilium_bgp
|
# IP range, or Cilium BGP is enabled (Cilium then provides its own load
|
||||||
# default keeps this safe when Cilium variables are not in scope at all (#644)
|
# balancing). The cilium_bgp default keeps this safe when Cilium variables are
|
||||||
# while still deploying MetalLB when a non-BGP Cilium CNI is in use.
|
# not in scope at all (#644) while still deploying MetalLB when a non-BGP
|
||||||
when: kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
# Cilium CNI is in use.
|
||||||
|
when: metal_lb_enabled and kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
||||||
|
|
||||||
- name: Deploy kube-vip manifest
|
- name: Deploy kube-vip manifest
|
||||||
ansible.builtin.include_tasks: kube-vip.yml
|
ansible.builtin.include_tasks: kube-vip.yml
|
||||||
tags: kubevip
|
tags: kubevip
|
||||||
when: kube_vip_lb_ip_range is defined
|
when: kube_vip_enabled and kube_vip_lb_ip_range is defined
|
||||||
|
|
||||||
- name: Initialize and verify the K3s control plane
|
- name: Initialize and verify the K3s control plane
|
||||||
any_errors_fatal: true
|
any_errors_fatal: true
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ spec:
|
|||||||
value: {{ kube_vip_iface }}
|
value: {{ kube_vip_iface }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- name: vip_subnet
|
- name: vip_subnet
|
||||||
value: "{{ apiserver_endpoint | ansible.utils.ipsubnet | ansible.utils.ipaddr('prefix') }}"
|
value: "{{ (kube_vip_endpoint | default(apiserver_endpoint, true)) | ansible.utils.ipsubnet | ansible.utils.ipaddr('prefix') }}"
|
||||||
- name: cp_enable
|
- name: cp_enable
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: cp_namespace
|
- name: cp_namespace
|
||||||
@@ -55,7 +55,7 @@ spec:
|
|||||||
- name: vip_retryperiod
|
- name: vip_retryperiod
|
||||||
value: "2"
|
value: "2"
|
||||||
- name: address
|
- name: address
|
||||||
value: {{ apiserver_endpoint }}
|
value: {{ kube_vip_endpoint | default(apiserver_endpoint, true) }}
|
||||||
{% if kube_vip_bgp | default(false) | bool %}
|
{% if kube_vip_bgp | default(false) | bool %}
|
||||||
{% if kube_vip_bgp_routerid is defined %}
|
{% if kube_vip_bgp_routerid is defined %}
|
||||||
- name: bgp_routerid
|
- name: bgp_routerid
|
||||||
|
|||||||
@@ -39,5 +39,6 @@ group_name_master: master
|
|||||||
metal_lb_mode: layer2
|
metal_lb_mode: layer2
|
||||||
metal_lb_available_timeout: 240s
|
metal_lb_available_timeout: 240s
|
||||||
metal_lb_controller_tag_version: v0.16.0
|
metal_lb_controller_tag_version: v0.16.0
|
||||||
|
metal_lb_enabled: true
|
||||||
metal_lb_interfaces: []
|
metal_lb_interfaces: []
|
||||||
metal_lb_ip_range: 192.168.30.80-192.168.30.90
|
metal_lb_ip_range: 192.168.30.80-192.168.30.90
|
||||||
@@ -133,6 +133,13 @@ argument_specs:
|
|||||||
description: IP range for kube-vip load balancer
|
description: IP range for kube-vip load balancer
|
||||||
default: ~
|
default: ~
|
||||||
|
|
||||||
|
metal_lb_enabled:
|
||||||
|
description:
|
||||||
|
- Enable installing the MetalLB pool for service load balancing.
|
||||||
|
- Set false to skip MetalLB, for example when an external load balancer is used.
|
||||||
|
default: true
|
||||||
|
type: bool
|
||||||
|
|
||||||
metal_lb_available_timeout:
|
metal_lb_available_timeout:
|
||||||
description: Wait for MetalLB resources
|
description: Wait for MetalLB resources
|
||||||
default: 240s
|
default: 240s
|
||||||
|
|||||||
@@ -12,11 +12,12 @@
|
|||||||
- name: Deploy metallb pool
|
- name: Deploy metallb pool
|
||||||
ansible.builtin.include_tasks: metallb.yml
|
ansible.builtin.include_tasks: metallb.yml
|
||||||
tags: metallb
|
tags: metallb
|
||||||
# Deploy MetalLB unless kube-vip owns the load balancer IP range, or Cilium
|
# Deploy MetalLB unless explicitly disabled, kube-vip owns the load balancer
|
||||||
# BGP is enabled (Cilium then provides its own load balancing). The cilium_bgp
|
# IP range, or Cilium BGP is enabled (Cilium then provides its own load
|
||||||
# default keeps this safe when Cilium variables are not in scope at all (#644)
|
# balancing). The cilium_bgp default keeps this safe when Cilium variables are
|
||||||
# while still deploying MetalLB when a non-BGP Cilium CNI is in use.
|
# not in scope at all (#644) while still deploying MetalLB when a non-BGP
|
||||||
when: kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
# Cilium CNI is in use.
|
||||||
|
when: metal_lb_enabled and kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
||||||
|
|
||||||
- name: Remove tmp directory used for manifests
|
- name: Remove tmp directory used for manifests
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -40,8 +40,14 @@
|
|||||||
|
|
||||||
- name: Test metallb-system namespace
|
- name: Test metallb-system namespace
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ k3s_kubectl_binary | default('k3s kubectl') }} -n metallb-system
|
{{ k3s_kubectl_binary | default('k3s kubectl') }} get namespace metallb-system
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
# The kube API can briefly return ServiceUnavailable while MetalLB converges,
|
||||||
|
# which would otherwise abort the whole converge play on a transient error.
|
||||||
|
register: metallb_namespace_result
|
||||||
|
until: metallb_namespace_result.rc == 0
|
||||||
|
retries: "{{ download_retries }}"
|
||||||
|
delay: "{{ download_delay }}"
|
||||||
with_items: "{{ groups[group_name_master | default('master')] }}"
|
with_items: "{{ groups[group_name_master | default('master')] }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
|
|
||||||
@@ -99,6 +105,12 @@
|
|||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ k3s_kubectl_binary | default('k3s kubectl') }} -n metallb-system get endpoints {{ metallb_webhook_service_name }}
|
{{ k3s_kubectl_binary | default('k3s kubectl') }} -n metallb-system get endpoints {{ metallb_webhook_service_name }}
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
# The kube API can briefly return ServiceUnavailable while MetalLB converges,
|
||||||
|
# which would otherwise abort the whole converge play on a transient error.
|
||||||
|
register: metallb_webhook_result
|
||||||
|
until: metallb_webhook_result.rc == 0
|
||||||
|
retries: "{{ download_retries }}"
|
||||||
|
delay: "{{ download_delay }}"
|
||||||
with_items: "{{ groups[group_name_master | default('master')] }}"
|
with_items: "{{ groups[group_name_master | default('master')] }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
disable_swap: true
|
||||||
|
|
||||||
secure_path:
|
secure_path:
|
||||||
RedHat: /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
|
RedHat: /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
|
||||||
Suse: /usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin
|
Suse: /usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin
|
||||||
@@ -4,6 +4,28 @@
|
|||||||
name: "{{ system_timezone }}"
|
name: "{{ system_timezone }}"
|
||||||
when: (system_timezone is defined) and (system_timezone != "Your/Timezone")
|
when: (system_timezone is defined) and (system_timezone != "Your/Timezone")
|
||||||
|
|
||||||
|
# k3s recommends swap be disabled on all nodes. Disabling swap is all-or-nothing
|
||||||
|
# across the cluster: leaving it enabled on some nodes but not others creates
|
||||||
|
# uneven scheduling/latency behavior. This block turns swap off and comments out
|
||||||
|
# the swap entries in /etc/fstab so it stays off across reboots. It is idempotent
|
||||||
|
# and a no-op when swap is already disabled or swapoff is unavailable.
|
||||||
|
- name: Disable swap on all cluster nodes
|
||||||
|
when: disable_swap
|
||||||
|
block:
|
||||||
|
- name: Turn off swap now
|
||||||
|
ansible.builtin.command: swapoff -a
|
||||||
|
register: swapoff_result
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Comment out swap entries in fstab
|
||||||
|
ansible.builtin.replace:
|
||||||
|
path: /etc/fstab
|
||||||
|
regexp: '^([^#][^\n]*\s+swap\s+)'
|
||||||
|
replace: '# \\1'
|
||||||
|
register: fstab_swap
|
||||||
|
|
||||||
|
|
||||||
- name: Set SELinux to disabled state
|
- name: Set SELinux to disabled state
|
||||||
ansible.posix.selinux:
|
ansible.posix.selinux:
|
||||||
state: disabled
|
state: disabled
|
||||||
|
|||||||
Reference in new issue
Block a user