forked from tim/k3s-ansible
287d8b7a27
* feat(kube-vip): add endpoint override for the internal listening address Add a kube_vip_endpoint variable so the address kube-vip binds and listens on can differ from the announced apiserver_endpoint. This is useful for complex routing and site-to-site tunnels where the VIP kube-vip advertises over ARP differs from the address it listens on internally. - roles/k3s_server/templates/vip.yaml.j2: use kube_vip_endpoint (defaulting to apiserver_endpoint) for the `address` env and for deriving `vip_subnet` - roles/k3s_server/defaults/main.yml: add kube_vip_endpoint default (null) - roles/k3s_server/meta/main.yml: add kube_vip_endpoint argument_spec - inventory/sample/group_vars/all.yml: document the new sample variable - README.md: document the kube_vip_endpoint option - .github/scripts/test-kube-vip-manifest.py: extend regression test to cover the default (apiserver_endpoint) and the override case Closes #221 * chore(ci): extend molecule job timeout to 3 hours The default scenario occasionally takes longer than 150 minutes on the shared nested-virt runner (k3s agent notify-wait can exceed the limit under load), and a single timeout aborts the whole run before the other four scenarios execute. Raise timeout-minutes from 150 to 180 so a slow-but-progressing run completes instead of aborting. The default scenario remains first in the matrix so a failure surfaces fastest. * fix(kube-vip): fall back on null kube_vip_endpoint and cover it in the test - vip_subnet and address use default(apiserver_endpoint, true) so the null role default falls back to the apiserver endpoint instead of rendering an empty/invalid address and subnet - change the manifest regression test default case to pass kube_vip_endpoint as None so it pins the real runtime null condition and fails fast on this regression rather than timing out in CI
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""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
|
|
`bgppeers` and `vip_cidr` names. This test proves the rendered manifest uses
|
|
the keys the target image actually parses.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
import subprocess
|
|
|
|
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
|
|
|
|
|
def repo_root():
|
|
return subprocess.check_output(
|
|
["git", "rev-parse", "--show-toplevel"], text=True
|
|
).strip()
|
|
|
|
|
|
def fail(message):
|
|
raise SystemExit("kube-vip manifest test failed: " + message)
|
|
|
|
|
|
def fake_ipsubnet(value):
|
|
# ansible.utils.ipsubnet -> network of the address as x.y.z.0/24
|
|
parts = value.split(".")
|
|
return ".".join(parts[:3]) + ".0/24"
|
|
|
|
|
|
def fake_ipaddr(_value, expr=None):
|
|
# ansible.utils.ipaddr('prefix') -> prefix length
|
|
return "24"
|
|
|
|
|
|
def fake_bool(value):
|
|
# Minimal stand-in for Ansible's truthiness filter used by the template.
|
|
if isinstance(value, bool):
|
|
return value
|
|
return str(value).lower() in ("1", "true", "yes", "on")
|
|
|
|
|
|
def fake_map(seq, *args, **kwargs):
|
|
# Minimal stand-in for Ansible's map() filter in the two forms used by the
|
|
# template: map(attribute='x') on a list of dicts, and map('join', sep) on
|
|
# a list of sequences.
|
|
if "attribute" in kwargs:
|
|
return [item[kwargs["attribute"]] for item in seq]
|
|
if kwargs:
|
|
# e.g. map(default='x') not used here; ignore unknown kwargs.
|
|
return list(seq)
|
|
if args:
|
|
filter_name = args[0]
|
|
sep = args[1] if len(args) > 1 else ""
|
|
if filter_name == "join":
|
|
return [sep.join(str(x) for x in item) for item in seq]
|
|
return list(seq)
|
|
|
|
|
|
def fake_zip(*seqs):
|
|
return list(zip(*seqs))
|
|
|
|
|
|
def render(env, extra_vars):
|
|
base_vars = {
|
|
"apiserver_endpoint": "192.168.30.222",
|
|
"kube_vip_iface": "",
|
|
"kube_vip_arp": True,
|
|
"kube_vip_bgp": True,
|
|
"kube_vip_bgp_routerid": "127.0.0.1",
|
|
"_kube_vip_bgp_peers": [
|
|
{"peer_address": "192.168.30.1", "peer_asn": "64512"},
|
|
{"peer_address": "192.168.30.2", "peer_asn": "64513"},
|
|
],
|
|
"kube_vip_tag_version": "v1.2.2",
|
|
}
|
|
base_vars.update(extra_vars)
|
|
template = env.get_template("vip.yaml.j2")
|
|
return template.render(**base_vars)
|
|
|
|
|
|
def main():
|
|
root = repo_root()
|
|
template_dir = os.path.join(root, "roles", "k3s_server", "templates")
|
|
env = Environment(
|
|
loader=FileSystemLoader(template_dir), undefined=StrictUndefined
|
|
)
|
|
env.filters["ansible.utils.ipsubnet"] = fake_ipsubnet
|
|
env.filters["ansible.utils.ipaddr"] = fake_ipaddr
|
|
env.filters["bool"] = fake_bool
|
|
env.filters["map"] = fake_map
|
|
env.filters["zip"] = fake_zip
|
|
|
|
# Multi-peer BGP armed: must emit bgp_peers, never bgppeers.
|
|
output = render(env, {})
|
|
if "name: bgp_peers" not in output:
|
|
fail("rendered manifest is missing bgp_peers")
|
|
if "name: bgppeers" in output:
|
|
fail("rendered manifest still uses the ignored bgppeers key")
|
|
if "name: vip_subnet" not in output:
|
|
fail("rendered manifest is missing vip_subnet")
|
|
if "name: vip_cidr" in output:
|
|
fail("rendered manifest still uses the ignored vip_cidr key")
|
|
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")
|
|
if "ghcr.io/kube-vip/kube-vip:v1.2.2" not in output:
|
|
fail("kube-vip image tag is not v1.2.2")
|
|
|
|
# BGP enabled with no merged peers: single-peer fallback vars, no bgp_peers.
|
|
output = render(
|
|
env,
|
|
{
|
|
"_kube_vip_bgp_peers": [],
|
|
"kube_vip_bgp_as": "64513",
|
|
"kube_vip_bgp_peeraddress": "192.168.30.1",
|
|
"kube_vip_bgp_peeras": "64512",
|
|
},
|
|
)
|
|
if "name: bgp_as" not in output:
|
|
fail("single-peer bgp_as was not rendered")
|
|
if "name: bgp_peers" in output:
|
|
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")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|