diff --git a/src/apm/helper.go b/src/apm/helper.go index 1ad78baf..03b5e6f1 100644 --- a/src/apm/helper.go +++ b/src/apm/helper.go @@ -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. diff --git a/src/apm/php.go b/src/apm/php.go index 70984aeb..3230c24f 100644 --- a/src/apm/php.go +++ b/src/apm/php.go @@ -16,6 +16,7 @@ limitations under the License. package apm import ( + "errors" "fmt" corev1 "k8s.io/api/core/v1" @@ -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] @@ -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) + + 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{ + 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) { diff --git a/src/instrumentation/annotation.go b/src/instrumentation/annotation.go index ac24bf6e..ba549791 100644 --- a/src/instrumentation/annotation.go +++ b/src/instrumentation/annotation.go @@ -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. diff --git a/src/instrumentation/upgrade/upgrade.go b/src/instrumentation/upgrade/upgrade.go index 251ca597..14df6066 100644 --- a/src/instrumentation/upgrade/upgrade.go +++ b/src/instrumentation/upgrade/upgrade.go @@ -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] diff --git a/src/main.go b/src/main.go index 438af916..a9a06140 100644 --- a/src/main.go +++ b/src/main.go @@ -87,6 +87,7 @@ func main() { autoInstrumentationPython string autoInstrumentationDotNet string autoInstrumentationRuby string + autoInstrumentationPhp string labelsFilter []string webhookPort int tlsOpt tlsConfig @@ -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.") @@ -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, @@ -143,6 +146,7 @@ func main() { config.WithAutoInstrumentationPythonImage(autoInstrumentationPython), config.WithAutoInstrumentationDotNetImage(autoInstrumentationDotNet), config.WithAutoInstrumentationRubyImage(autoInstrumentationRuby), + config.WithAutoInstrumentationPhpImage(autoInstrumentationPhp), config.WithAutoDetect(ad), config.WithLabelFilters(labelsFilter), ) @@ -204,6 +208,7 @@ func main() { v1alpha1.AnnotationDefaultAutoInstrumentationPython: autoInstrumentationPython, v1alpha1.AnnotationDefaultAutoInstrumentationDotNet: autoInstrumentationDotNet, v1alpha1.AnnotationDefaultAutoInstrumentationRuby: autoInstrumentationRuby, + v1alpha1.AnnotationDefaultAutoInstrumentationPhp: autoInstrumentationPhp, }, }, }).SetupWebhookWithManager(mgr); err != nil {