17 changed files with 657 additions and 116 deletions
@ -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 |
|||
... |
|||
@ -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>(); |
|||
} |
|||
} |
|||
@ -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> |
|||
Loading…
Reference in new issue