UNPKG

38.2 kBMarkdownView Raw
1# Amazon ECS Construct Library
2<!--BEGIN STABILITY BANNER-->
3
4---
5
6![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge)
7
8![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge)
9
10---
11
12<!--END STABILITY BANNER-->
13
14This package contains constructs for working with **Amazon Elastic Container
15Service** (Amazon ECS).
16
17Amazon Elastic Container Service (Amazon ECS) is a fully managed container orchestration service.
18
19For further information on Amazon ECS,
20see the [Amazon ECS documentation](https://docs.aws.amazon.com/ecs)
21
22The following example creates an Amazon ECS cluster, adds capacity to it, and
23runs a service on it:
24
25```ts
26declare const vpc: ec2.Vpc;
27
28// Create an ECS cluster
29const cluster = new ecs.Cluster(this, 'Cluster', {
30 vpc,
31});
32
33// Add capacity to it
34cluster.addCapacity('DefaultAutoScalingGroupCapacity', {
35 instanceType: new ec2.InstanceType("t2.xlarge"),
36 desiredCapacity: 3,
37});
38
39const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
40
41taskDefinition.addContainer('DefaultContainer', {
42 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
43 memoryLimitMiB: 512,
44});
45
46// Instantiate an Amazon ECS Service
47const ecsService = new ecs.Ec2Service(this, 'Service', {
48 cluster,
49 taskDefinition,
50});
51```
52
53For a set of constructs defining common ECS architectural patterns, see the `@aws-cdk/aws-ecs-patterns` package.
54
55## Launch Types: AWS Fargate vs Amazon EC2
56
57There are two sets of constructs in this library; one to run tasks on Amazon EC2 and
58one to run tasks on AWS Fargate.
59
60- Use the `Ec2TaskDefinition` and `Ec2Service` constructs to run tasks on Amazon EC2 instances running in your account.
61- Use the `FargateTaskDefinition` and `FargateService` constructs to run tasks on
62 instances that are managed for you by AWS.
63- Use the `ExternalTaskDefinition` and `ExternalService` constructs to run AWS ECS Anywhere tasks on self-managed infrastructure.
64
65Here are the main differences:
66
67- **Amazon EC2**: instances are under your control. Complete control of task to host
68 allocation. Required to specify at least a memory reservation or limit for
69 every container. Can use Host, Bridge and AwsVpc networking modes. Can attach
70 Classic Load Balancer. Can share volumes between container and host.
71- **AWS Fargate**: tasks run on AWS-managed instances, AWS manages task to host
72 allocation for you. Requires specification of memory and cpu sizes at the
73 taskdefinition level. Only supports AwsVpc networking modes and
74 Application/Network Load Balancers. Only the AWS log driver is supported.
75 Many host features are not supported such as adding kernel capabilities
76 and mounting host devices/volumes inside the container.
77- **AWS ECSAnywhere**: tasks are run and managed by AWS ECS Anywhere on infrastructure owned by the customer. Only Bridge networking mode is supported. Does not support autoscaling, load balancing, cloudmap or attachment of volumes.
78
79For more information on Amazon EC2 vs AWS Fargate, networking and ECS Anywhere see the AWS Documentation:
80[AWS Fargate](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html),
81[Task Networking](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html),
82[ECS Anywhere](https://aws.amazon.com/ecs/anywhere/)
83
84## Clusters
85
86A `Cluster` defines the infrastructure to run your
87tasks on. You can run many tasks on a single cluster.
88
89The following code creates a cluster that can run AWS Fargate tasks:
90
91```ts
92declare const vpc: ec2.Vpc;
93
94const cluster = new ecs.Cluster(this, 'Cluster', {
95 vpc,
96});
97```
98
99The following code imports an existing cluster using the ARN which can be used to
100import an Amazon ECS service either EC2 or Fargate.
101
102```ts
103const clusterArn = 'arn:aws:ecs:us-east-1:012345678910:cluster/clusterName';
104
105const cluster = ecs.Cluster.fromClusterArn(this, 'Cluster', clusterArn);
106```
107
108To use tasks with Amazon EC2 launch-type, you have to add capacity to
109the cluster in order for tasks to be scheduled on your instances. Typically,
110you add an AutoScalingGroup with instances running the latest
111Amazon ECS-optimized AMI to the cluster. There is a method to build and add such an
112AutoScalingGroup automatically, or you can supply a customized AutoScalingGroup
113that you construct yourself. It's possible to add multiple AutoScalingGroups
114with various instance types.
115
116The following example creates an Amazon ECS cluster and adds capacity to it:
117
118```ts
119declare const vpc: ec2.Vpc;
120
121const cluster = new ecs.Cluster(this, 'Cluster', {
122 vpc,
123});
124
125// Either add default capacity
126cluster.addCapacity('DefaultAutoScalingGroupCapacity', {
127 instanceType: new ec2.InstanceType("t2.xlarge"),
128 desiredCapacity: 3,
129});
130
131// Or add customized capacity. Be sure to start the Amazon ECS-optimized AMI.
132const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
133 vpc,
134 instanceType: new ec2.InstanceType('t2.xlarge'),
135 machineImage: ecs.EcsOptimizedImage.amazonLinux(),
136 // Or use Amazon ECS-Optimized Amazon Linux 2 AMI
137 // machineImage: EcsOptimizedImage.amazonLinux2(),
138 desiredCapacity: 3,
139 // ... other options here ...
140});
141
142cluster.addAutoScalingGroup(autoScalingGroup);
143```
144
145If you omit the property `vpc`, the construct will create a new VPC with two AZs.
146
147By default, all machine images will auto-update to the latest version
148on each deployment, causing a replacement of the instances in your AutoScalingGroup
149if the AMI has been updated since the last deployment.
150
151If task draining is enabled, ECS will transparently reschedule tasks on to the new
152instances before terminating your old instances. If you have disabled task draining,
153the tasks will be terminated along with the instance. To prevent that, you
154can pick a non-updating AMI by passing `cacheInContext: true`, but be sure
155to periodically update to the latest AMI manually by using the [CDK CLI
156context management commands](https://docs.aws.amazon.com/cdk/latest/guide/context.html):
157
158```ts
159declare const vpc: ec2.Vpc;
160const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
161 machineImage: ecs.EcsOptimizedImage.amazonLinux({ cachedInContext: true }),
162 vpc,
163 instanceType: new ec2.InstanceType('t2.micro'),
164});
165```
166
167### Bottlerocket
168
169[Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open source operating system that is
170purpose-built by AWS for running containers. You can launch Amazon ECS container instances with the Bottlerocket AMI.
171
172The following example will create a capacity with self-managed Amazon EC2 capacity of 2 `c5.large` Linux instances running with `Bottlerocket` AMI.
173
174The following example adds Bottlerocket capacity to the cluster:
175
176```ts
177declare const cluster: ecs.Cluster;
178
179cluster.addCapacity('bottlerocket-asg', {
180 minCapacity: 2,
181 instanceType: new ec2.InstanceType('c5.large'),
182 machineImage: new ecs.BottleRocketImage(),
183});
184```
185
186### ARM64 (Graviton) Instances
187
188To launch instances with ARM64 hardware, you can use the Amazon ECS-optimized
189Amazon Linux 2 (arm64) AMI. Based on Amazon Linux 2, this AMI is recommended
190for use when launching your EC2 instances that are powered by Arm-based AWS
191Graviton Processors.
192
193```ts
194declare const cluster: ecs.Cluster;
195
196cluster.addCapacity('graviton-cluster', {
197 minCapacity: 2,
198 instanceType: new ec2.InstanceType('c6g.large'),
199 machineImage: ecs.EcsOptimizedImage.amazonLinux2(ecs.AmiHardwareType.ARM),
200});
201```
202
203Bottlerocket is also supported:
204
205```ts
206declare const cluster: ecs.Cluster;
207
208cluster.addCapacity('graviton-cluster', {
209 minCapacity: 2,
210 instanceType: new ec2.InstanceType('c6g.large'),
211 machineImageType: ecs.MachineImageType.BOTTLEROCKET,
212});
213```
214
215### Spot Instances
216
217To add spot instances into the cluster, you must specify the `spotPrice` in the `ecs.AddCapacityOptions` and optionally enable the `spotInstanceDraining` property.
218
219```ts
220declare const cluster: ecs.Cluster;
221
222// Add an AutoScalingGroup with spot instances to the existing cluster
223cluster.addCapacity('AsgSpot', {
224 maxCapacity: 2,
225 minCapacity: 2,
226 desiredCapacity: 2,
227 instanceType: new ec2.InstanceType('c5.xlarge'),
228 spotPrice: '0.0735',
229 // Enable the Automated Spot Draining support for Amazon ECS
230 spotInstanceDraining: true,
231});
232```
233
234### SNS Topic Encryption
235
236When the `ecs.AddCapacityOptions` that you provide has a non-zero `taskDrainTime` (the default) then an SNS topic and Lambda are created to ensure that the
237cluster's instances have been properly drained of tasks before terminating. The SNS Topic is sent the instance-terminating lifecycle event from the AutoScalingGroup,
238and the Lambda acts on that event. If you wish to engage [server-side encryption](https://docs.aws.amazon.com/sns/latest/dg/sns-data-encryption.html) for this SNS Topic
239then you may do so by providing a KMS key for the `topicEncryptionKey` property of `ecs.AddCapacityOptions`.
240
241```ts
242// Given
243declare const cluster: ecs.Cluster;
244declare const key: kms.Key;
245// Then, use that key to encrypt the lifecycle-event SNS Topic.
246cluster.addCapacity('ASGEncryptedSNS', {
247 instanceType: new ec2.InstanceType("t2.xlarge"),
248 desiredCapacity: 3,
249 topicEncryptionKey: key,
250});
251```
252
253## Task definitions
254
255A task definition describes what a single copy of a **task** should look like.
256A task definition has one or more containers; typically, it has one
257main container (the *default container* is the first one that's added
258to the task definition, and it is marked *essential*) and optionally
259some supporting containers which are used to support the main container,
260doings things like upload logs or metrics to monitoring services.
261
262To run a task or service with Amazon EC2 launch type, use the `Ec2TaskDefinition`. For AWS Fargate tasks/services, use the
263`FargateTaskDefinition`. For AWS ECS Anywhere use the `ExternalTaskDefinition`. These classes
264provide simplified APIs that only contain properties relevant for each specific launch type.
265
266For a `FargateTaskDefinition`, specify the task size (`memoryLimitMiB` and `cpu`):
267
268```ts
269const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
270 memoryLimitMiB: 512,
271 cpu: 256,
272});
273```
274
275On Fargate Platform Version 1.4.0 or later, you may specify up to 200GiB of
276[ephemeral storage](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-task-storage.html#fargate-task-storage-pv14):
277
278```ts
279const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
280 memoryLimitMiB: 512,
281 cpu: 256,
282 ephemeralStorageGiB: 100,
283});
284```
285
286To add containers to a task definition, call `addContainer()`:
287
288```ts
289const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
290 memoryLimitMiB: 512,
291 cpu: 256,
292});
293const container = fargateTaskDefinition.addContainer("WebContainer", {
294 // Use an image from DockerHub
295 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
296 // ... other options here ...
297});
298```
299
300For a `Ec2TaskDefinition`:
301
302```ts
303const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef', {
304 networkMode: ecs.NetworkMode.BRIDGE,
305});
306
307const container = ec2TaskDefinition.addContainer("WebContainer", {
308 // Use an image from DockerHub
309 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
310 memoryLimitMiB: 1024,
311 // ... other options here ...
312});
313```
314
315For an `ExternalTaskDefinition`:
316
317```ts
318const externalTaskDefinition = new ecs.ExternalTaskDefinition(this, 'TaskDef');
319
320const container = externalTaskDefinition.addContainer("WebContainer", {
321 // Use an image from DockerHub
322 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
323 memoryLimitMiB: 1024,
324 // ... other options here ...
325});
326```
327
328You can specify container properties when you add them to the task definition, or with various methods, e.g.:
329
330To add a port mapping when adding a container to the task definition, specify the `portMappings` option:
331
332```ts
333declare const taskDefinition: ecs.TaskDefinition;
334
335taskDefinition.addContainer("WebContainer", {
336 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
337 memoryLimitMiB: 1024,
338 portMappings: [{ containerPort: 3000 }],
339});
340```
341
342To add port mappings directly to a container definition, call `addPortMappings()`:
343
344```ts
345declare const container: ecs.ContainerDefinition;
346
347container.addPortMappings({
348 containerPort: 3000,
349});
350```
351
352To add data volumes to a task definition, call `addVolume()`:
353
354```ts
355const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
356 memoryLimitMiB: 512,
357 cpu: 256,
358});
359const volume = {
360 // Use an Elastic FileSystem
361 name: "mydatavolume",
362 efsVolumeConfiguration: {
363 fileSystemId: "EFS",
364 // ... other options here ...
365 },
366};
367
368const container = fargateTaskDefinition.addVolume(volume);
369```
370
371> Note: ECS Anywhere doesn't support volume attachments in the task definition.
372
373To use a TaskDefinition that can be used with either Amazon EC2 or
374AWS Fargate launch types, use the `TaskDefinition` construct.
375
376When creating a task definition you have to specify what kind of
377tasks you intend to run: Amazon EC2, AWS Fargate, or both.
378The following example uses both:
379
380```ts
381const taskDefinition = new ecs.TaskDefinition(this, 'TaskDef', {
382 memoryMiB: '512',
383 cpu: '256',
384 networkMode: ecs.NetworkMode.AWS_VPC,
385 compatibility: ecs.Compatibility.EC2_AND_FARGATE,
386});
387```
388
389### Images
390
391Images supply the software that runs inside the container. Images can be
392obtained from either DockerHub or from ECR repositories, built directly from a local Dockerfile, or use an existing tarball.
393
394- `ecs.ContainerImage.fromRegistry(imageName)`: use a public image.
395- `ecs.ContainerImage.fromRegistry(imageName, { credentials: mySecret })`: use a private image that requires credentials.
396- `ecs.ContainerImage.fromEcrRepository(repo, tagOrDigest)`: use the given ECR repository as the image
397 to start. If no tag or digest is provided, "latest" is assumed.
398- `ecs.ContainerImage.fromAsset('./image')`: build and upload an
399 image directly from a `Dockerfile` in your source directory.
400- `ecs.ContainerImage.fromDockerImageAsset(asset)`: uses an existing
401 `@aws-cdk/aws-ecr-assets.DockerImageAsset` as a container image.
402- `ecs.ContainerImage.fromTarball(file)`: use an existing tarball.
403- `new ecs.TagParameterContainerImage(repository)`: use the given ECR repository as the image
404 but a CloudFormation parameter as the tag.
405
406### Environment variables
407
408To pass environment variables to the container, you can use the `environment`, `environmentFiles`, and `secrets` props.
409
410```ts
411declare const secret: secretsmanager.Secret;
412declare const dbSecret: secretsmanager.Secret;
413declare const parameter: ssm.StringParameter;
414declare const taskDefinition: ecs.TaskDefinition;
415declare const s3Bucket: s3.Bucket;
416
417const newContainer = taskDefinition.addContainer('container', {
418 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
419 memoryLimitMiB: 1024,
420 environment: { // clear text, not for sensitive data
421 STAGE: 'prod',
422 },
423 environmentFiles: [ // list of environment files hosted either on local disk or S3
424 ecs.EnvironmentFile.fromAsset('./demo-env-file.env'),
425 ecs.EnvironmentFile.fromBucket(s3Bucket, 'assets/demo-env-file.env'),
426 ],
427 secrets: { // Retrieved from AWS Secrets Manager or AWS Systems Manager Parameter Store at container start-up.
428 SECRET: ecs.Secret.fromSecretsManager(secret),
429 DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), // Reference a specific JSON field, (requires platform version 1.4.0 or later for Fargate tasks)
430 API_KEY: ecs.Secret.fromSecretsManagerVersion(secret, { versionId: '12345' }, 'apiKey'), // Reference a specific version of the secret by its version id or version stage (requires platform version 1.4.0 or later for Fargate tasks)
431 PARAMETER: ecs.Secret.fromSsmParameter(parameter),
432 },
433});
434newContainer.addEnvironment('QUEUE_NAME', 'MyQueue');
435```
436
437The task execution role is automatically granted read permissions on the secrets/parameters. Support for environment
438files is restricted to the EC2 launch type for files hosted on S3. Further details provided in the AWS documentation
439about [specifying environment variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html).
440
441### System controls
442
443To set system controls (kernel parameters) on the container, use the `systemControls` prop:
444
445```ts
446declare const taskDefinition: ecs.TaskDefinition;
447
448taskDefinition.addContainer('container', {
449 image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
450 memoryLimitMiB: 1024,
451 systemControls: [
452 {
453 namespace: 'net',
454 value: 'ipv4.tcp_tw_recycle',
455 },
456 ],
457});
458```
459
460### Using Windows containers on Fargate
461
462AWS Fargate supports Amazon ECS Windows containers. For more details, please see this [blog post](https://aws.amazon.com/tw/blogs/containers/running-windows-containers-with-amazon-ecs-on-aws-fargate/)
463
464```ts
465// Create a Task Definition for the Windows container to start
466const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
467 runtimePlatform: {
468 operatingSystemFamily: ecs.OperatingSystemFamily.WINDOWS_SERVER_2019_CORE,
469 cpuArchitecture: ecs.CpuArchitecture.X86_64,
470 },
471 cpu: 1024,
472 memoryLimitMiB: 2048,
473});
474
475taskDefinition.addContainer('windowsservercore', {
476 logging: ecs.LogDriver.awsLogs({ streamPrefix: 'win-iis-on-fargate' }),
477 portMappings: [{ containerPort: 80 }],
478 image: ecs.ContainerImage.fromRegistry('mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019'),
479});
480```
481
482### Using Graviton2 with Fargate
483
484AWS Graviton2 supports AWS Fargate. For more details, please see this [blog post](https://aws.amazon.com/blogs/aws/announcing-aws-graviton2-support-for-aws-fargate-get-up-to-40-better-price-performance-for-your-serverless-containers/)
485
486```ts
487// Create a Task Definition for running container on Graviton Runtime.
488const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
489 runtimePlatform: {
490 operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
491 cpuArchitecture: ecs.CpuArchitecture.ARM64,
492 },
493 cpu: 1024,
494 memoryLimitMiB: 2048,
495});
496
497taskDefinition.addContainer('webarm64', {
498 logging: ecs.LogDriver.awsLogs({ streamPrefix: 'graviton2-on-fargate' }),
499 portMappings: [{ containerPort: 80 }],
500 image: ecs.ContainerImage.fromRegistry('public.ecr.aws/nginx/nginx:latest-arm64v8'),
501});
502```
503
504## Service
505
506A `Service` instantiates a `TaskDefinition` on a `Cluster` a given number of
507times, optionally associating them with a load balancer.
508If a task fails,
509Amazon ECS automatically restarts the task.
510
511```ts
512declare const cluster: ecs.Cluster;
513declare const taskDefinition: ecs.TaskDefinition;
514
515const service = new ecs.FargateService(this, 'Service', {
516 cluster,
517 taskDefinition,
518 desiredCount: 5,
519});
520```
521
522ECS Anywhere service definition looks like:
523
524```ts
525declare const cluster: ecs.Cluster;
526declare const taskDefinition: ecs.TaskDefinition;
527
528const service = new ecs.ExternalService(this, 'Service', {
529 cluster,
530 taskDefinition,
531 desiredCount: 5,
532});
533```
534
535`Services` by default will create a security group if not provided.
536If you'd like to specify which security groups to use you can override the `securityGroups` property.
537
538### Deployment circuit breaker and rollback
539
540Amazon ECS [deployment circuit breaker](https://aws.amazon.com/tw/blogs/containers/announcing-amazon-ecs-deployment-circuit-breaker/)
541automatically rolls back unhealthy service deployments without the need for manual intervention. Use `circuitBreaker` to enable
542deployment circuit breaker and optionally enable `rollback` for automatic rollback. See [Using the deployment circuit breaker](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html)
543for more details.
544
545```ts
546declare const cluster: ecs.Cluster;
547declare const taskDefinition: ecs.TaskDefinition;
548const service = new ecs.FargateService(this, 'Service', {
549 cluster,
550 taskDefinition,
551 circuitBreaker: { rollback: true },
552});
553```
554
555> Note: ECS Anywhere doesn't support deployment circuit breakers and rollback.
556
557### Include an application/network load balancer
558
559`Services` are load balancing targets and can be added to a target group, which will be attached to an application/network load balancers:
560
561```ts
562declare const vpc: ec2.Vpc;
563declare const cluster: ecs.Cluster;
564declare const taskDefinition: ecs.TaskDefinition;
565const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition });
566
567const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true });
568const listener = lb.addListener('Listener', { port: 80 });
569const targetGroup1 = listener.addTargets('ECS1', {
570 port: 80,
571 targets: [service],
572});
573const targetGroup2 = listener.addTargets('ECS2', {
574 port: 80,
575 targets: [service.loadBalancerTarget({
576 containerName: 'MyContainer',
577 containerPort: 8080
578 })],
579});
580```
581
582> Note: ECS Anywhere doesn't support application/network load balancers.
583
584Note that in the example above, the default `service` only allows you to register the first essential container or the first mapped port on the container as a target and add it to a new target group. To have more control over which container and port to register as targets, you can use `service.loadBalancerTarget()` to return a load balancing target for a specific container and port.
585
586Alternatively, you can also create all load balancer targets to be registered in this service, add them to target groups, and attach target groups to listeners accordingly.
587
588```ts
589declare const cluster: ecs.Cluster;
590declare const taskDefinition: ecs.TaskDefinition;
591declare const vpc: ec2.Vpc;
592const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition });
593
594const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true });
595const listener = lb.addListener('Listener', { port: 80 });
596service.registerLoadBalancerTargets(
597 {
598 containerName: 'web',
599 containerPort: 80,
600 newTargetGroupId: 'ECS',
601 listener: ecs.ListenerConfig.applicationListener(listener, {
602 protocol: elbv2.ApplicationProtocol.HTTPS
603 }),
604 },
605);
606```
607
608### Using a Load Balancer from a different Stack
609
610If you want to put your Load Balancer and the Service it is load balancing to in
611different stacks, you may not be able to use the convenience methods
612`loadBalancer.addListener()` and `listener.addTargets()`.
613
614The reason is that these methods will create resources in the same Stack as the
615object they're called on, which may lead to cyclic references between stacks.
616Instead, you will have to create an `ApplicationListener` in the service stack,
617or an empty `TargetGroup` in the load balancer stack that you attach your
618service to.
619
620See the [ecs/cross-stack-load-balancer example](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/cross-stack-load-balancer/)
621for the alternatives.
622
623### Include a classic load balancer
624
625`Services` can also be directly attached to a classic load balancer as targets:
626
627```ts
628declare const cluster: ecs.Cluster;
629declare const taskDefinition: ecs.TaskDefinition;
630declare const vpc: ec2.Vpc;
631const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition });
632
633const lb = new elb.LoadBalancer(this, 'LB', { vpc });
634lb.addListener({ externalPort: 80 });
635lb.addTarget(service);
636```
637
638Similarly, if you want to have more control over load balancer targeting:
639
640```ts
641declare const cluster: ecs.Cluster;
642declare const taskDefinition: ecs.TaskDefinition;
643declare const vpc: ec2.Vpc;
644const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition });
645
646const lb = new elb.LoadBalancer(this, 'LB', { vpc });
647lb.addListener({ externalPort: 80 });
648lb.addTarget(service.loadBalancerTarget({
649 containerName: 'MyContainer',
650 containerPort: 80,
651}));
652```
653
654There are two higher-level constructs available which include a load balancer for you that can be found in the aws-ecs-patterns module:
655
656- `LoadBalancedFargateService`
657- `LoadBalancedEc2Service`
658
659## Task Auto-Scaling
660
661You can configure the task count of a service to match demand. Task auto-scaling is
662configured by calling `autoScaleTaskCount()`:
663
664```ts
665declare const target: elbv2.ApplicationTargetGroup;
666declare const service: ecs.BaseService;
667const scaling = service.autoScaleTaskCount({ maxCapacity: 10 });
668scaling.scaleOnCpuUtilization('CpuScaling', {
669 targetUtilizationPercent: 50,
670});
671
672scaling.scaleOnRequestCount('RequestScaling', {
673 requestsPerTarget: 10000,
674 targetGroup: target,
675});
676```
677
678Task auto-scaling is powered by *Application Auto-Scaling*.
679See that section for details.
680
681## Integration with CloudWatch Events
682
683To start an Amazon ECS task on an Amazon EC2-backed Cluster, instantiate an
684`@aws-cdk/aws-events-targets.EcsTask` instead of an `Ec2Service`:
685
686```ts
687declare const cluster: ecs.Cluster;
688// Create a Task Definition for the container to start
689const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
690taskDefinition.addContainer('TheContainer', {
691 image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, '..', 'eventhandler-image')),
692 memoryLimitMiB: 256,
693 logging: new ecs.AwsLogDriver({ streamPrefix: 'EventDemo', mode: ecs.AwsLogDriverMode.NON_BLOCKING }),
694});
695
696// An Rule that describes the event trigger (in this case a scheduled run)
697const rule = new events.Rule(this, 'Rule', {
698 schedule: events.Schedule.expression('rate(1 min)'),
699});
700
701// Pass an environment variable to the container 'TheContainer' in the task
702rule.addTarget(new targets.EcsTask({
703 cluster,
704 taskDefinition,
705 taskCount: 1,
706 containerOverrides: [{
707 containerName: 'TheContainer',
708 environment: [{
709 name: 'I_WAS_TRIGGERED',
710 value: 'From CloudWatch Events'
711 }],
712 }],
713}));
714```
715
716## Log Drivers
717
718Currently Supported Log Drivers:
719
720- awslogs
721- fluentd
722- gelf
723- journald
724- json-file
725- splunk
726- syslog
727- awsfirelens
728- Generic
729
730### awslogs Log Driver
731
732```ts
733// Create a Task Definition for the container to start
734const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
735taskDefinition.addContainer('TheContainer', {
736 image: ecs.ContainerImage.fromRegistry('example-image'),
737 memoryLimitMiB: 256,
738 logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'EventDemo' }),
739});
740```
741
742### fluentd Log Driver
743
744```ts
745// Create a Task Definition for the container to start
746const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
747taskDefinition.addContainer('TheContainer', {
748 image: ecs.ContainerImage.fromRegistry('example-image'),
749 memoryLimitMiB: 256,
750 logging: ecs.LogDrivers.fluentd(),
751});
752```
753
754### gelf Log Driver
755
756```ts
757// Create a Task Definition for the container to start
758const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
759taskDefinition.addContainer('TheContainer', {
760 image: ecs.ContainerImage.fromRegistry('example-image'),
761 memoryLimitMiB: 256,
762 logging: ecs.LogDrivers.gelf({ address: 'my-gelf-address' }),
763});
764```
765
766### journald Log Driver
767
768```ts
769// Create a Task Definition for the container to start
770const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
771taskDefinition.addContainer('TheContainer', {
772 image: ecs.ContainerImage.fromRegistry('example-image'),
773 memoryLimitMiB: 256,
774 logging: ecs.LogDrivers.journald(),
775});
776```
777
778### json-file Log Driver
779
780```ts
781// Create a Task Definition for the container to start
782const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
783taskDefinition.addContainer('TheContainer', {
784 image: ecs.ContainerImage.fromRegistry('example-image'),
785 memoryLimitMiB: 256,
786 logging: ecs.LogDrivers.jsonFile(),
787});
788```
789
790### splunk Log Driver
791
792```ts
793// Create a Task Definition for the container to start
794const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
795taskDefinition.addContainer('TheContainer', {
796 image: ecs.ContainerImage.fromRegistry('example-image'),
797 memoryLimitMiB: 256,
798 logging: ecs.LogDrivers.splunk({
799 token: SecretValue.secretsManager('my-splunk-token'),
800 url: 'my-splunk-url',
801 }),
802});
803```
804
805### syslog Log Driver
806
807```ts
808// Create a Task Definition for the container to start
809const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
810taskDefinition.addContainer('TheContainer', {
811 image: ecs.ContainerImage.fromRegistry('example-image'),
812 memoryLimitMiB: 256,
813 logging: ecs.LogDrivers.syslog(),
814});
815```
816
817### firelens Log Driver
818
819```ts
820// Create a Task Definition for the container to start
821const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
822taskDefinition.addContainer('TheContainer', {
823 image: ecs.ContainerImage.fromRegistry('example-image'),
824 memoryLimitMiB: 256,
825 logging: ecs.LogDrivers.firelens({
826 options: {
827 Name: 'firehose',
828 region: 'us-west-2',
829 delivery_stream: 'my-stream',
830 },
831 }),
832});
833```
834
835To pass secrets to the log configuration, use the `secretOptions` property of the log configuration. The task execution role is automatically granted read permissions on the secrets/parameters.
836
837```ts
838declare const secret: secretsmanager.Secret;
839declare const parameter: ssm.StringParameter;
840
841const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
842taskDefinition.addContainer('TheContainer', {
843 image: ecs.ContainerImage.fromRegistry('example-image'),
844 memoryLimitMiB: 256,
845 logging: ecs.LogDrivers.firelens({
846 options: {
847 // ... log driver options here ...
848 },
849 secretOptions: { // Retrieved from AWS Secrets Manager or AWS Systems Manager Parameter Store
850 apikey: ecs.Secret.fromSecretsManager(secret),
851 host: ecs.Secret.fromSsmParameter(parameter),
852 },
853 }),
854});
855```
856
857### Generic Log Driver
858
859A generic log driver object exists to provide a lower level abstraction of the log driver configuration.
860
861```ts
862// Create a Task Definition for the container to start
863const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
864taskDefinition.addContainer('TheContainer', {
865 image: ecs.ContainerImage.fromRegistry('example-image'),
866 memoryLimitMiB: 256,
867 logging: new ecs.GenericLogDriver({
868 logDriver: 'fluentd',
869 options: {
870 tag: 'example-tag',
871 },
872 }),
873});
874```
875
876## CloudMap Service Discovery
877
878To register your ECS service with a CloudMap Service Registry, you may add the
879`cloudMapOptions` property to your service:
880
881```ts
882declare const taskDefinition: ecs.TaskDefinition;
883declare const cluster: ecs.Cluster;
884
885const service = new ecs.Ec2Service(this, 'Service', {
886 cluster,
887 taskDefinition,
888 cloudMapOptions: {
889 // Create A records - useful for AWSVPC network mode.
890 dnsRecordType: cloudmap.DnsRecordType.A,
891 },
892});
893```
894
895With `bridge` or `host` network modes, only `SRV` DNS record types are supported.
896By default, `SRV` DNS record types will target the default container and default
897port. However, you may target a different container and port on the same ECS task:
898
899```ts
900declare const taskDefinition: ecs.TaskDefinition;
901declare const cluster: ecs.Cluster;
902
903// Add a container to the task definition
904const specificContainer = taskDefinition.addContainer('Container', {
905 image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'),
906 memoryLimitMiB: 2048,
907});
908
909// Add a port mapping
910specificContainer.addPortMappings({
911 containerPort: 7600,
912 protocol: ecs.Protocol.TCP,
913});
914
915new ecs.Ec2Service(this, 'Service', {
916 cluster,
917 taskDefinition,
918 cloudMapOptions: {
919 // Create SRV records - useful for bridge networking
920 dnsRecordType: cloudmap.DnsRecordType.SRV,
921 // Targets port TCP port 7600 `specificContainer`
922 container: specificContainer,
923 containerPort: 7600,
924 },
925});
926```
927
928### Associate With a Specific CloudMap Service
929
930You may associate an ECS service with a specific CloudMap service. To do
931this, use the service's `associateCloudMapService` method:
932
933```ts
934declare const cloudMapService: cloudmap.Service;
935declare const ecsService: ecs.FargateService;
936
937ecsService.associateCloudMapService({
938 service: cloudMapService,
939});
940```
941
942## Capacity Providers
943
944There are two major families of Capacity Providers: [AWS
945Fargate](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-capacity-providers.html)
946(including Fargate Spot) and EC2 [Auto Scaling
947Group](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/asg-capacity-providers.html)
948Capacity Providers. Both are supported.
949
950### Fargate Capacity Providers
951
952To enable Fargate capacity providers, you can either set
953`enableFargateCapacityProviders` to `true` when creating your cluster, or by
954invoking the `enableFargateCapacityProviders()` method after creating your
955cluster. This will add both `FARGATE` and `FARGATE_SPOT` as available capacity
956providers on your cluster.
957
958```ts
959declare const vpc: ec2.Vpc;
960
961const cluster = new ecs.Cluster(this, 'FargateCPCluster', {
962 vpc,
963 enableFargateCapacityProviders: true,
964});
965
966const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef');
967
968taskDefinition.addContainer('web', {
969 image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
970});
971
972new ecs.FargateService(this, 'FargateService', {
973 cluster,
974 taskDefinition,
975 capacityProviderStrategies: [
976 {
977 capacityProvider: 'FARGATE_SPOT',
978 weight: 2,
979 },
980 {
981 capacityProvider: 'FARGATE',
982 weight: 1,
983 },
984 ],
985});
986```
987
988### Auto Scaling Group Capacity Providers
989
990To add an Auto Scaling Group Capacity Provider, first create an EC2 Auto Scaling
991Group. Then, create an `AsgCapacityProvider` and pass the Auto Scaling Group to
992it in the constructor. Then add the Capacity Provider to the cluster. Finally,
993you can refer to the Provider by its name in your service's or task's Capacity
994Provider strategy.
995
996By default, an Auto Scaling Group Capacity Provider will manage the Auto Scaling
997Group's size for you. It will also enable managed termination protection, in
998order to prevent EC2 Auto Scaling from terminating EC2 instances that have tasks
999running on them. If you want to disable this behavior, set both
1000`enableManagedScaling` to and `enableManagedTerminationProtection` to `false`.
1001
1002```ts
1003declare const vpc: ec2.Vpc;
1004
1005const cluster = new ecs.Cluster(this, 'Cluster', {
1006 vpc,
1007});
1008
1009const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
1010 vpc,
1011 instanceType: new ec2.InstanceType('t2.micro'),
1012 machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
1013 minCapacity: 0,
1014 maxCapacity: 100,
1015});
1016
1017const capacityProvider = new ecs.AsgCapacityProvider(this, 'AsgCapacityProvider', {
1018 autoScalingGroup,
1019});
1020cluster.addAsgCapacityProvider(capacityProvider);
1021
1022const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
1023
1024taskDefinition.addContainer('web', {
1025 image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
1026 memoryReservationMiB: 256,
1027});
1028
1029new ecs.Ec2Service(this, 'EC2Service', {
1030 cluster,
1031 taskDefinition,
1032 capacityProviderStrategies: [
1033 {
1034 capacityProvider: capacityProvider.capacityProviderName,
1035 weight: 1,
1036 },
1037 ],
1038});
1039```
1040
1041## Elastic Inference Accelerators
1042
1043Currently, this feature is only supported for services with EC2 launch types.
1044
1045To add elastic inference accelerators to your EC2 instance, first add
1046`inferenceAccelerators` field to the Ec2TaskDefinition and set the `deviceName`
1047and `deviceType` properties.
1048
1049```ts
1050const inferenceAccelerators = [{
1051 deviceName: 'device1',
1052 deviceType: 'eia2.medium',
1053}];
1054
1055const taskDefinition = new ecs.Ec2TaskDefinition(this, 'Ec2TaskDef', {
1056 inferenceAccelerators,
1057});
1058```
1059
1060To enable using the inference accelerators in the containers, add `inferenceAcceleratorResources`
1061field and set it to a list of device names used for the inference accelerators. Each value in the
1062list should match a `DeviceName` for an `InferenceAccelerator` specified in the task definition.
1063
1064```ts
1065declare const taskDefinition: ecs.TaskDefinition;
1066const inferenceAcceleratorResources = ['device1'];
1067
1068taskDefinition.addContainer('cont', {
1069 image: ecs.ContainerImage.fromRegistry('test'),
1070 memoryLimitMiB: 1024,
1071 inferenceAcceleratorResources,
1072});
1073```
1074
1075## ECS Exec command
1076
1077Please note, ECS Exec leverages AWS Systems Manager (SSM). So as a prerequisite for the exec command
1078to work, you need to have the SSM plugin for the AWS CLI installed locally. For more information, see
1079[Install Session Manager plugin for AWS CLI](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html).
1080
1081To enable the ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in
1082your `Ec2Service` or `FargateService`.
1083
1084```ts
1085declare const cluster: ecs.Cluster;
1086declare const taskDefinition: ecs.TaskDefinition;
1087
1088const service = new ecs.Ec2Service(this, 'Service', {
1089 cluster,
1090 taskDefinition,
1091 enableExecuteCommand: true,
1092});
1093```
1094
1095### Enabling logging
1096
1097You can enable sending logs of your execute session commands to a CloudWatch log group or S3 bucket by configuring
1098the `executeCommandConfiguration` property for your cluster. The default configuration will send the
1099logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note,
1100when using your own `logConfiguration` the log group or S3 Bucket specified must already be created.
1101
1102To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKey` field
1103of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key
1104to these resources on creation.
1105
1106```ts
1107declare const vpc: ec2.Vpc;
1108const kmsKey = new kms.Key(this, 'KmsKey');
1109
1110// Pass the KMS key in the `encryptionKey` field to associate the key to the log group
1111const logGroup = new logs.LogGroup(this, 'LogGroup', {
1112 encryptionKey: kmsKey,
1113});
1114
1115// Pass the KMS key in the `encryptionKey` field to associate the key to the S3 bucket
1116const execBucket = new s3.Bucket(this, 'EcsExecBucket', {
1117 encryptionKey: kmsKey,
1118});
1119
1120const cluster = new ecs.Cluster(this, 'Cluster', {
1121 vpc,
1122 executeCommandConfiguration: {
1123 kmsKey,
1124 logConfiguration: {
1125 cloudWatchLogGroup: logGroup,
1126 cloudWatchEncryptionEnabled: true,
1127 s3Bucket: execBucket,
1128 s3EncryptionEnabled: true,
1129 s3KeyPrefix: 'exec-command-output',
1130 },
1131 logging: ecs.ExecuteCommandLogging.OVERRIDE,
1132 },
1133});
1134```