mirror of https://github.com/dotnet/tye.git
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