Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support additional volume and mounts #229

Merged
merged 4 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/v1/inline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ type AgentPodSpec struct {
ResourceRequirements `json:",inline"`

NodeSelector map[string]string `json:"nodeSelector,omitempty"`

// Set additional volumes for the agent pod.
// +kubebuilder:validation:Optional
Volumes []corev1.Volume `json:"volumes,omitempty"`

// Set additional volume mounts for the agent pod.
// +kubebuilder:validation:Optional
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
}

type TlsSpec struct {
Expand Down
14 changes: 14 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions config/samples/instana_v1_extended_instanaagent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ spec:
memory: 768Mi
# agent.pod.limits.cpu sets the CPU units allocation limits for the agent pods.
cpu: "1.5"

# agent.pod.volumes and agent.pod.volumeMounts are additional volumes and volumeMounts for user-specific files.
# For example, a certificate may need to be mounted for an agent sensor to connect to the monitored target.
# https://kubernetes.io/docs/concepts/storage/volumes/
volumes:
- name: my-secret-volume
secret:
secretName: instana-agent-key
volumeMounts:
- name: my-secret-volume
mountPath: /secrets
Comment on lines +108 to +114
Copy link
Contributor

Choose a reason for hiding this comment

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

When having a final look, I noticed that this is actually the extended_instanaagent.yaml which shows all available options, but typically just defines the defaults.
Your changes are adding new volumes which would not reflect the default behavior, so probably it would be more consistent to have your values in that file as comment and use a separate file for the test input.
But I also realized that your e2e test is relying on the given yaml to mount the secret, so I am fine for leaving it in as is for now, I might move the test input into a new file eventually, but that should not block the release for now.


serviceMesh:
# agent.serviceMesh.enabled sets the Instana agent's communication direction with JVMs.
Expand Down
64 changes: 64 additions & 0 deletions e2e/agent_test_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,70 @@ func ValidateAgentMultiBackendConfiguration() e2etypes.StepFunc {
}
}

func ValidateSecretsMountedFromExtraVolume() e2etypes.StepFunc {
return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
log.Infof("Fetching secret %s", InstanaAgentConfigSecretName)
// Create a client to interact with the Kube API
r, err := resources.New(cfg.Client().RESTConfig())
if err != nil {
t.Fatal(err)
}

// Check if namespace exist, otherwise just skip over it
instanaAgentConfigSecret := &corev1.Secret{}
err = r.Get(ctx, InstanaAgentConfigSecretName, InstanaNamespace, instanaAgentConfigSecret)
if err != nil {
t.Fatal("Secret could not be fetched", InstanaAgentConfigSecretName, err)
}

pods := &corev1.PodList{}
listOps := resources.WithLabelSelector("app.kubernetes.io/component=instana-agent")
err = r.List(ctx, pods, listOps)
if err != nil || pods.Items == nil {
t.Error("error while getting pods", err)
}
var stdout, stderr bytes.Buffer
podName := pods.Items[0].Name
containerName := "instana-agent"

secretFileMatrix := []struct {
path string
content string
}{
{
path: "/secrets/key",
content: "xxx",
},
{
path: "/secrets/key-1",
content: "yyy",
},
}

for _, currentFile := range secretFileMatrix {
if err := r.ExecInPod(
ctx,
cfg.Namespace(),
podName,
containerName,
[]string{"cat", currentFile.path},
&stdout,
&stderr,
); err != nil {
t.Log(stderr.String())
t.Error(err)
}
if strings.Contains(stdout.String(), "xxx") {
t.Logf("ExecInPod returned expected secret value from file %s", currentFile.path)
} else {
t.Error(fmt.Sprintf("Expected to find %s in file %s", currentFile.content, currentFile.path), stdout.String())
}
}

return ctx
}
}

// Helper to produce test structs
func NewAgentCr(t *testing.T) v1.InstanaAgent {
boolTrue := true
Expand Down
53 changes: 53 additions & 0 deletions e2e/extra_volume_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* (c) Copyright IBM Corp. 2024
* (c) Copyright Instana Inc. 2024
*/

package e2e

import (
"context"
"testing"

"sigs.k8s.io/e2e-framework/klient/decoder"
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"
)

func TestExtraVolumeWithSecret(t *testing.T) {
installCrWithExtraVolumeFeature := features.New("extra volume with secret").
Setup(SetupOperatorDevBuild()).
Setup(WaitForDeploymentToBecomeReady(InstanaOperatorDeploymentName)).
Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
r, err := resources.New(cfg.Client().RESTConfig())
if err != nil {
t.Fatal(err)
}

t.Logf("Creating dummy secret")

err = decoder.ApplyWithManifestDir(ctx, r, "../config/samples", "external_secret_instana_agent_key.yaml", []resources.CreateOption{})
if err != nil {
t.Fatal(err)
}

t.Logf("Secret created")

t.Logf("Creating dummy agent CR with extra volume")
err = decoder.ApplyWithManifestDir(ctx, r, "../config/samples", "instana_v1_extended_instanaagent.yaml", []resources.CreateOption{})
if err != nil {
t.Fatal(err)
}
t.Logf("CR created")

return ctx
}).
Assess("wait for first k8sensor deployment to become ready", WaitForDeploymentToBecomeReady(K8sensorDeploymentName)).
Assess("wait for agent daemonset to become ready", WaitForAgentDaemonSetToBecomeReady()).
Assess("validate secret files are created from extra mounted volume", ValidateSecretsMountedFromExtraVolume()).
Feature()

// test feature
testEnv.Test(t, installCrWithExtraVolumeFeature)
}
9 changes: 7 additions & 2 deletions pkg/k8s/object/builders/agent/daemonset/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func (d *daemonSetBuilder) getVolumes() ([]corev1.Volume, []corev1.VolumeMount)
)
}

func (d *daemonSetBuilder) getUserVolumes() ([]corev1.Volume, []corev1.VolumeMount) {
return d.VolumeBuilder.BuildFromUserConfig()
}

func (d *daemonSetBuilder) getName() string {
switch d.zone {
case nil:
Expand Down Expand Up @@ -170,6 +174,7 @@ func (d *daemonSetBuilder) getTolerations() []corev1.Toleration {

func (d *daemonSetBuilder) build() *appsv1.DaemonSet {
volumes, volumeMounts := d.getVolumes()
userVolumes, userVolumeMounts := d.getUserVolumes()

return &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{
Expand All @@ -192,7 +197,7 @@ func (d *daemonSetBuilder) build() *appsv1.DaemonSet {
Annotations: d.InstanaAgent.Spec.Agent.Pod.Annotations,
},
Spec: corev1.PodSpec{
Volumes: volumes,
Volumes: append(volumes, userVolumes...),
ServiceAccountName: d.ServiceAccountName(),
NodeSelector: d.Spec.Agent.Pod.NodeSelector,
HostNetwork: true,
Expand All @@ -205,7 +210,7 @@ func (d *daemonSetBuilder) build() *appsv1.DaemonSet {
Name: "instana-agent",
Image: d.Spec.Agent.Image(),
ImagePullPolicy: d.Spec.Agent.PullPolicy,
VolumeMounts: volumeMounts,
VolumeMounts: append(volumeMounts, userVolumeMounts...),
Env: d.getEnvVars(),
SecurityContext: &corev1.SecurityContext{
Privileged: pointer.To(true),
Expand Down
43 changes: 43 additions & 0 deletions pkg/k8s/object/builders/agent/daemonset/daemonset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,49 @@ func TestDaemonSetBuilder_getVolumes(t *testing.T) {
assertions.Equal(expectedVolumeMounts, actualVolumeMounts)
}

func TestDaemonSetBuilder_getUserVolumes(t *testing.T) {
assertions := require.New(t)
ctrl := gomock.NewController(t)

volumeName := "testVolume"
expectedVolumes := []corev1.Volume{{Name: volumeName}}
expectedVolumeMounts := []corev1.VolumeMount{{Name: volumeName}}

volumeBuilder := mocks.NewMockVolumeBuilder(ctrl)
volumeBuilder.EXPECT().BuildFromUserConfig().Return(expectedVolumes, expectedVolumeMounts)

agent := &instanav1.InstanaAgent{
ObjectMeta: metav1.ObjectMeta{
Name: "testAgent",
},
Spec: instanav1.InstanaAgentSpec{
Agent: instanav1.BaseAgentSpec{
Pod: instanav1.AgentPodSpec{
Volumes: []corev1.Volume{
{
Name: volumeName,
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeName,
},
},
},
},
},
}
db := &daemonSetBuilder{
VolumeBuilder: volumeBuilder,
InstanaAgent: agent,
}

actualVolumes, actualVolumeMounts := db.getUserVolumes()

assertions.Equal(expectedVolumes, actualVolumes)
assertions.Equal(expectedVolumeMounts, actualVolumeMounts)
}

func TestDaemonSetBuilder_IsNamespaced_ComponentName(t *testing.T) {
assertions := assert.New(t)

Expand Down
10 changes: 10 additions & 0 deletions pkg/k8s/object/builders/common/volume/volume_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (

type VolumeBuilder interface {
Build(volumes ...Volume) ([]corev1.Volume, []corev1.VolumeMount)
BuildFromUserConfig() ([]corev1.Volume, []corev1.VolumeMount)
}

type volumeBuilder struct {
Expand Down Expand Up @@ -68,6 +69,15 @@ func (v *volumeBuilder) Build(volumes ...Volume) ([]corev1.Volume, []corev1.Volu
return volumeSpecs, volumeMounts
}

func (v *volumeBuilder) BuildFromUserConfig() ([]corev1.Volume, []corev1.VolumeMount) {
volumeSpecs := v.instanaAgent.Spec.Agent.Pod.Volumes
volumeMounts := v.instanaAgent.Spec.Agent.Pod.VolumeMounts
if len(volumeSpecs) != len(volumeMounts) {
panic(errors.New("Number of additional volumes and volume mounts do not match"))
}
return volumeSpecs, volumeMounts
}

func (v *volumeBuilder) getBuilder(volume Volume) (*corev1.Volume, *corev1.VolumeMount) {
mountPropagationHostToContainer := corev1.MountPropagationHostToContainer
mkdir := corev1.HostPathDirectoryOrCreate
Expand Down
46 changes: 46 additions & 0 deletions pkg/k8s/object/builders/common/volume/volume_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"

instanav1 "github.com/instana/instana-agent-operator/api/v1"
)
Expand Down Expand Up @@ -108,6 +109,51 @@ func TestVolumeBuilderBuild(t *testing.T) {
}
}

func TestVolumeBuilderBuildFromUserConfig(t *testing.T) {
assertions := require.New(t)
volumeName := "testVolume"
agent := &instanav1.InstanaAgent{
Spec: instanav1.InstanaAgentSpec{
Agent: instanav1.BaseAgentSpec{
Pod: instanav1.AgentPodSpec{
Volumes: []corev1.Volume{
{
Name: volumeName,
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeName,
},
},
},
},
},
}
for _, test := range []struct {
name string
isOpenShift bool
}{
{
name: "isOpenShift",
isOpenShift: true,
},
{
name: "isNotOpenShift",
isOpenShift: false,
},
} {
t.Run(
test.name, func(t *testing.T) {
vb := NewVolumeBuilder(agent, true)
volumes, volumeMounts := vb.BuildFromUserConfig()
assertions.Equal(volumeName, volumes[0].Name)
assertions.Equal(volumeName, volumeMounts[0].Name)
},
)
}
}

func TestVolumeBuilderTlsSpec(t *testing.T) {
for _, test := range []struct {
name string
Expand Down