зеркало из https://github.com/dotnet/tye.git
Add a diagnostics sidecar
This commit is contained in:
Родитель
d1d516da34
Коммит
5ab6d46160
|
@ -0,0 +1,40 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: zipkin
|
||||
labels:
|
||||
app.kubernetes.io/name: 'zipkin'
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: zipkin
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: 'zipkin'
|
||||
spec:
|
||||
containers:
|
||||
- name: zipkin
|
||||
image: openzipkin/zipkin
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 9411
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: zipkin
|
||||
labels:
|
||||
app.kubernetes.io/name: 'zipkin'
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: zipkin
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 9411
|
||||
targetPort: 9411
|
||||
...
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -49,12 +50,22 @@ namespace Microsoft.Tye
|
|||
// some duplication with the code in m8s (Application.cs) for populating environments.
|
||||
//
|
||||
// service.Service.Bindings is the bindings OUT - this step computes bindings IN.
|
||||
var bindings = new ComputedBindings();
|
||||
service.Outputs.Add(bindings);
|
||||
service.Outputs.Add(ComputeBindings(application, service.Dependencies));
|
||||
|
||||
foreach (var o in service.Dependencies)
|
||||
foreach (var sidecar in project.Sidecars)
|
||||
{
|
||||
var other = application.Services.Single(a => a.Name == o);
|
||||
sidecar.Outputs.Add(ComputeBindings(application, sidecar.Dependencies));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private ComputedBindings ComputeBindings(ApplicationBuilder application, IEnumerable<string> dependencies)
|
||||
{
|
||||
var bindings = new ComputedBindings();
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
var other = application.Services.Single(a => a.Name == dependency);
|
||||
|
||||
foreach (var binding in other.Bindings)
|
||||
{
|
||||
|
@ -126,7 +137,7 @@ namespace Microsoft.Tye
|
|||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,6 +274,36 @@ namespace Microsoft.Tye
|
|||
spec = new YamlMappingNode();
|
||||
template.Add("spec", spec);
|
||||
|
||||
if (project.Sidecars.Count > 0)
|
||||
{
|
||||
// Share process namespace when we have sidecars. So we can list other processes.
|
||||
// see: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/#understanding-process-namespace-sharing
|
||||
spec.Add("shareProcessNamespace", new YamlScalarNode("true") { Style = ScalarStyle.Plain });
|
||||
}
|
||||
|
||||
if (project.RelocateDiagnosticsDomainSockets)
|
||||
{
|
||||
// Our diagnostics functionality uses $TMPDIR to locate other dotnet processes through
|
||||
// eventpipe. see: https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md#transport
|
||||
//
|
||||
// In order for diagnostics features to 'find' each other, we need to make $TMPDIR into
|
||||
// something shared.
|
||||
//
|
||||
// see: https://kubernetes.io/docs/tasks/access-application-cluster/communicate-containers-same-pod-shared-volume/
|
||||
project.EnvironmentVariables.Add(new EnvironmentVariableBuilder("TMPDIR")
|
||||
{
|
||||
Value = "/var/tye/diagnostics",
|
||||
});
|
||||
|
||||
foreach (var sidecar in project.Sidecars)
|
||||
{
|
||||
sidecar.EnvironmentVariables.Add(new EnvironmentVariableBuilder("TMPDIR")
|
||||
{
|
||||
Value = "/var/tye/diagnostics",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var containers = new YamlSequenceNode();
|
||||
spec.Add("containers", containers);
|
||||
|
||||
|
@ -292,7 +322,7 @@ namespace Microsoft.Tye
|
|||
project.Bindings.Any(b => b.Protocol == "http" || b.Protocol is null) ||
|
||||
|
||||
// We generate environment variables for other services if there dependencies
|
||||
(bindings is object && bindings.Bindings.Any()))
|
||||
bindings?.Bindings.Count > 0)
|
||||
{
|
||||
var env = new YamlSequenceNode();
|
||||
container.Add("env", env);
|
||||
|
@ -308,88 +338,21 @@ namespace Microsoft.Tye
|
|||
|
||||
if (bindings is object)
|
||||
{
|
||||
foreach (var binding in bindings.Bindings.OfType<EnvironmentVariableInputBinding>())
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", binding.Name },
|
||||
{ "value", new YamlScalarNode(binding.Value) { Style = ScalarStyle.SingleQuoted, } },
|
||||
});
|
||||
}
|
||||
AddEnvironmentVariablesForComputedBindings(env, bindings);
|
||||
}
|
||||
|
||||
foreach (var binding in bindings.Bindings.OfType<SecretInputBinding>())
|
||||
{
|
||||
//- name: SECRET_USERNAME
|
||||
// valueFrom:
|
||||
// secretKeyRef:
|
||||
// name: mysecret
|
||||
// key: username
|
||||
if (project.RelocateDiagnosticsDomainSockets)
|
||||
{
|
||||
// volumeMounts:
|
||||
// - name: shared-data
|
||||
// mountPath: /usr/share/nginx/html
|
||||
var volumeMounts = new YamlSequenceNode();
|
||||
container.Add("volumeMounts", volumeMounts);
|
||||
|
||||
if (binding is SecretConnectionStringInputBinding connectionStringBinding)
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", connectionStringBinding.KeyName },
|
||||
{ "valueFrom", new YamlMappingNode()
|
||||
{
|
||||
{ "secretKeyRef", new YamlMappingNode()
|
||||
{
|
||||
{ "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } },
|
||||
{ "key", new YamlScalarNode("connectionstring") { Style = ScalarStyle.SingleQuoted } },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
else if (binding is SecretUrlInputBinding urlBinding)
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", $"{urlBinding.KeyNameBase}__PROTOCOL" },
|
||||
{ "valueFrom", new YamlMappingNode()
|
||||
{
|
||||
{ "secretKeyRef", new YamlMappingNode()
|
||||
{
|
||||
{ "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } },
|
||||
{ "key", new YamlScalarNode("protocol") { Style = ScalarStyle.SingleQuoted } },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", $"{urlBinding.KeyNameBase}__HOST" },
|
||||
{ "valueFrom", new YamlMappingNode()
|
||||
{
|
||||
{ "secretKeyRef", new YamlMappingNode()
|
||||
{
|
||||
{ "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } },
|
||||
{ "key", new YamlScalarNode("host") { Style = ScalarStyle.SingleQuoted } },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", $"{urlBinding.KeyNameBase}__PORT" },
|
||||
{ "valueFrom", new YamlMappingNode()
|
||||
{
|
||||
{ "secretKeyRef", new YamlMappingNode()
|
||||
{
|
||||
{ "name", new YamlScalarNode(binding.Name) { Style = ScalarStyle.SingleQuoted } },
|
||||
{ "key", new YamlScalarNode("port") { Style = ScalarStyle.SingleQuoted } },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
var volumeMount = new YamlMappingNode();
|
||||
volumeMounts.Add(volumeMount);
|
||||
volumeMount.Add("name", "tye-diagnostics");
|
||||
volumeMount.Add("mountPath", "/var/tye/diagnostics");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +380,129 @@ namespace Microsoft.Tye
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var sidecar in project.Sidecars)
|
||||
{
|
||||
var container = new YamlMappingNode();
|
||||
containers.Add(container);
|
||||
container.Add("name", sidecar.Name); // NOTE: to really support multiple images we'd need to generate unique names.
|
||||
container.Add("image", $"{sidecar.ImageName}:{sidecar.ImageTag}");
|
||||
container.Add("imagePullPolicy", "Always"); // helps avoid problems with development + weak versioning
|
||||
|
||||
if (sidecar.Args.Count > 0)
|
||||
{
|
||||
var args = new YamlSequenceNode();
|
||||
container.Add("args", args);
|
||||
|
||||
foreach (var arg in sidecar.Args)
|
||||
{
|
||||
args.Add(new YamlScalarNode(arg) { Style = ScalarStyle.SingleQuoted, });
|
||||
}
|
||||
}
|
||||
|
||||
var sidecarBindings = sidecar.Outputs.OfType<ComputedBindings>().FirstOrDefault();
|
||||
if (sidecar.EnvironmentVariables.Count > 0 || sidecarBindings?.Bindings.Count > 0)
|
||||
{
|
||||
var env = new YamlSequenceNode();
|
||||
container.Add("env", env);
|
||||
|
||||
foreach (var kvp in sidecar.EnvironmentVariables)
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", kvp.Name },
|
||||
{ "value", new YamlScalarNode(kvp.Value) { Style = ScalarStyle.SingleQuoted, } },
|
||||
});
|
||||
}
|
||||
|
||||
if (sidecarBindings is object)
|
||||
{
|
||||
AddEnvironmentVariablesForComputedBindings(env, sidecarBindings);
|
||||
}
|
||||
}
|
||||
|
||||
if (project.RelocateDiagnosticsDomainSockets)
|
||||
{
|
||||
// volumeMounts:
|
||||
// - name: shared-data
|
||||
// mountPath: /usr/share/nginx/html
|
||||
var volumeMounts = new YamlSequenceNode();
|
||||
container.Add("volumeMounts", volumeMounts);
|
||||
|
||||
var volumeMount = new YamlMappingNode();
|
||||
volumeMounts.Add(volumeMount);
|
||||
volumeMount.Add("name", "tye-diagnostics");
|
||||
volumeMount.Add("mountPath", "/var/tye/diagnostics");
|
||||
}
|
||||
}
|
||||
|
||||
if (project.RelocateDiagnosticsDomainSockets)
|
||||
{
|
||||
// volumes:
|
||||
// - name: shared-data
|
||||
// emptyDir: {}
|
||||
var volumes = new YamlSequenceNode();
|
||||
spec.Add("volumes", volumes);
|
||||
|
||||
var volume = new YamlMappingNode();
|
||||
volumes.Add(volume);
|
||||
volume.Add("name", "tye-diagnostics");
|
||||
volume.Add("emptyDir", new YamlMappingNode());
|
||||
}
|
||||
|
||||
return new KubernetesDeploymentOutput(project.Name, new YamlDocument(root));
|
||||
}
|
||||
|
||||
private static void AddEnvironmentVariablesForComputedBindings(YamlSequenceNode env, ComputedBindings bindings)
|
||||
{
|
||||
foreach (var binding in bindings.Bindings.OfType<EnvironmentVariableInputBinding>())
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", binding.Name },
|
||||
{ "value", new YamlScalarNode(binding.Value) { Style = ScalarStyle.SingleQuoted, } },
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var binding in bindings.Bindings.OfType<SecretInputBinding>())
|
||||
{
|
||||
//- name: SECRET_USERNAME
|
||||
// valueFrom:
|
||||
// secretKeyRef:
|
||||
// name: mysecret
|
||||
// key: username
|
||||
|
||||
if (binding is SecretConnectionStringInputBinding connectionStringBinding)
|
||||
{
|
||||
AddSecret(env, connectionStringBinding.KeyName, binding.Name, "connectionstring");
|
||||
|
||||
}
|
||||
else if (binding is SecretUrlInputBinding urlBinding)
|
||||
{
|
||||
AddSecret(env, $"{urlBinding.KeyNameBase}__PROTOCOL", binding.Name, "protocol");
|
||||
AddSecret(env, $"{urlBinding.KeyNameBase}__HOST", binding.Name, "host");
|
||||
AddSecret(env, $"{urlBinding.KeyNameBase}__PORT", binding.Name, "port");
|
||||
}
|
||||
}
|
||||
|
||||
static void AddSecret(YamlSequenceNode env, string name, string secret, string key)
|
||||
{
|
||||
env.Add(new YamlMappingNode()
|
||||
{
|
||||
{ "name", name },
|
||||
{
|
||||
"valueFrom", new YamlMappingNode()
|
||||
{
|
||||
{
|
||||
"secretKeyRef", new YamlMappingNode()
|
||||
{
|
||||
{ "name", new YamlScalarNode(secret) { Style = ScalarStyle.SingleQuoted } },
|
||||
{ "key", new YamlScalarNode(key) { Style = ScalarStyle.SingleQuoted } },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace Microsoft.Tye
|
|||
public string PublishDir { get; set; } = default!;
|
||||
public string IntermediateOutputPath { get; set; } = default!;
|
||||
public bool IsAspNet { get; set; }
|
||||
public bool RelocateDiagnosticsDomainSockets { get; set; }
|
||||
|
||||
// Data used for building containers
|
||||
public ContainerInfo? ContainerInfo { get; set; }
|
||||
|
@ -51,5 +52,7 @@ namespace Microsoft.Tye
|
|||
public List<VolumeBuilder> Volumes { get; } = new List<VolumeBuilder>();
|
||||
|
||||
public Dictionary<string, string> BuildProperties { get; } = new Dictionary<string, string>();
|
||||
|
||||
public List<SidecarBuilder> Sidecars { get; } = new List<SidecarBuilder>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Tye
|
||||
{
|
||||
public class SidecarBuilder
|
||||
{
|
||||
public SidecarBuilder(string name, string imageName, string imageTag)
|
||||
{
|
||||
Name = name;
|
||||
ImageName = imageName;
|
||||
ImageTag = imageTag;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string ImageName { get; set; }
|
||||
|
||||
public string ImageTag { get; set; }
|
||||
|
||||
public List<string> Args { get; } = new List<string>();
|
||||
|
||||
public HashSet<string> Dependencies { get; } = new HashSet<string>();
|
||||
|
||||
public List<ServiceOutput> Outputs { get; } = new List<ServiceOutput>();
|
||||
|
||||
public List<EnvironmentVariableBuilder> EnvironmentVariables { get; } = new List<EnvironmentVariableBuilder>();
|
||||
}
|
||||
}
|
|
@ -14,50 +14,75 @@ namespace Microsoft.Tye.Extensions.Zipkin
|
|||
{
|
||||
public override Task ProcessAsync(ExtensionContext context, ExtensionConfiguration config)
|
||||
{
|
||||
if (context.Operation == ExtensionContext.OperationKind.LocalRun)
|
||||
if (context.Application.Services.Any(s => s.Name == "zipkin"))
|
||||
{
|
||||
if (context.Application.Services.Any(s => s.Name == "zipkin"))
|
||||
context.Output.WriteDebugLine("zipkin service already configured. Skipping...");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Output.WriteDebugLine("Injecting zipkin service...");
|
||||
var service = new ContainerServiceBuilder("zipkin", "openzipkin/zipkin")
|
||||
{
|
||||
context.Output.WriteDebugLine("zipkin service already configured. Skipping...");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Output.WriteDebugLine("Injecting zipkin service...");
|
||||
|
||||
var service = new ContainerServiceBuilder("zipkin", "openzipkin/zipkin")
|
||||
Bindings =
|
||||
{
|
||||
Bindings =
|
||||
new BindingBuilder()
|
||||
{
|
||||
new BindingBuilder()
|
||||
{
|
||||
Port = 9411,
|
||||
ContainerPort = 9411,
|
||||
Protocol = "http",
|
||||
},
|
||||
Port = 9411,
|
||||
ContainerPort = 9411,
|
||||
Protocol = "http",
|
||||
},
|
||||
};
|
||||
context.Application.Services.Add(service);
|
||||
},
|
||||
};
|
||||
context.Application.Services.Add(service);
|
||||
|
||||
foreach (var s in context.Application.Services)
|
||||
foreach (var s in context.Application.Services)
|
||||
{
|
||||
if (object.ReferenceEquals(s, service))
|
||||
{
|
||||
if (object.ReferenceEquals(s, service))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// make zipkin available as a dependency of everything.
|
||||
if (!s.Dependencies.Contains(service.Name))
|
||||
{
|
||||
s.Dependencies.Add(service.Name);
|
||||
}
|
||||
// make zipkin available as a dependency of everything.
|
||||
if (!s.Dependencies.Contains(service.Name))
|
||||
{
|
||||
s.Dependencies.Add(service.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Operation == ExtensionContext.OperationKind.LocalRun)
|
||||
{
|
||||
if (context.Options!.DistributedTraceProvider is null)
|
||||
{
|
||||
// For local development we hardcode the port and hostname
|
||||
context.Options.DistributedTraceProvider = "zipkin=http://localhost:9411";
|
||||
}
|
||||
}
|
||||
else if (context.Operation == ExtensionContext.OperationKind.Deploy)
|
||||
{
|
||||
foreach (var project in context.Application.Services.OfType<ProjectServiceBuilder>())
|
||||
{
|
||||
// Bring your rain boots.
|
||||
project.RelocateDiagnosticsDomainSockets = true;
|
||||
|
||||
var sidecar = new SidecarBuilder("tye-diag-agent", "rynowak/tye-diag-agent", "0.1")
|
||||
{
|
||||
Args =
|
||||
{
|
||||
"--kubernetes=true",
|
||||
"--provider:0=zipkin=service:zipkin",
|
||||
$"--service={project.Name}",
|
||||
$"--assemblyName={project.AssemblyName}",
|
||||
},
|
||||
Dependencies =
|
||||
{
|
||||
// Inject the zipkin service discovery variables
|
||||
"zipkin",
|
||||
},
|
||||
};
|
||||
project.Sidecars.Add(sidecar);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace Microsoft.Tye.Hosting.Diagnostics
|
|||
{
|
||||
argsJson = exceptionJson;
|
||||
exceptionJson = eventName;
|
||||
eventName = null;
|
||||
eventName = null!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(argsJson))
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Tye.Hosting.Diagnostics;
|
||||
|
||||
namespace Microsoft.Tye
|
||||
{
|
||||
public class DiagnosticsMonitor : BackgroundService
|
||||
{
|
||||
private readonly ILogger<DiagnosticsMonitor> logger;
|
||||
private readonly IOptions<DiagnosticsMonitorOptions> options;
|
||||
|
||||
public DiagnosticsMonitor(ILogger<DiagnosticsMonitor> logger, IOptions<DiagnosticsMonitorOptions> options)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var collector = InitializeCollector();
|
||||
|
||||
var replicaInfo = new ReplicaInfo(
|
||||
selector: processes =>
|
||||
{
|
||||
// Find process by looking for the entry point dll in the arguments.
|
||||
return processes.FirstOrDefault(p =>
|
||||
{
|
||||
logger.LogDebug("Checking process {PID}.", p.Id);
|
||||
|
||||
var command = GetCommand(p);
|
||||
logger.LogDebug("Searching command '{Command}'.", command);
|
||||
|
||||
return command.Contains($"{options.Value.AssemblyName}.dll");
|
||||
});
|
||||
},
|
||||
assemblyName: options.Value.AssemblyName,
|
||||
service: options.Value.Service,
|
||||
replica: options.Value.Kubernetes ? Dns.GetHostName() : options.Value.Service,
|
||||
metrics: new ConcurrentDictionary<string, string>());
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
// The collector has a timeout in waiting for its filter to pass, so we
|
||||
// won't burn the CPU to a crisp by doing this repetatively.
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Starting data collection");
|
||||
await collector.CollectAsync(replicaInfo, stoppingToken);
|
||||
}
|
||||
catch (Exception ex) when (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogError(ex, "Data collection threw an exception.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DiagnosticsCollector InitializeCollector()
|
||||
{
|
||||
var collector = new DiagnosticsCollector(this.logger)
|
||||
{
|
||||
SelectProcessTimeout = TimeSpan.FromSeconds(60),
|
||||
};
|
||||
|
||||
foreach (var provider in options.Value.Providers)
|
||||
{
|
||||
if (!DiagnosticsProvider.WellKnownProviders.TryGetValue(provider.Key, out var wellKnown))
|
||||
{
|
||||
logger.LogError("Unknown provider type {Provider}. Skipping.", provider.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (wellKnown.Kind)
|
||||
{
|
||||
case DiagnosticsProvider.ProviderKind.Logging:
|
||||
{
|
||||
if (collector.LoggingSink is object)
|
||||
{
|
||||
logger.LogError("Logging is already initialized. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation(wellKnown.LogFormat, provider.Key);
|
||||
collector.LoggingSink = new LoggingSink(logger, provider);
|
||||
break;
|
||||
}
|
||||
|
||||
case DiagnosticsProvider.ProviderKind.Metrics:
|
||||
{
|
||||
if (collector.MetricSink is object)
|
||||
{
|
||||
logger.LogError("Metrics is already initialized. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO metrics
|
||||
break;
|
||||
}
|
||||
|
||||
case DiagnosticsProvider.ProviderKind.Tracing:
|
||||
{
|
||||
if (collector.TracingSink is object)
|
||||
{
|
||||
logger.LogError("Tracing is already initialized. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation(wellKnown.LogFormat, provider.Value);
|
||||
collector.TracingSink = new TracingSink(logger, provider);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.LogError("Unknown provider type. Skipping.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
private static string GetCommand(Process target)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
using var process = new Process()
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = "ps",
|
||||
Arguments = $"-p {target.Id.ToString(CultureInfo.InvariantCulture)} -o command=",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.StandardOutput.ReadToEnd().Trim();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Tye
|
||||
{
|
||||
public class DiagnosticsMonitorOptions
|
||||
{
|
||||
public bool Kubernetes { get; set; }
|
||||
|
||||
public string Service { get; set; } = default!;
|
||||
|
||||
public string AssemblyName { get; set; } = default!;
|
||||
|
||||
public List<DiagnosticsProvider> Providers { get; } = new List<DiagnosticsProvider>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
FROM mcr.microsoft.com/dotnet/core/runtime:3.1
|
||||
WORKDIR /app
|
||||
|
||||
# We need `ps`
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y procps \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . .
|
||||
ENTRYPOINT ["dotnet", "tye-diag-agent.dll"]
|
|
@ -0,0 +1,67 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.Tye
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
var configuration = hostContext.Configuration;
|
||||
services.Configure<DiagnosticsMonitorOptions>(options =>
|
||||
{
|
||||
options.Kubernetes = string.Equals(bool.TrueString, configuration["kubernetes"], StringComparison.OrdinalIgnoreCase);
|
||||
options.AssemblyName = configuration["assemblyName"];
|
||||
options.Service = configuration["service"];
|
||||
|
||||
var section = configuration.GetSection("provider");
|
||||
foreach (var child in section.GetChildren())
|
||||
{
|
||||
if (!DiagnosticsProvider.TryParse(child.Value, DiagnosticsProvider.ProviderKind.Unknown, out var provider))
|
||||
{
|
||||
throw new InvalidOperationException("Could not parse diagnostics provider: " + child.Value);
|
||||
}
|
||||
|
||||
// The value used to connect to the service should be accessible with Tye service discovery.
|
||||
if (provider.Value is string && provider.Value.StartsWith("service:"))
|
||||
{
|
||||
var service = provider.Value.Substring("service:".Length);
|
||||
var value = configuration.GetServiceUri(service)?.AbsoluteUri ?? configuration.GetConnectionString(service);
|
||||
provider = new DiagnosticsProvider(provider.Key, value, provider.Kind);
|
||||
}
|
||||
|
||||
options.Providers.Add(provider);
|
||||
}
|
||||
|
||||
if (options.Providers.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("At least one provider must be configured.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(options.AssemblyName))
|
||||
{
|
||||
throw new InvalidOperationException("The assembly name is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(options.Service))
|
||||
{
|
||||
throw new InvalidOperationException("The service name is required.");
|
||||
}
|
||||
});
|
||||
services.AddHostedService<DiagnosticsMonitor>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"profiles": {
|
||||
"tye_diag_agent": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Tye": "Debug",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Tye": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
source="${BASH_SOURCE[0]}"
|
||||
|
||||
# resolve $SOURCE until the file is no longer a symlink
|
||||
while [[ -h $source ]]; do
|
||||
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
|
||||
source="$(readlink "$source")"
|
||||
|
||||
# if $source was a relative symlink, we need to resolve it relative to the path where the
|
||||
# symlink file was located
|
||||
[[ $source != /* ]] && source="$scriptroot/$source"
|
||||
done
|
||||
|
||||
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
|
||||
|
||||
rm -rf "$scriptroot/../../artifacts/tye-diag-agent-publish"
|
||||
dotnet publish "$scriptroot" \
|
||||
-c Release \
|
||||
-o "$scriptroot/../../artifacts/tye-diag-agent-publish" \
|
||||
-r linux-x64 --self-contained false
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
docker build "$scriptroot/../../artifacts/tye-diag-agent-publish" -f "$scriptroot/Dockerfile"
|
||||
else
|
||||
docker build "$scriptroot/../../artifacts/tye-diag-agent-publish" -f "$scriptroot/Dockerfile" -t "rynowak/tye-diag-agent:$1"
|
||||
fi
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<RootNamespace>Microsoft.Tye</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Tye.Hosting.Diagnostics\Microsoft.Tye.Hosting.Diagnostics.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
15
tye.sln
15
tye.sln
|
@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Extensions.Co
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Infrastructure", "test\Test.Infrastructure\Test.Infrastructure.csproj", "{F9A7C801-26F3-4D20-B084-2A6A6343E869}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tye-diag-agent", "src\tye-diag-agent\tye-diag-agent.csproj", "{7173D2B6-B102-4DCA-8000-5DAE684BFC13}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -185,6 +187,18 @@ Global
|
|||
{F9A7C801-26F3-4D20-B084-2A6A6343E869}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F9A7C801-26F3-4D20-B084-2A6A6343E869}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F9A7C801-26F3-4D20-B084-2A6A6343E869}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -202,6 +216,7 @@ Global
|
|||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7} = {F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}
|
||||
{F9A7C801-26F3-4D20-B084-2A6A6343E869} = {F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}
|
||||
{7173D2B6-B102-4DCA-8000-5DAE684BFC13} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D8002603-BB27-4500-BF86-274A8E72D302}
|
||||
|
|
Загрузка…
Ссылка в новой задаче