mirror of
https://github.com/techno-tim/k3s-ansible.git
synced 2026-10-23 20:22:03 +02:00
Compare commits
2
Commits
db85fa960c
...
f5483cdabe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5483cdabe | ||
|
|
88159b3875 |
No files matched your search
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Assert the sample inventory resolves the flannel interface per host.
|
||||||
|
|
||||||
|
flannel_iface defaults to the host's default IPv4 interface rather than a
|
||||||
|
hardcoded eth0. This test extracts the flannel_iface expression from the
|
||||||
|
sample inventory and proves that a host whose primary interface is not named
|
||||||
|
eth0 (e.g. enp1s0, ens3) resolves the interface from ansible facts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from jinja2 import Environment, StrictUndefined
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root():
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "rev-parse", "--show-toplevel"], text=True
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
raise SystemExit("default-interface test failed: " + message)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeAnsibleFacts(object):
|
||||||
|
"""Stand-in for the per-host ``ansible_facts`` dict."""
|
||||||
|
|
||||||
|
def __init__(self, default_iface, iface_ip):
|
||||||
|
self._default = {"interface": default_iface, "address": iface_ip}
|
||||||
|
self._ifaces = {
|
||||||
|
default_iface: {"ipv4": {"address": iface_ip}},
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_ipv4(self):
|
||||||
|
return self._default
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._ifaces[key]
|
||||||
|
|
||||||
|
|
||||||
|
def read_all_yml(root):
|
||||||
|
path = os.path.join(root, "inventory", "sample", "group_vars", "all.yml")
|
||||||
|
with open(path, "r") as handle:
|
||||||
|
return handle.read()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_value(content, key):
|
||||||
|
# Match a quoted value assigned to the key, e.g. flannel_iface: "...".
|
||||||
|
match = re.search(r"^%s:\s*\"(.+)\"\s*$" % re.escape(key), content, re.M)
|
||||||
|
if not match:
|
||||||
|
fail("could not find %s in the sample inventory" % key)
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve(env, expression, facts):
|
||||||
|
template = env.from_string(expression)
|
||||||
|
return template.render(ansible_facts=facts)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = repo_root()
|
||||||
|
content = read_all_yml(root)
|
||||||
|
env = Environment(undefined=StrictUndefined)
|
||||||
|
|
||||||
|
flannel_expr = extract_value(content, "flannel_iface")
|
||||||
|
if "default_ipv4.interface" not in flannel_expr:
|
||||||
|
fail("flannel_iface no longer defaults from ansible facts")
|
||||||
|
|
||||||
|
# A host whose primary interface is enp1s0 (the core #621 scenario).
|
||||||
|
facts = FakeAnsibleFacts("enp1s0", "192.168.30.11")
|
||||||
|
resolved = resolve(env, flannel_expr, facts)
|
||||||
|
if resolved != "enp1s0":
|
||||||
|
fail("flannel_iface resolved to %r, expected enp1s0" % resolved)
|
||||||
|
|
||||||
|
# A different host with a different interface must resolve independently.
|
||||||
|
facts2 = FakeAnsibleFacts("ens3", "192.168.30.12")
|
||||||
|
resolved2 = resolve(env, flannel_expr, facts2)
|
||||||
|
if resolved2 != "ens3":
|
||||||
|
fail("flannel_iface resolved to %r, expected ens3" % resolved2)
|
||||||
|
|
||||||
|
print("default-interface regression test passed")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -84,3 +84,11 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: ^roles/k3s_server/tasks/metallb\.yml$|^\.github/scripts/test-metallb-remote-read\.sh$
|
files: ^roles/k3s_server/tasks/metallb\.yml$|^\.github/scripts/test-metallb-remote-read\.sh$
|
||||||
|
- id: default-interface-test
|
||||||
|
name: default interface test
|
||||||
|
entry: python3 .github/scripts/test-default-interface.py
|
||||||
|
language: python
|
||||||
|
additional_dependencies:
|
||||||
|
- Jinja2>=3.1
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^inventory/sample/group_vars/all\.yml$|^\.github/scripts/test-default-interface\.py$
|
||||||
@@ -8,16 +8,18 @@ systemd_dir: /etc/systemd/system
|
|||||||
system_timezone: Your/Timezone
|
system_timezone: Your/Timezone
|
||||||
|
|
||||||
# interface which will be used for flannel
|
# interface which will be used for flannel
|
||||||
flannel_iface: eth0
|
# 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.
|
||||||
|
flannel_iface: "{{ ansible_facts.default_ipv4.interface }}"
|
||||||
|
|
||||||
# uncomment calico_iface to use tigera operator/calico cni instead of flannel https://docs.tigera.io/calico/latest/about
|
# uncomment calico_iface to use tigera operator/calico cni instead of flannel https://docs.tigera.io/calico/latest/about
|
||||||
# calico_iface: "eth0"
|
# calico_iface: "{{ ansible_facts.default_ipv4.interface }}"
|
||||||
calico_ebpf: false # use eBPF dataplane instead of iptables
|
calico_ebpf: false # use eBPF dataplane instead of iptables
|
||||||
calico_tag: v3.32.1 # calico version tag
|
calico_tag: v3.32.1 # calico version tag
|
||||||
|
|
||||||
# uncomment cilium_iface to use cilium cni instead of flannel or calico
|
# uncomment cilium_iface to use cilium cni instead of flannel or calico
|
||||||
# ensure v4.19.57, v5.1.16, v5.2.0 or more recent kernel
|
# ensure v4.19.57, v5.1.16, v5.2.0 or more recent kernel
|
||||||
# cilium_iface: "eth0"
|
# cilium_iface: "{{ ansible_facts.default_ipv4.interface }}"
|
||||||
cilium_mode: native # native when nodes are on the same subnet or use BGP, otherwise set tunnel
|
cilium_mode: native # native when nodes are on the same subnet or use BGP, otherwise set tunnel
|
||||||
cilium_tag: v1.20.0 # cilium version tag
|
cilium_tag: v1.20.0 # cilium version tag
|
||||||
cilium_cli_tag: v0.19.7 # cilium cli version tag
|
cilium_cli_tag: v0.19.7 # cilium cli version tag
|
||||||
|
|||||||
@@ -38,6 +38,30 @@
|
|||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.metadata.name }} ready"
|
label: "{{ item.metadata.name }} ready"
|
||||||
|
|
||||||
|
- name: Assert every node registered a node IP from its interface
|
||||||
|
# Each k3s node is launched with --node-ip derived from flannel_iface.
|
||||||
|
# Confirm every node carries a real InternalIP (not a loopback), which
|
||||||
|
# proves k3s bound to the cluster interface rather than defaulting to 127.0.0.1.
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: >-
|
||||||
|
(node_internal_ips | length) >= 1 and
|
||||||
|
(node_internal_ips | reject('eq', '127.0.0.1') | list | length) == node_internal_ips | length
|
||||||
|
success_msg: "{{ item.metadata.name }} is bound to {{ node_internal_ips | join(', ') }}"
|
||||||
|
fail_msg: >-
|
||||||
|
{{ item.metadata.name }} has no non-loopback InternalIP
|
||||||
|
(got: {{ node_internal_ips | join(', ') }})
|
||||||
|
vars:
|
||||||
|
node_internal_ips: >-
|
||||||
|
{{
|
||||||
|
(item.status.addresses | default([]))
|
||||||
|
| selectattr('type', 'equalto', 'InternalIP')
|
||||||
|
| map(attribute='address')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
loop: "{{ verify_nodes.resources }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.metadata.name }} InternalIP"
|
||||||
|
|
||||||
- name: Get any Calico namespaces with Flannel enabled
|
- name: Get any Calico namespaces with Flannel enabled
|
||||||
kubernetes.core.k8s_info:
|
kubernetes.core.k8s_info:
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
|
|||||||
@@ -33,6 +33,27 @@
|
|||||||
reload: true
|
reload: true
|
||||||
tags: sysctl
|
tags: sysctl
|
||||||
|
|
||||||
|
- name: Check if br_netfilter module exists
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
find /lib/modules/$(uname -r) -name "br_netfilter.ko*" | wc -l
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
register: br_netfilter_exists
|
||||||
|
changed_when: false
|
||||||
|
when: ansible_os_family == "RedHat"
|
||||||
|
tags: sysctl
|
||||||
|
|
||||||
|
- name: Install kernel-modules-extra if br_netfilter missing
|
||||||
|
ansible.builtin.yum:
|
||||||
|
name: kernel-modules-extra
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
register: kernel_modules_installed
|
||||||
|
when:
|
||||||
|
- ansible_os_family == "RedHat"
|
||||||
|
- br_netfilter_exists.stdout | int == 0
|
||||||
|
|
||||||
- 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
|
||||||
|
|||||||
Reference in new issue
Block a user