зеркало из https://github.com/dotnet/tye.git
Added container proxy for talking to the host (#292)
* Added container proxy for talking to the host - This change introduces a container proxy which makes it possible for docker containers can talk to host services using container networking. These proxies will not show up in the dashboard as they are "infrastructure" containers. - Added Private and NetworkAlias to DockerRunInfo but did not expose these configuration. - All container communication is done using host names. - Fix host shutdown again - Bind to all interfaces on linux
This commit is contained in:
Родитель
9873e05f2f
Коммит
d2be7d504f
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ApplicationA
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:2755",
|
||||
"sslPort": 44369
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"ApplicationA": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ApplicationA
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from Application A " + Environment.GetEnvironmentVariable("APP_INSTANCE") ?? Environment.GetEnvironmentVariable("HOSTNAME"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ApplicationB
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:19251",
|
||||
"sslPort": 44343
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"ApplicationB": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ApplicationB
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from Application B " + Environment.GetEnvironmentVariable("APP_INSTANCE") ?? Environment.GetEnvironmentVariable("HOSTNAME"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationA", "ApplicationA\ApplicationA.csproj", "{5A9DC239-55BB-4951-B081-35931BF8C867}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationB", "ApplicationB\ApplicationB.csproj", "{AE1F10D3-BFAE-4D23-ADCF-06770237285D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5A9DC239-55BB-4951-B081-35931BF8C867}.Release|x86.Build.0 = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{AE1F10D3-BFAE-4D23-ADCF-06770237285D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,25 @@
|
|||
server {
|
||||
listen 80;
|
||||
|
||||
location /A {
|
||||
proxy_pass http://appA/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /B {
|
||||
proxy_pass http://appB/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
services:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
bindings:
|
||||
- protocol: http
|
||||
volumes:
|
||||
- source: nginx.conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
- name: appA
|
||||
project: ApplicationA/ApplicationA.csproj
|
||||
bindings:
|
||||
replicas: 2
|
||||
- name: appB
|
||||
project: ApplicationB/ApplicationB.csproj
|
||||
bindings:
|
||||
replicas: 2
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Tye.Hosting.Model;
|
||||
|
||||
|
@ -28,10 +29,28 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
public async Task StopAsync(Application application)
|
||||
{
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
// Shutdown in the opposite order
|
||||
foreach (var processor in _applicationProcessors.Reverse())
|
||||
{
|
||||
await processor.StopAsync(application);
|
||||
try
|
||||
{
|
||||
await processor.StopAsync(application);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count == 1)
|
||||
{
|
||||
ExceptionDispatchInfo.Throw(exceptions[0]);
|
||||
}
|
||||
else if (exceptions.Count > 0)
|
||||
{
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,49 @@ namespace Microsoft.Tye.Hosting
|
|||
return;
|
||||
}
|
||||
|
||||
var proxies = new List<Service>();
|
||||
foreach (var service in application.Services.Values)
|
||||
{
|
||||
if (service.Description.RunInfo is DockerRunInfo || service.Description.Bindings.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Inject a proxy per non-container service. This allows the container to use normal host names within the
|
||||
// container network to talk to services on the host
|
||||
var proxyContanier = new DockerRunInfo($"mcr.microsoft.com/dotnet/core/sdk:3.1", "dotnet Microsoft.Tye.Proxy.dll")
|
||||
{
|
||||
WorkingDirectory = "/app",
|
||||
NetworkAlias = service.Description.Name,
|
||||
Private = true
|
||||
};
|
||||
var proxyLocation = Path.GetDirectoryName(typeof(Microsoft.Tye.Proxy.Program).Assembly.Location);
|
||||
proxyContanier.VolumeMappings.Add(new DockerVolume(proxyLocation, name: null, target: "/app"));
|
||||
var proxyDescription = new ServiceDescription($"{service.Description.Name}-proxy", proxyContanier);
|
||||
foreach (var binding in service.Description.Bindings)
|
||||
{
|
||||
if (binding.Port == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var b = new ServiceBinding()
|
||||
{
|
||||
ConnectionString = binding.ConnectionString,
|
||||
Host = binding.Host,
|
||||
ContainerPort = binding.ContainerPort,
|
||||
Name = binding.Name,
|
||||
Port = binding.Port,
|
||||
Protocol = binding.Protocol
|
||||
};
|
||||
b.ReplicaPorts.Add(b.Port.Value);
|
||||
proxyDescription.Bindings.Add(b);
|
||||
}
|
||||
var proxyContanierService = new Service(proxyDescription);
|
||||
containers.Add(proxyContanierService);
|
||||
proxies.Add(proxyContanierService);
|
||||
}
|
||||
|
||||
string? dockerNetwork = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(application.Network))
|
||||
|
@ -91,6 +134,9 @@ namespace Microsoft.Tye.Hosting
|
|||
await ProcessUtil.RunAsync("docker", command);
|
||||
}
|
||||
|
||||
// Stash information outside of the application services
|
||||
application.Items[typeof(DockerApplicationInformation)] = new DockerApplicationInformation(dockerNetwork, proxies);
|
||||
|
||||
var tasks = new Task[containers.Count];
|
||||
var index = 0;
|
||||
|
||||
|
@ -106,23 +152,34 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
public async Task StopAsync(Application application)
|
||||
{
|
||||
if (!application.Items.TryGetValue(typeof(DockerApplicationInformation), out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var info = (DockerApplicationInformation)value;
|
||||
|
||||
var services = application.Services;
|
||||
|
||||
var index = 0;
|
||||
var tasks = new Task[services.Count];
|
||||
var tasks = new Task[services.Count + info.Proxies.Count];
|
||||
foreach (var s in services.Values)
|
||||
{
|
||||
var state = s;
|
||||
tasks[index++] = StopContainerAsync(state);
|
||||
tasks[index++] = StopContainerAsync(s);
|
||||
}
|
||||
|
||||
foreach (var s in info.Proxies)
|
||||
{
|
||||
tasks[index++] = StopContainerAsync(s);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (string.IsNullOrEmpty(application.Network) && application.Items.TryGetValue("dockerNetwork", out var dockerNetwork))
|
||||
if (string.IsNullOrEmpty(application.Network) && !string.IsNullOrEmpty(info.DockerNetwork))
|
||||
{
|
||||
_logger.LogInformation("Removing docker network {Network}", dockerNetwork);
|
||||
_logger.LogInformation("Removing docker network {Network}", info.DockerNetwork);
|
||||
|
||||
var command = $"network rm {dockerNetwork}";
|
||||
var command = $"network rm {info.DockerNetwork}";
|
||||
|
||||
_logger.LogInformation("Running docker command {Command}", command);
|
||||
|
||||
|
@ -189,7 +246,7 @@ namespace Microsoft.Tye.Hosting
|
|||
// These are the ports that the application should use for binding
|
||||
|
||||
// 1. Tell the docker container what port to bind to
|
||||
portString = string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.ContainerPort ?? p.Port}"));
|
||||
portString = docker.Private ? "" : string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.ContainerPort ?? p.Port}"));
|
||||
|
||||
// 2. Configure ASP.NET Core to bind to those same ports
|
||||
environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://*:{p.ContainerPort ?? p.Port}"));
|
||||
|
@ -206,6 +263,9 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
// 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list.
|
||||
environment["PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}"));
|
||||
|
||||
// This the port for the container proxy (containerport:externalport)
|
||||
environment["PROXY_PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}:{p.ExternalPort}"));
|
||||
}
|
||||
|
||||
// See: https://github.com/docker/for-linux/issues/264
|
||||
|
@ -215,6 +275,7 @@ namespace Microsoft.Tye.Hosting
|
|||
application.PopulateEnvironment(service, (key, value) => environment[key] = value, hostname);
|
||||
|
||||
environment["APP_INSTANCE"] = replica;
|
||||
environment["CONTAINER_HOST"] = hostname;
|
||||
|
||||
status.Environment = environment;
|
||||
|
||||
|
@ -284,9 +345,9 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
if (!string.IsNullOrEmpty(dockerNetwork))
|
||||
{
|
||||
status.DockerNetworkAlias = serviceDescription.Name;
|
||||
status.DockerNetworkAlias = docker.NetworkAlias ?? serviceDescription.Name;
|
||||
|
||||
var networkCommand = $"network connect {dockerNetwork} {replica} --alias {serviceDescription.Name}";
|
||||
var networkCommand = $"network connect {dockerNetwork} {replica} --alias {status.DockerNetworkAlias}";
|
||||
|
||||
service.Logs.OnNext($"[{replica}]: docker {networkCommand}");
|
||||
|
||||
|
@ -478,5 +539,18 @@ namespace Microsoft.Tye.Hosting
|
|||
public Task[] Tasks { get; }
|
||||
public CancellationTokenSource StoppingTokenSource { get; } = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private class DockerApplicationInformation
|
||||
{
|
||||
public DockerApplicationInformation(string? dockerNetwork, List<Service> proxies)
|
||||
{
|
||||
DockerNetwork = dockerNetwork;
|
||||
Proxies = proxies;
|
||||
}
|
||||
|
||||
public string? DockerNetwork { get; set; }
|
||||
|
||||
public List<Service> Proxies { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<ProjectReference Include="..\Microsoft.Tye.Hosting.Diagnostics\Microsoft.Tye.Hosting.Diagnostics.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Tye.Hosting.Runtime\Microsoft.Tye.Hosting.Runtime.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Tye.Proxy\Microsoft.Tye.Proxy.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -112,16 +112,14 @@ namespace Microsoft.Tye.Hosting.Model
|
|||
|
||||
if (b.Port != null)
|
||||
{
|
||||
var port = (service.Description.RunInfo is DockerRunInfo &&
|
||||
targetService.RunInfo is DockerRunInfo) ? b.ContainerPort ?? b.Port.Value : b.Port.Value;
|
||||
var port = (service.Description.RunInfo is DockerRunInfo) ? b.ContainerPort ?? b.Port.Value : b.Port.Value;
|
||||
|
||||
set($"SERVICE__{configName}__PORT", port.ToString());
|
||||
set($"{envName}_SERVICE_PORT", port.ToString());
|
||||
}
|
||||
|
||||
// Use the container name as the host name if there's a single replica (current limitation)
|
||||
var host = b.Host ?? (service.Description.RunInfo is DockerRunInfo &&
|
||||
targetService.RunInfo is DockerRunInfo ? targetService.Name : defaultHost);
|
||||
var host = b.Host ?? (service.Description.RunInfo is DockerRunInfo ? targetService.Name : defaultHost);
|
||||
|
||||
set($"SERVICE__{configName}__HOST", host);
|
||||
set($"{envName}_SERVICE_HOST", host);
|
||||
|
|
|
@ -14,6 +14,10 @@ namespace Microsoft.Tye.Hosting.Model
|
|||
Args = args;
|
||||
}
|
||||
|
||||
public bool Private { get; set; }
|
||||
|
||||
public string? NetworkAlias { get; set; }
|
||||
|
||||
public string? WorkingDirectory { get; set; }
|
||||
|
||||
public List<DockerVolume> VolumeMappings { get; } = new List<DockerVolume>();
|
||||
|
|
|
@ -78,22 +78,18 @@ namespace Microsoft.Tye.Hosting
|
|||
binding.Name ?? binding.Protocol);
|
||||
}
|
||||
|
||||
// Only set the container port if we're running in a container
|
||||
if (service.Description.RunInfo is DockerRunInfo)
|
||||
var httpBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "http");
|
||||
var httpsBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "https");
|
||||
|
||||
// Default the first http and https port to 80 and 443
|
||||
if (httpBinding != null)
|
||||
{
|
||||
var httpBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "http");
|
||||
var httpsBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "https");
|
||||
httpBinding.ContainerPort ??= 80;
|
||||
}
|
||||
|
||||
// Default the first http and https port to 80 and 443
|
||||
if (httpBinding != null)
|
||||
{
|
||||
httpBinding.ContainerPort ??= 80;
|
||||
}
|
||||
|
||||
if (httpsBinding != null)
|
||||
{
|
||||
httpsBinding.ContainerPort ??= 443;
|
||||
}
|
||||
if (httpsBinding != null)
|
||||
{
|
||||
httpsBinding.ContainerPort ??= 443;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.IO;
|
|||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bedrock.Framework;
|
||||
|
@ -59,7 +60,12 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
var ports = binding.ReplicaPorts;
|
||||
|
||||
sockets.Listen(IPAddress.Loopback, binding.Port.Value, o =>
|
||||
// We need to bind to all interfaces on linux since the container -> host communication won't work
|
||||
// if we use the IP address to reach out of the host. This works fine on osx and windows
|
||||
// but doesn't work on linux.
|
||||
var host = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? IPAddress.Any : IPAddress.Loopback;
|
||||
|
||||
sockets.Listen(host, binding.Port.Value, o =>
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
|
|
|
@ -281,7 +281,6 @@ namespace Microsoft.Tye.Hosting
|
|||
{
|
||||
await _processor.StopAsync(_application);
|
||||
}
|
||||
_processor = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -295,6 +294,8 @@ namespace Microsoft.Tye.Hosting
|
|||
await DashboardWebApplication.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
_processor = null;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include *.deps.json and *.runtimeconfig.json in ContentWithTargetPath so they will be copied to the output folder of projects
|
||||
that reference this one. -->
|
||||
<Target Name="AddRuntimeDependenciesToContent" Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp'" BeforeTargets="GetCopyToOutputDirectoryItems">
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
|
||||
|
||||
<ContentWithTargetPath Include="$(ProjectRuntimeConfigFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectRuntimeConfigFileName)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,135 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Bedrock.Framework;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Tye.Proxy
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var serviceName = Environment.GetEnvironmentVariable("APP_INSTANCE");
|
||||
var containerHost = Environment.GetEnvironmentVariable("CONTAINER_HOST");
|
||||
|
||||
using var host = new HostBuilder()
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.AddConsole();
|
||||
logging.SetMinimumLevel(LogLevel.Debug);
|
||||
})
|
||||
.ConfigureServer(server =>
|
||||
{
|
||||
var logger = server.ApplicationServices.GetRequiredService<ILogger<Program>>();
|
||||
|
||||
logger.LogInformation("Received connection information {Host}:{Port}", containerHost, Environment.GetEnvironmentVariable("PROXY_PORT"));
|
||||
|
||||
static (int Port, int ExternalPort) ResolvePort(string portValue)
|
||||
{
|
||||
var pair = portValue.Split(':');
|
||||
return (int.Parse(pair[0]), int.Parse(pair[1]));
|
||||
}
|
||||
|
||||
var ports = Environment.GetEnvironmentVariable("PROXY_PORT")?.Split(';').Select(ResolvePort) ?? Enumerable.Empty<(int, int)>();
|
||||
|
||||
server.UseSockets(sockets =>
|
||||
{
|
||||
foreach (var mapping in ports)
|
||||
{
|
||||
sockets.Listen(IPAddress.Any, mapping.Port, o =>
|
||||
{
|
||||
// o.UseConnectionLogging("Microsoft.Tye.Proxy");
|
||||
|
||||
o.Run(async connection =>
|
||||
{
|
||||
var notificationFeature = connection.Features.Get<IConnectionLifetimeNotificationFeature>();
|
||||
|
||||
NetworkStream? targetStream = null;
|
||||
|
||||
try
|
||||
{
|
||||
var target = new Socket(SocketType.Stream, ProtocolType.Tcp)
|
||||
{
|
||||
NoDelay = true
|
||||
};
|
||||
|
||||
logger.LogDebug("Attempting to connect to {ServiceName} listening on {Port}:{ExternalPort}", serviceName, mapping.Port, mapping.ExternalPort);
|
||||
|
||||
await target.ConnectAsync(containerHost, mapping.ExternalPort);
|
||||
|
||||
logger.LogDebug("Successfully connected to {ServiceName} listening on {Port}:{ExternalPort}", serviceName, mapping.Port, mapping.ExternalPort);
|
||||
|
||||
targetStream = new NetworkStream(target, ownsSocket: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogDebug(ex, "Proxy error for service {ServiceName}", serviceName);
|
||||
|
||||
if (targetStream is object)
|
||||
{
|
||||
await targetStream.DisposeAsync();
|
||||
}
|
||||
|
||||
connection.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogDebug("Proxying traffic to {ServiceName} {Port}:{ExternalPort}", serviceName, mapping.Port, mapping.ExternalPort);
|
||||
|
||||
// external -> internal
|
||||
var reading = Task.Run(() => connection.Transport.Input.CopyToAsync(targetStream, notificationFeature.ConnectionClosedRequested));
|
||||
|
||||
// internal -> external
|
||||
var writing = Task.Run(() => targetStream.CopyToAsync(connection.Transport.Output, notificationFeature.ConnectionClosedRequested));
|
||||
|
||||
await Task.WhenAll(reading, writing);
|
||||
}
|
||||
catch (ConnectionResetException)
|
||||
{
|
||||
// Connection was reset
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Reset can also appear as an IOException with an inner SocketException
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
if (!notificationFeature.ConnectionClosedRequested.IsCancellationRequested)
|
||||
{
|
||||
logger.LogDebug(0, ex, "Proxy error for service {ServiceName}", serviceName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogDebug(0, ex, "Proxy error for service {ServiceName}", serviceName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await targetStream.DisposeAsync();
|
||||
}
|
||||
|
||||
// This needs to reconnect to the target port(s) until its bound
|
||||
// it has to stop if the service is no longer running
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -439,9 +439,15 @@ namespace E2ETest
|
|||
|
||||
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
|
||||
var ingressUri = await GetServiceUrl(client, uri, "ingress");
|
||||
var appAUri = await GetServiceUrl(client, uri, "appA");
|
||||
var appBUri = await GetServiceUrl(client, uri, "appB");
|
||||
|
||||
var appAResponse = await client.GetAsync(appAUri);
|
||||
var appBResponse = await client.GetAsync(appBUri);
|
||||
|
||||
Assert.True(appAResponse.IsSuccessStatusCode);
|
||||
Assert.True(appBResponse.IsSuccessStatusCode);
|
||||
|
||||
var responseA = await client.GetAsync(ingressUri + "/A");
|
||||
var responseB = await client.GetAsync(ingressUri + "/B");
|
||||
|
@ -462,6 +468,46 @@ namespace E2ETest
|
|||
});
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDockerNotRunning]
|
||||
public async Task NginxIngressTest()
|
||||
{
|
||||
using var projectDirectory = CopySampleProjectDirectory("nginx-ingress");
|
||||
|
||||
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
|
||||
var outputContext = new OutputContext(_sink, Verbosity.Debug);
|
||||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
|
||||
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
|
||||
AllowAutoRedirect = false
|
||||
};
|
||||
|
||||
var client = new HttpClient(new RetryHandler(handler));
|
||||
|
||||
await RunHostingApplication(application, Array.Empty<string>(), async (app, uri) =>
|
||||
{
|
||||
var nginxUri = await GetServiceUrl(client, uri, "nginx");
|
||||
var appAUri = await GetServiceUrl(client, uri, "appA");
|
||||
var appBUri = await GetServiceUrl(client, uri, "appB");
|
||||
|
||||
var nginxResponse = await client.GetAsync(nginxUri);
|
||||
var appAResponse = await client.GetAsync(appAUri);
|
||||
var appBResponse = await client.GetAsync(appBUri);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, nginxResponse.StatusCode);
|
||||
Assert.True(appAResponse.IsSuccessStatusCode);
|
||||
Assert.True(appBResponse.IsSuccessStatusCode);
|
||||
|
||||
var responseA = await client.GetAsync(nginxUri + "/A");
|
||||
var responseB = await client.GetAsync(nginxUri + "/B");
|
||||
|
||||
Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
|
||||
Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullDebugTargetsDoesNotThrow()
|
||||
{
|
||||
|
|
27
tye.sln
27
tye.sln
|
@ -11,17 +11,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F19B02EB-A
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETest", "test\E2ETest\E2ETest.csproj", "{D15E5FF6-C1E7-4110-A2BE-06ADA7ACA82B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting", "src\Microsoft.Tye.Hosting\Microsoft.Tye.Hosting.csproj", "{0F4F5A86-DD27-4AF9-BF97-221EAD5040E7}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Hosting", "src\Microsoft.Tye.Hosting\Microsoft.Tye.Hosting.csproj", "{0F4F5A86-DD27-4AF9-BF97-221EAD5040E7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting.Diagnostics", "src\Microsoft.Tye.Hosting.Diagnostics\Microsoft.Tye.Hosting.Diagnostics.csproj", "{CEBFC149-8162-4A0A-9AD4-40498B9172CD}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Hosting.Diagnostics", "src\Microsoft.Tye.Hosting.Diagnostics\Microsoft.Tye.Hosting.Diagnostics.csproj", "{CEBFC149-8162-4A0A-9AD4-40498B9172CD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting.Runtime", "src\Microsoft.Tye.Hosting.Runtime\Microsoft.Tye.Hosting.Runtime.csproj", "{34719884-1338-4965-BA2A-F98DB03733C2}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Hosting.Runtime", "src\Microsoft.Tye.Hosting.Runtime\Microsoft.Tye.Hosting.Runtime.csproj", "{34719884-1338-4965-BA2A-F98DB03733C2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Core", "src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj", "{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Core", "src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj", "{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Tye.Extensions", "src\Microsoft.Tye.Extensions\Microsoft.Tye.Extensions.csproj", "{AAF0CE0B-E53A-4E10-AA82-BF7200AB2B0C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Extensions", "src\Microsoft.Tye.Extensions\Microsoft.Tye.Extensions.csproj", "{AAF0CE0B-E53A-4E10-AA82-BF7200AB2B0C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Tye.Extensions.Configuration", "src\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj", "{B07394E4-30A7-429A-BC5A-747B54D5A447}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Extensions.Configuration", "src\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj", "{B07394E4-30A7-429A-BC5A-747B54D5A447}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Tye.Proxy", "src\Microsoft.Tye.Proxy\Microsoft.Tye.Proxy.csproj", "{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -129,6 +131,18 @@ Global
|
|||
{B07394E4-30A7-429A-BC5A-747B54D5A447}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B07394E4-30A7-429A-BC5A-747B54D5A447}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B07394E4-30A7-429A-BC5A-747B54D5A447}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -142,6 +156,7 @@ Global
|
|||
{D0359C69-6EA9-4B03-9455-90E8E04F1CB0} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{AAF0CE0B-E53A-4E10-AA82-BF7200AB2B0C} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{B07394E4-30A7-429A-BC5A-747B54D5A447} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D8002603-BB27-4500-BF86-274A8E72D302}
|
||||
|
|
Загрузка…
Ссылка в новой задаче