Compare commits

...

1 Commits

Author SHA1 Message Date
David Fowler 20c9bfeed2 WIP 6 years ago
  1. 6
      src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
  2. 54
      src/Microsoft.Tye.Hosting/AddressAssigner.cs
  3. 13
      src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
  4. 3
      src/Microsoft.Tye.Hosting/DockerRunner.cs
  5. 2
      src/Microsoft.Tye.Hosting/Model/Service.cs
  6. 8
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  7. 2
      src/Microsoft.Tye.Hosting/ProxyService.cs
  8. 2
      src/Microsoft.Tye.Hosting/TyeHost.cs

6
src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Tye;
using Tye.Serialization;
using YamlDotNet.Serialization;
@ -106,7 +107,7 @@ namespace Microsoft.Tye.ConfigModel
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSameName("service"));
}
if (service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1)
{
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("service"));
}
@ -166,7 +167,8 @@ namespace Microsoft.Tye.ConfigModel
{
throw new TyeYamlException(CoreStrings.IngressBindingMustBeHttpOrHttps);
}
if (ingress.Bindings.Count(o => o.Port == binding.Port) > 1)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ingress.Bindings.Count(o => o.Port == binding.Port) > 1)
{
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("ingress"));
}

54
src/Microsoft.Tye.Hosting/PortAssigner.cs → src/Microsoft.Tye.Hosting/AddressAssigner.cs

@ -9,20 +9,23 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.Tye.Hosting.Model;
using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices;
namespace Microsoft.Tye.Hosting
{
public class PortAssigner : IApplicationProcessor
public class AddressAssigner : IApplicationProcessor
{
private readonly ILogger _logger;
public PortAssigner(ILogger logger)
public AddressAssigner(ILogger logger)
{
_logger = logger;
}
public Task StartAsync(Application application)
{
// Nobody's going to have > 255 services right?
var octect = 0;
foreach (var service in application.Services.Values)
{
if (service.Description.RunInfo == null)
@ -30,7 +33,9 @@ namespace Microsoft.Tye.Hosting
continue;
}
static int GetNextPort()
octect++;
static int GetNextPort(IPAddress address)
{
// Let the OS assign the next available port. Unless we cycle through all ports
// on a test run, the OS will always increment the port number when making these calls.
@ -38,16 +43,53 @@ namespace Microsoft.Tye.Hosting
// a given port, and a new test is able to bind to the same port due to port
// reuse being enabled by default by the OS.
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
socket.Bind(new IPEndPoint(address, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
static bool IsPortAlreadyInUse(IPAddress address, int port)
{
var endpoint = new IPEndPoint(address, port);
try
{
using var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(endpoint);
return false;
}
catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
return true;
}
}
var httpUsed = false;
var httpsUsed = false;
// 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 address = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? IPAddress.Any : IPAddress.Parse($"127.0.0.{octect}");
service.Address = address;
foreach (var binding in service.Description.Bindings)
{
// Auto assign a port
if (binding.Port == null)
{
binding.Port = GetNextPort();
if (!httpUsed && binding.Protocol == "http" && !IsPortAlreadyInUse(address, 80))
{
binding.Port = 80;
httpUsed = true;
}
else if (!httpsUsed && binding.Protocol == "https" && !IsPortAlreadyInUse(address, 443))
{
binding.Port = 443;
httpsUsed = true;
}
else
{
binding.Port = GetNextPort(address);
}
}
if (service.Description.Replicas == 1)
@ -60,7 +102,7 @@ namespace Microsoft.Tye.Hosting
for (var i = 0; i < service.Description.Replicas; i++)
{
// Reserve a port for each replica
var port = GetNextPort();
var port = GetNextPort(address);
binding.ReplicaPorts.Add(port);
}

13
src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor

@ -44,12 +44,12 @@
{
if (b.Protocol == "http" || b.Protocol == "https")
{
var url = GetUrl(b);
var url = GetUrl(service, b);
<span><a href="@url" target="_blank">@url</a></span>
}
else
{
<span>@GetUrl(b)</span>
<span>@GetUrl(service, b)</span>
}
}
else
@ -77,9 +77,14 @@
private List<IDisposable> _subscriptions = new List<IDisposable>();
string GetUrl(ServiceBinding b)
string GetUrl(Service service, ServiceBinding b)
{
return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}";
var url = $"{(b.Protocol ?? "tcp")}://{service.Address}";
if (b.Port != 80 && b.Port != 443)
{
url += $":{b.Port}";
}
return url;
}
protected override void OnInitialized()

3
src/Microsoft.Tye.Hosting/DockerRunner.cs

@ -244,6 +244,7 @@ namespace Microsoft.Tye.Hosting
status.Ports = ports.Select(p => p.Port);
// These are the ports that the application should use for binding
var host = service.Address!.ToString();
// 1. Tell the docker container what port to bind to
portString = docker.Private ? "" : string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.ContainerPort ?? p.Port}"));
@ -262,7 +263,7 @@ 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}"));
environment["PORT"] = string.Join(";", ports.Select(p => $"{host}:{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}"));

2
src/Microsoft.Tye.Hosting/Model/Service.cs

@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Reactive.Subjects;
namespace Microsoft.Tye.Hosting.Model
@ -69,5 +70,6 @@ namespace Microsoft.Tye.Hosting.Model
public Subject<string> Logs { get; } = new Subject<string>();
public Subject<ReplicaEvent> ReplicaEvents { get; } = new Subject<ReplicaEvent>();
public IPAddress? Address { get; set; }
}
}

8
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@ -168,10 +169,7 @@ namespace Microsoft.Tye.Hosting
if (hasPorts)
{
// 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) ? "*" : "localhost";
var host = service.Address == IPAddress.Any ? "*" : service.Address!.ToString();
// These are the ports that the application should use for binding
@ -235,7 +233,7 @@ namespace Microsoft.Tye.Hosting
{
if (hasPorts)
{
_logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}")));
_logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://{service.Address}:{p.Port}")));
}
else
{

2
src/Microsoft.Tye.Hosting/ProxyService.cs

@ -63,7 +63,7 @@ namespace Microsoft.Tye.Hosting
// 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;
var host = service.Address;
sockets.Listen(host, binding.Port.Value, o =>
{

2
src/Microsoft.Tye.Hosting/TyeHost.cs

@ -263,7 +263,7 @@ namespace Microsoft.Tye.Hosting
var processors = new List<IApplicationProcessor>
{
new EventPipeDiagnosticsRunner(logger, diagnosticsCollector),
new PortAssigner(logger),
new AddressAssigner(logger),
new ProxyService(logger),
new HttpProxyService(logger),
new DockerImagePuller(logger),

Loading…
Cancel
Save