mirror of
https://github.com/techno-tim/k3s-ansible.git
synced 2026-10-23 12:12:04 +02:00
Compare commits
1
Commits
master
..
e0ac53dba3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0ac53dba3 |
No files matched your search
@@ -1,112 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Assert the k3s-init log destination guard skips when unset.
|
|
||||||
|
|
||||||
``roles/k3s_server/tasks/main.yml`` fetches k3s-init diagnostics only when
|
|
||||||
``ANSIBLE_K3S_LOG_DIR`` is set. The lookup default must be an empty string:
|
|
||||||
Jinja stringifies the boolean ``False`` to ``'False'``, which is non-empty, so
|
|
||||||
a boolean default makes the guard pass and the role creates a directory
|
|
||||||
literally named ``False`` in the operator's working directory on every run.
|
|
||||||
|
|
||||||
This test extracts the real ``log_destination`` expression from the role and
|
|
||||||
renders it for both the unset and set cases.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from jinja2 import Environment
|
|
||||||
|
|
||||||
ROLE = os.path.join("roles", "k3s_server", "tasks", "main.yml")
|
|
||||||
|
|
||||||
|
|
||||||
def repo_root():
|
|
||||||
return subprocess.check_output(
|
|
||||||
["git", "rev-parse", "--show-toplevel"], text=True
|
|
||||||
).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def fail(message):
|
|
||||||
raise SystemExit("k3s-init log destination test failed: " + message)
|
|
||||||
|
|
||||||
|
|
||||||
def find_log_task(root):
|
|
||||||
"""Return the 'Save logs of k3s-init.service' task from the role."""
|
|
||||||
with open(os.path.join(root, ROLE), "r") as handle:
|
|
||||||
tasks = yaml.safe_load(handle)
|
|
||||||
|
|
||||||
def walk(entries):
|
|
||||||
for entry in entries or []:
|
|
||||||
if not isinstance(entry, dict):
|
|
||||||
continue
|
|
||||||
if "Save logs of k3s-init.service" in str(entry.get("name", "")):
|
|
||||||
return entry
|
|
||||||
for key in ("block", "always", "rescue"):
|
|
||||||
found = walk(entry.get(key))
|
|
||||||
if found:
|
|
||||||
return found
|
|
||||||
return None
|
|
||||||
|
|
||||||
task = walk(tasks)
|
|
||||||
if task is None:
|
|
||||||
fail("could not find the 'Save logs of k3s-init.service' task in " + ROLE)
|
|
||||||
return task
|
|
||||||
|
|
||||||
|
|
||||||
def env_lookup(name, default=None, **kwargs):
|
|
||||||
"""Stand-in for the env lookup: unset names fall back to the default."""
|
|
||||||
return {"ANSIBLE_K3S_LOG_DIR": None}.get(name, default)
|
|
||||||
|
|
||||||
|
|
||||||
def render(task, env, value):
|
|
||||||
"""Render log_destination the way Ansible folds a '>-' scalar."""
|
|
||||||
expression = str(task["vars"]["log_destination"]).strip()
|
|
||||||
template = env.from_string(expression)
|
|
||||||
|
|
||||||
def lookup(*args, **kwargs):
|
|
||||||
name = args[1] if len(args) > 1 else kwargs.get("name")
|
|
||||||
if name != "ANSIBLE_K3S_LOG_DIR":
|
|
||||||
return kwargs.get("default")
|
|
||||||
return kwargs["default"] if value is None else value
|
|
||||||
|
|
||||||
return template.render(lookup=lookup).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
root = repo_root()
|
|
||||||
task = find_log_task(root)
|
|
||||||
env = Environment()
|
|
||||||
|
|
||||||
unset = render(task, env, None)
|
|
||||||
if unset != "":
|
|
||||||
fail(
|
|
||||||
"log_destination resolved to %r when ANSIBLE_K3S_LOG_DIR is unset; "
|
|
||||||
"it must be an empty string so the guard skips" % unset
|
|
||||||
)
|
|
||||||
if task["when"].strip() == "":
|
|
||||||
fail("the guard on the log task is empty, so it would always run")
|
|
||||||
|
|
||||||
set_path = render(task, env, "/var/log/k3s-init")
|
|
||||||
if set_path != "/var/log/k3s-init":
|
|
||||||
fail(
|
|
||||||
"log_destination resolved to %r when ANSIBLE_K3S_LOG_DIR is set; "
|
|
||||||
"expected the path to be passed through unchanged" % set_path
|
|
||||||
)
|
|
||||||
|
|
||||||
# A stray default= would leave the boolean in place even though the
|
|
||||||
# expression still renders empty, so assert the source no longer carries
|
|
||||||
# it. This is the exact regression that produced the False/ directory.
|
|
||||||
if re.search(r"default\s*=\s*False", yaml.safe_dump(task)):
|
|
||||||
fail(
|
|
||||||
"log_destination still uses a boolean default; Jinja would "
|
|
||||||
"stringify it to 'False' and create a directory named False"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("k3s-init log destination regression test passed")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -26,7 +26,7 @@ if grep -Fq -- "node-role.kubernetes.io/master=true' -o=jsonpath" "$main_tasks";
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
grep -Fq -- "map('extract', hostvars, ['ansible_facts', 'hostname'])" "$main_tasks"
|
grep -Fq -- "map('extract', hostvars, 'ansible_hostname')" "$main_tasks"
|
||||||
grep -Fq -- 'difference(nodes.stdout.split())' "$main_tasks"
|
grep -Fq -- 'difference(nodes.stdout.split())' "$main_tasks"
|
||||||
grep -Fq -- 'crd/addons.k3s.cattle.io' "$main_tasks"
|
grep -Fq -- 'crd/addons.k3s.cattle.io' "$main_tasks"
|
||||||
grep -Fq -- 'crd/helmcharts.helm.cattle.io' "$main_tasks"
|
grep -Fq -- 'crd/helmcharts.helm.cattle.io' "$main_tasks"
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
#!/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.3 reads `bgp_peers` and `vip_subnet`; it ignores the older
|
kube-vip v1.2.2 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.3",
|
"kube_vip_tag_version": "v1.2.2",
|
||||||
}
|
}
|
||||||
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.3" not in output:
|
if "ghcr.io/kube-vip/kube-vip:v1.2.2" not in output:
|
||||||
fail("kube-vip image tag is not v1.2.3")
|
fail("kube-vip image tag is not v1.2.2")
|
||||||
|
|
||||||
# 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(
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ 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 MetalLB is explicitly disabled, kube-vip owns the
|
every topology except when kube-vip owns the VIP range or Cilium BGP is enabled.
|
||||||
VIP range, or Cilium BGP is enabled.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
@@ -24,7 +23,6 @@ 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)"
|
||||||
)
|
)
|
||||||
@@ -90,7 +88,6 @@ 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,
|
||||||
},
|
},
|
||||||
@@ -101,7 +98,6 @@ 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,
|
||||||
@@ -110,7 +106,6 @@ 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",
|
||||||
},
|
},
|
||||||
@@ -120,7 +115,6 @@ 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",
|
||||||
},
|
},
|
||||||
@@ -130,21 +124,12 @@ 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():
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Regression test for the MetalLB converge checks.
|
"""Regression test for the MetalLB namespace existence check.
|
||||||
|
|
||||||
The MetalLB tasks in roles/k3s_server_post/tasks/metallb.yml must actually
|
The "Test metallb-system namespace" task in
|
||||||
verify resources through an explicit kubectl get, and must retry on a
|
roles/k3s_server_post/tasks/metallb.yml must actually verify the namespace
|
||||||
transient kube API error while MetalLB converges.
|
exists. A previous version ran `k3s kubectl -n metallb-system` with no
|
||||||
|
subcommand, which only printed a usage page and always exited 0, so the task
|
||||||
|
always succeeded even when the namespace did not exist (issue #350).
|
||||||
|
|
||||||
The "Test metallb-system namespace" task previously ran `k3s kubectl -n
|
This test loads the real task and asserts the command performs an explicit
|
||||||
metallb-system` with no subcommand, which only printed a usage page and always
|
`get namespace metallb-system`, which returns non-zero when the namespace is
|
||||||
exited 0, so it always succeeded even when the namespace did not exist (issue
|
absent.
|
||||||
#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
|
from __future__ import print_function
|
||||||
@@ -32,55 +27,11 @@ def repo_root():
|
|||||||
|
|
||||||
|
|
||||||
def fail(message):
|
def fail(message):
|
||||||
raise SystemExit("MetalLB namespace test failed: " + 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():
|
def main():
|
||||||
task_file = os.path.join(
|
task_file = os.path.join(
|
||||||
repo_root(), "roles", "k3s_server_post", "tasks", "metallb.yml"
|
repo_root(), "roles", "k3s_server_post", "tasks", "metallb.yml"
|
||||||
@@ -88,18 +39,31 @@ def main():
|
|||||||
with open(task_file, encoding="utf-8") as handle:
|
with open(task_file, encoding="utf-8") as handle:
|
||||||
tasks = yaml.safe_load(handle)
|
tasks = yaml.safe_load(handle)
|
||||||
|
|
||||||
namespace_task = find_task(tasks, "Test metallb-system namespace")
|
task = None
|
||||||
|
for entry in tasks:
|
||||||
|
if entry.get("name") == "Test metallb-system namespace":
|
||||||
|
task = entry
|
||||||
|
break
|
||||||
|
if task is None:
|
||||||
|
fail("could not find the 'Test metallb-system namespace' 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")
|
||||||
|
|
||||||
|
command_text = cmd if isinstance(cmd, str) else " ".join(cmd)
|
||||||
|
|
||||||
# A bare `-n metallb-system` with no subcommand prints kubectl usage and
|
# 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
|
# always exits 0, so it never proves the namespace exists. The fix must
|
||||||
# use an explicit get.
|
# use an explicit get.
|
||||||
check_explicit_get(namespace_task, "Test metallb-system namespace",
|
if "get namespace metallb-system" not in command_text:
|
||||||
"get namespace metallb-system")
|
fail(
|
||||||
check_retry_wiring(namespace_task, "Test metallb-system namespace")
|
"command does not run 'get namespace metallb-system'; "
|
||||||
|
"the task would only print usage and never verify the namespace "
|
||||||
webhook_task = find_task(tasks, "Test metallb-system webhook-service endpoint")
|
"(got: {0!r})".format(command_text)
|
||||||
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")
|
print("MetalLB namespace check regression test passed")
|
||||||
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
repo_root="$(git rev-parse --show-toplevel)"
|
|
||||||
tasks_file="$repo_root/roles/raspberrypi/tasks/main.yml"
|
|
||||||
|
|
||||||
if [ ! -f "$tasks_file" ]; then
|
|
||||||
printf 'cannot find %s\n' "$tasks_file" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The role decides whether a host is a Raspberry Pi from the return code of two
|
|
||||||
# ansible.builtin.command probes. The command module never executes under
|
|
||||||
# --check: it returns rc 0 with "Command would have run if not in check mode".
|
|
||||||
# Without check_mode: false on both probes every host looks like a Raspberry Pi
|
|
||||||
# in a check run, so distro specific tasks (e.g. the Ubuntu cgroup
|
|
||||||
# cmdline.txt edit) run against hosts that must not be touched.
|
|
||||||
probe_count="$(grep -c -- 'check_mode: false' "$tasks_file" || true)"
|
|
||||||
if [ "$probe_count" -lt 2 ]; then
|
|
||||||
printf 'raspberrypi detection probes must set check_mode: false (found %s, expected 2)\n' \
|
|
||||||
"$probe_count" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Both probes must be the detection greps, not some other task.
|
|
||||||
for probe in \
|
|
||||||
'grep -E "Raspberry Pi|BCM2708|BCM2709|BCM2835|BCM2836" /proc/cpuinfo' \
|
|
||||||
'grep -E "Raspberry Pi" /proc/device-tree/model'; do
|
|
||||||
grep -Fq -- "$probe" "$tasks_file" || {
|
|
||||||
printf 'missing raspberry pi detection probe: %s\n' "$probe" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
# The probes must stay registered under the names the gate consumes.
|
|
||||||
for var in grep_cpuinfo_raspberrypi grep_device_tree_model_raspberrypi; do
|
|
||||||
grep -Fq -- "register: $var" "$tasks_file" || {
|
|
||||||
printf 'detection probe is not registered as %s\n' "$var" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
# The gate must keep keying on rc 0 and must tolerate a missing rc so an
|
|
||||||
# undefined result cannot be read as "this host is a Raspberry Pi".
|
|
||||||
grep -Eq -- 'when:.*grep_cpuinfo_raspberrypi\.rc.*== 0.*grep_device_tree_model_raspberrypi\.rc.*== 0' \
|
|
||||||
"$tasks_file" || {
|
|
||||||
printf 'raspberry_pi gate no longer tests both probe return codes for 0\n' >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
grep -Eq -- 'grep_cpuinfo_raspberrypi\.rc \| default\(1\)' "$tasks_file" || {
|
|
||||||
printf 'raspberry_pi gate does not default the /proc/cpuinfo probe rc\n' >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
grep -Eq -- 'grep_device_tree_model_raspberrypi\.rc \| default\(1\)' "$tasks_file" || {
|
|
||||||
printf 'raspberry_pi gate does not default the /proc/device-tree probe rc\n' >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# The distro specific include must remain gated on the detection result so a
|
|
||||||
# non Raspberry Pi host skips setup/ and teardown/ entirely.
|
|
||||||
grep -Eq -- 'raspberry_pi\|default\(false\)' "$tasks_file" || {
|
|
||||||
printf 'OS specific tasks are no longer gated on raspberry_pi\n' >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
printf 'Raspberry Pi detection regression test passed\n'
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
#!/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@62574f011e0d1967d555a862bd28a7abba8684fe # 5.0.9
|
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@46cfe808a5f1588656ef299eedd0ce2fd7ec0dcc # 5.0.6
|
||||||
with:
|
with:
|
||||||
allowlist: |
|
allowlist: |
|
||||||
aws-actions/
|
aws-actions/
|
||||||
|
|||||||
+4
-35
@@ -23,6 +23,10 @@ repos:
|
|||||||
additional_dependencies: [ansible-core==2.18.0]
|
additional_dependencies: [ansible-core==2.18.0]
|
||||||
language_version: python3.12
|
language_version: python3.12
|
||||||
args: [--offline]
|
args: [--offline]
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: v0.9.0.6
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.4
|
rev: v1.5.4
|
||||||
hooks:
|
hooks:
|
||||||
@@ -34,12 +38,6 @@ repos:
|
|||||||
- id: fix-smartquotes
|
- id: fix-smartquotes
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
|
||||||
name: shellcheck
|
|
||||||
entry: shellcheck
|
|
||||||
language: python
|
|
||||||
additional_dependencies: [shellcheck-py==0.11.0.1]
|
|
||||||
types: [shell]
|
|
||||||
- id: cleanup-runner-resources-test
|
- id: cleanup-runner-resources-test
|
||||||
name: cleanup runner resources test
|
name: cleanup runner resources test
|
||||||
entry: .github/scripts/test-cleanup-runner-resources.sh
|
entry: .github/scripts/test-cleanup-runner-resources.sh
|
||||||
@@ -64,12 +62,6 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^roles/k3s_server/tasks/(main|join_master)\.yml$|^\.github/scripts/test-k3s-server-bootstrap\.sh$
|
files: ^roles/k3s_server/tasks/(main|join_master)\.yml$|^\.github/scripts/test-k3s-server-bootstrap\.sh$
|
||||||
- id: k3s-init-log-destination-test
|
|
||||||
name: K3s init log destination test
|
|
||||||
entry: .github/scripts/test-k3s-init-log-destination.py
|
|
||||||
language: system
|
|
||||||
pass_filenames: false
|
|
||||||
files: ^roles/k3s_server/tasks/main\.yml$|^\.github/scripts/test-k3s-init-log-destination\.py$
|
|
||||||
- id: unique-hostname-precheck-test
|
- id: unique-hostname-precheck-test
|
||||||
name: Unique hostname precheck test
|
name: Unique hostname precheck test
|
||||||
entry: .github/scripts/test-unique-hostname-precheck.sh
|
entry: .github/scripts/test-unique-hostname-precheck.sh
|
||||||
@@ -82,12 +74,6 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^roles/prereq/(tasks/main|defaults/main)\.yml$|^\.github/scripts/test-disable-swap\.sh$
|
files: ^roles/prereq/(tasks/main|defaults/main)\.yml$|^\.github/scripts/test-disable-swap\.sh$
|
||||||
- id: raspberrypi-detection-test
|
|
||||||
name: Raspberry Pi detection test
|
|
||||||
entry: .github/scripts/test-raspberrypi-detection.sh
|
|
||||||
language: system
|
|
||||||
pass_filenames: false
|
|
||||||
files: ^roles/raspberrypi/(tasks/main|defaults/main)\.yml$|^\.github/scripts/test-raspberrypi-detection\.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
|
||||||
@@ -144,15 +130,6 @@ 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
|
||||||
@@ -161,11 +138,3 @@ 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]
|
|
||||||
@@ -220,11 +220,9 @@ See the commands [here](https://technotim.com/posts/k3s-etcd-ansible/#testing-yo
|
|||||||
| `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_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_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_tag_version` | string | `v1.2.2` | 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` |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
k3s_version: v1.36.4+k3s1
|
k3s_version: v1.36.2+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
|
||||||
@@ -102,11 +102,7 @@ extra_agent_args: >-
|
|||||||
{{ extra_args }}
|
{{ extra_args }}
|
||||||
|
|
||||||
# image tag for kube-vip
|
# image tag for kube-vip
|
||||||
kube_vip_tag_version: v1.2.3
|
kube_vip_tag_version: v1.2.2
|
||||||
|
|
||||||
# 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"
|
||||||
@@ -115,10 +111,6 @@ kube_vip_enabled: true
|
|||||||
# (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,16 +69,7 @@
|
|||||||
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
|
||||||
# The MetalLB speaker announces the VIP on the host-only network, and the
|
failed_when: "'Welcome to nginx!' not in result.content"
|
||||||
# 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.13
|
ansible-core>=2.19.11
|
||||||
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.13
|
ansible-core==2.19.11
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# ansible-compat
|
# ansible-compat
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ kube_vip_arp: true
|
|||||||
kube_vip_iface:
|
kube_vip_iface:
|
||||||
kube_vip_endpoint:
|
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.3
|
kube_vip_tag_version: v1.2.2
|
||||||
|
|
||||||
kube_vip_bgp: false
|
kube_vip_bgp: false
|
||||||
kube_vip_bgp_routerid: 127.0.0.1
|
kube_vip_bgp_routerid: 127.0.0.1
|
||||||
@@ -20,9 +20,6 @@ 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
|
||||||
@@ -38,7 +35,7 @@ retry_count: 20
|
|||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
server_init_args: >-
|
server_init_args: >-
|
||||||
{% if groups[group_name_master | default('master')] | length > 1 %}
|
{% if groups[group_name_master | default('master')] | length > 1 %}
|
||||||
{% if ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname'] %}
|
{% if ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] %}
|
||||||
--cluster-init
|
--cluster-init
|
||||||
{% else %}
|
{% else %}
|
||||||
--server https://{{ hostvars[groups[group_name_master | default('master')][0]].k3s_node_ip | split(",") | first | ansible.utils.ipwrap }}:6443
|
--server https://{{ hostvars[groups[group_name_master | default('master')][0]].k3s_node_ip | split(",") | first | ansible.utils.ipwrap }}:6443
|
||||||
|
|||||||
@@ -89,31 +89,16 @@ argument_specs:
|
|||||||
|
|
||||||
kube_vip_tag_version:
|
kube_vip_tag_version:
|
||||||
description: Image tag for kube-vip
|
description: Image tag for kube-vip
|
||||||
default: v1.2.3
|
default: v1.2.2
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
no_log: true
|
no_log: true
|
||||||
register: k3s_input_firewall_rules
|
register: k3s_input_firewall_rules
|
||||||
|
|
||||||
- name: Ping the primary Kubernetes API address from {{ ansible_facts.hostname }}
|
- name: Ping the primary Kubernetes API address from {{ ansible_hostname }}
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
argv:
|
argv:
|
||||||
- ping
|
- ping
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
no_log: true
|
no_log: true
|
||||||
register: k3s_primary_api_ping
|
register: k3s_primary_api_ping
|
||||||
|
|
||||||
- name: Probe primary Kubernetes API from {{ ansible_facts.hostname }}
|
- name: Probe primary Kubernetes API from {{ ansible_hostname }}
|
||||||
ansible.builtin.wait_for:
|
ansible.builtin.wait_for:
|
||||||
host: "{{ hostvars[groups[group_name_master | default('master')][0]].k3s_node_ip | split(',') | first }}"
|
host: "{{ hostvars[groups[group_name_master | default('master')][0]].k3s_node_ip | split(',') | first }}"
|
||||||
port: 6443
|
port: 6443
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
become: false
|
become: false
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: content.j2
|
src: content.j2
|
||||||
dest: "{{ log_destination }}/k3s-init@{{ ansible_facts['hostname'] }}.log"
|
dest: "{{ log_destination }}/k3s-init@{{ ansible_hostname }}.log"
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
vars:
|
vars:
|
||||||
content: |
|
content: |
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
become: false
|
become: false
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: content.j2
|
src: content.j2
|
||||||
dest: "{{ log_destination }}/network@{{ ansible_facts['hostname'] }}.log"
|
dest: "{{ log_destination }}/network@{{ ansible_hostname }}.log"
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
vars:
|
vars:
|
||||||
content: |
|
content: |
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: >-
|
cmd: >-
|
||||||
{{ k3s_kubectl_binary | default('k3s kubectl') }} get node
|
{{ k3s_kubectl_binary | default('k3s kubectl') }} get node
|
||||||
{{ hostvars[joining_master].ansible_facts['hostname'] }}
|
{{ hostvars[joining_master].ansible_hostname }}
|
||||||
delegate_to: "{{ groups[group_name_master | default('master')][0] }}"
|
delegate_to: "{{ groups[group_name_master | default('master')][0] }}"
|
||||||
register: joined_master
|
register: joined_master
|
||||||
until: joined_master.rc == 0
|
until: joined_master.rc == 0
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Download vip cloud provider manifest to first master
|
- name: Download vip cloud provider manifest to first master
|
||||||
ansible.builtin.get_url:
|
ansible.builtin.get_url:
|
||||||
@@ -20,8 +19,7 @@
|
|||||||
retries: "{{ download_retries }}"
|
retries: "{{ download_retries }}"
|
||||||
delay: "{{ download_delay }}"
|
delay: "{{ download_delay }}"
|
||||||
until: kube_vip_manifest_download is succeeded
|
until: kube_vip_manifest_download is succeeded
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Copy kubevip configMap manifest to first master
|
- name: Copy kubevip configMap manifest to first master
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -30,5 +28,4 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
@@ -24,21 +24,19 @@
|
|||||||
|
|
||||||
- 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 explicitly disabled, kube-vip owns the load balancer
|
# Deploy MetalLB unless kube-vip owns the load balancer IP range, or Cilium
|
||||||
# IP range, or Cilium BGP is enabled (Cilium then provides its own load
|
# BGP is enabled (Cilium then provides its own load balancing). The cilium_bgp
|
||||||
# balancing). The cilium_bgp default keeps this safe when Cilium variables are
|
# default keeps this safe when Cilium variables are not in scope at all (#644)
|
||||||
# not in scope at all (#644) while still deploying MetalLB when a non-BGP
|
# while still deploying MetalLB when a non-BGP Cilium CNI is in use.
|
||||||
# Cilium CNI is in use.
|
when: kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
||||||
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_enabled and kube_vip_lb_ip_range is defined
|
when: 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
|
||||||
@@ -104,7 +102,7 @@
|
|||||||
expected_control_plane_names: >-
|
expected_control_plane_names: >-
|
||||||
{{
|
{{
|
||||||
groups[group_name_master | default('master')]
|
groups[group_name_master | default('master')]
|
||||||
| map('extract', hostvars, ['ansible_facts', 'hostname'])
|
| map('extract', hostvars, 'ansible_hostname')
|
||||||
| list
|
| list
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -124,18 +122,13 @@
|
|||||||
always:
|
always:
|
||||||
- name: Save logs of k3s-init.service
|
- name: Save logs of k3s-init.service
|
||||||
ansible.builtin.include_tasks: fetch_k3s_init_logs.yml
|
ansible.builtin.include_tasks: fetch_k3s_init_logs.yml
|
||||||
# The env lookup default must be an empty string, not the boolean
|
# ANSIBLE_K3S_LOG_DIR is a path string when set; evaluate it as a boolean
|
||||||
# False. The `!= ''` comparison below always evaluates to a bool, so
|
# so the conditional is a real boolean (ansible-core 2.19 rejects string
|
||||||
# ansible-core 2.19 never sees a non-boolean conditional and no boolean
|
# conditionals derived from env vars).
|
||||||
# default is needed. With a boolean default, an unset ANSIBLE_K3S_LOG_DIR
|
|
||||||
# leaves log_destination as the bool False, and `False != ''` is True
|
|
||||||
# because False == 0. The guard would then always pass, running the
|
|
||||||
# diagnostics on every provisioning run and creating a directory
|
|
||||||
# literally named 'False' in the operator's working directory.
|
|
||||||
when: log_destination | default('') != ''
|
when: log_destination | default('') != ''
|
||||||
vars:
|
vars:
|
||||||
log_destination: >-
|
log_destination: >-
|
||||||
{{ lookup('ansible.builtin.env', 'ANSIBLE_K3S_LOG_DIR', default='') }}
|
{{ lookup('ansible.builtin.env', 'ANSIBLE_K3S_LOG_DIR', default=False) }}
|
||||||
- name: Kill the temporary service used for initialization
|
- name: Kill the temporary service used for initialization
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: k3s-init
|
name: k3s-init
|
||||||
@@ -188,24 +181,24 @@
|
|||||||
|
|
||||||
- name: Create directory .kube
|
- name: Create directory .kube
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ ansible_facts['user_dir'] }}/.kube"
|
path: "{{ ansible_user_dir }}/.kube"
|
||||||
state: directory
|
state: directory
|
||||||
owner: "{{ ansible_facts['user_id'] }}"
|
owner: "{{ ansible_user_id }}"
|
||||||
mode: u=rwx,g=rx,o=
|
mode: u=rwx,g=rx,o=
|
||||||
|
|
||||||
- name: Copy config file to user home directory
|
- name: Copy config file to user home directory
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: /etc/rancher/k3s/k3s.yaml
|
src: /etc/rancher/k3s/k3s.yaml
|
||||||
dest: "{{ ansible_facts['user_dir'] }}/.kube/config"
|
dest: "{{ ansible_user_dir }}/.kube/config"
|
||||||
remote_src: true
|
remote_src: true
|
||||||
owner: "{{ ansible_facts['user_id'] }}"
|
owner: "{{ ansible_user_id }}"
|
||||||
mode: u=rw,g=,o=
|
mode: u=rw,g=,o=
|
||||||
|
|
||||||
- name: Configure kubectl cluster to {{ endpoint_url }}
|
- name: Configure kubectl cluster to {{ endpoint_url }}
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ k3s_kubectl_binary | default('k3s kubectl') }} config set-cluster default
|
{{ k3s_kubectl_binary | default('k3s kubectl') }} config set-cluster default
|
||||||
--server={{ endpoint_url }}
|
--server={{ endpoint_url }}
|
||||||
--kubeconfig {{ ansible_facts['user_dir'] }}/.kube/config
|
--kubeconfig {{ ansible_user_dir }}/.kube/config
|
||||||
changed_when: true
|
changed_when: true
|
||||||
vars:
|
vars:
|
||||||
endpoint_url: >-
|
endpoint_url: >-
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: "Download to first master: manifest for metallb-{{ metal_lb_type }}"
|
- name: "Download to first master: manifest for metallb-{{ metal_lb_type }}"
|
||||||
ansible.builtin.get_url:
|
ansible.builtin.get_url:
|
||||||
@@ -20,8 +19,7 @@
|
|||||||
retries: "{{ download_retries }}"
|
retries: "{{ download_retries }}"
|
||||||
delay: "{{ download_delay }}"
|
delay: "{{ download_delay }}"
|
||||||
until: metallb_manifest_download is succeeded
|
until: metallb_manifest_download is succeeded
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Set image versions in manifest for metallb-{{ metal_lb_type }}
|
- name: Set image versions in manifest for metallb-{{ metal_lb_type }}
|
||||||
ansible.builtin.replace:
|
ansible.builtin.replace:
|
||||||
@@ -33,15 +31,13 @@
|
|||||||
to: metallb/speaker:{{ metal_lb_speaker_tag_version }}
|
to: metallb/speaker:{{ metal_lb_speaker_tag_version }}
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.change }} => {{ item.to }}"
|
label: "{{ item.change }} => {{ item.to }}"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Read back MetalLB manifest from first master
|
- name: Read back MetalLB manifest from first master
|
||||||
ansible.builtin.slurp:
|
ansible.builtin.slurp:
|
||||||
src: /var/lib/rancher/k3s/server/manifests/metallb-crds.yaml
|
src: /var/lib/rancher/k3s/server/manifests/metallb-crds.yaml
|
||||||
register: metallb_manifest
|
register: metallb_manifest
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Check the MetalLB speaker image reference is present
|
- name: Check the MetalLB speaker image reference is present
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -50,8 +46,7 @@
|
|||||||
('quay.io/metallb/speaker:' ~ metal_lb_speaker_tag_version) in
|
('quay.io/metallb/speaker:' ~ metal_lb_speaker_tag_version) in
|
||||||
(metallb_manifest.content | default('') | b64decode)
|
(metallb_manifest.content | default('') | b64decode)
|
||||||
}}
|
}}
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Fail if MetalLB speaker tag was not applied to the manifest
|
- name: Fail if MetalLB speaker tag was not applied to the manifest
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
@@ -61,6 +56,5 @@
|
|||||||
was not found in the downloaded manifest. The upstream image
|
was not found in the downloaded manifest. The upstream image
|
||||||
reference may have changed.
|
reference may have changed.
|
||||||
when:
|
when:
|
||||||
- >-
|
- ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
- not metallb_manifest_has_speaker_tag
|
- not metallb_manifest_has_speaker_tag
|
||||||
@@ -10,8 +10,7 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Copy kube-vip RBAC manifest to first master
|
- name: Copy kube-vip RBAC manifest to first master
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -20,8 +19,7 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
|
|
||||||
- name: Copy vip manifest to first master
|
- name: Copy vip manifest to first master
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -30,5 +28,4 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
@@ -39,6 +39,5 @@ 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,13 +133,6 @@ 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
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy Calico to cluster
|
- name: Deploy Calico to cluster
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
run_once: true
|
run_once: true
|
||||||
block:
|
block:
|
||||||
- name: Create manifests directory on first master
|
- name: Create manifests directory on first master
|
||||||
|
|||||||
@@ -184,7 +184,7 @@
|
|||||||
--helm-set loadBalancer.mode={{ bpf_lb_mode }}
|
--helm-set loadBalancer.mode={{ bpf_lb_mode }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
environment:
|
environment:
|
||||||
KUBECONFIG: "{{ ansible_facts['user_dir'] }}/.kube/config"
|
KUBECONFIG: "{{ ansible_user_dir }}/.kube/config"
|
||||||
register: cilium_install_result
|
register: cilium_install_result
|
||||||
changed_when: cilium_install_result.rc == 0
|
changed_when: cilium_install_result.rc == 0
|
||||||
# cilium install/upgrade fetches the Helm chart from helm.cilium.io, which is
|
# cilium install/upgrade fetches the Helm chart from helm.cilium.io, which is
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
- name: Wait for Cilium status to be healthy
|
- name: Wait for Cilium status to be healthy
|
||||||
ansible.builtin.command: cilium status --wait
|
ansible.builtin.command: cilium status --wait
|
||||||
environment:
|
environment:
|
||||||
KUBECONFIG: "{{ ansible_facts['user_dir'] }}/.kube/config"
|
KUBECONFIG: "{{ ansible_user_dir }}/.kube/config"
|
||||||
register: cilium_status
|
register: cilium_status
|
||||||
changed_when: false
|
changed_when: false
|
||||||
until: cilium_status.rc == 0
|
until: cilium_status.rc == 0
|
||||||
|
|||||||
@@ -12,12 +12,11 @@
|
|||||||
- 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 explicitly disabled, kube-vip owns the load balancer
|
# Deploy MetalLB unless kube-vip owns the load balancer IP range, or Cilium
|
||||||
# IP range, or Cilium BGP is enabled (Cilium then provides its own load
|
# BGP is enabled (Cilium then provides its own load balancing). The cilium_bgp
|
||||||
# balancing). The cilium_bgp default keeps this safe when Cilium variables are
|
# default keeps this safe when Cilium variables are not in scope at all (#644)
|
||||||
# not in scope at all (#644) while still deploying MetalLB when a non-BGP
|
# while still deploying MetalLB when a non-BGP Cilium CNI is in use.
|
||||||
# Cilium CNI is in use.
|
when: kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
|
||||||
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:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /tmp/k3s
|
path: /tmp/k3s
|
||||||
state: directory
|
state: directory
|
||||||
owner: "{{ ansible_facts['user_id'] }}"
|
owner: "{{ ansible_user_id }}"
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
with_items: "{{ groups[group_name_master | default('master')] }}"
|
with_items: "{{ groups[group_name_master | default('master')] }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: metallb.crs.j2
|
src: metallb.crs.j2
|
||||||
dest: /tmp/k3s/metallb-crs.yaml
|
dest: /tmp/k3s/metallb-crs.yaml
|
||||||
owner: "{{ ansible_facts['user_id'] }}"
|
owner: "{{ ansible_user_id }}"
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
with_items: "{{ groups[group_name_master | default('master')] }}"
|
with_items: "{{ groups[group_name_master | default('master')] }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
@@ -42,12 +42,6 @@
|
|||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ k3s_kubectl_binary | default('k3s kubectl') }} get namespace 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
|
||||||
|
|
||||||
@@ -105,12 +99,6 @@
|
|||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
- name: Set SELinux to disabled state
|
- name: Set SELinux to disabled state
|
||||||
ansible.posix.selinux:
|
ansible.posix.selinux:
|
||||||
state: disabled
|
state: disabled
|
||||||
when: ansible_facts.os_family == "RedHat"
|
when: ansible_os_family == "RedHat"
|
||||||
|
|
||||||
- name: Enable IPv4 forwarding
|
- name: Enable IPv4 forwarding
|
||||||
ansible.posix.sysctl:
|
ansible.posix.sysctl:
|
||||||
@@ -63,39 +63,31 @@
|
|||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
register: br_netfilter_exists
|
register: br_netfilter_exists
|
||||||
changed_when: false
|
changed_when: false
|
||||||
when: ansible_facts.os_family == "RedHat"
|
when: ansible_os_family == "RedHat"
|
||||||
tags: sysctl
|
tags: sysctl
|
||||||
|
|
||||||
- name: Install kernel-modules-extra if br_netfilter missing
|
- name: Install kernel-modules-extra if br_netfilter missing
|
||||||
ansible.builtin.yum:
|
ansible.builtin.yum:
|
||||||
name: "kernel-modules-extra-{{ ansible_kernel }}"
|
name: kernel-modules-extra
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
register: kernel_modules_installed
|
register: kernel_modules_installed
|
||||||
when:
|
when:
|
||||||
- ansible_facts.os_family == "RedHat"
|
- ansible_os_family == "RedHat"
|
||||||
- br_netfilter_exists.stdout | int == 0
|
- br_netfilter_exists.stdout | int == 0
|
||||||
|
|
||||||
- name: Reboot node if kernel modules package was installed
|
|
||||||
ansible.builtin.reboot:
|
|
||||||
msg: "Rebooting to apply new kernel modules package"
|
|
||||||
reboot_timeout: 300
|
|
||||||
when:
|
|
||||||
- ansible_facts.os_family == "RedHat"
|
|
||||||
- kernel_modules_installed is changed
|
|
||||||
|
|
||||||
- name: Add br_netfilter to /etc/modules-load.d/
|
- name: Add br_netfilter to /etc/modules-load.d/
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: br_netfilter
|
content: br_netfilter
|
||||||
dest: /etc/modules-load.d/br_netfilter.conf
|
dest: /etc/modules-load.d/br_netfilter.conf
|
||||||
mode: u=rw,g=,o=
|
mode: u=rw,g=,o=
|
||||||
when: ansible_facts.os_family == "RedHat"
|
when: ansible_os_family == "RedHat"
|
||||||
|
|
||||||
- name: Load br_netfilter
|
- name: Load br_netfilter
|
||||||
community.general.modprobe:
|
community.general.modprobe:
|
||||||
name: br_netfilter
|
name: br_netfilter
|
||||||
state: present
|
state: present
|
||||||
when: ansible_facts.os_family == "RedHat"
|
when: ansible_os_family == "RedHat"
|
||||||
|
|
||||||
- name: Set bridge-nf-call-iptables (just to be sure)
|
- name: Set bridge-nf-call-iptables (just to be sure)
|
||||||
ansible.posix.sysctl:
|
ansible.posix.sysctl:
|
||||||
@@ -103,7 +95,7 @@
|
|||||||
value: "1"
|
value: "1"
|
||||||
state: present
|
state: present
|
||||||
reload: true
|
reload: true
|
||||||
when: ansible_facts.os_family == "RedHat"
|
when: ansible_os_family == "RedHat"
|
||||||
loop:
|
loop:
|
||||||
- net.bridge.bridge-nf-call-iptables
|
- net.bridge.bridge-nf-call-iptables
|
||||||
- net.bridge.bridge-nf-call-ip6tables
|
- net.bridge.bridge-nf-call-ip6tables
|
||||||
@@ -111,10 +103,10 @@
|
|||||||
|
|
||||||
- name: Add /usr/local/bin to sudo secure_path
|
- name: Add /usr/local/bin to sudo secure_path
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
line: Defaults secure_path = {{ secure_path[ansible_facts.os_family] }}
|
line: Defaults secure_path = {{ secure_path[ansible_os_family] }}
|
||||||
regexp: Defaults(\s)*secure_path(\s)*=
|
regexp: Defaults(\s)*secure_path(\s)*=
|
||||||
state: present
|
state: present
|
||||||
insertafter: EOF
|
insertafter: EOF
|
||||||
path: /etc/sudoers
|
path: /etc/sudoers
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when: ansible_facts.os_family in [ "RedHat", "Suse" ]
|
when: ansible_os_family in [ "RedHat", "Suse" ]
|
||||||
@@ -1,27 +1,20 @@
|
|||||||
---
|
---
|
||||||
# The probes below must run in check mode as well. The command module does not
|
|
||||||
# execute under --check, it returns rc 0 with "Command would have run if not in
|
|
||||||
# check mode", which would otherwise mark every host as a Raspberry Pi and make
|
|
||||||
# check runs apply the distro specific tasks. The rc values mean: 0 matched, 1 no
|
|
||||||
# match, 2 the file is absent (no /proc/device-tree on non ARM systems).
|
|
||||||
- name: Test for raspberry pi /proc/cpuinfo
|
- name: Test for raspberry pi /proc/cpuinfo
|
||||||
ansible.builtin.command: grep -E "Raspberry Pi|BCM2708|BCM2709|BCM2835|BCM2836" /proc/cpuinfo
|
ansible.builtin.command: grep -E "Raspberry Pi|BCM2708|BCM2709|BCM2835|BCM2836" /proc/cpuinfo
|
||||||
register: grep_cpuinfo_raspberrypi
|
register: grep_cpuinfo_raspberrypi
|
||||||
failed_when: false
|
failed_when: false
|
||||||
changed_when: false
|
changed_when: false
|
||||||
check_mode: false
|
|
||||||
|
|
||||||
- name: Test for raspberry pi /proc/device-tree/model
|
- name: Test for raspberry pi /proc/device-tree/model
|
||||||
ansible.builtin.command: grep -E "Raspberry Pi" /proc/device-tree/model
|
ansible.builtin.command: grep -E "Raspberry Pi" /proc/device-tree/model
|
||||||
register: grep_device_tree_model_raspberrypi
|
register: grep_device_tree_model_raspberrypi
|
||||||
failed_when: false
|
failed_when: false
|
||||||
changed_when: false
|
changed_when: false
|
||||||
check_mode: false
|
|
||||||
|
|
||||||
- name: Set raspberry_pi fact to true
|
- name: Set raspberry_pi fact to true
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
raspberry_pi: true
|
raspberry_pi: true
|
||||||
when: (grep_cpuinfo_raspberrypi.rc | default(1)) == 0 or (grep_device_tree_model_raspberrypi.rc | default(1)) == 0
|
when: grep_cpuinfo_raspberrypi.rc == 0 or grep_device_tree_model_raspberrypi.rc == 0
|
||||||
|
|
||||||
- name: Set detected_distribution to Raspbian (ARM64 on Raspbian, Debian Buster/Bullseye/Bookworm)
|
- name: Set detected_distribution to Raspbian (ARM64 on Raspbian, Debian Buster/Bullseye/Bookworm)
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -56,8 +49,8 @@
|
|||||||
with_first_found:
|
with_first_found:
|
||||||
- "{{ action_ }}/{{ detected_distribution }}-{{ detected_distribution_major_version }}.yml"
|
- "{{ action_ }}/{{ detected_distribution }}-{{ detected_distribution_major_version }}.yml"
|
||||||
- "{{ action_ }}/{{ detected_distribution }}.yml"
|
- "{{ action_ }}/{{ detected_distribution }}.yml"
|
||||||
- "{{ action_ }}/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
- "{{ action_ }}/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
|
||||||
- "{{ action_ }}/{{ ansible_facts['distribution'] }}.yml"
|
- "{{ action_ }}/{{ ansible_distribution }}.yml"
|
||||||
- "{{ action_ }}/default.yml"
|
- "{{ action_ }}/default.yml"
|
||||||
vars:
|
vars:
|
||||||
action_: >-
|
action_: >-
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
cluster_hostnames: >-
|
cluster_hostnames: >-
|
||||||
{{
|
{{
|
||||||
groups['k3s_cluster']
|
groups['k3s_cluster']
|
||||||
| map('extract', hostvars, ['ansible_facts', 'hostname'])
|
| map('extract', hostvars, 'ansible_hostname')
|
||||||
| list
|
| list
|
||||||
}}
|
}}
|
||||||
run_once: true
|
run_once: true
|
||||||
@@ -77,13 +77,9 @@
|
|||||||
hosts: master
|
hosts: master
|
||||||
environment: "{{ proxy_env | default({}) }}"
|
environment: "{{ proxy_env | default({}) }}"
|
||||||
tasks:
|
tasks:
|
||||||
- name: >-
|
- name: Copying kubeconfig from {{ hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] }}
|
||||||
Copying kubeconfig from
|
|
||||||
{{ hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname'] }}
|
|
||||||
ansible.builtin.fetch:
|
ansible.builtin.fetch:
|
||||||
src: "{{ ansible_facts['user_dir'] }}/.kube/config"
|
src: "{{ ansible_user_dir }}/.kube/config"
|
||||||
dest: ./kubeconfig
|
dest: ./kubeconfig
|
||||||
flat: true
|
flat: true
|
||||||
when: >-
|
when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname']
|
||||||
ansible_facts.hostname ==
|
|
||||||
hostvars[groups[group_name_master | default('master')][0]]['ansible_facts']['hostname']
|
|
||||||
Reference in new issue
Block a user