Compare commits
2 Commits
main
...
davidfowl/
| Author | SHA1 | Date |
|---|---|---|
|
|
0c481e4b8f | 6 years ago |
|
|
1227c5bd1d | 6 years ago |
40 changed files with 500 additions and 136 deletions
@ -1,8 +1,15 @@ |
|||||
# tye application configuration file |
# tye application configuration file |
||||
# read all about it at https://github.com/dotnet/tye |
# read all about it at https://github.com/dotnet/tye |
||||
name: frontend-backend |
name: frontend-backend |
||||
|
|
||||
|
ingress: |
||||
|
name: ingress |
||||
|
rules: |
||||
|
- service: frontend |
||||
|
|
||||
services: |
services: |
||||
- name: backend |
- name: backend |
||||
project: backend/backend.csproj |
project: backend/backend.csproj |
||||
- name: frontend |
- name: frontend |
||||
project: frontend/frontend.csproj |
project: frontend/frontend.csproj |
||||
|
replicas: 2 |
||||
|
|||||
@ -1,21 +0,0 @@ |
|||||
apiVersion: extensions/v1beta1 |
|
||||
kind: Ingress |
|
||||
metadata: |
|
||||
name: ingress-basic |
|
||||
namespace: default |
|
||||
annotations: |
|
||||
kubernetes.io/ingress.class: nginx |
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false" |
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2 |
|
||||
spec: |
|
||||
rules: |
|
||||
- http: |
|
||||
paths: |
|
||||
- backend: |
|
||||
serviceName: vote |
|
||||
servicePort: 80 |
|
||||
path: /vote(/|$)(.*) |
|
||||
- backend: |
|
||||
serviceName: results |
|
||||
servicePort: 80 |
|
||||
path: /results(/|$)(.*) |
|
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<!-- 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,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 Microsoft.Tye.HttpProxy |
||||
|
{ |
||||
|
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:54967", |
||||
|
"sslPort": 44309 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"Microsoft.Tye.HttpProxy": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Linq; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Sockets; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Microsoft.Tye.HttpProxy |
||||
|
{ |
||||
|
public class RoundRobinLoadBalancer : DelegatingHandler |
||||
|
{ |
||||
|
private readonly ConcurrentDictionary<string, DnsCache> _dnsCache = new ConcurrentDictionary<string, DnsCache>(); |
||||
|
private readonly ILogger _logger; |
||||
|
public RoundRobinLoadBalancer(ILogger logger, HttpMessageHandler innerHandler) : base(innerHandler) |
||||
|
{ |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var host = request.RequestUri.Host; |
||||
|
if (!_dnsCache.TryGetValue(host, out var cache)) |
||||
|
{ |
||||
|
var addresses = await Dns.GetHostAddressesAsync(host); |
||||
|
|
||||
|
_logger.LogInformation("Resolved {Host} to {Addresses}", host, addresses); |
||||
|
|
||||
|
cache = new DnsCache(addresses); |
||||
|
_dnsCache[host] = cache; |
||||
|
} |
||||
|
|
||||
|
// Allocations!
|
||||
|
request.RequestUri = new UriBuilder(request.RequestUri) |
||||
|
{ |
||||
|
Host = cache.GetAddress() |
||||
|
}.Uri; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
return await base.SendAsync(request, cancellationToken); |
||||
|
} |
||||
|
catch (HttpRequestException ex) when (ex.InnerException is SocketException) |
||||
|
{ |
||||
|
// Connection error, remove this host (the target might have died)
|
||||
|
_dnsCache.TryRemove(host, out _); |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private class DnsCache |
||||
|
{ |
||||
|
private int _index; |
||||
|
private readonly string[] _addresses; |
||||
|
|
||||
|
public DnsCache(IPAddress[] addresses) |
||||
|
{ |
||||
|
_addresses = addresses.Select(a => a.ToString()).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public string GetAddress() |
||||
|
{ |
||||
|
var next = Interlocked.Increment(ref _index) % _addresses.Length; |
||||
|
return _addresses[next]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
using System; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Proxy; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.AspNetCore.Routing.Matching; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Microsoft.Tye.HttpProxy |
||||
|
{ |
||||
|
public class Startup |
||||
|
{ |
||||
|
public void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.AddSingleton<MatcherPolicy, IngressHostMatcherPolicy>(); |
||||
|
} |
||||
|
|
||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
public void Configure(IApplicationBuilder app, ILogger<Startup> logger, IWebHostEnvironment env, IConfiguration configuration) |
||||
|
{ |
||||
|
var invoker = new HttpMessageInvoker(new ConnectionRetryHandler(new RoundRobinLoadBalancer(logger, new SocketsHttpHandler |
||||
|
{ |
||||
|
AllowAutoRedirect = false, |
||||
|
AutomaticDecompression = DecompressionMethods.None, |
||||
|
UseProxy = false |
||||
|
}))); |
||||
|
|
||||
|
if (env.IsDevelopment()) |
||||
|
{ |
||||
|
app.UseDeveloperExceptionPage(); |
||||
|
} |
||||
|
|
||||
|
app.UseRouting(); |
||||
|
|
||||
|
app.UseEndpoints(endpoints => |
||||
|
{ |
||||
|
foreach (var rule in configuration.GetSection("Rules").GetChildren()) |
||||
|
{ |
||||
|
var host = rule["Host"]; |
||||
|
var path = rule["Path"]; |
||||
|
var preservePath = rule.GetSection("PreservePath").Get<bool>(); |
||||
|
var service = rule["Service"]; |
||||
|
var port = rule["Port"]; |
||||
|
var protocol = rule["Protocol"]; |
||||
|
|
||||
|
var url = $"{protocol}://{service}:{port}"; |
||||
|
|
||||
|
RequestDelegate del = context => |
||||
|
{ |
||||
|
var uri = new UriBuilder(url) |
||||
|
{ |
||||
|
Path = preservePath ? context.Request.Path.ToString() : (string)context.Request.RouteValues["path"] ?? "/" |
||||
|
}; |
||||
|
|
||||
|
return context.ProxyRequest(invoker, uri.Uri); |
||||
|
}; |
||||
|
|
||||
|
IEndpointConventionBuilder conventions = null!; |
||||
|
|
||||
|
if (path != null) |
||||
|
{ |
||||
|
conventions = endpoints.Map(path.TrimEnd('/') + "/{**path}", del); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
conventions = endpoints.MapFallback(del); |
||||
|
} |
||||
|
|
||||
|
if (host != null) |
||||
|
{ |
||||
|
conventions.WithMetadata(new IngressHostMetadata(host)); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"LogLevel": { |
||||
|
"Default": "Information", |
||||
|
"Microsoft": "Information", |
||||
|
"Microsoft.Hosting.Lifetime": "Information" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"LogLevel": { |
||||
|
"Default": "Information", |
||||
|
"Microsoft": "Information", |
||||
|
"Microsoft.Hosting.Lifetime": "Information" |
||||
|
} |
||||
|
}, |
||||
|
"AllowedHosts": "*" |
||||
|
} |
||||
Loading…
Reference in new issue