Skip to content

Commit

Permalink
Add an ASP.NET app that runs in AWS ECS Fargate (#532)
Browse files Browse the repository at this point in the history
* Add an ASP.NET app that runs in AWS ECS Fargate

This adds an example of a basic ASP.NET app that runs in AWS ECS
Fargate. This includes the infrastructure required to build and publish
that application as a Docker container image to a private ECR repo,
spin up a new ECS Fargate cluster, run a load balancer listening for
external Internet traffic on port 80, and run services across all
subnets in the default VPC for the target deployment region.

* Incorporate code review feedback

* More brace movement
  • Loading branch information
joeduffy authored Jan 30, 2020
1 parent 4a0d9a6 commit 58126f4
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 0 deletions.
2 changes: 2 additions & 0 deletions aws-cs-fargate/App/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin/
/obj/
2 changes: 2 additions & 0 deletions aws-cs-fargate/App/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin/
/obj/
16 changes: 16 additions & 0 deletions aws-cs-fargate/App/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /app/src

# First restore dependencies so app changes are faster.
COPY *.csproj .
RUN dotnet restore

# Next copy the rest of the app and build it.
COPY * ./
RUN dotnet publish -c release -o /app/bin --no-restore

# Create a new, smaller image stage, that just runs the DLL.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=build /app/bin ./
ENTRYPOINT [ "dotnet", "aws-cs-fargate.dll" ]
26 changes: 26 additions & 0 deletions aws-cs-fargate/App/Program.cs
Original file line number Diff line number Diff line change
@@ -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 BasicWebsiteExample
{
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>();
});
}
}
27 changes: 27 additions & 0 deletions aws-cs-fargate/App/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:39528",
"sslPort": 44392
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dotnet_core_tutorial": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
40 changes: 40 additions & 0 deletions aws-cs-fargate/App/Startup.cs
Original file line number Diff line number Diff line change
@@ -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 BasicWebsiteExample
{
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 World!");
});
});
}
}
}
10 changes: 10 additions & 0 deletions aws-cs-fargate/App/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
8 changes: 8 additions & 0 deletions aws-cs-fargate/App/aws-cs-fargate.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RootNamespace>dotnet_core_tutorial</RootNamespace>
</PropertyGroup>

</Project>
2 changes: 2 additions & 0 deletions aws-cs-fargate/Infra/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin/
/obj/
14 changes: 14 additions & 0 deletions aws-cs-fargate/Infra/Infra.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Pulumi.Aws" Version="1.15.0-preview" />
<PackageReference Include="Pulumi.Docker" Version="1.1.0-preview" />
</ItemGroup>

</Project>
170 changes: 170 additions & 0 deletions aws-cs-fargate/Infra/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

using Pulumi;
using Docker = Pulumi.Docker;
using Ec2 = Pulumi.Aws.Ec2;
using Ecs = Pulumi.Aws.Ecs;
using Ecr = Pulumi.Aws.Ecr;
using Elb = Pulumi.Aws.ElasticLoadBalancingV2;
using Iam = Pulumi.Aws.Iam;

class Program
{
static Task<int> Main()
{
return Deployment.RunAsync(async () =>
{
// Read back the default VPC and public subnets, which we will use.
var vpc = await Ec2.Invokes.GetVpc(new Ec2.GetVpcArgs { Default = true });
var subnet = await Ec2.Invokes.GetSubnetIds(new Ec2.GetSubnetIdsArgs { VpcId = vpc.Id });

// Create a SecurityGroup that permits HTTP ingress and unrestricted egress.
var webSg = new Ec2.SecurityGroup("web-sg", new Ec2.SecurityGroupArgs
{
VpcId = vpc.Id,
Egress =
{
new Ec2.Inputs.SecurityGroupEgressArgs
{
Protocol = "-1",
FromPort = 0,
ToPort = 0,
CidrBlocks = { "0.0.0.0/0" },
},
},
Ingress =
{
new Ec2.Inputs.SecurityGroupIngressArgs
{
Protocol = "tcp",
FromPort = 80,
ToPort = 80,
CidrBlocks = { "0.0.0.0/0" },
},
},
});

// Create an ECS cluster to run a container-based service.
var cluster = new Ecs.Cluster("app-cluster");

// Create an IAM role that can be used by our service's task.
var taskExecRole = new Iam.Role("task-exec-role", new Iam.RoleArgs
{
AssumeRolePolicy = @"{
""Version"": ""2008-10-17"",
""Statement"": [{
""Sid"": """",
""Effect"": ""Allow"",
""Principal"": {
""Service"": ""ecs-tasks.amazonaws.com""
},
""Action"": ""sts:AssumeRole""
}]
}",
});
var taskExecAttach = new Iam.RolePolicyAttachment("task-exec-policy", new Iam.RolePolicyAttachmentArgs
{
Role = taskExecRole.Name,
PolicyArn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});

// Create a load balancer to listen for HTTP traffic on port 80.
var webLb = new Elb.LoadBalancer("web-lb", new Elb.LoadBalancerArgs
{
Subnets = subnet.Ids,
SecurityGroups = { webSg.Id },
});
var webTg = new Elb.TargetGroup("web-tg", new Elb.TargetGroupArgs
{
Port = 80,
Protocol = "HTTP",
TargetType = "ip",
VpcId = vpc.Id,
});
var webListener = new Elb.Listener("web-listener", new Elb.ListenerArgs
{
LoadBalancerArn = webLb.Arn,
Port = 80,
DefaultActions =
{
new Elb.Inputs.ListenerDefaultActionsArgs
{
Type = "forward",
TargetGroupArn = webTg.Arn,
},
},
});

// Create a private ECR registry and build and publish our app's container image to it.
var appRepo = new Ecr.Repository("app-repo");
var appRepoCreds = appRepo.RegistryId.Apply(async rid =>
{
var creds = await Ecr.Invokes.GetCredentials(new Ecr.GetCredentialsArgs { RegistryId = rid });
var credsData = Convert.FromBase64String(creds.AuthorizationToken);
return Encoding.UTF8.GetString(credsData).Split(":");
});
var image = new Docker.Image("app-img", new Docker.ImageArgs
{
Build = "../App",
ImageName = appRepo.RepositoryUrl,
Registry = new Docker.ImageRegistry
{
Server = appRepo.RepositoryUrl,
Username = appRepoCreds.Apply(creds => creds[0]),
Password = appRepoCreds.Apply(creds => creds[1]),
},
});

// Spin up a load balanced service running our container image.
var appTask = new Ecs.TaskDefinition("app-task", new Ecs.TaskDefinitionArgs
{
Family = "fargate-task-definition",
Cpu = "256",
Memory = "512",
NetworkMode = "awsvpc",
RequiresCompatibilities = { "FARGATE" },
ExecutionRoleArn = taskExecRole.Arn,
ContainerDefinitions = image.ImageName.Apply(imageName => @"[{
""name"": ""my-app"",
""image"": """ + imageName + @""",
""portMappings"": [{
""containerPort"": 80,
""hostPort"": 80,
""protocol"": ""tcp""
}]
}]"),
});
var appSvc = new Ecs.Service("app-svc", new Ecs.ServiceArgs
{
Cluster = cluster.Arn,
DesiredCount = 3,
LaunchType = "FARGATE",
TaskDefinition = appTask.Arn,
NetworkConfiguration = new Ecs.Inputs.ServiceNetworkConfigurationArgs
{
AssignPublicIp = true,
Subnets = subnet.Ids,
SecurityGroups = { webSg.Id },
},
LoadBalancers =
{
new Ecs.Inputs.ServiceLoadBalancersArgs
{
TargetGroupArn = webTg.Arn,
ContainerName = "my-app",
ContainerPort = 80,
},
},
}, new CustomResourceOptions { DependsOn = { webListener } });

// Export the resulting web address.
return new Dictionary<string, object?>
{
{ "url", Output.Format($"http://{webLb.DnsName}") },
};
});
}
}
4 changes: 4 additions & 0 deletions aws-cs-fargate/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: aws-cs-fargate
runtime: dotnet
main: Infra
description: An ASP.NET application running in AWS ECS Fargate, using C# infrastructure as code
Loading

0 comments on commit 58126f4

Please sign in to comment.