packages/aws-cdk-lib/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts (125 lines of code) (raw):

import { Construct } from 'constructs'; import { ISecurityGroup, SubnetSelection } from '../../../aws-ec2'; import { FargateService, FargateTaskDefinition, HealthCheck } from '../../../aws-ecs'; import { FeatureFlags, Token } from '../../../core'; import * as cxapi from '../../../cx-api'; import { ApplicationLoadBalancedServiceBase, ApplicationLoadBalancedServiceBaseProps } from '../base/application-load-balanced-service-base'; import { FargateServiceBaseProps } from '../base/fargate-service-base'; /** * The properties for the ApplicationLoadBalancedFargateService service. */ export interface ApplicationLoadBalancedFargateServiceProps extends ApplicationLoadBalancedServiceBaseProps, FargateServiceBaseProps { /** * Determines whether the service will be assigned a public IP address. * * @default false */ readonly assignPublicIp?: boolean; /** * The subnets to associate with the service. * * @default - Public subnets if `assignPublicIp` is set, otherwise the first available one of Private, Isolated, Public, in that order. */ readonly taskSubnets?: SubnetSelection; /** * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * @default - A new security group is created. */ readonly securityGroups?: ISecurityGroup[]; /** * The health check command and associated configuration parameters for the container. * * @default - Health check configuration from container. */ readonly healthCheck?: HealthCheck; /** * The minimum number of CPU units to reserve for the container. * * @default - No minimum CPU units reserved. */ readonly containerCpu?: number; /** * The amount (in MiB) of memory to present to the container. * * If your container attempts to exceed the allocated memory, the container * is terminated. * * @default - No memory limit. */ readonly containerMemoryLimitMiB?: number; } /** * A Fargate service running on an ECS cluster fronted by an application load balancer. */ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalancedServiceBase { /** * Determines whether the service will be assigned a public IP address. */ public readonly assignPublicIp: boolean; /** * The Fargate service in this construct. */ public readonly service: FargateService; /** * The Fargate task definition in this construct. */ public readonly taskDefinition: FargateTaskDefinition; /** * Constructs a new instance of the ApplicationLoadBalancedFargateService class. */ constructor(scope: Construct, id: string, props: ApplicationLoadBalancedFargateServiceProps = {}) { super(scope, id, props); this.assignPublicIp = props.assignPublicIp ?? false; if (props.taskDefinition && props.taskImageOptions) { throw new Error('You must specify either a taskDefinition or an image, not both.'); } else if (props.taskDefinition) { this.taskDefinition = props.taskDefinition; } else if (props.taskImageOptions) { const taskImageOptions = props.taskImageOptions; this.taskDefinition = new FargateTaskDefinition(this, 'TaskDef', { memoryLimitMiB: props.memoryLimitMiB, cpu: props.cpu, ephemeralStorageGiB: props.ephemeralStorageGiB, executionRole: taskImageOptions.executionRole, taskRole: taskImageOptions.taskRole, family: taskImageOptions.family, runtimePlatform: props.runtimePlatform, }); // Create log driver if logging is enabled const enableLogging = taskImageOptions.enableLogging ?? true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging ? this.createAWSLogDriver(this.node.id) : undefined; this.validateContainerCpu(this.taskDefinition.cpu, props.containerCpu); this.validateContainerMemoryLimitMiB(this.taskDefinition.memoryMiB, props.containerMemoryLimitMiB); const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, healthCheck: props.healthCheck, logging: logDriver, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, dockerLabels: taskImageOptions.dockerLabels, command: taskImageOptions.command, entryPoint: taskImageOptions.entryPoint, cpu: props.containerCpu, memoryLimitMiB: props.containerMemoryLimitMiB, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, }); } else { throw new Error('You must specify one of: taskDefinition or image'); } this.validateHealthyPercentage('minHealthyPercent', props.minHealthyPercent); this.validateHealthyPercentage('maxHealthyPercent', props.maxHealthyPercent); if ( props.minHealthyPercent && !Token.isUnresolved(props.minHealthyPercent) && props.maxHealthyPercent && !Token.isUnresolved(props.maxHealthyPercent) && props.minHealthyPercent >= props.maxHealthyPercent ) { throw new Error('Minimum healthy percent must be less than maximum healthy percent.'); } const desiredCount = FeatureFlags.of(this).isEnabled(cxapi.ECS_REMOVE_DEFAULT_DESIRED_COUNT) ? this.internalDesiredCount : this.desiredCount; this.service = new FargateService(this, 'Service', { cluster: this.cluster, desiredCount: desiredCount, taskDefinition: this.taskDefinition, assignPublicIp: this.assignPublicIp, serviceName: props.serviceName, healthCheckGracePeriod: props.healthCheckGracePeriod, minHealthyPercent: props.minHealthyPercent, maxHealthyPercent: props.maxHealthyPercent, propagateTags: props.propagateTags, enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, deploymentController: props.deploymentController, circuitBreaker: props.circuitBreaker, securityGroups: props.securityGroups, vpcSubnets: props.taskSubnets, enableExecuteCommand: props.enableExecuteCommand, capacityProviderStrategies: props.capacityProviderStrategies, }); this.addServiceAsTarget(this.service); } /** * Throws an error if the specified percent is not an integer or negative. */ private validateHealthyPercentage(name: string, value?: number) { if (value === undefined || Token.isUnresolved(value)) { return; } if (!Number.isInteger(value) || value < 0) { throw new Error(`${name}: Must be a non-negative integer; received ${value}`); } } private validateContainerCpu(cpu: number, containerCpu?: number) { if (containerCpu === undefined || Token.isUnresolved(containerCpu) || Token.isUnresolved(cpu)) { return; } if (containerCpu > cpu) { throw new Error(`containerCpu must be less than to cpu; received containerCpu: ${containerCpu}, cpu: ${cpu}`); } // If containerCPU is 0, it is not an error. if (containerCpu < 0 || !Number.isInteger(containerCpu)) { throw new Error(`containerCpu must be a non-negative integer; received ${containerCpu}`); } } private validateContainerMemoryLimitMiB(memoryLimitMiB: number, containerMemoryLimitMiB?: number) { if (containerMemoryLimitMiB === undefined || Token.isUnresolved(containerMemoryLimitMiB) || Token.isUnresolved(memoryLimitMiB)) { return; } if (containerMemoryLimitMiB > memoryLimitMiB) { throw new Error(`containerMemoryLimitMiB must be less than to memoryLimitMiB; received containerMemoryLimitMiB: ${containerMemoryLimitMiB}, memoryLimitMiB: ${memoryLimitMiB}`); } if (containerMemoryLimitMiB <= 0 || !Number.isInteger(containerMemoryLimitMiB)) { throw new Error(`containerMemoryLimitMiB must be a positive integer; received ${containerMemoryLimitMiB}`); } } }