Skip to content

Commit 53680cf

Browse files
authored
feat: add Multus CNI integration with socket-based readiness (#1367)
**What problem does this PR solve?**: Implement Multus CNI as an internal addon that automatically deploys alongside primary CNI providers (Cilium/Calico) on supported cloud providers (EKS/Nutanix). - Add socket-based readiness detection using CNI socket paths - Implement template function following CAREN standard pattern - Register Multus handler in lifecycle hooks - Add values template with Go template syntax for socket configuration - Deploy Multus using HelmAddon strategy with readinessIndicatorFile Multus is not exposed in the API and deploys automatically based on cloud provider and CNI selection. Current PR is to deploy by default(i.e always) **Which issue(s) this PR fixes**: Fixes # **How Has This Been Tested?**: <!-- Please describe the tests that you ran to verify your changes. Provide output from the tests and any manual steps needed to replicate the tests. --> **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. --> Currently it supports both Nutanix and EKS for testing purpose, will remove Nutanix once we finalize handler implementation.
1 parent 525d099 commit 53680cf

File tree

14 files changed

+370
-0
lines changed

14 files changed

+370
-0
lines changed

charts/cluster-api-runtime-extensions-nutanix/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ A Helm chart for cluster-api-runtime-extensions-nutanix
7878
| hooks.cni.cilium.crsStrategy.defaultCiliumConfigMap.name | string | `"cilium"` | |
7979
| hooks.cni.cilium.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
8080
| hooks.cni.cilium.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-cilium-cni-helm-values-template"` | |
81+
| hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
82+
| hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-multus-values-template"` | |
8183
| hooks.cosi.controller.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
8284
| hooks.cosi.controller.helmAddonStrategy.defaultValueTemplateConfigMap.name | string | `"default-cosi-controller-helm-values-template"` | |
8385
| hooks.csi.aws-ebs.helmAddonStrategy.defaultValueTemplateConfigMap.create | bool | `true` | |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Multus daemon configuration overrides
2+
daemonConfig:
3+
readinessIndicatorFile: "{{ .ReadinessSocketPath }}"
4+
5+
{{- if .ReadinessSocketPath }}
6+
# Volumes for CNI readiness socket
7+
volumes:
8+
- name: cni-readiness-sock
9+
hostPath:
10+
path: "{{ .ReadinessSocketPath }}"
11+
type: Socket
12+
13+
# Volume mounts for CNI readiness socket
14+
volumeMounts:
15+
- name: cni-readiness-sock
16+
mountPath: "{{ .ReadinessSocketPath }}"
17+
readOnly: true
18+
{{- end }}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright 2024 Nutanix. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if .Values.hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.create }}
5+
apiVersion: v1
6+
kind: ConfigMap
7+
metadata:
8+
name: '{{ .Values.hooks.cni.multus.helmAddonStrategy.defaultValueTemplateConfigMap.name }}'
9+
data:
10+
values.yaml: |-
11+
{{- .Files.Get "addons/cni/multus/values-template.yaml" | nindent 4 }}
12+
{{- end -}}

charts/cluster-api-runtime-extensions-nutanix/templates/helm-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ data:
4343
ChartName: metallb
4444
ChartVersion: 0.15.2
4545
RepositoryURL: '{{ if .Values.helmRepository.enabled }}oci://helm-repository.{{ .Release.Namespace }}.svc/charts{{ else }}https://metallb.github.io/metallb{{ end }}'
46+
multus: |
47+
ChartName: multus
48+
ChartVersion: 0.1.0
49+
RepositoryURL: '{{ if .Values.helmRepository.enabled }}oci://helm-repository.{{ .Release.Namespace }}.svc/charts{{ else }}https://mesosphere.github.io/charts/stable/{{ end }}'
4650
nfd: |
4751
ChartName: node-feature-discovery
4852
ChartVersion: 0.18.1

charts/cluster-api-runtime-extensions-nutanix/values.schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,27 @@
381381
}
382382
}
383383
}
384+
},
385+
"multus": {
386+
"type": "object",
387+
"properties": {
388+
"helmAddonStrategy": {
389+
"type": "object",
390+
"properties": {
391+
"defaultValueTemplateConfigMap": {
392+
"type": "object",
393+
"properties": {
394+
"create": {
395+
"type": "boolean"
396+
},
397+
"name": {
398+
"type": "string"
399+
}
400+
}
401+
}
402+
}
403+
}
404+
}
384405
}
385406
}
386407
},

charts/cluster-api-runtime-extensions-nutanix/values.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ hooks:
4343
defaultValueTemplateConfigMap:
4444
create: true
4545
name: default-cilium-cni-helm-values-template
46+
multus:
47+
helmAddonStrategy:
48+
defaultValueTemplateConfigMap:
49+
create: true
50+
name: default-multus-values-template
4651
csi:
4752
nutanix:
4853
helmAddonStrategy:

hack/addons/helm-chart-bundler/repos.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ repositories:
5151
charts:
5252
metallb:
5353
- 0.15.2
54+
multus:
55+
repoURL: https://mesosphere.github.io/charts/stable/
56+
charts:
57+
multus:
58+
- 0.1.0
5459
node-feature-discovery:
5560
repoURL: https://kubernetes-sigs.github.io/node-feature-discovery/charts
5661
charts:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2024 Nutanix. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
apiVersion: kustomize.config.k8s.io/v1beta1
5+
kind: Kustomization
6+
7+
metadata:
8+
name: multus
9+
10+
sortOptions:
11+
order: fifo
12+
13+
helmCharts:
14+
- name: multus
15+
namespace: kube-system
16+
repo: https://mesosphere.github.io/charts/stable/
17+
releaseName: multus
18+
version: 0.1.0
19+
includeCRDs: true
20+
skipTests: true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2024 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package multus provides a standalone lifecycle handler for Multus CNI that automatically
5+
// deploys Multus when:
6+
// - The cluster is on EKS cloud provider
7+
// - A supported CNI provider is configured (Cilium or Calico)
8+
//
9+
// MultusHandler implements the cluster lifecycle hooks and:
10+
// - Detects the cloud provider from the cluster infrastructure
11+
// - Reads CNI configuration from cluster variables
12+
// - Gets the readiness socket path for the configured CNI (via cni.ReadinessSocketPath)
13+
// - Automatically deploys Multus with socket-based configuration
14+
//
15+
// helmAddonStrategy is the internal strategy that handles:
16+
// - Templating Helm values with the CNI socket path
17+
// - Deploying Multus using HelmAddon strategy with Go template-based values
18+
//
19+
// Multus relies on the readinessIndicatorFile configuration to wait for the primary CNI
20+
// to be ready, eliminating the need for explicit wait logic in the strategy.
21+
//
22+
// This package does NOT expose Multus in the API - it's an internal addon
23+
// that deploys automatically based on cloud provider and CNI selection.
24+
package multus
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2024 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package multus
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/spf13/pflag"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
13+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
16+
17+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
18+
commonhandlers "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/lifecycle"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
21+
capiutils "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/utils"
22+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/addons"
23+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/config"
24+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options"
25+
)
26+
27+
const (
28+
defaultMultusReleaseName = "multus"
29+
defaultMultusNamespace = metav1.NamespaceSystem
30+
)
31+
32+
type MultusConfig struct {
33+
*options.GlobalOptions
34+
35+
helmAddonConfig *addons.HelmAddonConfig
36+
}
37+
38+
func NewMultusConfig(globalOptions *options.GlobalOptions) *MultusConfig {
39+
return &MultusConfig{
40+
GlobalOptions: globalOptions,
41+
helmAddonConfig: addons.NewHelmAddonConfig(
42+
"default-multus-values-template",
43+
defaultMultusNamespace,
44+
defaultMultusReleaseName,
45+
),
46+
}
47+
}
48+
49+
func (m *MultusConfig) AddFlags(prefix string, flags *pflag.FlagSet) {
50+
m.helmAddonConfig.AddFlags(prefix+".helm-addon", flags)
51+
}
52+
53+
type MultusHandler struct {
54+
client ctrlclient.Client
55+
config *MultusConfig
56+
helmChartInfoGetter *config.HelmChartGetter
57+
}
58+
59+
var (
60+
_ commonhandlers.Named = &MultusHandler{}
61+
_ lifecycle.AfterControlPlaneInitialized = &MultusHandler{}
62+
_ lifecycle.BeforeClusterUpgrade = &MultusHandler{}
63+
)
64+
65+
func New(
66+
c ctrlclient.Client,
67+
cfg *MultusConfig,
68+
helmChartInfoGetter *config.HelmChartGetter,
69+
) *MultusHandler {
70+
return &MultusHandler{
71+
client: c,
72+
config: cfg,
73+
helmChartInfoGetter: helmChartInfoGetter,
74+
}
75+
}
76+
77+
func (m *MultusHandler) Name() string {
78+
return "MultusHandler"
79+
}
80+
81+
func (m *MultusHandler) AfterControlPlaneInitialized(
82+
ctx context.Context,
83+
req *runtimehooksv1.AfterControlPlaneInitializedRequest,
84+
resp *runtimehooksv1.AfterControlPlaneInitializedResponse,
85+
) {
86+
commonResponse := &runtimehooksv1.CommonResponse{}
87+
m.apply(ctx, &req.Cluster, commonResponse)
88+
resp.Status = commonResponse.GetStatus()
89+
resp.Message = commonResponse.GetMessage()
90+
}
91+
92+
func (m *MultusHandler) BeforeClusterUpgrade(
93+
ctx context.Context,
94+
req *runtimehooksv1.BeforeClusterUpgradeRequest,
95+
resp *runtimehooksv1.BeforeClusterUpgradeResponse,
96+
) {
97+
commonResponse := &runtimehooksv1.CommonResponse{}
98+
m.apply(ctx, &req.Cluster, commonResponse)
99+
resp.Status = commonResponse.GetStatus()
100+
resp.Message = commonResponse.GetMessage()
101+
}
102+
103+
func (m *MultusHandler) apply(
104+
ctx context.Context,
105+
cluster *clusterv1.Cluster,
106+
resp *runtimehooksv1.CommonResponse,
107+
) {
108+
clusterKey := ctrlclient.ObjectKeyFromObject(cluster)
109+
110+
log := ctrl.LoggerFrom(ctx).WithValues(
111+
"cluster",
112+
clusterKey,
113+
)
114+
115+
// Check if Multus is supported for this cloud provider
116+
provider := capiutils.GetProvider(cluster)
117+
if provider != "eks" {
118+
log.V(5).Info(
119+
"Multus is not supported for this cloud provider. Skipping Multus deployment.",
120+
)
121+
return
122+
}
123+
124+
log.Info(fmt.Sprintf("Cluster is %s. Checking CNI configuration for Multus deployment.", provider))
125+
126+
// Read CNI configuration to detect which CNI is deployed
127+
varMap := variables.ClusterVariablesToVariablesMap(cluster.Spec.Topology.Variables)
128+
129+
cniVar, err := variables.Get[v1alpha1.CNI](
130+
varMap,
131+
v1alpha1.ClusterConfigVariableName,
132+
[]string{"addons", v1alpha1.CNIVariableName}...)
133+
if err != nil {
134+
if variables.IsNotFoundError(err) {
135+
log.V(5).Info("No CNI specified in cluster config. Skipping Multus deployment.")
136+
return
137+
}
138+
log.Error(err, "failed to read CNI configuration from cluster definition")
139+
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
140+
resp.SetMessage(fmt.Sprintf("failed to read CNI configuration: %v", err))
141+
return
142+
}
143+
144+
log.Info(fmt.Sprintf("Auto-deploying Multus for %s cluster with %s CNI", provider, cniVar.Provider))
145+
146+
// Get helm chart configuration
147+
helmChart, err := m.helmChartInfoGetter.For(ctx, log, config.Multus)
148+
if err != nil {
149+
log.Error(
150+
err,
151+
"failed to get configmap with helm settings",
152+
)
153+
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
154+
resp.SetMessage(
155+
fmt.Sprintf("failed to get configuration to create helm addon: %v",
156+
err,
157+
),
158+
)
159+
return
160+
}
161+
162+
// Create and apply helm addon using existing addons package
163+
targetNamespace := m.config.DefaultsNamespace()
164+
strategy := addons.NewHelmAddonApplier(
165+
m.config.helmAddonConfig,
166+
m.client,
167+
helmChart,
168+
).
169+
WithValueTemplater(templateValuesFunc(cniVar)).
170+
WithDefaultWaiter()
171+
172+
if err := strategy.Apply(ctx, cluster, targetNamespace, log); err != nil {
173+
log.Error(err, "failed to deploy Multus")
174+
resp.SetStatus(runtimehooksv1.ResponseStatusFailure)
175+
resp.SetMessage(fmt.Sprintf("failed to deploy Multus: %v", err))
176+
return
177+
}
178+
179+
resp.SetStatus(runtimehooksv1.ResponseStatusSuccess)
180+
resp.SetMessage("Multus deployed successfully")
181+
}

0 commit comments

Comments
 (0)