Referencing existing AWS VPC with CDK

How can I reference existing resources´╝č

As you using CDK with deploy your project and sometimes it may separated into vary GitHub repo, and wondering how can I easy share VPC and Subnet within those projects and so on.

To do that, we will setup 3 different projects which name vpcStack (VPC), LambdaStack (Lambda with ApiGateway) and ecrStack (docker image handled by Fargate and a Loadbalancer)

Example vpcStack

// lib/vpcStack.ts

import * as cdk from '@aws-cdk/core';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'
import * as ec2 from '@aws-cdk/aws-ec2'

export class SygnaVpcStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, `VPC`, {
      cidr: '10.192.0.0/16',
      maxAzs: 2,
      natGateways: 1,
      enableDnsHostnames: true,
      enableDnsSupport: true,
    })

    const nlb = new elbv2.NetworkLoadBalancer(this, `ElB`, {
      vpc:vpc,
    })
    new cdk.CfnOutput(this, "VPC-id", {value: vpc.vpcId });

  }
}

After you deployed to AWS Cloudformation, you will get something like this vpc-05ea90e, this will be your current VPCId

ApiStack

// lib/ApiStack.ts

const vpc = ec2.Vpc.fromLookup(this, "referenced-vpc", {
      vpcId: "vpc-0axxxx0e"
    });
    const ElbARN =
      "arn:aws:elasticloadbalancing:ap-southeast-1:9xxxxxx587:loadbalancer/net/8Mxxxx/2e0xxxxxd0";

    const Elb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(
      this,
      "api-gateway-elb",
      {
        loadBalancerArn: ElbARN,
        vpc: vpc
      }
    );

    const ApiGateway = new apigateway.VpcLink(
      this,
      "apigateway",
      {
        description: "Api Gateway build by CDK",
        vpcLinkName: "api-gateway"
      }
    );

    // Attaching Api gateway to current VPC
    ApiGateway.addTargets(sygnaElb);

    const api = new lambda.Function(this, "myLambda", {
      runtime: lambda.Runtime.NODEJS_10_X,
      code: lambda.AssetCode.fromAsset("lambda"),
      handler: "index.handler",
      vpc: vpc,
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
      environment: {
        NODE_ENV: environment.NODE_ENV,
      }
    });

    api.role?.addToPolicy(
      new iam.PolicyStatement({
        actions: ["dynamodb:*"],
        resources: [
          `arn:aws:dynamodb:${environment.AWS_DYNAMO_REGION}:*:table/*`
        ]
      })
    );

    api.role?.addToPolicy(
      new iam.PolicyStatement({
        actions: ["ssm:*"],
        resources: ["*"]
      })
    );

ecrStack example

// lib/ecrStack.tx

const ROLE_ARN = `arn:aws:iam::${ARNID}:role/ecsTaskExecutionRole`
const dbPrefix = `${stage}_`
const jwt_secret = `arn:aws:ssm:${REGION}:${ARNID}:parameter/back-office/dev/JWT_SECRET`

const role = iam.Role.fromRoleArn(this, 'Role', ROLE_ARN)
const JWT_SECRET = sm.Secret.fromSecretAttributes(this, 'JWT_SECRET', {secretArn: jwt_secret})

const vpcNamespaceName = `bob${stage}`


const vpc = ec2.Vpc.fromLookup(this, 'referenced-vpc', {
      vpcId: 'vpc-0xxx0e',
    })

    const vpcNamespace = new servicediscovery.PrivateDnsNamespace(
      this,
      'VPCNamespace',
      {
        name: vpcNamespaceName,
        vpc,
      },
    )
    const vpcService = vpcNamespace.createService('vpcService', {
      dnsRecordType: servicediscovery.DnsRecordType.A,
      dnsTtl: cdk.Duration.seconds(300),
      loadBalancer: true,
    })

    const cluster = new ecs.Cluster(this, 'Cluster', {
      vpc: vpc,
    })
    const GatewaytaskDef = new ecs.FargateTaskDefinition(
      this,
      'GatewayTaskDefinition',
      {
        executionRole: role,
      },
    )

const GatewayContainer = new ecs.ContainerDefinition(
      this,
      'servicegateway',
      {
        image: ecs.ContainerImage.fromRegistry(Gateway_IMAGE),
        taskDefinition: GatewaytaskDef,
        logging: new ecs.AwsLogDriver({ streamPrefix: 'GatewayContainer' }),
        environment: {
          DB_TABLE_PREFIX: dbPrefix,
        },

        secrets: {
          JWT_SECRET: ecs.Secret.fromSecretsManager(JWT_SECRET),
        },
      },
    )

    GatewayContainer.addPortMappings({
      hostPort: gatewayPort,
      containerPort: gatewayPort,
    })

    const GatewayFargateService = new ecs.FargateService(
      this,
      'serviceGatewayFargate',
      {
        cluster: cluster,
        taskDefinition: GatewaytaskDef,
        assignPublicIp: true,
        desiredCount: 2,
        serviceName: 'servicegateway',
        vpcSubnets: vpc.selectSubnets({
          subnetType: ec2.SubnetType.PUBLIC,
        }),
      },
    )
    GatewayFargateService.connections.allowFromAnyIpv4(
      ec2.Port.tcpRange(0, 65535),
      'allow all port',
    )
    GatewayFargateService.enableCloudMap({
      cloudMapNamespace: vpcNamespace,
      name: 'servicegateway',
      dnsRecordType: servicediscovery.DnsRecordType.A,
      dnsTtl: cdk.Duration.seconds(60),
      //failureThreshold: 10
    })

const Gatewayscaling = GatewayFargateService.autoScaleTaskCount({
      maxCapacity: 4,
      minCapacity: 2,
    })

    Gatewayscaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50,
    })

    const alb = new elb.ApplicationLoadBalancer(this, 'LoadBalancer', {
      vpc: vpc,
      internetFacing: true,
      ipAddressType: elb.IpAddressType.IPV4,
      vpcSubnets: vpc.selectSubnets({
        subnetType: ec2.SubnetType.PUBLIC,
      }),
    })

    vpcService.registerLoadBalancer('LoadBalancer', alb)

    const tg = new elb.ApplicationTargetGroup(this, 'TargetGroup', {
      protocol: elb.ApplicationProtocol.HTTP,
      port: 80,
      vpc: vpc,
      targetType: elb.TargetType.IP,
      targets: [GatewayFargateService],
      healthCheck: {
        interval: cdk.Duration.seconds(60),
        path: '/.well-known/apollo/server-health',
        timeout: cdk.Duration.seconds(5),
      },
    })
    const listener = alb.addListener('Listener', {
      protocol: elb.ApplicationProtocol.HTTP,
      port: 80,
      defaultTargetGroups: [tg],
    })
    const rule = new elb.ApplicationListenerRule(this, 'rule', {
      listener,
      priority: 1,
      pathPattern: '*',
      targetGroups: [tg],
    })

    new cdk.CfnOutput(this, 'ExportLoadBalancerDNS', {
      value: `http://${alb.loadBalancerDnsName}/`,
    })

That’s it for the demo

When you use fromLookup function, you have to specified AWS Region where your current VPC is located. For example I use Singapore, aka ap-southeast-1

// bin/stack.ts

import { CdkStack } from '../lib/cdk-stack'

const envSingaporeDev = { account: '96000007', region: 'ap-southeast-1' }

const bob = new CdkStack(app, 'CdkStack', {env: envSingaporeDev})

While deploy, you have to do cdk synth --profile xxx-sg to get let CDK able to get VPC id that you referencing. Final step, deploy to Cloudformation, cdk deploy --profile xxx-sg.

Source

  1. https://garbe.io/blog/2019/09/20/hey-cdk-how-to-use-existing-resources/
  2. https://gitter.im/awslabs/aws-cdk?at=5cadf5c6a84e0c501a0a9b8a
  3. https://github.com/aws/aws-cdk/issues/4794
  4. https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts
  5. https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.NetworkLoadBalancer.html#static-from-wbr-network-wbr-load-wbr-balancer-wbr-attributesscope-id-attrs
  6. https://docs.aws.amazon.com/cdk/latest/guide/stack_how_to_create_multiple_stacks.html
  7. https://stackoverflow.com/questions/59393111/cdk-split-api-gateway-stack-into-2-small-stacks