Files
puppet-prod/hieradata/roles/infra/k8s.yaml
T
unkinben 10854b6501 fix: preload correct rke2 airgap images to break CNI bootstrap deadlock (#512)
## Why

Four newly-provisioned el9_8 compute nodes (prodnxsr0014/0015/0018/0019) hang with canal's kube-flannel container in `ImagePullBackOff`: the flannel VXLAN overlay never comes up, so the node can't reach any in-cluster `198.18.200.x` LoadBalancer VIP.

Root cause is a stale airgap-preload version. The nodes boot rke2 **v1.33.11+rke2r1** from the rolling `latest/1.33` repo, whose canal manifest requests `hardened-flannel:v0.28.4` / `hardened-calico:v3.31.5` (build20260415). But `rke2::install` pinned the preloaded bundle to **v1.33.4+rke2r1** (flannel v0.27.2 / calico v3.30.2), so those images were never on disk. containerd then falls back to the `docker.io` mirror (artifactapi, `disable-default-registry-endpoint: true`), reachable only via the pod-overlay VIP that requires the very flannel image being pulled — a bootstrap deadlock. Older nodes (0001-0008) are versionlocked at 1.33.4 and still match their original bundle, so they were unaffected.

## Changes

- Bump pinned `rke2_version` `1.33.4` -> `1.33.11` so the versionlock, RPM `ensure`, and preloaded bundle all line up with the canal image tags the running binary requests. The default `rke2-images.linux-amd64.tar.zst` bundle already contains the canal CNI images (it is RKE2's default CNI), so no extra tarball is needed.
- Wire the airgap archive `source` to the `container_archive_source` class parameter (previously declared in the module but never consumed). The module keeps its generic upstream default; the artifactapi override (the pre-CNI-reachable source, same BGP/physical path the rke2 yum repos already use) lives in the k8s role hiera as `rke2::container_archive_source`.

Applies to servers and agents alike (`rke2::install` runs for both) and preloads `before => Service`, so the bundle lands before rke2 starts.

Notes:
- The `latest/1.33` repo is rolling, so the pinned version must be maintained as the repo advances; a follow-up to pin the yum channel to a fixed patch would remove the drift entirely.
- No terraform-artifactapi change is required. (If a canal-only preload were ever wanted, the github generic remote allowlist would need `rancher/rke2/.*/rke2-images-canal.linux-amd64.tar.zst$` added — but the default bundle already carries those images, so it is unnecessary.)

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Reviewed-on: #512
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-08 18:18:55 +10:00

209 lines
5.6 KiB
YAML

---
hiera_include:
- profiles::selinux::setenforce
- profiles::ceph::node
- profiles::ceph::osd
- profiles::ceph::client
- exporters::frr_exporter
- frrouting
- rke2
# manage rke2
rke2::bootstrap_node: prodnxsr0001.main.unkin.net
rke2::join_url: https://join-k8s.service.consul:9345
# pull the airgap image bundle via artifactapi (host-reachable pre-CNI), not github directly
rke2::container_archive_source: 'https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/github/rancher/rke2/releases/download'
rke2::manage_registries: true
rke2::registries:
docker.io:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "dockerhub/$1"
disable-default-registry-endpoint: true
ghcr.io:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "ghcr/$1"
disable-default-registry-endpoint: true
quay.io:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "quay/$1"
disable-default-registry-endpoint: true
registry.k8s.io:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "k8s-registry/$1"
disable-default-registry-endpoint: true
registry.gitlab.com:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "gitlab/$1"
disable-default-registry-endpoint: true
docker.elastic.co:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "elastic/$1"
disable-default-registry-endpoint: true
gcr.io:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "gcr/$1"
disable-default-registry-endpoint: true
docker.litellm.ai:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "litellm/$1"
disable-default-registry-endpoint: true
public.ecr.aws:
endpoint:
- "https://artifactapi.k8s.syd1.au.unkin.net"
rewrite:
"^(.*)$": "ecr-public/$1"
disable-default-registry-endpoint: true
rke2::config_hash:
bind-address: "%{hiera('networking_loopback0_ip')}"
node-ip: "%{hiera('networking_loopback0_ip')}"
node-external-ip: "%{hiera('networking_loopback0_ip')}"
write-kubeconfig-mode: 644
kubelet-arg:
- '--node-status-update-frequency=4s'
- '--max-pods=100'
node-label:
- "region=%{facts.region}"
- "country=%{facts.country}"
- "asset=%{facts.dmi.product.serial_number}"
- "zone=%{zone}"
# FIXME: puppet-python wants to try manage python-dev, which is required by the ceph package
python::manage_dev_package: false
profiles::packages::include:
bridge-utils: {}
cephadm: {}
profiles::selinux::setenforce::mode: disabled
profiles::ceph::client::manage_ceph_conf: false
profiles::ceph::client::manage_ceph_package: false
profiles::ceph::client::manage_ceph_paths: false
profiles::ceph::client::fsid: 'de96a98f-3d23-465a-a899-86d3d67edab8'
profiles::ceph::client::mons:
- 198.18.23.9
- 198.18.23.10
- 198.18.23.11
- 198.18.23.12
- 198.18.23.13
# additional repos
profiles::yum::global::repos:
ceph:
ensure: present
ceph-noarch:
ensure: present
frr-extras:
ensure: present
frr-stable:
ensure: present
rancher-rke2-common-latest:
ensure: present
rancher-rke2-1-33-latest:
ensure: present
# dns
profiles::dns::base::primary_interface: loopback0
# networking
systemd::manage_networkd: true
systemd::manage_all_network_files: true
networking::interfaces:
"%{hiera('networking_1000_iface')}":
type: physical
ipaddress: "%{hiera('networking_1000_ip')}"
gateway: 198.18.15.254
txqueuelen: 10000
forwarding: true
"%{hiera('networking_2500_iface')}":
type: physical
ipaddress: "%{hiera('networking_2500_ip')}"
mtu: 1500
txqueuelen: 10000
forwarding: true
loopback0:
type: dummy
ipaddress: "%{hiera('networking_loopback0_ip')}"
netmask: 255.255.255.255
mtu: 1500
loopback1:
type: dummy
ipaddress: "%{hiera('networking_loopback1_ip')}"
netmask: 255.255.255.255
mtu: 1500
loopback2:
type: dummy
ipaddress: "%{hiera('networking_loopback2_ip')}"
netmask: 255.255.255.255
mtu: 1500
# configure consul service
profiles::consul::client::host_addr: "%{hiera('networking_loopback0_ip')}"
profiles::consul::client::node_rules:
- resource: service
segment: frr_exporter
disposition: write
# frrouting
exporters::frr_exporter::enable: true
frrouting::ospfd_router_id: "%{hiera('networking_loopback0_ip')}"
frrouting::ospf_preferred_source_enable: true
frrouting::ospf_preferred_source: "%{hiera('networking_loopback0_ip')}"
frrouting::ospfd_redistribute:
- connected
frrouting::ospfd_interfaces:
"%{hiera('networking_1000_iface')}":
area: 0.0.0.0
"%{hiera('networking_2500_iface')}":
area: 0.0.0.0
loopback0:
area: 0.0.0.0
loopback1:
area: 0.0.0.0
loopback2:
area: 0.0.0.0
frrouting::daemons:
ospfd: true
frrouting::ospf_exclude_k8s_enable: true
frrouting::k8s_cluster_cidr: '10.42.0.0/16' # RKE2 cluster-cidr (pods)
frrouting::k8s_service_cidr: '10.43.0.0/16' # RKE2 service-cidr
# sysctl recommendations
sysctl::base::values:
net.ipv4.conf.default.rp_filter:
value: '0'
net.ipv4.conf.all.rp_filter:
value: '0'
fs.inotify.max_user_watches:
value: '524288'
fs.inotify.max_user_instances:
value: '512'
# add loopback interfaces to ssh list
ssh::server::options:
ListenAddress:
- "%{hiera('networking_loopback0_ip')}"
- "%{hiera('networking_1000_ip')}"
- "%{hiera('networking_2500_ip')}"
profiles::ssh::sign::principals:
- "%{hiera('networking_loopback0_ip')}"
- "%{hiera('networking_1000_ip')}"
- "%{hiera('networking_2500_ip')}"