Skip to content

Commit 521d1d7

Browse files
committed
using configmap to inject runtime and csi sideacr
Signed-off-by: jicheng.sk <jicheng.sk@alibaba-inc.com>
1 parent 4d5825c commit 521d1d7

File tree

11 files changed

+1783
-12
lines changed

11 files changed

+1783
-12
lines changed

api/v1alpha1/sandbox_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ const (
3535
// Note: SandboxSet creates sandboxes with priority 0 by default.
3636
// Sandbox Manager or Sandbox Claim creates high-priority sandboxes by default.
3737
SandboxAnnotationPriority = "agents.kruise.io/sandbox-priority"
38+
39+
// ShouldInjectCsiMount is the annotation key for inject csi mount plugin container.
40+
// If set, the csi sidecar will be injected into the pod when the sandbox is created.
41+
// The csi mount sidecar is used to mount the remote oss/nas storage to the sandbox container.
42+
ShouldInjectCsiMount = "agents.kruise.io/should-inject-csi-mount-plugin"
43+
44+
// ShouldInjectAgentRuntime is the annotation key for inject agent runtime sidecar in init container.
45+
// If set, the agent runtime sidecar will be injected into the pod when the sandbox is created.
46+
// Some binary tools which are contained in the init agent runtime container. These are the basic tools for sandbox running.
47+
ShouldInjectAgentRuntime = "agents.kruise.io/should-inject-agent-runtime"
3848
)
3949

4050
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
golang.org/x/time v0.11.0
2222
google.golang.org/grpc v1.75.1
2323
google.golang.org/protobuf v1.36.11
24+
gopkg.in/evanphx/json-patch.v4 v4.12.0
2425
k8s.io/api v0.33.0
2526
k8s.io/apimachinery v0.33.0
2627
k8s.io/apiserver v0.33.0
@@ -103,7 +104,6 @@ require (
103104
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
104105
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
105106
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
106-
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
107107
gopkg.in/inf.v0 v0.9.1 // indirect
108108
gopkg.in/yaml.v3 v3.0.1 // indirect
109109
k8s.io/apiextensions-apiserver v0.33.0 // indirect
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package core
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
)
6+
7+
const (
8+
KEY_CSI_INJECTION_CONFIG = "csi-config"
9+
KEY_RUNTIME_INJECTION_CONFIG = "agent-runtime-config"
10+
sandboxInjectionConfigName = "sandbox-injection-config"
11+
)
12+
13+
type SidecarInjectConfig struct {
14+
// Configuration injection for the main container (by convention, one container is designated as the main container)
15+
// Injection configuration items for business-specified main containers, such as volumeMount, environment variables, etc. Format: corev1.Container
16+
MainContainer corev1.Container `json:"mainContainer"`
17+
// Support injection for multiple independent sidecar containers; CSI container plugins are all injected from this
18+
Sidecars []corev1.Container `json:"csiSidecar"`
19+
// Support injection for volume mount configurations
20+
Volumes []corev1.Volume `json:"volume"`
21+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package core
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
"k8s.io/apimachinery/pkg/api/errors"
9+
"k8s.io/apimachinery/pkg/types"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
logf "sigs.k8s.io/controller-runtime/pkg/log"
12+
13+
agentsv1alpha1 "github.com/openkruise/agents/api/v1alpha1"
14+
"github.com/openkruise/agents/pkg/utils/webhookutils"
15+
)
16+
17+
func enableInjectCsiMountConfig(sandbox *agentsv1alpha1.Sandbox) bool {
18+
return sandbox.Annotations[agentsv1alpha1.ShouldInjectCsiMount] == "true"
19+
}
20+
21+
func enableInjectAgentRuntimeConfig(sandbox *agentsv1alpha1.Sandbox) bool {
22+
return sandbox.Annotations[agentsv1alpha1.ShouldInjectAgentRuntime] == "true"
23+
}
24+
25+
// fetchInjectionConfiguration retrieves the sidecar injection configuration from the ConfigMap.
26+
// It attempts to fetch the ConfigMap named sandboxInjectionConfigName from the sandbox-system namespace.
27+
// If the ConfigMap exists, it returns the data map containing configuration keys like
28+
// KEY_CSI_INJECTION_CONFIG and KEY_RUNTIME_INJECTION_CONFIG. If the ConfigMap is not found
29+
// or any error occurs during retrieval, it returns nil data and no error.
30+
//
31+
// Parameters:
32+
// - ctx: context.Context for the operation
33+
// - client: Kubernetes client.Interface used to retrieve the ConfigMap
34+
//
35+
// Returns:
36+
// - map[string]string: The configuration data from the ConfigMap, or nil if not found or on error
37+
// - error: Always returns nil (errors are logged but not propagated)
38+
func fetchInjectionConfiguration(ctx context.Context, cli client.Client) (map[string]string, error) {
39+
logger := logf.FromContext(ctx)
40+
config := &corev1.ConfigMap{}
41+
err := cli.Get(ctx, types.NamespacedName{
42+
Namespace: webhookutils.GetNamespace(), // Todo considering the security concern and rbac issue
43+
Name: sandboxInjectionConfigName,
44+
}, config)
45+
if err != nil {
46+
if errors.IsNotFound(err) {
47+
logger.Info("no found the injection configuration, using default")
48+
return map[string]string{}, nil
49+
}
50+
return map[string]string{}, err
51+
}
52+
return config.Data, nil
53+
}
54+
55+
// parseInjectConfig parses the sidecar injection configuration from raw ConfigMap data for a specific config key.
56+
// It extracts the value associated with the given configKey (e.g., KEY_CSI_INJECTION_CONFIG or KEY_RUNTIME_INJECTION_CONFIG)
57+
// and unmarshals it into a SidecarInjectConfig struct. If the key is not present in the configRaw map or the value
58+
// is empty, it returns an empty SidecarInjectConfig without error. The configuration includes the main container
59+
// settings, sidecar containers list (CSI sidecars or runtime init containers), and volumes to be injected.
60+
//
61+
// Parameters:
62+
// - ctx: context.Context for the operation, used for logging
63+
// - configKey: string representing the configuration key to look up (KEY_CSI_INJECTION_CONFIG or KEY_RUNTIME_INJECTION_CONFIG)
64+
// - configRaw: map[string]string containing the raw ConfigMap data with potential injection config
65+
//
66+
// Returns:
67+
// - SidecarInjectConfig: The parsed configuration containing main container, sidecars, and volumes
68+
// - error: An error if JSON unmarshaling fails, nil otherwise or when config key is missing/empty
69+
func parseInjectConfig(ctx context.Context, configKey string, configRaw map[string]string) (SidecarInjectConfig, error) {
70+
log := logf.FromContext(ctx)
71+
csiMountConfig := SidecarInjectConfig{}
72+
73+
configValue, exists := configRaw[configKey]
74+
if !exists || configValue == "" {
75+
log.Info("config key not found or empty, using default configuration")
76+
return csiMountConfig, nil
77+
}
78+
79+
err := json.Unmarshal([]byte(configRaw[configKey]), &csiMountConfig)
80+
if err != nil {
81+
log.Error(err, "failed to json unmarshal csi mount config")
82+
return csiMountConfig, err
83+
}
84+
return csiMountConfig, nil
85+
}
86+
87+
// parseAgentRuntimeConfig parses the agent runtime injection configuration from raw ConfigMap data.
88+
// It extracts the value associated with KEY_RUNTIME_INJECTION_CONFIG key and unmarshals it into
89+
// a SidecarInjectConfig struct. If the key is not present in the configRaw map, it returns
90+
// an empty SidecarInjectConfig without error. The configuration includes the main container
91+
// settings with lifecycle hooks, init runtime containers list, and volumes to be injected.
92+
//
93+
// Parameters:
94+
// - ctx: context.Context for the operation (currently unused but kept for future extensibility)
95+
// - configRaw: map[string]string containing the raw ConfigMap data with potential runtime config
96+
//
97+
// Returns:
98+
// - SidecarInjectConfig: The parsed configuration containing main container, sidecars (init containers), and volumes
99+
// - error: An error if JSON unmarshaling fails, nil otherwise
100+
101+
102+
// setCSIMountContainer injects CSI mount configurations into the SandboxTemplate's pod spec.
103+
// It configures the main container (first container in the spec) with CSI sidecar settings,
104+
// appends additional CSI sidecar containers, and mounts shared volumes.
105+
// Volumes are only added if they don't already exist in the template.
106+
func setCSIMountContainer(ctx context.Context, obj *corev1.PodTemplateSpec, config SidecarInjectConfig) {
107+
log := logf.FromContext(ctx)
108+
109+
// set main container, the first container is the main container
110+
if len(obj.Spec.Containers) == 0 {
111+
log.Info("no found the template containers")
112+
return
113+
}
114+
115+
mainContainer := &obj.Spec.Containers[0]
116+
setMainContainerWhenInjectCSISidecar(mainContainer, config)
117+
118+
// set csi sidecars
119+
for _, csiSidecar := range config.Sidecars {
120+
obj.Spec.Containers = append(obj.Spec.Containers, csiSidecar)
121+
}
122+
123+
// set share volume
124+
if len(config.Volumes) > 0 {
125+
if obj.Spec.Volumes == nil {
126+
obj.Spec.Volumes = make([]corev1.Volume, 0, len(config.Volumes))
127+
}
128+
for _, vol := range config.Volumes {
129+
if findVolumeByName(obj.Spec.Volumes, vol.Name) {
130+
continue
131+
}
132+
obj.Spec.Volumes = append(obj.Spec.Volumes, vol)
133+
}
134+
}
135+
}
136+
137+
// setMainContainerWhenInjectCSISidecar configures the main container with environment variables and volume mounts from the CSI sidecar configuration.
138+
// It appends environment variables and volume mounts to the main container, skipping any that already exist (matched by name) to avoid duplicates.
139+
func setMainContainerWhenInjectCSISidecar(mainContainer *corev1.Container, config SidecarInjectConfig) {
140+
// append some envs in main container when processing csi mount
141+
if mainContainer.Env == nil {
142+
mainContainer.Env = make([]corev1.EnvVar, 0, 1)
143+
}
144+
for _, env := range config.MainContainer.Env {
145+
if findEnvByName(mainContainer.Env, env.Name) {
146+
continue
147+
}
148+
mainContainer.Env = append(mainContainer.Env, env)
149+
}
150+
151+
// append some volumeMounts config in main container
152+
if config.MainContainer.VolumeMounts != nil {
153+
if mainContainer.VolumeMounts == nil {
154+
mainContainer.VolumeMounts = make([]corev1.VolumeMount, 0, len(config.MainContainer.VolumeMounts))
155+
}
156+
for _, volMount := range config.MainContainer.VolumeMounts {
157+
if findVolumeMountByName(mainContainer.VolumeMounts, volMount.Name) {
158+
continue
159+
}
160+
mainContainer.VolumeMounts = append(mainContainer.VolumeMounts, volMount)
161+
}
162+
}
163+
}
164+
165+
// setAgentRuntimeContainer injects agent runtime configurations into the SandboxTemplate's pod spec.
166+
// It appends agent runtime containers as init containers and configures the main container (first container) with runtime settings.
167+
// The init containers run before the main containers to prepare the runtime environment.
168+
func setAgentRuntimeContainer(ctx context.Context, obj *corev1.PodTemplateSpec, config SidecarInjectConfig) {
169+
log := logf.FromContext(ctx)
170+
171+
// append init agent runtime container
172+
if obj.Spec.InitContainers == nil {
173+
obj.Spec.InitContainers = make([]corev1.Container, 0, 1)
174+
}
175+
obj.Spec.InitContainers = append(obj.Spec.InitContainers, config.Sidecars...)
176+
177+
if len(obj.Spec.Containers) == 0 {
178+
log.Info("no found the template container, default main container is the first container")
179+
return
180+
}
181+
mainContainer := &obj.Spec.Containers[0]
182+
setMainContainerConfigWhenInjectRuntimeSidecar(mainContainer, config)
183+
184+
obj.Spec.Volumes = append(obj.Spec.Volumes, config.Volumes...)
185+
}
186+
187+
func setMainContainerConfigWhenInjectRuntimeSidecar(mainContainer *corev1.Container, config SidecarInjectConfig) {
188+
// set main container lifecycle
189+
if mainContainer.Lifecycle == nil {
190+
mainContainer.Lifecycle = &corev1.Lifecycle{}
191+
}
192+
if mainContainer.Lifecycle.PostStart == nil {
193+
mainContainer.Lifecycle.PostStart = &corev1.LifecycleHandler{}
194+
}
195+
196+
// using config to override the main container lifecycle post start
197+
if config.MainContainer.Lifecycle != nil && config.MainContainer.Lifecycle.PostStart != nil {
198+
mainContainer.Lifecycle.PostStart = config.MainContainer.Lifecycle.PostStart
199+
}
200+
201+
// set main container env
202+
if mainContainer.Env == nil {
203+
mainContainer.Env = make([]corev1.EnvVar, 0, len(config.MainContainer.Env))
204+
}
205+
mainContainer.Env = append(mainContainer.Env, config.MainContainer.Env...)
206+
207+
// set main container volumeMounts
208+
if mainContainer.VolumeMounts == nil {
209+
mainContainer.VolumeMounts = make([]corev1.VolumeMount, 0, len(config.MainContainer.VolumeMounts))
210+
}
211+
mainContainer.VolumeMounts = append(mainContainer.VolumeMounts, config.MainContainer.VolumeMounts...)
212+
}
213+
214+
func findVolumeMountByName(volumeMounts []corev1.VolumeMount, name string) bool {
215+
for _, volumeMount := range volumeMounts {
216+
if volumeMount.Name == name {
217+
return true
218+
}
219+
}
220+
return false
221+
}
222+
223+
func findVolumeByName(volumes []corev1.Volume, name string) bool {
224+
for _, volume := range volumes {
225+
if volume.Name == name {
226+
return true
227+
}
228+
}
229+
return false
230+
}
231+
232+
func findEnvByName(envs []corev1.EnvVar, name string) bool {
233+
for _, env := range envs {
234+
if env.Name == name {
235+
return true
236+
}
237+
}
238+
return false
239+
}

0 commit comments

Comments
 (0)