API Gateway load balanced Fargate service with Cloud Map using CDK construct

Pahud Hsieh profile picture
Pahud Hsieh
Senior SA at AWS

About

ApiGatewayLoadBalancedFargateService is an AWS Cloud Development Kit(CDK) L3 construct that allows you to deploy a web service with Amazon API Gateway and route the traffic through VPC link to the Fargate service running in the VPC private subnets. No application or network load balancer is required. The service discovery capability is provided by the AWS Cloud Map service that comes with ECS service connect.

Private subnetECS FargateVPC LinkVPCAPI GatewayCloud Map

Sample

Language: ts
new ApiGatewayLoadBalancedFargateService(stack, 'DemoService', {
  vpc,
  cluster,
  taskDefinition,
  desiredCount: 2,
  vpcLinkIntegration: VpcLinkIntegration.CLOUDMAP,
});

Setup Cloud Development Kit

To use this pattern you need TypeScript and Node. First, ensure that you have Node.js installed on your development machine. Then create the following files:

  • package.json
  • tsconfig.dev.json
  • cdk.json
File: package.jsonLanguage: json
{
  "name": "ecs-fargate-apigateway-cloudmap-cdk",
  "repository": {
    "type": "git",
    "url": "https://github.com/aws-samples/container-patterns.git"
  },
  "scripts": {
    "build": "npx projen build",
    "bump": "npx projen bump",
    "clobber": "npx projen clobber",
    "compat": "npx projen compat",
    "compile": "npx projen compile",
    "default": "npx projen default",
    "docgen": "npx projen docgen",
    "eject": "npx projen eject",
    "eslint": "npx projen eslint",
    "package": "npx projen package",
    "package-all": "npx projen package-all",
    "package:js": "npx projen package:js",
    "post-compile": "npx projen post-compile",
    "post-upgrade": "npx projen post-upgrade",
    "pre-compile": "npx projen pre-compile",
    "release": "npx projen release",
    "test": "npx projen test",
    "test:watch": "npx projen test:watch",
    "unbump": "npx projen unbump",
    "upgrade": "npx projen upgrade",
    "watch": "npx projen watch",
    "projen": "npx projen"
  },
  "author": {
    "name": "Pahud Hsieh",
    "email": "pahudnet@gmail.com",
    "organization": false
  },
  "devDependencies": {
    "@types/jest": "^29.5.3",
    "@types/node": "^16",
    "@typescript-eslint/eslint-plugin": "^5",
    "@typescript-eslint/parser": "^5",
    "aws-cdk": "2.80.0",
    "aws-cdk-lib": "2.80.0",
    "constructs": "10.0.5",
    "eslint": "^8",
    "eslint-import-resolver-node": "^0.3.7",
    "eslint-import-resolver-typescript": "^3.5.5",
    "eslint-plugin-import": "^2.27.5",
    "jest": "^29.6.1",
    "jest-junit": "^15",
    "jsii": "~5.0.0",
    "jsii-diff": "^1.84.0",
    "jsii-docgen": "^9.1.1",
    "jsii-pacmak": "^1.84.0",
    "jsii-rosetta": "~5.0.0",
    "npm-check-updates": "^16",
    "projen": "^0.71.138",
    "standard-version": "^9",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.6"
  },
  "peerDependencies": {
    "@aws-cdk/aws-apigatewayv2-alpha": "2.80.0-alpha.0",
    "@aws-cdk/aws-lambda-python-alpha": "2.80.0-alpha.0",
    "aws-cdk-lib": "^2.80.0",
    "constructs": "^10.0.5"
  },
  "dependencies": {
    "@aws-cdk/aws-apigatewayv2-alpha": "2.80.0-alpha.0",
    "@aws-cdk/aws-lambda-python-alpha": "2.80.0-alpha.0"
  },
  "keywords": [
    "cdk"
  ],
  "main": "lib/index.js",
  "license": "Apache-2.0",
  "version": "0.0.0",
  "jest": {
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.ts?(x)",
      "<rootDir>/(test|src)/**/*(*.)@(spec|test).ts?(x)"
    ],
    "clearMocks": true,
    "collectCoverage": true,
    "coverageReporters": [
      "json",
      "lcov",
      "clover",
      "cobertura",
      "text"
    ],
    "coverageDirectory": "coverage",
    "coveragePathIgnorePatterns": [
      "/node_modules/"
    ],
    "testPathIgnorePatterns": [
      "/node_modules/"
    ],
    "watchPathIgnorePatterns": [
      "/node_modules/"
    ],
    "reporters": [
      "default",
      [
        "jest-junit",
        {
          "outputDirectory": "test-reports"
        }
      ]
    ],
    "preset": "ts-jest",
    "globals": {
      "ts-jest": {
        "tsconfig": "tsconfig.dev.json"
      }
    }
  },
  "types": "lib/index.d.ts",
  "stability": "stable",
  "jsii": {
    "outdir": "dist",
    "targets": {},
    "tsc": {
      "outDir": "lib",
      "rootDir": "src"
    }
  },
  "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"."
}

The files above serve the following purpose:

  • package.json - This file is used by NPM or Yarn to identify and install all the required dependencies:
  • tsconfig.dev.json - Configures the TypeScript settings for the project:
  • cdk.json - Tells CDK what command to run, and provides a place to pass other contextual settings to CDK.

Run the following commands to install dependencies and setup your AWS account for the deployment:

Language: sh
yarn install
npx cdk bootstrap

Deploy the sample App

File: integ.default.tsLanguage: ts
import {
  App, Stack,
  aws_ecs as ecs,
  aws_ec2 as ec2,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { ApiGatewayLoadBalancedFargateService, VpcLinkIntegration } from './agw-balanced-fargate-service';

export class IntegTesting {
  readonly stack: Stack[];
  constructor() {
    const app = new App();
    const env = { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT };
    const stack = new Stack(app, 'integ-testing', { env });

    const vpc = this.getVpc(stack);
    const cluster = new ecs.Cluster(stack, 'Cluster', {
      vpc,
      enableFargateCapacityProviders: true,
    });

    cluster.addDefaultCapacityProviderStrategy([
      { capacityProvider: 'FARGATE_SPOT', base: 2, weight: 50 },
      { capacityProvider: 'FARGATE', weight: 50 },
    ]);

    const taskDefinition = new ecs.FargateTaskDefinition(stack, 'Task', {
      memoryLimitMiB: 512,
      cpu: 256,
    });

    taskDefinition.addContainer('nyancat', {
      image: ecs.ContainerImage.fromRegistry('public.ecr.aws/pahudnet/nyancat-docker-image:latest'),
      portMappings: [{ containerPort: 80, name: 'default' }],
      healthCheck: {
        command: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1'],
      },
    });

    new ApiGatewayLoadBalancedFargateService(stack, 'DemoService', {
      vpc,
      cluster,
      taskDefinition,
      desiredCount: 2,
      vpcLinkIntegration: VpcLinkIntegration.CLOUDMAP,
    });
    this.stack = [stack];
  }
  private getVpc(scope: Construct): ec2.IVpc {
    return scope.node.tryGetContext('use_default_vpc') === '1' ?
      ec2.Vpc.fromLookup(scope, 'Vpc', { isDefault: true }) :
      scope.node.tryGetContext('use_vpc_id') != undefined ?
        ec2.Vpc.fromLookup(scope, 'Vpc', { vpcId: scope.node.tryGetContext('use_vpc_id') }) :
        new ec2.Vpc(scope, 'Vpc', { natGateways: 1 });
  }
};

new IntegTesting();
Language: sh
npx cdk diff

Deploy the stack:

Language: sh
npx cdk deploy

You will see an Outputs section that shows the endpoint URL of the API Gateway. When you load up that URL you should see the nyancat demo animation.

Next Steps

As this sample comes with a L3 construct ApiGatewayLoadBalancedFargateService, you can modify the typescript under files/src to create your own CDK application using the provided contruct with custom properties. For example, if you create your sample app and save as sample.ts, run this command in files to deploy your CDK app in sample.ts:

Language: sh
npx cdk -a 'npx ts-node --prefer-ts-exts src/sample.ts' diff
npx cdk -a 'npx ts-node --prefer-ts-exts src/sample.ts' deploy

Clean Up

You can tear down the stack using the following command:

Language: sh
npx cdk destroy