From ece560d54546f21aa2994280f06d2efc2bd8c140 Mon Sep 17 00:00:00 2001 From: Andrew Lozoya Date: Thu, 21 Nov 2024 09:17:48 -0600 Subject: [PATCH] Edge case supporting IIS restarts --- src/apm/dotnet_windows/BeforeSetup.ps1 | 30 ++++ src/apm/dotnet_windows/Dockerfile.windows | 2 + src/apm/dotnetedgewindows.go | 171 ++++++++++++++++++++++ src/instrumentation/podmutator_test.go | 1 + 4 files changed, 204 insertions(+) create mode 100644 src/apm/dotnet_windows/BeforeSetup.ps1 create mode 100644 src/apm/dotnetedgewindows.go diff --git a/src/apm/dotnet_windows/BeforeSetup.ps1 b/src/apm/dotnet_windows/BeforeSetup.ps1 new file mode 100644 index 00000000..48ecd12a --- /dev/null +++ b/src/apm/dotnet_windows/BeforeSetup.ps1 @@ -0,0 +1,30 @@ +# Allow some time before starting IIS +Start-Sleep -Seconds 5 + +# Log the restarting message +Write-Output "Restarting IIS service..." + +# Restart IIS +Stop-Service -Name 'w3svc' -ErrorAction Stop +Start-Service -Name 'w3svc' -ErrorAction Stop + +# Log that IIS has been restarted +Write-Output "IIS service has been restarted." + +# Monitor IIS status and ensure it's running and alive +while ($true) { + try { + $iisStatus = Get-Service -Name 'w3svc' -ErrorAction Stop + if ($iisStatus.Status -eq 'Running') { + Write-Output "IIS is healthy." + } + else { + Write-Output "IIS is not running. Attempting to restart..." + Start-Service -Name 'w3svc' -ErrorAction Stop + } + } + catch { + Write-Output "Failed to get IIS status or restart IIS: $_" + } + Start-Sleep -Seconds 10 +} diff --git a/src/apm/dotnet_windows/Dockerfile.windows b/src/apm/dotnet_windows/Dockerfile.windows index f2dd406b..7ffb8bbb 100644 --- a/src/apm/dotnet_windows/Dockerfile.windows +++ b/src/apm/dotnet_windows/Dockerfile.windows @@ -8,6 +8,8 @@ ARG VERSION # Set the working directory WORKDIR C:\\instrumentation +COPY ./BeforeSetup.ps1 BeforeSetup.ps1 + # Download and extract the New Relic .NET agent RUN curl.exe -L -o newrelic-agent.zip https://download.newrelic.com/dot_net_agent/latest_release/NewRelicDotNetAgent_%VERSION%_x64.zip\ && tar.exe -xzf newrelic-agent.zip\ diff --git a/src/apm/dotnetedgewindows.go b/src/apm/dotnetedgewindows.go new file mode 100644 index 00000000..e60e42eb --- /dev/null +++ b/src/apm/dotnetedgewindows.go @@ -0,0 +1,171 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package apm + +import ( + "context" + "errors" + + corev1 "k8s.io/api/core/v1" + + "github.com/newrelic-experimental/k8s-agents-operator-windows/src/api/v1alpha2" +) + +const ( + //dotnetEdge Framework + envdotnetEdgeFrameworkWindowsClrEnableProfiling = "COR_ENABLE_PROFILING" + envdotnetEdgeFrameworkWindowsClrProfiler = "COR_PROFILER" + envdotnetEdgeFrameworkWindowsClrProfilerPath = "COR_PROFILER_PATH" + envdotnetEdgeFrameworkWindowsNewrelicHome = "NEWRELIC_HOME" + dotnetEdgeFrameworkWindowsClrEnableProfilingEnabled = "1" + dotnetEdgeFrameworkWindowsClrProfilerID = "{71DA0A04-7777-4EC6-9643-7D28B46A8A41}" + dotnetEdgeFrameworkWindowsClrProfilerPath = "C:\\newrelic-instrumentation\\netframework\\NewRelic.Profiler.dll" + dotnetEdgeFrameworkWindowsNewrelicHomePath = "C:\\newrelic-instrumentation\\netframework" + + //dotnetEdge Core + envdotnetEdgeWindowsClrEnableProfiling = "CORECLR_ENABLE_PROFILING" + envdotnetEdgeWindowsClrProfiler = "CORECLR_PROFILER" + envdotnetEdgeWindowsClrProfilerPath = "CORECLR_PROFILER_PATH" + envdotnetEdgeWindowsNewrelicHome = "CORECLR_NEWRELIC_HOME" + dotnetEdgeCoreWindowsClrEnableProfilingEnabled = "1" + dotnetEdgeCoreWindowsClrProfilerID = "{36032161-FFC0-4B61-B559-F6C5D41BAE5A}" + dotnetEdgeCoreWindowsClrProfilerPath = "C:\\newrelic-instrumentation\\netcore\\NewRelic.Profiler.dll" + dotnetEdgeCoreWindowsNewrelicHomePath = "C:\\newrelic-instrumentation\\netcore" + dotnetEdgeWindowsInitContainerName = initContainerName + "-dotnetEdge-windows" +) + +var _ Injector = (*DotnetEdgeWindowsInjector)(nil) + +func init() { + DefaultInjectorRegistry.MustRegister(&DotnetEdgeWindowsInjector{}) +} + +type DotnetEdgeWindowsInjector struct { + baseInjector +} + +func (i *DotnetEdgeWindowsInjector) Language() string { + return "dotnetEdge-windows" +} + +func (i *DotnetEdgeWindowsInjector) acceptable(inst v1alpha2.Instrumentation, pod corev1.Pod) bool { + if inst.Spec.Agent.Language != i.Language() { + return false + } + if len(pod.Spec.Containers) == 0 { + return false + } + return true +} + +func (i DotnetEdgeWindowsInjector) Inject(ctx context.Context, inst v1alpha2.Instrumentation, ns corev1.Namespace, pod corev1.Pod) (corev1.Pod, error) { + if !i.acceptable(inst, pod) { + return pod, nil + } + if err := i.validate(inst); err != nil { + return pod, err + } + + firstContainer := 0 + // caller checks if there is at least one container. + container := &pod.Spec.Containers[firstContainer] + + // check if CORECLR_NEWRELIC_HOME env var is already set in the container + if getIndexOfEnv(container.Env, envdotnetEdgeWindowsNewrelicHome) > -1 { + return pod, errors.New("CORECLR_NEWRELIC_HOME environment variable is already set in the container") + } + + // check if NEWRELIC_HOME env var is already set in the container + if getIndexOfEnv(container.Env, envdotnetEdgeFrameworkWindowsNewrelicHome) > -1 { + return pod, errors.New("NEWRELIC_HOME environment variable is already set in the container") + } + + // inject .NET instrumentation spec env vars. + for _, env := range inst.Spec.Agent.Env { + idx := getIndexOfEnv(container.Env, env.Name) + if idx == -1 { + container.Env = append(container.Env, env) + } + } + + setEnvVar(container, envdotnetEdgeWindowsClrEnableProfiling, dotnetEdgeCoreWindowsClrEnableProfilingEnabled, false) + setEnvVar(container, envdotnetEdgeWindowsClrProfiler, dotnetEdgeCoreWindowsClrProfilerID, false) + setEnvVar(container, envdotnetEdgeWindowsClrProfilerPath, dotnetEdgeCoreWindowsClrProfilerPath, false) + setEnvVar(container, envdotnetEdgeWindowsNewrelicHome, dotnetEdgeCoreWindowsNewrelicHomePath, false) + setEnvVar(container, envdotnetEdgeFrameworkWindowsClrEnableProfiling, dotnetEdgeFrameworkWindowsClrEnableProfilingEnabled, false) + setEnvVar(container, envdotnetEdgeFrameworkWindowsClrProfiler, dotnetEdgeFrameworkWindowsClrProfilerID, false) + setEnvVar(container, envdotnetEdgeFrameworkWindowsClrProfilerPath, dotnetEdgeFrameworkWindowsClrProfilerPath, false) + setEnvVar(container, envdotnetEdgeFrameworkWindowsNewrelicHome, dotnetEdgeFrameworkWindowsNewrelicHomePath, false) + + // Add mount point for Scripts directory + if isContainerVolumeMissing(container, volumeName) { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: "C:\\newrelic-instrumentation", + }) + } + + if isContainerVolumeMissing(container, "scripts-volume") { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: "scripts-volume", + MountPath: "C:\\inetpub\\wwwroot\\Scripts", + }) + } + + // Update the InitContainer command to include the xcopy command for BeforeSetup.ps1 + if isInitContainerMissing(pod, dotnetEdgeWindowsInitContainerName) { + if isPodVolumeMissing(pod, volumeName) { + pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + } + if isPodVolumeMissing(pod, "scripts-volume") { + hostPathType := corev1.HostPathDirectory + pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ + Name: "scripts-volume", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "C:\\inetpub\\wwwroot\\Scripts", + Type: &hostPathType, + }, + }, + }) + } + + pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{ + Name: dotnetEdgeWindowsInitContainerName, + Image: inst.Spec.Agent.Image, + Command: []string{"cmd", "/C", "xcopy C:\\instrumentation C:\\newrelic-instrumentation /E /I /H /Y && xcopy C:\\instrumentation\\BeforeSetup.ps1 C:\\inetpub\\wwwroot\\Scripts\\BeforeSetup.ps1 /Y"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "C:\\newrelic-instrumentation", + }, + { + Name: "scripts-volume", + MountPath: "C:\\inetpub\\wwwroot\\Scripts", + }, + }, + }) + } + + pod = i.injectNewrelicConfig(ctx, inst.Spec.Resource, ns, pod, firstContainer, inst.Spec.LicenseKeySecret) + + return pod, nil +} diff --git a/src/instrumentation/podmutator_test.go b/src/instrumentation/podmutator_test.go index 456cceea..af54d908 100644 --- a/src/instrumentation/podmutator_test.go +++ b/src/instrumentation/podmutator_test.go @@ -277,6 +277,7 @@ func TestMutatePod(t *testing.T) { apmInjectors := []apm.Injector{ &apm.DotnetInjector{}, &apm.DotnetWindowsInjector{}, + &apm.DotnetEdgeWindowsInjector{}, &apm.GoInjector{}, &apm.JavaInjector{}, &apm.NodejsInjector{},