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(php): add agent installation logic to operator #79

Merged
merged 6 commits into from
Sep 4, 2024
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
2 changes: 1 addition & 1 deletion src/apm/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ const (
annotationInjectPhpContainersName = "instrumentation.newrelic.com/php-container-names"
annotationInjectRuby = "instrumentation.newrelic.com/inject-ruby"
annotationInjectRubyContainersName = "instrumentation.newrelic.com/ruby-container-names"
annotationPhpExecCmd = "instrumentation.newrelic.com/php-exec-command"
annotationInjectContainerName = "instrumentation.newrelic.com/container-name"
annotationInjectGo = "instrumentation.opentelemetry.io/inject-go"
annotationGoExecPath = "instrumentation.opentelemetry.io/otel-go-auto-target-exe"
annotationInjectGoContainerName = "instrumentation.opentelemetry.io/go-container-name"
annotationPhpVersion = "instrumentation.newrelic.com/php-version"
)

// Calculate if we already inject InitContainers.
Expand Down
98 changes: 60 additions & 38 deletions src/apm/php.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package apm

import (
"errors"
"fmt"

corev1 "k8s.io/api/core/v1"
Expand All @@ -24,16 +25,32 @@ import (
)

const (
envPhpsymbolicOption = "NR_INSTALL_USE_CP_NOT_LN"
phpSymbolicOptionArgument = "1"
envPhpSilentOption = "NR_INSTALL_SILENT"
phpSilentOptionArgument = "1"
phpInitContainerName = initContainerName + "-php"
phpVolumeName = volumeName + "-php"
phpInstallArgument = "/newrelic-instrumentation/newrelic-install install && sed -i -e \"s/PHP Application/$NEW_RELIC_APP_NAME/g; s/REPLACE_WITH_REAL_KEY/$NEW_RELIC_LICENSE_KEY/g\" /usr/local/etc/php/conf.d/newrelic.ini"
envIniScanDirKey = "PHP_INI_SCAN_DIR"
envIniScanDirVal = "/newrelic-instrumentation/php-agent/ini"
)

var phpApiMap = map[string]string{
"7.2": "20170718",
"7.3": "20180731",
"7.4": "20190902",
"8.0": "20200930",
"8.1": "20210902",
"8.2": "20220829",
"8.3": "20230831",
}

func InjectPhpagent(phpSpec v1alpha1.Php, pod corev1.Pod, index int) (corev1.Pod, error) {
// exit early if we're missing mandatory annotations
phpVer, ok := pod.Annotations[annotationPhpVersion]
if !ok {
return pod, errors.New("missing php version annotation")
}

apiNum, ok := phpApiMap[phpVer]
if !ok {
return pod, errors.New("invalid php version")
}

// caller checks if there is at least one container.
container := &pod.Spec.Containers[index]

Expand All @@ -45,52 +62,57 @@ func InjectPhpagent(phpSpec v1alpha1.Php, pod corev1.Pod, index int) (corev1.Pod
}
}

const (
phpConcatEnvValues = false
concatEnvValues = true
)

setPhpEnvVar(container, envPhpsymbolicOption, phpSymbolicOptionArgument, phpConcatEnvValues)

setPhpEnvVar(container, envPhpSilentOption, phpSilentOptionArgument, phpConcatEnvValues)

container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
// define instrumentation artifact volume
instVolume := corev1.VolumeMount{
Name: volumeName,
MountPath: "/newrelic-instrumentation",
})
}

setPhpEnvVar(container, envIniScanDirKey, envIniScanDirVal, true)
mfulb marked this conversation as resolved.
Show resolved Hide resolved

container.VolumeMounts = append(container.VolumeMounts, instVolume)

// We just inject Volumes and init containers for the first processed container.
// We only inject Volumes and init containers for the first processed container.
if isInitContainerMissing(pod) {
initContainer := corev1.Container{
Name: initContainerName,
Image: phpSpec.Image,
Command: []string{"/bin/sh"},
Args: []string{
"-c", "cp -a /instrumentation/. /newrelic-instrumentation/ && /newrelic-instrumentation/k8s-php-install.sh " + apiNum + " && /newrelic-instrumentation/nr_env_to_ini.sh",
},
VolumeMounts: []corev1.VolumeMount{instVolume},
}

// inject the spec env vars into the initcontainer in order to construct the ini file
initContainer.Env = append(initContainer.Env, container.Env...)

// inject the license key secret
optional := true
initContainer.Env = append(initContainer.Env, corev1.EnvVar{
Name: "NEW_RELIC_LICENSE_KEY",
ValueFrom: &corev1.EnvVarSource{
mfulb marked this conversation as resolved.
Show resolved Hide resolved
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "newrelic-key-secret"},
Key: "new_relic_license_key",
Optional: &optional,
},
},
})

pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
}})

pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{
Name: initContainerName,
Image: phpSpec.Image,
Command: []string{"cp", "-a", "/instrumentation/.", "/newrelic-instrumentation/"},
VolumeMounts: []corev1.VolumeMount{{
Name: volumeName,
MountPath: "/newrelic-instrumentation",
}},
})
}

// Continue with the function regardless of whether annotationPhpExecCmd is present or not
execCmd, ok := pod.Annotations[annotationPhpExecCmd]
if ok {
// Add phpInstallArgument to the command field.
container.Command = append(container.Command, "/bin/sh", "-c", phpInstallArgument+" && "+execCmd)
} else {
container.Command = append(container.Command, "/bin/sh", "-c", phpInstallArgument)
pod.Spec.InitContainers = append(pod.Spec.InitContainers, initContainer)
}

return pod, nil
}

// setDotNetEnvVar function sets env var to the container if not exist already.
// setPhpEnvVar function sets env var to the container if not exist already.
// value of concatValues should be set to true if the env var supports multiple values separated by :.
// If it is set to false, the original container's env var value has priority.
func setPhpEnvVar(container *corev1.Container, envVarName string, envVarValue string, concatValues bool) {
Expand Down
2 changes: 1 addition & 1 deletion src/instrumentation/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ const (
annotationInjectPhpContainersName = "instrumentation.newrelic.com/php-container-names"
annotationInjectRuby = "instrumentation.newrelic.com/inject-ruby"
annotationInjectRubyContainersName = "instrumentation.newrelic.com/ruby-container-names"
annotationPhpExecCmd = "instrumentation.newrelic.com/php-exec-command"
annotationInjectContainerName = "instrumentation.newrelic.com/container-name"
annotationInjectGo = "instrumentation.opentelemetry.io/inject-go"
annotationGoExecPath = "instrumentation.opentelemetry.io/otel-go-auto-target-exe"
annotationInjectGoContainerName = "instrumentation.opentelemetry.io/go-container-name"
annotationPhpVersion = "instrumentation.newrelic.com/php-version"
)

// annotationValue returns the effective annotation value, based on the annotations from the pod and namespace.
Expand Down
2 changes: 1 addition & 1 deletion src/instrumentation/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (u *InstrumentationUpgrade) upgrade(_ context.Context, inst v1alpha1.Instru
// upgrade the image only if the image matches the annotation
if inst.Spec.Php.Image == autoInstPhp {
inst.Spec.Php.Image = u.DefaultAutoInstPhp
inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationDotNet] = u.DefaultAutoInstPhp
inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationPhp] = u.DefaultAutoInstPhp
}
}
autoInstRuby := inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationRuby]
Expand Down
5 changes: 5 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func main() {
autoInstrumentationPython string
autoInstrumentationDotNet string
autoInstrumentationRuby string
autoInstrumentationPhp string
labelsFilter []string
webhookPort int
tlsOpt tlsConfig
Expand All @@ -102,6 +103,7 @@ func main() {
pflag.StringVar(&autoInstrumentationPython, "auto-instrumentation-python-image", fmt.Sprintf("newrelic/newrelic-python-init:%s", v.AutoInstrumentationPython), "The default New Relic Python instrumentation image. This image is used when no image is specified in the CustomResource.")
pflag.StringVar(&autoInstrumentationDotNet, "auto-instrumentation-dotnet-image", fmt.Sprintf("newrelic/newrelic-dotnet-init:%s", v.AutoInstrumentationDotNet), "The default New Relic DotNet instrumentation image. This image is used when no image is specified in the CustomResource.")
pflag.StringVar(&autoInstrumentationRuby, "auto-instrumentation-ruby-image", fmt.Sprintf("newrelic/newrelic-ruby-init:%s", v.AutoInstrumentationRuby), "The default New Relic Ruby instrumentation image. This image is used when no image is specified in the CustomResource.")
pflag.StringVar(&autoInstrumentationPhp, "auto-instrumentation-php-image", fmt.Sprintf("newrelic/newrelic-php-init:%s", v.AutoInstrumentationPhp), "The default New Relic Php instrumentation image. This image is used when no image is specified in the CustomResource.")

pflag.StringArrayVar(&labelsFilter, "labels", []string{}, "Labels to filter away from propagating onto deploys")
pflag.IntVar(&webhookPort, "webhook-port", 9443, "The port the webhook endpoint binds to.")
Expand All @@ -119,6 +121,7 @@ func main() {
"auto-instrumentation-python", autoInstrumentationPython,
"auto-instrumentation-dotnet", autoInstrumentationDotNet,
"auto-instrumentation-ruby", autoInstrumentationRuby,
"auto-instrumentation-php", autoInstrumentationPhp,
"build-date", v.BuildDate,
"go-version", v.Go,
"go-arch", runtime.GOARCH,
Expand All @@ -143,6 +146,7 @@ func main() {
config.WithAutoInstrumentationPythonImage(autoInstrumentationPython),
config.WithAutoInstrumentationDotNetImage(autoInstrumentationDotNet),
config.WithAutoInstrumentationRubyImage(autoInstrumentationRuby),
config.WithAutoInstrumentationPhpImage(autoInstrumentationPhp),
config.WithAutoDetect(ad),
config.WithLabelFilters(labelsFilter),
)
Expand Down Expand Up @@ -204,6 +208,7 @@ func main() {
v1alpha1.AnnotationDefaultAutoInstrumentationPython: autoInstrumentationPython,
v1alpha1.AnnotationDefaultAutoInstrumentationDotNet: autoInstrumentationDotNet,
v1alpha1.AnnotationDefaultAutoInstrumentationRuby: autoInstrumentationRuby,
v1alpha1.AnnotationDefaultAutoInstrumentationPhp: autoInstrumentationPhp,
},
},
}).SetupWebhookWithManager(mgr); err != nil {
Expand Down
Loading