skip to Main Content

My goal is to have an AWS code commit repo, that on push to main branch will run a code pipeline CI/CD process to deploy a single node app to AWS.

I went through the setup to get this working with fargate via CDK using ApplicationLoadBalancedFargateService, but ultimately ran into issues because the ALB requires two availability zones, and I don’t want to run two instance of my app (I’m not concerned with high availability, and in this case it’s a chat bot that I don’t want "logged on" twice).

Does anyone have any recommendations here? Perhaps EBS is the service I want? (I’ve gone down that path pre-container, but maybe I should revisit?)

I also read about the code deploy agent and EC2, but that seems more of a manual process, where I’m hoping to be able to automated the creation of all resources with CDK.


resolution: I believe this is a case of me not understanding fargate well enough, shoutout @Victor Smirnov for helping break down everything for me.

There is in fact only a single task registered when my CDK stack builds.

I think the issue I ran into was I’d use the CDK codepipeline ECS deploy action, which would start deploying a second task before deregistering the first (which I think is just a fargate "feature" to avoid downtime, ie blue/green deploy). I mistakenly expected only a single container to be running at a given time, but that’s just not how Services work.

I think Victor had a good point about the health checks as well. It took me a few tries to get all the ports lined up, and when they were misaligned and health checks were failing I’d see the "old failed task" getting "deregistered" alongside the "new task that hadn’t failed yet" which made me think I had two concurrent tasks running.

2

Answers


  1. Your application can still be in one AZ. The fact that ALB requires two AZs is only related to ALB itself. So you do not have to create any extra instance of your application in other AZ if you don’t want. Though it could be a good idea for high-availability.

    Login or Signup to reply.
  2. Below is an example of the ApplicationLoadBalancedFargateService pattern used to create the Fargate service with one running task. I deployed the stack when I wrote the answer to the question.

    The Application load balancer has three availability zones because my VPC has three public subnets. It means that the loader balancer itself has the IP addresses in three different zones.

    enter image description here

    The load balancer has only one target. There is no requirement that the load balancer should have a target in each zone.

    enter image description here

    I put everything in the public VPC zone because I do not have NAT. You might want to have your Fargate tasks in the private zone for better security.

    I added the health check path with default values because, most likely, you will want to define a custom URL for your service. We can omit the default definition.

    import { App, RemovalPolicy, Stack } from 'aws-cdk-lib'
    import { Certificate, CertificateValidation } from 'aws-cdk-lib/aws-certificatemanager'
    import { Vpc } from 'aws-cdk-lib/aws-ec2'
    import { Cluster, ContainerImage, LogDriver } from 'aws-cdk-lib/aws-ecs'
    import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns'
    import { ApplicationProtocol } from 'aws-cdk-lib/aws-elasticloadbalancingv2'
    import { LogGroup } from 'aws-cdk-lib/aws-logs'
    import { HostedZone } from 'aws-cdk-lib/aws-route53'
    import { env } from 'process'
    
    function createStack (scope, id, props) {
      const stack = new Stack(scope, id, props)
    
      const logGroup = new LogGroup(stack, 'LogGroup', { logGroupName: 'so-service', removalPolicy: RemovalPolicy.DESTROY })
    
      const vpc = Vpc.fromLookup(stack, 'Vpc', { vpcName: 'BlogVpc' })
    
      const domainZone = HostedZone.fromLookup(stack, 'ZonePublic', { domainName: 'victorsmirnov.blog' })
    
      const domainName = 'service.victorsmirnov.blog'
      const certificate = new Certificate(stack, 'SslCertificate', {
        domainName,
        validation: CertificateValidation.fromDns(domainZone)
      })
    
      const cluster = new Cluster(stack, 'Cluster', {
        clusterName: 'so-cluster',
        containerInsights: true,
        enableFargateCapacityProviders: true,
        vpc,
      })
    
      const service = new ApplicationLoadBalancedFargateService(stack, id, {
        assignPublicIp: true,
        certificate,
        cluster,
        cpu: 256,
        desiredCount: 1,
        domainName,
        domainZone,
        memoryLimitMiB: 512,
        openListener: true,
        protocol: ApplicationProtocol.HTTPS,
        publicLoadBalancer: true,
        redirectHTTP: true,
        targetProtocol: ApplicationProtocol.HTTP,
        taskImageOptions: {
          containerName: 'nginx',
          containerPort: 80,
          enableLogging: true,
          family: 'so-service',
          image: ContainerImage.fromRegistry('nginx'),
          logDriver: LogDriver.awsLogs({ streamPrefix: 'nginx', logGroup })
        }
      })
    
      service.targetGroup.configureHealthCheck({
        path: '/',
      })
    
      return stack
    }
    
    const app = new App()
    createStack(app, 'SingleInstanceAlbService', {
      env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
    })
    
    

    Content for the cdk.json and package.json for completeness.

    {
        "app": "node question.js",
        "context": {
            "@aws-cdk/core:newStyleStackSynthesis": true
        }
    }
    
    {
        "name": "alb-single-instance",
        "version": "0.1.0",
        "dependencies": {
            "aws-cdk-lib": "^2.37.1",
            "cdk": "^2.37.1",
            "constructs": "^10.1.76"
        },
        "devDependencies": {
            "rimraf": "^3.0.2",
            "snazzy": "^9.0.0",
            "standard": "^17.0.0"
        },
        "scripts": {
            "cdk": "cdk",
            "clean": "rimraf cdk.out dist",
            "format": "standard --fix --verbose | snazzy",
            "test": "standard --verbose | snazzy"
        },
        "type": "module"
    }
    

    This should be enough to have a fully functional setup where everything is configured automatically using the CDK.

    Maybe you do not need the load balancer because there is no need to balance traffic for only one task. You can set up the Service discovery for your service and use the DNS name for your task without a load balancer. This should save money if you want.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search