Skip to content

Commit e38cc4b

Browse files
authored
Add an ASP.NET app that runs in AWS ECS Fargate (pulumi#532)
* 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
1 parent 506cf5b commit e38cc4b

13 files changed

+446
-0
lines changed

aws-cs-fargate/App/.dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
/obj/

aws-cs-fargate/App/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
/obj/

aws-cs-fargate/App/Dockerfile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
2+
WORKDIR /app/src
3+
4+
# First restore dependencies so app changes are faster.
5+
COPY *.csproj .
6+
RUN dotnet restore
7+
8+
# Next copy the rest of the app and build it.
9+
COPY * ./
10+
RUN dotnet publish -c release -o /app/bin --no-restore
11+
12+
# Create a new, smaller image stage, that just runs the DLL.
13+
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
14+
WORKDIR /app
15+
COPY --from=build /app/bin ./
16+
ENTRYPOINT [ "dotnet", "aws-cs-fargate.dll" ]

aws-cs-fargate/App/Program.cs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace BasicWebsiteExample
11+
{
12+
public class Program
13+
{
14+
public static void Main(string[] args)
15+
{
16+
CreateHostBuilder(args).Build().Run();
17+
}
18+
19+
public static IHostBuilder CreateHostBuilder(string[] args) =>
20+
Host.CreateDefaultBuilder(args)
21+
.ConfigureWebHostDefaults(webBuilder =>
22+
{
23+
webBuilder.UseStartup<Startup>();
24+
});
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:39528",
7+
"sslPort": 44392
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"dotnet_core_tutorial": {
19+
"commandName": "Project",
20+
"launchBrowser": true,
21+
"applicationUrl": "https://localhost:5001;http://localhost:5000",
22+
"environmentVariables": {
23+
"ASPNETCORE_ENVIRONMENT": "Development"
24+
}
25+
}
26+
}
27+
}

aws-cs-fargate/App/Startup.cs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
10+
11+
namespace BasicWebsiteExample
12+
{
13+
public class Startup
14+
{
15+
// This method gets called by the runtime. Use this method to add services to the container.
16+
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
17+
public void ConfigureServices(IServiceCollection services)
18+
{
19+
}
20+
21+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
22+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
23+
{
24+
if (env.IsDevelopment())
25+
{
26+
app.UseDeveloperExceptionPage();
27+
}
28+
29+
app.UseRouting();
30+
31+
app.UseEndpoints(endpoints =>
32+
{
33+
endpoints.MapGet("/", async context =>
34+
{
35+
await context.Response.WriteAsync("Hello World!");
36+
});
37+
});
38+
}
39+
}
40+
}

aws-cs-fargate/App/appsettings.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft": "Warning",
6+
"Microsoft.Hosting.Lifetime": "Information"
7+
}
8+
},
9+
"AllowedHosts": "*"
10+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<RootNamespace>dotnet_core_tutorial</RootNamespace>
6+
</PropertyGroup>
7+
8+
</Project>

aws-cs-fargate/Infra/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
/obj/

aws-cs-fargate/Infra/Infra.csproj

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp3.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Pulumi.Aws" Version="1.15.0-preview" />
11+
<PackageReference Include="Pulumi.Docker" Version="1.1.0-preview" />
12+
</ItemGroup>
13+
14+
</Project>

aws-cs-fargate/Infra/Program.cs

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using System.Threading.Tasks;
5+
6+
using Pulumi;
7+
using Docker = Pulumi.Docker;
8+
using Ec2 = Pulumi.Aws.Ec2;
9+
using Ecs = Pulumi.Aws.Ecs;
10+
using Ecr = Pulumi.Aws.Ecr;
11+
using Elb = Pulumi.Aws.ElasticLoadBalancingV2;
12+
using Iam = Pulumi.Aws.Iam;
13+
14+
class Program
15+
{
16+
static Task<int> Main()
17+
{
18+
return Deployment.RunAsync(async () =>
19+
{
20+
// Read back the default VPC and public subnets, which we will use.
21+
var vpc = await Ec2.Invokes.GetVpc(new Ec2.GetVpcArgs { Default = true });
22+
var subnet = await Ec2.Invokes.GetSubnetIds(new Ec2.GetSubnetIdsArgs { VpcId = vpc.Id });
23+
24+
// Create a SecurityGroup that permits HTTP ingress and unrestricted egress.
25+
var webSg = new Ec2.SecurityGroup("web-sg", new Ec2.SecurityGroupArgs
26+
{
27+
VpcId = vpc.Id,
28+
Egress =
29+
{
30+
new Ec2.Inputs.SecurityGroupEgressArgs
31+
{
32+
Protocol = "-1",
33+
FromPort = 0,
34+
ToPort = 0,
35+
CidrBlocks = { "0.0.0.0/0" },
36+
},
37+
},
38+
Ingress =
39+
{
40+
new Ec2.Inputs.SecurityGroupIngressArgs
41+
{
42+
Protocol = "tcp",
43+
FromPort = 80,
44+
ToPort = 80,
45+
CidrBlocks = { "0.0.0.0/0" },
46+
},
47+
},
48+
});
49+
50+
// Create an ECS cluster to run a container-based service.
51+
var cluster = new Ecs.Cluster("app-cluster");
52+
53+
// Create an IAM role that can be used by our service's task.
54+
var taskExecRole = new Iam.Role("task-exec-role", new Iam.RoleArgs
55+
{
56+
AssumeRolePolicy = @"{
57+
""Version"": ""2008-10-17"",
58+
""Statement"": [{
59+
""Sid"": """",
60+
""Effect"": ""Allow"",
61+
""Principal"": {
62+
""Service"": ""ecs-tasks.amazonaws.com""
63+
},
64+
""Action"": ""sts:AssumeRole""
65+
}]
66+
}",
67+
});
68+
var taskExecAttach = new Iam.RolePolicyAttachment("task-exec-policy", new Iam.RolePolicyAttachmentArgs
69+
{
70+
Role = taskExecRole.Name,
71+
PolicyArn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
72+
});
73+
74+
// Create a load balancer to listen for HTTP traffic on port 80.
75+
var webLb = new Elb.LoadBalancer("web-lb", new Elb.LoadBalancerArgs
76+
{
77+
Subnets = subnet.Ids,
78+
SecurityGroups = { webSg.Id },
79+
});
80+
var webTg = new Elb.TargetGroup("web-tg", new Elb.TargetGroupArgs
81+
{
82+
Port = 80,
83+
Protocol = "HTTP",
84+
TargetType = "ip",
85+
VpcId = vpc.Id,
86+
});
87+
var webListener = new Elb.Listener("web-listener", new Elb.ListenerArgs
88+
{
89+
LoadBalancerArn = webLb.Arn,
90+
Port = 80,
91+
DefaultActions =
92+
{
93+
new Elb.Inputs.ListenerDefaultActionsArgs
94+
{
95+
Type = "forward",
96+
TargetGroupArn = webTg.Arn,
97+
},
98+
},
99+
});
100+
101+
// Create a private ECR registry and build and publish our app's container image to it.
102+
var appRepo = new Ecr.Repository("app-repo");
103+
var appRepoCreds = appRepo.RegistryId.Apply(async rid =>
104+
{
105+
var creds = await Ecr.Invokes.GetCredentials(new Ecr.GetCredentialsArgs { RegistryId = rid });
106+
var credsData = Convert.FromBase64String(creds.AuthorizationToken);
107+
return Encoding.UTF8.GetString(credsData).Split(":");
108+
});
109+
var image = new Docker.Image("app-img", new Docker.ImageArgs
110+
{
111+
Build = "../App",
112+
ImageName = appRepo.RepositoryUrl,
113+
Registry = new Docker.ImageRegistry
114+
{
115+
Server = appRepo.RepositoryUrl,
116+
Username = appRepoCreds.Apply(creds => creds[0]),
117+
Password = appRepoCreds.Apply(creds => creds[1]),
118+
},
119+
});
120+
121+
// Spin up a load balanced service running our container image.
122+
var appTask = new Ecs.TaskDefinition("app-task", new Ecs.TaskDefinitionArgs
123+
{
124+
Family = "fargate-task-definition",
125+
Cpu = "256",
126+
Memory = "512",
127+
NetworkMode = "awsvpc",
128+
RequiresCompatibilities = { "FARGATE" },
129+
ExecutionRoleArn = taskExecRole.Arn,
130+
ContainerDefinitions = image.ImageName.Apply(imageName => @"[{
131+
""name"": ""my-app"",
132+
""image"": """ + imageName + @""",
133+
""portMappings"": [{
134+
""containerPort"": 80,
135+
""hostPort"": 80,
136+
""protocol"": ""tcp""
137+
}]
138+
}]"),
139+
});
140+
var appSvc = new Ecs.Service("app-svc", new Ecs.ServiceArgs
141+
{
142+
Cluster = cluster.Arn,
143+
DesiredCount = 3,
144+
LaunchType = "FARGATE",
145+
TaskDefinition = appTask.Arn,
146+
NetworkConfiguration = new Ecs.Inputs.ServiceNetworkConfigurationArgs
147+
{
148+
AssignPublicIp = true,
149+
Subnets = subnet.Ids,
150+
SecurityGroups = { webSg.Id },
151+
},
152+
LoadBalancers =
153+
{
154+
new Ecs.Inputs.ServiceLoadBalancersArgs
155+
{
156+
TargetGroupArn = webTg.Arn,
157+
ContainerName = "my-app",
158+
ContainerPort = 80,
159+
},
160+
},
161+
}, new CustomResourceOptions { DependsOn = { webListener } });
162+
163+
// Export the resulting web address.
164+
return new Dictionary<string, object?>
165+
{
166+
{ "url", Output.Format($"http://{webLb.DnsName}") },
167+
};
168+
});
169+
}
170+
}

aws-cs-fargate/Pulumi.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: aws-cs-fargate
2+
runtime: dotnet
3+
main: Infra
4+
description: An ASP.NET application running in AWS ECS Fargate, using C# infrastructure as code

0 commit comments

Comments
 (0)