Skip to content

Commit

Permalink
feat(ecs/elbv2): add example showing how to share LB across stacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Rico Huijbers committed Oct 24, 2019
1 parent 8fe471d commit e32d594
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 6 deletions.
8 changes: 2 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,7 @@ dmypy.json
.pyre/

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.vscode

### VisualStudioCode Patch ###
# Ignore all local history of files
Expand Down Expand Up @@ -383,4 +379,4 @@ $RECYCLE.BIN/
cdk.context.json
package-lock.json
.cdk.staging
cdk.out
cdk.out
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ $ cdk destroy
| [ecs-service-with-task-networking](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/ecs-service-with-task-networking/) | Starting an ECS service with task networking, allowing ingress traffic to the task but blocking for the instance |
| [fargate-load-balanced-service](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/fargate-load-balanced-service/) | Starting a container fronted by a load balancer on Fargate |
| [fargate-service-with-auto-scaling](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/fargate-service-with-auto-scaling/) | Starting an ECS service of FARGATE launch type that auto scales based on average CPU Utilization |
| [ecs-cross-stack-load-balancer](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/cross-stack-load-balancer/) | Shows how to use a single load balancer with services in other stacks |
| [lambda-cron](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/lambda-cron/) | Running a Lambda on a schedule |
| [my-widget-service](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/my-widget-service/) | Use Lambda to serve up widgets |
| [resource-overrides](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/resource-overrides/) | Shows how to override generated CloudFormation code |
Expand Down
25 changes: 25 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
This example shows how to use a load balancer in a different
stack to load balance to an ECS service.

You must either create an `ApplicationListener` in the same stack as your
ECS service, or create an empty `TargetGroup` in your load balancer stack
and register your ECS service into that.

This example demoes both of these possibilities. It uses Fargate,
but the same principles hold for EC2 services.

(A third option, not pictured here, is to create the shared listener
with a fixed response and add a new listener rule in the service stack).

Option 1: Split at listener
---------------------------

Shown in `split-at-listener.ts`, create an `ApplicationLoadBalancer`
in a shared stack, and an `ApplicationListener` in the service stack.


Option 1: Split at target group
-------------------------------

Shown in `split-at-targetgroup.ts`, create an empty `TargetGroup` in the load
balancer stack, and register a `Service` into it in the service stack.
3 changes: 3 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "node index"
}
48 changes: 48 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import ecs = require('@aws-cdk/aws-ecs');
import ec2 = require('@aws-cdk/aws-ec2');
import { Stack, Construct, StackProps, App } from '@aws-cdk/core';
import { SplitAtListener_LoadBalancerStack, SplitAtListener_ServiceStack } from './split-at-listener';
import { SplitAtTargetGroup_LoadBalancerStack, SplitAtTargetGroup_ServiceStack } from './split-at-targetgroup';

/**
* Shared infrastructure -- VPC and Cluster
*/
class SharedInfraStack extends Stack {
public readonly vpc: ec2.Vpc;
public readonly cluster: ecs.Cluster;

constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);

this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2 });
this.cluster = new ecs.Cluster(this, 'Cluster', {
vpc: this.vpc
});
}
}

const app = new App();

const infra = new SharedInfraStack(app, 'CrossStackLBInfra');

// Demo that splits at Listener
const splitAtListenerLBStack = new SplitAtListener_LoadBalancerStack(app, 'SplitAtListener-LBStack', {
vpc: infra.vpc,
});
new SplitAtListener_ServiceStack(app, 'SplitAtListener-ServiceStack', {
cluster: infra.cluster,
vpc: infra.vpc,
loadBalancer: splitAtListenerLBStack.loadBalancer
});

// Demo that splits at Target Group
const splitAtTargetGroupLBStack = new SplitAtTargetGroup_LoadBalancerStack(app, 'SplitAtTargetGroup-LBStack', {
vpc: infra.vpc,
});
new SplitAtTargetGroup_ServiceStack(app, 'SplitAtTargetGroup-ServiceStack', {
cluster: infra.cluster,
vpc: infra.vpc,
targetGroup: splitAtTargetGroupLBStack.targetGroup
});

app.synth();
27 changes: 27 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "cross-stack-load-balancer",
"version": "1.0.0",
"description": "EC2 Service with Application Load Balancer in a different stack",
"private": true,
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"cdk": "cdk"
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"organization": true
},
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^8.10.38",
"typescript": "^3.2.4"
},
"dependencies": {
"@aws-cdk/aws-ec2": "*",
"@aws-cdk/aws-ecs": "*",
"@aws-cdk/aws-elasticloadbalancingv2": "*",
"@aws-cdk/core": "*"
}
}
70 changes: 70 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/split-at-listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ecs = require('@aws-cdk/aws-ecs');
import ec2 = require('@aws-cdk/aws-ec2');
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import { Stack, Construct, StackProps, CfnOutput } from '@aws-cdk/core';


//---------------------------------------------------------------------------
// Load balancer stack

export interface SplitAtListener_LoadBalancerStackProps extends StackProps {
vpc: ec2.IVpc;
}

export class SplitAtListener_LoadBalancerStack extends Stack {
public readonly loadBalancer: elbv2.ApplicationLoadBalancer;

constructor(scope: Construct, id: string, props: SplitAtListener_LoadBalancerStackProps) {
super(scope, id, props);

this.loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
vpc: props.vpc,
internetFacing: true
});

new CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName, });
}
}

//---------------------------------------------------------------------------
// Service stack

export interface SplitAtListener_ServiceStackProps extends StackProps {
vpc: ec2.IVpc;
cluster: ecs.ICluster;
loadBalancer: elbv2.IApplicationLoadBalancer;
}

export class SplitAtListener_ServiceStack extends Stack {
constructor(scope: Construct, id: string, props: SplitAtListener_ServiceStackProps) {
super(scope, id, props);

// Standard ECS service setup
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef');
const container = taskDefinition.addContainer('web', {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
memoryLimitMiB: 256,
});

container.addPortMappings({
containerPort: 80,
protocol: ecs.Protocol.TCP
});

const service = new ecs.FargateService(this, "Service", {
cluster: props.cluster,
taskDefinition,
});

// Create a new listener in the current scope, add targets to it
const listener = new elbv2.ApplicationListener(this, 'Listener', {
loadBalancer: props.loadBalancer, // ! need to pass load balancer to attach to !
port: 80,
});

listener.addTargets('ECS', {
port: 80,
targets: [service],
});
}
}
78 changes: 78 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/split-at-targetgroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import ecs = require('@aws-cdk/aws-ecs');
import ec2 = require('@aws-cdk/aws-ec2');
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import { Stack, Construct, StackProps, CfnOutput } from '@aws-cdk/core';


//---------------------------------------------------------------------------
// Load balancer stack

export interface SplitAtTargetGroup_LoadBalancerStackProps extends StackProps {
vpc: ec2.IVpc;
}

export class SplitAtTargetGroup_LoadBalancerStack extends Stack {
public readonly targetGroup: elbv2.ApplicationTargetGroup;

constructor(scope: Construct, id: string, props: SplitAtTargetGroup_LoadBalancerStackProps) {
super(scope, id, props);

const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
vpc: props.vpc,
internetFacing: true
});

this.targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
vpc: props.vpc,
port: 80,
});

loadBalancer.addListener('Listener', {
port: 80,
defaultTargetGroups: [this.targetGroup]
});

new CfnOutput(this, 'LoadBalancerDNS', { value: loadBalancer.loadBalancerDnsName, });
}
}

//---------------------------------------------------------------------------
// Service stack

export interface SplitAtTargetGroup_ServiceStackProps extends StackProps {
vpc: ec2.IVpc;
cluster: ecs.ICluster;

// NOTE: Temporarily an ApplicationTargetGroup (instead of the corresponding interface)
// because the interface does not contain addTarget() yet. Can be rectified after CDK
// 1.15.0.
targetGroup: elbv2.ApplicationTargetGroup;
}

export class SplitAtTargetGroup_ServiceStack extends Stack {
constructor(scope: Construct, id: string, props: SplitAtTargetGroup_ServiceStackProps) {
super(scope, id, props);

// Standard ECS service setup
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef');
const container = taskDefinition.addContainer('web', {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
memoryLimitMiB: 256,
});

container.addPortMappings({
containerPort: 80,
protocol: ecs.Protocol.TCP
});

const service = new ecs.FargateService(this, "Service", {
cluster: props.cluster,
taskDefinition,
});

// Connect service to TargetGroup
// NOTE: This does not introduce a cycle because ECS Services are self-registering
// (they point to the TargetGroup instead of the other way around).
props.targetGroup.addTarget(service);
}
}
21 changes: 21 additions & 0 deletions typescript/ecs/cross-stack-load-balancer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target":"ES2018",
"module": "commonjs",
"lib": ["es2016", "es2017.object", "es2017.string"],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization":false
}
}

0 comments on commit e32d594

Please sign in to comment.