NAME
Rex::Rancher::Cilium - Cilium CNI installation for Rancher Kubernetes distributions
VERSION
version 0.003
SYNOPSIS
use Rex::Rancher::Cilium;
use JSON::MaybeXS; # JSON()->true below
# Install Cilium on an RKE2 cluster (defaults to version 1.20.0)
install_cilium(
distribution => 'rke2',
);
# Install, upgrade or leave alone -- decided from the Helm release,
# read through the local kubeconfig; with Gateway API and extra values
install_cilium(
distribution => 'rke2',
kubeconfig => "$ENV{HOME}/.kube/mycluster.yaml",
version => '1.20.0',
gateway_api => 1,
gateway_api_version => 'v1.6.1',
helm_values => { hubble => { relay => { enabled => JSON()->true } } },
);
# Install Cilium on a K3s cluster with explicit version
install_cilium(
distribution => 'k3s',
k8s_service_host => '10.0.0.1', # the control plane, not localhost
version => '1.20.0',
cli_version => 'v0.19.7',
);
# Upgrade an existing Cilium installation, keeping what it runs,
# and wait until it is ready again
upgrade_cilium(
distribution => 'rke2',
kubeconfig => "$ENV{HOME}/.kube/mycluster.yaml",
version => '1.20.0',
wait => 1,
);
# Only move the Gateway API CRDs (restarts cilium-operator if applied)
ensure_gateway_api_crds(
kubeconfig => "$ENV{HOME}/.kube/mycluster.yaml",
version => 'v1.6.1',
);
DESCRIPTION
Rex::Rancher::Cilium provides Cilium CNI installation and upgrade for Rancher Kubernetes distributions (RKE2 and K3s). The Cilium CLI runs on the remote server host via SSH; the Helm release state and the Gateway API CRDs are read and written from the local machine via Kubernetes::REST when a local kubeconfig is given.
Prerequisites
The server must already be running with its own CNI switched off in config.yaml, so that Cilium is the only one: on RKE2 cni: none and disable-kube-proxy: true, on K3s flannel-backend: none, disable-network-policy: true, disable-kube-proxy: true and cluster-cidr: 10.42.0.0/16; on both Cilium takes over kube-proxy's role. "install_server" in Rex::Rancher::Server sets these options by default when cilium => 1.
Helm values
Distribution-specific Helm values are written to /tmp/cilium-values-<dist>.yaml:
- RKE2
-
kubeProxyReplacement: true,k8sServiceHost: 127.0.0.1,k8sServicePort: "6443",cni.exclusive: false,operator.replicas: 1,ipam.mode: kubernetes(oripam_mode); withcluster_cidralsoipam.operator.clusterPoolIPv4PodCIDRList: [cluster_cidr], which Cilium uses only incluster-poolmode (ipam_modeorhelm_values; underkubernetesthe pods follow the nodepodCIDRs). - K3s
-
kubeProxyReplacement: true,k8sServiceHost:k8s_service_host,k8sServicePort: "6443",cni.exclusive: true,operator.replicas: 1,ipam.mode: cluster-pool(oripam_mode) withipam.operator.clusterPoolIPv4PodCIDRList: [10.42.0.0/16](K3s'scluster-cidr;cluster_cidrreplaces it).
Both distributions share the same CNI binary/config paths (/opt/cni/bin, /etc/cni/net.d). gateway_api adds gatewayAPI.enabled: true, and helm_values is merged over all of it. --set kubeProxyReplacement=true is also passed on the command line and wins over any value file. With kubeconfig and a running Cilium, its IPAM mode, pool, K3s k8sServiceHost and operator.replicas replace these defaults (see "install_cilium").
Default versions
The module ships with pinned defaults for reproducibility: Cilium 1.20.0 and Cilium CLI v0.19.7, the pair kubernetes-ocp runs. Override with the version and cli_version options. The default version is that of a fresh install: a running Cilium of another minor version keeps its own (see version in "install_cilium"). The Gateway API version has no default; see gateway_api_version (v1.6.1 or newer for Cilium 1.20).
FUNCTIONS
install_cilium(%opts)
Install Cilium CNI on a Rancher Kubernetes cluster (RKE2 or K3s), or bring an existing installation to the requested version and values.
The Cilium CLI binary is downloaded from GitHub to /usr/local/bin/cilium on the remote host (skipped if the correct version is already present). Distribution-appropriate Helm values, merged with helm_values, are written to /tmp/cilium-values-<dist>.yaml and handed to the CLI.
With kubeconfig (a local kubeconfig path), the existing Helm release is read from the cluster via Kubernetes::REST before anything is installed, and the outcome depends on its state:
no release:
cilium install.deployedat the requested version with every requested value already in effect: nothing is done.deployedat another version, or with a requested value differing:cilium upgrade.failedwith an earlier revision stilldeployed(a failed upgrade):cilium upgradeagain.failedorpending-installwith nodeployedrevision (a failed or interrupted first install),uninstallingoruninstalled: the stale release is removed (cilium uninstall, then its Helm release Secrets) and Cilium is installed fresh. No working Cilium exists in these states, so nothing running is taken down. A Secret already gone (404) is fine; any other API error deleting one dies before the install.an upgrade that would change
ipam.mode(the release sets one explicitly and the requested values differ): dies beforecilium upgrade, naming both modes. Cilium cannot switch IPAM mode under running pods; redeploy the cluster, or pass the deployed mode inhelm_values. (With the running configuration below, this only fires when release and ConfigMap disagree.)pending-upgradeorpending-rollback, orpending-installover adeployedrevision: dies before the host is touched, with the message of "upgrade_cilium". An earlier run was interrupted or another is still running, and the deployed revision still carries the pod network; the message names the Secret to delete once no other deploy is running.
What a Cilium on the cluster runs is read first and kept wherever the caller did not ask for something else, before anything touches the host. This does not depend on the release state: a failed or pending-install release, or none at all, can still have left cilium-config and pods behind, and the ConfigMap is what those pods run with, so a reinstall uses it too:
ipam.modeand the pool (ipam.operator.clusterPoolIPv4PodCIDRList) come from the ConfigMapkube-system/cilium-config(ipam,cluster-pool-ipv4-cidr), not from the release values: a Cilium installed without an explicit mode runs the chart defaultcluster-pool, and thekubernetesdefault of the RKE2 values must not switch it (its pods would lose their addresses). A ConfigMap withoutipamcounts ascluster-pool, the agent's default. The default pool gives way to the running one; in any other mode it is dropped.a mode or pool the caller set in
helm_valuesthat differs from the running one dies, naming both: Cilium cannot change either under running pods. The pool is compared only incluster-poolmode, as a set.cluster_cidris the pool of a fresh install, not a requested one: on a runningcluster-poolCilium with another pool the running pool is kept, with a warning naming both.ipam_modeis likewise the mode of a fresh install: on a running Cilium in another mode the running mode (and, incluster-pool, its pool) is kept, with a warning naming both. Onlyipam.modeinhelm_valuescounts as requested and dies on a difference.on K3s without
k8s_service_host(orhelm_values->{k8sServiceHost}),k8sServiceHostis theKUBERNETES_SERVICE_HOSTof the runningciliumDaemonSet.operator.replicaskeeps thespec.replicasof the runningcilium-operatorDeployment (without one, the release's value) instead of the default1, unlesshelm_valuessets it.
Any API error other than a 404 while reading these dies rather than fall back to the defaults.
After a fresh install the cilium DaemonSet must exist, or it dies: the CLI has been seen to exit 0 without creating anything. An API error other than a 404 reading it dies naming that error. With wait, the function returns only once Cilium is ready (see there).
Without kubeconfig the release state cannot be read, and the previous behaviour applies: cilium install runs, and its "cannot re-use a name" error (the release already exists) counts as success, with a warning that version and values were not reconciled. Helm refuses to install over the existing release, so nothing about the running Cilium changes on this path; changing it needs kubeconfig, here or with "upgrade_cilium".
On both distributions kubeProxyReplacement=true is passed to enable Cilium's eBPF-based kube-proxy replacement, so the server config must have switched off the distribution's CNI and kube-proxy: on RKE2 cni: none and disable-kube-proxy: true, on K3s flannel-backend: none, disable-network-policy: true, disable-kube-proxy: true and cluster-cidr: 10.42.0.0/16 ("install_server" in Rex::Rancher::Server writes these). Cilium then needs an API server address that works before any Service does, on every node: on RKE2 that is 127.0.0.1:6443, where servers and agents alike serve it. K3s agents serve it on 127.0.0.1:6444 instead, so on K3s Cilium is given the control plane's own address, k8s_service_host, and dies without one.
Both distributions have been run live through Rex::Rancher. The k3s values are those kubernetes-ocp verified live (k3s v1.36.4+k3s1, Cilium 1.20.0, Gateway API v1.6.1 standard), with the Cilium and CLI versions this module defaults to; Rex::Rancher's k3s path has since brought Cilium up live with k8s_service_host (see Rex::Rancher::Distribution::K3s).
Options:
distribution-
rke2(default) ork3s. version-
Cilium version to install, e.g.
1.20.0. Default:1.20.0.Cilium upgrades and rolls back one minor version at a time. With
kubeconfig, the version a Cilium on the cluster runs (thecilium-agentimage tag of theciliumDaemonSet, else the chart of adeployedrelease) is read before the host is touched:a
versionmore than one minor version from it, up or down, dies naming the minor to go to first;without
version, the default applies only to a fresh install and to a newer patch of the running minor. A running Cilium of an older minor keeps its version, with a warning (passversionto upgrade, one minor at a time); one newer than the default keeps its version too. So a re-run after a Rex::Rancher upgrade never moves a cluster by more than a patch release.
Without
kubeconfignothing is compared, and nothing running changes (see below). Cilium1.18and newer need Linux 5.10 or newer on every node (4.18 on RHEL 8.10); Cilium1.20is tested on Kubernetes 1.33 to 1.36. cli_version-
Cilium CLI version to download, e.g.
v0.19.7. Default:v0.19.7, which supports Cilium 1.16 and newer; an older Cilium needs an older CLI. k8s_service_host-
K3s only, and required there: the control plane address Cilium reaches the API server at on port 6443 from every node (
k8sServiceHost), e.g. the server's IP or a name in its certificate. A loopback address dies, because K3s agents serve the API on127.0.0.1:6444, not 6443.helm_values->{k8sServiceHost}may take its place, and withkubeconfiga running Cilium's own address does (see above); without any of them it dies before the host is touched. "rancher_deploy_server" in Rex::Rancher passes the firsttls_san. Passing it on RKE2 dies: RKE2 uses127.0.0.1, which works on every node there. cluster_cidr-
The cluster's pod network, the server's
cluster_cidr(see "install_server" in Rex::Rancher::Server): one IPv4 CIDR, anything else dies before the host is touched. Written as Cilium's pool,ipam.operator.clusterPoolIPv4PodCIDRList, unlesshelm_valuessets one. It takes effect only incluster-poolmode: on K3s, in place of10.42.0.0/16, and on RKE2 withipam_mode => 'cluster-pool'(or that mode inhelm_values). RKE2's default staysipam.mode: kubernetes, where Cilium ignores the pool value and pods get addresses from the nodepodCIDRs the cluster cuts from itscluster-cidr-- the same range, reached through the server'sconfig.yaml. It is the pool of a fresh install: a runningcluster-poolCilium keeps its own pool, with a warning when it differs (see above). "rancher_deploy_server" in Rex::Rancher passes its owncluster_cidr. ipam_mode-
Cilium's IPAM mode (
ipam.mode) on a fresh install:kubernetesorcluster-pool, anything else dies before the host is touched (other modes need more than a mode and stay withhelm_values). Default: "default_ipam_mode" in Rex::Rancher::Distribution,kuberneteson RKE2 andcluster-poolon K3s. Withcluster-pool,cluster_cidris the pool on RKE2 too. A Cilium already running (kube-system/cilium-configreadable throughkubeconfig) keeps its own mode, with a warning when it differs (see above); withoutkubeconfignothing running is changed anyway. Anipam.modeinhelm_valuesthat differs from it dies as contradictory. "rancher_deploy_server" in Rex::Rancher passes its ownipam_mode. api_server-
Kubernetes API server URL, passed as
--api-serverto the CLI. Optional; the CLI uses the kubeconfig's server address if omitted. kubeconfig-
Local path to the cluster kubeconfig (as saved by "rancher_deploy_server" in Rex::Rancher). Enables the release-state handling and the running-configuration reads above and is required for
gateway_apiandwait. Optional. wait-
If true, wait after install, upgrade or no-op until Cilium is ready: the
ciliumDaemonSet has rolled out its current generation to every node it schedules on and all those pods are ready, and everycilium-operatorreplica is updated and ready. Requireskubeconfig; read from the local machine. Dies afterwait_durationnaming the last state (pods ready and updated of each); a missing DaemonSet or Deployment is waited out, any other API error (no access, no connection) dies at once. Default: off, which only checks that the DaemonSet exists after a fresh install. wait_duration-
Seconds
waitwaits, a whole number above 0. Default:600. Only used withwait. helm_values-
Hashref of additional Helm values, deep-merged over the defaults (hashes merge key by key, anything else replaces the default). Optional.
gateway_api-
If true, apply the Gateway API CRDs before Cilium and set
gatewayAPI.enabled: true. Default: off. Requireskubeconfigandgateway_api_version. The CRDs are fetched from the kubernetes-sigs/gateway-api GitHub release on the machine running Rex and applied through Kubernetes::REST (nokubectl). They are skipped when the cluster already carries that bundle version and channel; when they are applied to a cluster with a runningcilium-operator, the operator is restarted so it picks up the new CRDs. Only a 404 counts as missing: an API error reading the probe CRD (gateways.gateway.networking.k8s.io), a CRD waited on to become Established, or thecilium-operatorDeployment dies at once instead of applying, waiting out 30s, or skipping the restart.RKE2 v1.37+ ships the same CRDs as its own chart,
rke2-gateway-api-crd, which would overwrite them. "rancher_deploy_server" in Rex::Rancher disables it for you; calling this directly, put it ininstall_server'sdisable. While that chart's Helm release exists this dies before applying anything, naming the way out: disable the chart and restartrke2-server(Helm uninstalls it but keeps itsgateway.networking.k8s.ioCRDs, so Gateways and routes survive), or use RKE2's CRDs viahelm_valueswithoutgateway_api. gateway_api_version-
Gateway API release to apply, e.g.
v1.6.1. It must match what the Ciliumversionsupports; there is no default because the two are version-locked. Cilium 1.20 and newer needv1.6.1or newer (TLSRouteandBackendTLSPolicyat v1); an older one with such a Cilium dies before the host is touched -- for a pinnedversionalready in option checks, for the default once it is known whether a running Cilium keeps an older version. gateway_api_channel-
experimental(default) orstandard. What Cilium needs depends on both versions. Cilium up to 1.16 requiresTLSRoutev1alpha2, which only the experimental channel carries. Cilium 1.17 to 1.19 require standard-channel CRDs only and handleTLSRoutev1alpha2 when it is there; before Gateway API v1.5 the standard channel has noTLSRoute, sostandardcosts TLS passthrough. Gateway API v1.5 movedTLSRoute(as v1) into the standard channel, v1.6 alsoTCPRouteandUDPRoute; Cilium 1.20 requiresTLSRoutev1 andBackendTLSPolicyv1, which the v1.5+ standard channel carries. The default staysexperimental: it is the channel every Cilium version works with, and it keepsTLSRoutev1alpha2 objects of an older Cilium readable (the v1.6 standardTLSRouteCRD drops that version, and such objects disappear).Gateway API v1.5+ ships an admission policy that refuses experimental CRDs on top of standard ones: a cluster that started on
standardcannot move toexperimental.
upgrade_cilium(%opts)
Upgrade an existing Cilium installation to a new version using cilium upgrade, unconditionally. The Cilium CLI is updated first if needed. The same Helm values generation logic as "install_cilium" is used, and gateway_api applies the CRDs the same way (restarting a running cilium-operator when they were applied).
kubeconfig is required: without it the function dies before anything touches the host. It does the same, pointing to "install_cilium", when the Helm release cilium in kube-system has no deployed revision: cilium upgrade is a Helm upgrade and cannot install, so a ConfigMap kube-system/cilium-config or a cilium DaemonSet without a deployed release is not enough. A release left failed, pending-install, uninstalling or uninstalled without one is what "install_cilium" removes and installs again. A release stuck in pending-upgrade or pending-rollback (or pending-install over a deployed revision) dies before the host as well, with the message of "install_cilium" naming the Secret to delete. A failed upgrade over a deployed revision is upgraded again. The running configuration is read and kept exactly as in "install_cilium" (IPAM mode and pool from kube-system/cilium-config, K3s k8sServiceHost from the DaemonSet, operator.replicas from the cilium-operator Deployment), so an upgrade needs no values the caller has to look up first, and a change of IPAM mode or pool asked for in helm_values dies before the host is touched. The version follows the same one-minor-at-a-time rule as there: pass version to upgrade to a new minor. Without it, a running Cilium of an older minor keeps its version (with a warning) and cilium upgrade only re-applies the values. Without the API none of that can be checked, and the generated values would be applied as they stand -- on RKE2 the default ipam.mode: kubernetes switches a cluster-pool cluster and its pods lose their addresses.
Options are the same as "install_cilium", wait and wait_duration included, except that kubeconfig is not optional.
upgrade_cilium(
distribution => 'rke2',
kubeconfig => "$ENV{HOME}/.kube/mycluster.yaml",
version => '1.20.0',
);
ensure_gateway_api_crds(%opts)
Apply the Gateway API CRDs of one bundle version and channel, the same way gateway_api does in "install_cilium", without touching Cilium's Helm release: for a Gateway API pin that moved while Cilium did not. Everything runs from the local machine through Kubernetes::REST; the remote host is not used.
The CRDs are skipped when the cluster already carries that bundle version and channel. When they are applied and a cilium-operator Deployment exists, it is restarted so it picks up the new CRDs; API errors other than a 404 die, as with gateway_api. While RKE2's rke2-gateway-api-crd Helm release exists this dies before applying anything, as gateway_api does. Returns 1 when the CRDs were applied, 0 when they were already current.
Options:
kubeconfig-
Local path to the cluster kubeconfig. Required.
version-
Gateway API release, e.g.
v1.2.0. Required; it must match what the running Cilium supports. channel-
experimental(default) orstandard; seegateway_api_channelin "install_cilium".
ensure_gateway_api_crds(
kubeconfig => "$ENV{HOME}/.kube/mycluster.yaml",
version => 'v1.2.0',
channel => 'standard',
);
validate_cilium_opts(%opts)
Check the options of "install_cilium" without touching anything: dies with the same message install_cilium would for an unknown distribution, bad helm_values, cluster_cidr, ipam_mode or gateway_api settings, and -- also with kubeconfig, where install_cilium could still read it from a running Cilium -- a K3s cluster without a usable k8s_service_host. Returns 1. Not exported; "rancher_deploy_server" in Rex::Rancher calls it as Rex::Rancher::Cilium::validate_cilium_opts before the node is prepared.
SEE ALSO
Rex::Rancher, Rex::Rancher::Server, Rex, https://docs.cilium.io/
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/rex-rancher/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.