Skip to content

Support bootstrapping CNI configs from a template file - #2126

Open
twz123 wants to merge 9 commits into
cloudnativelabs:masterfrom
twz123:cni-config-template
Open

Support bootstrapping CNI configs from a template file#2126
twz123 wants to merge 9 commits into
cloudnativelabs:masterfrom
twz123:cni-config-template

Conversation

@twz123

@twz123 twz123 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

feature

What this PR does / why we need it:

A CNI configuration file that's copied into the CNI configuration directory by an init container is already visible to the container runtime before kube-router has completed it, so on a freshly booted node, pod sandbox creation may transiently fail with errors like "no IP ranges specified" until kube-router has filled in the missing parts.

To avoid this, introduce CNI configuration bootstrapping via a template file. If the KUBE_ROUTER_CNI_CONF_TEMPLATE_FILE environment variable is not empty, kube-router reads the config from the template instead, and writes the completed config to the usual CNI config file path, which doesn't need to exist beforehand. The template file's content is interpreted according to the CNI config file name. The template's own file name has no significance.

As a result, the container runtime won't pick up an incomplete configuration and try to set up pod networking too early. Since the config is re-derived from the pristine template on every start, the written config is wholly owned by kube-router.

Write this up in the user guide and add another example DaemonSet.

The PR can be reviewed commit by commit:

  • Quite some test refactoring commits,
  • one fix that hardens against panics for some more pathological CNI config inputs (there's still a gap around ipam fields that are null, IIRC),
  • and the final feature commit.

Which issue(s) this PR is related to:

The problem itself should dissolve as soon as the correct CNI config landed on disk. However, this is very reliably triggering
containernetworking/plugins#1265, especially in air-gapped scenarios when there's no images to be pulled and the container runtime can start pods right away.

Was AI used during the creation of this PR?

Yes, for design discussions, patch series review, and the initial draft of the changes to the user guide.

What, if any, amount of integration testing was done with this change in a Kubernetes environment?

Unit tests so far. I plan to give this a spin in the k0s CI, of course.

Does this PR introduce a breaking change?

No.

NONE

Anything else the reviewer should know that wasn't already covered?

I deliberately didn't try to do some write-temprary-file-then-rename dance to make the config file appear atomically. The time it takes to write the file is negligible, probably microseconds in practice. Getting this atomic thing right, along with all its edge cases, warrants its own library. (And, no, I don't think renameio gets this right, either.)

twz123 added 9 commits July 29, 2026 15:20
…ig test

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
This couples the content to its type, and eliminates quite a few extra
fields from the test tables.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
The check if the config needs to be parsed as conflist or as conf can be
done once during struct initialization. The struct can then simply check
if the confList field is non-nil. That's an implementation detail to
support (un-)marshalling and shouldn't be relevant to struct users.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
The tests were asserting the error messages only, so no need to wrap
them into errors. Also, remove the messages completely from those table
tests in which they were nil for every contained test case.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
The different test cases assert on an expected set of subnets, but
all used their own variants of pulling those subnets out of the
CNINetworkConfig struct.

Consolidate this by replacing those assertions with a one that checks
for the correctness of the keys returned by getPodCIDRsMapFromCNISpec.
That is much more compact, and has the same outcome.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
Those would've caused a panic later on. Instead of looking up the bridge
plugin config from the conflist on the fly, do it when the struct is
initialized, and store the pointer in the conf field. This allows for
the removal of getBridgePlugin.

Also harden the config parsing against some null JSON fields.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
A CNI configuration file that's copied into the CNI configuration
directory by an init container is already visible to the container
runtime before kube-router has completed it, so on a freshly booted
node, pod sandbox creation may transiently fail with errors like "no IP
ranges specified" until kube-router has filled in the missing parts.

To avoid this, introduce CNI configuration bootstrapping via a template
file. If the KUBE_ROUTER_CNI_CONF_TEMPLATE_FILE environment variable
is not empty, kube-router reads the config from the template instead,
and writes the completed config to the usual CNI config file path,
which doesn't need to exist beforehand. The template file's content is
interpreted according to the CNI config file name. The template's own
file name has no significance.

As a result, the container runtime won't pick up an incomplete
configuration and try to set up pod networking too early. Since the
config is re-derived from the pristine template on every start, the
written config is wholly owned by kube-router.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Greptile Summary

Adds template-based CNI configuration bootstrapping so kube-router can publish a completed configuration without first exposing an incomplete file to the container runtime.

  • Reads CNI configuration from an optional KUBE_ROUTER_CNI_CONF_TEMPLATE_FILE while continuing to write to the configured CNI destination.
  • Selects and validates the bridge plugin in conflists and improves handling of null or missing plugin entries.
  • Adds unit coverage, user-guide documentation, and an example DaemonSet using a ConfigMap-mounted template.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code defect identified.

The template path remains distinct from the destination path, parsing follows the destination filename as documented, bridge configuration updates are retained in the complete object written to disk, and the example manifest follows the repository’s established kube-router deployment requirements.

Important Files Changed

Filename Overview
pkg/utils/cni.go Adds separate template input and destination paths, validates conflist bridge selection, and preserves the selected configuration when writing.
pkg/controllers/routing/network_routes_controller.go Wires the template environment variable into controller initialization and permits a previously absent destination file when a template is configured.
pkg/utils/cni_test.go Refactors CNI fixtures and extends coverage for templates, null conflists, null plugins, missing bridge plugins, and destination writes.
daemonset/generic-kuberouter-cni-conf-template.yaml Adds an example deployment that mounts an incomplete CNI template outside the runtime-watched configuration directory.
docs/user-guide.md Documents CNI configuration management, template semantics, and the relationship between destination filenames and configuration-list parsing.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[KUBE_ROUTER_CNI_CONF_TEMPLATE_FILE configured?] -->|Yes| B[Read pristine template]
    A -->|No| C[Read existing CNI configuration]
    B --> D[Interpret using destination filename]
    C --> D
    D --> E[Select bridge plugin]
    E --> F[Insert node pod CIDRs and MTU]
    F --> G[Set up kube-bridge]
    G --> H[Write completed CNI configuration]
    H --> I[Container runtime discovers usable config]
Loading

Reviews (1): Last reviewed commit: "feat: Support bootstrapping CNI configs ..." | Re-trigger Greptile

@catherinetcai catherinetcai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @twz123 - just some minor, surface level comments.

What do you think about having this templating behavior be the user-facing default moving forward? I'm not sure if I see the benefit of continuing to push the legacy/racy behavior, so I think it's worth just updating all of the examples under the daemonsets folder to use the template instead and calling it out in the release notes.

Comment thread docs/user-guide.md
kube-router's own contribution is the routing of pod traffic between nodes and the management of the plugins'
configuration file, filling in the parts that are node-specific and only known at runtime. The latter is what
`--enable-cni` controls, which is enabled by default. Disable it to use kube-router's features alongside another CNI
provider. On startup, kube-router

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "On startup..." is a good spot to place a line break for readability.

I think adding a colon at the end helps visually for formatting with the bullet points right below.

Comment thread docs/user-guide.md

- inserts the node's pod CIDRs into the `host-local` IPAM ranges of the `bridge` plugin, as allocated to the node by
the kube-controller-manager, or as set via the `kube-router.io/pod-cidrs` annotation
- and sets the `bridge` plugin's MTU, if `--auto-mtu` is enabled (see [MTU](#mtu)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would drop the starting "and" for this second point and drop the ending period since this is a bulleted list and not meant to be read like complete sentences.

Comment thread pkg/utils/cni_test.go
content: getConfListWithIPv6DuplicateRanges(),
err: nil,
ranges: []string{"10.242.0.0/24", "10.242.1.0/24", "10.242.2.0/24", "2001:db8:42:2::/64"},
name: "Ensure conflist subnets get de-deduplicated with ranges when repeats exist",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test name is missing calling out IPv6

Comment thread pkg/utils/cni_test.go
isConfList: false,
content: getConfWithNoType(),
err: errors.New("error load CNI config, file appears to have no type: "),
name: "Ensure conf subnet get consolidated into ranges when only subnet exists",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the args for this test are the same as the "Attempt reading from conf" test:

{
			name:    "Attempt reading from conf",
			content: getConf(),
			ranges:  []string{"10.242.0.0/24"},
},

Comment thread pkg/utils/cni_test.go
isConfList: true,
content: getConfListWithNoPlugins(),
err: errors.New("CNI config list "),
name: "Ensure conflist subnet get consolidated into ranges when only subnet exists",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be the same args as a previous test:

{
			name:    "Attempt reading from conflist",
			content: getConfList(),
			ranges:  []string{"10.242.0.0/24"},
},

Comment thread pkg/utils/cni.go
}

func NewCNINetworkConfig(cniConfFilePath string) (*CNINetworkConfig, error) {
return NewCNINetworkConfigFromTemplate(cniConfFilePath, "")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this calling down to NewCNINtworkConfigFromTemplate just to try and not break the API? I think it's okay to just update the API for NewCNINetworkConfig to accept cniConfTemplateFilePath.

I think this entire utils package really belongs in internal (and we should do that refactor at some point...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants