Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable container-to-container service communication #5628

Merged
merged 20 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
var tableEndpoint = storage.GetEndpoint("table");

context.EnvironmentVariables.Add("ACCEPT_EULA", "Y");
context.EnvironmentVariables.Add("BLOB_SERVER", $"{blobEndpoint.ContainerHost}:{blobEndpoint.Port}");
context.EnvironmentVariables.Add("METADATA_SERVER", $"{tableEndpoint.ContainerHost}:{tableEndpoint.Port}");
context.EnvironmentVariables.Add("BLOB_SERVER", $"{blobEndpoint.Resource.Name}:{blobEndpoint.TargetPort}");
context.EnvironmentVariables.Add("METADATA_SERVER", $"{tableEndpoint.Resource.Name}:{tableEndpoint.TargetPort}");
}));

if (configureContainer != null)
Expand Down
8 changes: 6 additions & 2 deletions src/Aspire.Hosting.Kafka/KafkaBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ public static IResourceBuilder<KafkaServerResource> WithKafkaUI(this IResourceBu
static void ConfigureKafkaUIContainer(EnvironmentCallbackContext context, EndpointReference endpoint, int index)
{
var bootstrapServers = context.ExecutionContext.IsRunMode
? ReferenceExpression.Create($"{endpoint.ContainerHost}:{endpoint.Property(EndpointProperty.Port)}")
// In run mode, Kafka UI assumes Kafka is being accessed over a default Aspire container network and hardcodes the host as the Kafka resource name
// This will need to be refactored once updated service discovery APIs are available
? ReferenceExpression.Create($"{endpoint.Resource.Name}:{endpoint.Property(EndpointProperty.TargetPort)}")
: ReferenceExpression.Create($"{endpoint.Property(EndpointProperty.Host)}:{endpoint.Property(EndpointProperty.Port)}");

context.EnvironmentVariables.Add($"KAFKA_CLUSTERS_{index}_NAME", endpoint.Resource.Name);
Expand Down Expand Up @@ -164,7 +166,9 @@ private static void ConfigureKafkaContainer(EnvironmentCallbackContext context,
var internalEndpoint = resource.InternalEndpoint;

var advertisedListeners = context.ExecutionContext.IsRunMode
? ReferenceExpression.Create($"PLAINTEXT://localhost:29092,PLAINTEXT_HOST://localhost:{primaryEndpoint.Property(EndpointProperty.Port)},PLAINTEXT_INTERNAL://{internalEndpoint.ContainerHost}:{internalEndpoint.Property(EndpointProperty.Port)}")
// In run mode, PLAINTEXT_INTERNAL assumes kafka is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
? ReferenceExpression.Create($"PLAINTEXT://localhost:29092,PLAINTEXT_HOST://localhost:{primaryEndpoint.Property(EndpointProperty.Port)},PLAINTEXT_INTERNAL://{resource.Name}:{internalEndpoint.Property(EndpointProperty.TargetPort)}")
: ReferenceExpression.Create(
$"PLAINTEXT://{primaryEndpoint.Property(EndpointProperty.Host)}:29092,PLAINTEXT_HOST://{primaryEndpoint.Property(EndpointProperty.Host)}:{primaryEndpoint.Property(EndpointProperty.Port)},PLAINTEXT_INTERNAL://{internalEndpoint.Property(EndpointProperty.Host)}:{internalEndpoint.Property(EndpointProperty.Port)}");

Expand Down
18 changes: 10 additions & 8 deletions src/Aspire.Hosting.Milvus/MilvusBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public static class MilvusBuilderExtensions
/// var milvus = builder.AddMilvus("milvus");
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(milvus);
///
/// builder.Build().Run();
///
/// builder.Build().Run();
/// </code>
/// </example>
/// <remarks>
Expand Down Expand Up @@ -80,11 +80,11 @@ public static IResourceBuilder<MilvusServerResource> AddMilvus(this IDistributed
///
/// var booksdb = builder.AddMilvus("milvus");
/// .AddDatabase("booksdb");
///
///
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(booksdb);
///
/// builder.Build().Run();
///
/// builder.Build().Run();
/// </code>
/// </example>
/// <param name="builder">The Milvus server resource builder.</param>
Expand Down Expand Up @@ -117,8 +117,8 @@ public static IResourceBuilder<MilvusDatabaseResource> AddDatabase(this IResourc
/// .WithAttu();
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(milvus);
///
/// builder.Build().Run();
///
/// builder.Build().Run();
/// </code>
/// </example>
/// <param name="builder">The Milvus server resource builder.</param>
Expand Down Expand Up @@ -186,6 +186,8 @@ public static IResourceBuilder<MilvusServerResource> WithConfigurationBindMount(

private static void ConfigureAttuContainer(EnvironmentCallbackContext context, MilvusServerResource resource)
{
context.EnvironmentVariables.Add("MILVUS_URL", $"{resource.PrimaryEndpoint.Scheme}://{resource.PrimaryEndpoint.ContainerHost}:{resource.PrimaryEndpoint.Port}");
// Attu assumes Milvus is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("MILVUS_URL", $"{resource.PrimaryEndpoint.Scheme}://{resource.Name}:{resource.PrimaryEndpoint.TargetPort}");
}
}
4 changes: 3 additions & 1 deletion src/Aspire.Hosting.MongoDB/MongoDBBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ public static IResourceBuilder<MongoDBServerResource> WithInitBindMount(this IRe

private static void ConfigureMongoExpressContainer(EnvironmentCallbackContext context, MongoDBServerResource resource)
{
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_URL", $"mongodb://{resource.PrimaryEndpoint.ContainerHost}:{resource.PrimaryEndpoint.Port}/?directConnection=true");
// Mongo Exporess assumes Mongo is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("ME_CONFIG_MONGODB_URL", $"mongodb://{resource.Name}:{resource.PrimaryEndpoint.TargetPort}/?directConnection=true");
context.EnvironmentVariables.Add("ME_CONFIG_BASICAUTH", "false");
}
}
10 changes: 7 additions & 3 deletions src/Aspire.Hosting.MySql/MySqlBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ public static IResourceBuilder<T> WithPhpMyAdmin<T>(this IResourceBuilder<T> bui
var endpoint = singleInstance.PrimaryEndpoint;
phpMyAdminContainerBuilder.WithEnvironment(context =>
{
context.EnvironmentVariables.Add("PMA_HOST", $"{endpoint.ContainerHost}:{endpoint.Port}");
// PhpMyAdmin assumes MySql is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
context.EnvironmentVariables.Add("PMA_HOST", $"{endpoint.Resource.Name}:{endpoint.TargetPort}");
context.EnvironmentVariables.Add("PMA_USER", "root");
context.EnvironmentVariables.Add("PMA_PASSWORD", singleInstance.PasswordParameter.Value);
});
Expand All @@ -127,7 +129,9 @@ public static IResourceBuilder<T> WithPhpMyAdmin<T>(this IResourceBuilder<T> bui
{
var endpoint = mySqlInstance.PrimaryEndpoint;
writer.WriteLine("$i++;");
writer.WriteLine($"$cfg['Servers'][$i]['host'] = '{endpoint.ContainerHost}:{endpoint.Port}';");
// PhpMyAdmin assumes MySql is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
writer.WriteLine($"$cfg['Servers'][$i]['host'] = '{endpoint.Resource.Name}:{endpoint.TargetPort}';");
writer.WriteLine($"$cfg['Servers'][$i]['verbose'] = '{mySqlInstance.Name}';");
writer.WriteLine($"$cfg['Servers'][$i]['auth_type'] = 'cookie';");
writer.WriteLine($"$cfg['Servers'][$i]['user'] = 'root';");
Expand Down Expand Up @@ -157,7 +161,7 @@ public static IResourceBuilder<T> WithPhpMyAdmin<T>(this IResourceBuilder<T> bui
public static IResourceBuilder<PhpMyAdminContainerResource> WithHostPort(this IResourceBuilder<PhpMyAdminContainerResource> builder, int? port)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithEndpoint("http", endpoint =>
{
endpoint.Port = port;
Expand Down
18 changes: 11 additions & 7 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,10 @@ public static IResourceBuilder<T> WithPgAdmin<T>(this IResourceBuilder<T> builde
writer.WriteStartObject($"{serverIndex}");
writer.WriteString("Name", postgresInstance.Name);
writer.WriteString("Group", "Servers");
writer.WriteString("Host", endpoint.ContainerHost);
writer.WriteNumber("Port", endpoint.Port);
// PgAdmin assumes Postgres is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
writer.WriteString("Host", endpoint.Resource.Name);
writer.WriteNumber("Port", (int)endpoint.TargetPort!);
writer.WriteString("Username", "postgres");
writer.WriteString("SSLMode", "prefer");
writer.WriteString("MaintenanceDB", "postgres");
Expand Down Expand Up @@ -269,11 +271,11 @@ public static IResourceBuilder<PgWebContainerResource> WithHostPort(this IResour
/// var postgres = builder.AddPostgres("postgres")
/// .WithPgWeb();
/// var db = postgres.AddDatabase("db");
///
///
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
/// .WithReference(db);
///
/// builder.Build().Run();
///
/// builder.Build().Run();
/// </code>
/// </example>
/// <remarks>
Expand Down Expand Up @@ -320,9 +322,11 @@ public static IResourceBuilder<PostgresServerResource> WithPgWeb(this IResourceB
{
var user = postgresDatabase.Parent.UserNameParameter?.Value ?? "postgres";

// PgAdmin assumes Postgres is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
var fileContent = $"""
host = "{postgresDatabase.Parent.PrimaryEndpoint.ContainerHost}"
port = {postgresDatabase.Parent.PrimaryEndpoint.Port}
host = "{postgresDatabase.Parent.Name}"
port = {postgresDatabase.Parent.PrimaryEndpoint.TargetPort}
user = "{user}"
password = "{postgresDatabase.Parent.PasswordParameter.Value}"
database = "{postgresDatabase.DatabaseName}"
Expand Down
4 changes: 3 additions & 1 deletion src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ public static IResourceBuilder<RedisResource> WithRedisCommander(this IResourceB
{
if (redisInstance.PrimaryEndpoint.IsAllocated)
{
var hostString = $"{(hostsVariableBuilder.Length > 0 ? "," : string.Empty)}{redisInstance.Name}:{redisInstance.PrimaryEndpoint.ContainerHost}:{redisInstance.PrimaryEndpoint.Port}:0";
// Redis Commander assumes Redis is being accessed over a default Aspire container network and hardcodes the resource address
// This will need to be refactored once updated service discovery APIs are available
var hostString = $"{(hostsVariableBuilder.Length > 0 ? "," : string.Empty)}{redisInstance.Name}:{redisInstance.Name}:{redisInstance.PrimaryEndpoint.TargetPort}:0";
hostsVariableBuilder.Append(hostString);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ApplicationModel/EndpointReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,5 @@ public enum EndpointProperty
/// <summary>
/// The target port of the endpoint.
/// </summary>
TargetPort
TargetPort,
}
31 changes: 31 additions & 0 deletions src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ public async Task RunApplicationAsync(CancellationToken cancellationToken = defa

await CreateServicesAsync(cancellationToken).ConfigureAwait(false);

await CreateContainerNetworksAsync(cancellationToken).ConfigureAwait(false);

await CreateContainersAndExecutablesAsync(cancellationToken).ConfigureAwait(false);

var afterResourcesCreatedEvent = new AfterResourcesCreatedEvent(serviceProvider, _model);
Expand Down Expand Up @@ -901,6 +903,18 @@ await execution.ExecuteAsync(async (attemptCancellationToken) =>
}
}

private async Task CreateContainerNetworksAsync(CancellationToken cancellationToken)
{
var toCreate = _appResources.Where(r => r.DcpResource is ContainerNetwork);
foreach (var containerNetwork in toCreate)
{
if (containerNetwork.DcpResource is ContainerNetwork cn)
{
await kubernetesService.CreateAsync(cn, cancellationToken).ConfigureAwait(false);
}
}
}

private async Task CreateContainersAndExecutablesAsync(CancellationToken cancellationToken)
{
var toCreate = _appResources.Where(r => r.DcpResource is Container || r.DcpResource is Executable || r.DcpResource is ExecutableReplicaSet);
Expand Down Expand Up @@ -1394,6 +1408,15 @@ private void PrepareContainers()
}
}

ctr.Spec.Networks = new List<ContainerNetworkConnection>
{
new ContainerNetworkConnection
{
Name = "aspire-network",
Aliases = new List<string> { container.Name },
}
};

var containerAppResource = new AppResource(container, ctr);
AddServicesProducedInfo(container, ctr, containerAppResource);
_appResources.Add(containerAppResource);
Expand Down Expand Up @@ -1444,6 +1467,14 @@ await notificationService.PublishUpdateAsync(cr.ModelResource, s => s with
}

var tasks = new List<Task>();

// Create a custom container network for Aspire if there are container resources
if (containerResources.Any())
{
// The network will be created with a unique postfix to avoid conflicts with other Aspire AppHost networks
tasks.Add(kubernetesService.CreateAsync(ContainerNetwork.Create("aspire-network"), cancellationToken));
}

foreach (var cr in containerResources)
{
tasks.Add(CreateContainerAsyncCore(cr, cancellationToken));
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Hosting/Dcp/Model/GroupVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ static Dcp()
{
Schema.Add<Executable>(ExecutableKind, "executables");
Schema.Add<Container>(ContainerKind, "containers");
Schema.Add<ContainerNetwork>(ContainerNetworkKind, "containernetworks");
Schema.Add<Service>(ServiceKind, "services");
Schema.Add<Endpoint>(EndpointKind, "endpoints");
Schema.Add<ExecutableReplicaSet>(ExecutableReplicaSetKind, "executablereplicasets");
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/Dcp/Model/Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public ContainerNetwork(ContainerNetworkSpec spec) : base(spec) { }

public static ContainerNetwork Create(string name, bool useIpV6 = false)
{
var c = new ContainerNetwork(new ContainerNetworkSpec { NetworkName = name, IPV6 = useIpV6 });
var c = new ContainerNetwork(new ContainerNetworkSpec { IPV6 = useIpV6 });

c.Kind = Dcp.ContainerNetworkKind;
c.ApiVersion = Dcp.GroupVersion.ToString();
Expand Down
10 changes: 4 additions & 6 deletions tests/Aspire.Hosting.MongoDB.Tests/AddMongoDBTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,12 @@ public void WithMongoExpressSupportsChangingHostPort()
Assert.Equal(1000, endpoint.Port);
}

[Theory]
[InlineData("host.docker.internal")]
[InlineData("host.containers.internal")]
public async Task WithMongoExpressUsesContainerHost(string containerHost)
[Fact]
public async Task WithMongoExpressUsesContainerHost()
{
using var builder = TestDistributedApplicationBuilder.Create();
builder.AddMongoDB("mongo")
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 3000, containerHost))
.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 3000))
.WithMongoExpress();

var mongoExpress = Assert.Single(builder.Resources.OfType<MongoExpressContainerResource>());
Expand All @@ -154,7 +152,7 @@ public async Task WithMongoExpressUsesContainerHost(string containerHost)
e =>
{
Assert.Equal("ME_CONFIG_MONGODB_URL", e.Key);
Assert.Equal($"mongodb://{containerHost}:3000/?directConnection=true", e.Value);
Assert.Equal($"mongodb://mongo:3000/?directConnection=true", e.Value);
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
},
e =>
{
Expand Down
13 changes: 5 additions & 8 deletions tests/Aspire.Hosting.MySql.Tests/AddMySqlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@ public void WithMySqlTwiceEndsUpWithOneAdminContainer()
}

[Theory]
[InlineData("host.docker.internal")]
[InlineData("host.containers.internal")]
[InlineData("mySql")]
public async Task SingleMySqlInstanceProducesCorrectMySqlHostsVariable(string containerHost)
{
var builder = DistributedApplication.CreateBuilder();
Expand Down Expand Up @@ -266,17 +265,15 @@ public void WithPhpMyAdminAddsContainer()
Assert.Equal("/etc/phpmyadmin/config.user.inc.php", volume.Target);
}

[Theory]
[InlineData("host.docker.internal")]
[InlineData("host.containers.internal")]
public void WithPhpMyAdminProducesValidServerConfigFile(string containerHost)
[Fact]
public void WithPhpMyAdminProducesValidServerConfigFile()
{
var builder = DistributedApplication.CreateBuilder();
var mysql1 = builder.AddMySql("mysql1").WithPhpMyAdmin(c => c.WithHostPort(8081));
var mysql2 = builder.AddMySql("mysql2").WithPhpMyAdmin(c => c.WithHostPort(8081));

// Add fake allocated endpoints.
mysql1.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001, containerHost));
mysql1.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001));
mysql2.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5002, "host3"));

var myAdmin = builder.Resources.Single(r => r.Name.EndsWith("-phpmyadmin"));
Expand All @@ -291,7 +288,7 @@ public void WithPhpMyAdminProducesValidServerConfigFile(string containerHost)
var fileContents = new StreamReader(stream).ReadToEnd();

// check to see that the two hosts are in the file
string pattern1 = $@"\$cfg\['Servers'\]\[\$i\]\['host'\] = '{containerHost}:5001';";
string pattern1 = $@"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'mysql1:5001';";
string pattern2 = @"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'host3:5002';";
Match match1 = Regex.Match(fileContents, pattern1);
Assert.True(match1.Success);
Expand Down
Loading
Loading