-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
ec2-service.ts
280 lines (243 loc) · 10.3 KB
/
ec2-service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import * as ec2 from '@aws-cdk/aws-ec2';
import { Construct, Lazy, Resource, Stack } from '@aws-cdk/core';
import { BaseService, BaseServiceOptions, IService, LaunchType, PropagatedTagSource } from '../base/base-service';
import { NetworkMode, TaskDefinition } from '../base/task-definition';
import { CfnService } from '../ecs.generated';
import { PlacementConstraint, PlacementStrategy } from '../placement';
/**
* The properties for defining a service using the EC2 launch type.
*/
export interface Ec2ServiceProps extends BaseServiceOptions {
/**
* The task definition to use for tasks in the service.
*
* [disable-awslint:ref-via-interface]
*/
readonly taskDefinition: TaskDefinition;
/**
* Specifies whether the task's elastic network interface receives a public IP address.
* If true, each task will receive a public IP address.
*
* This property is only used for tasks that use the awsvpc network mode.
*
* @default - Use subnet default.
*/
readonly assignPublicIp?: boolean;
/**
* The subnets to associate with the service.
*
* This property is only used for tasks that use the awsvpc network mode.
*
* @default - Private subnets.
*/
readonly vpcSubnets?: ec2.SubnetSelection;
/**
* The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used.
*
* This property is only used for tasks that use the awsvpc network mode.
*
* @default - A new security group is created.
*/
readonly securityGroup?: ec2.ISecurityGroup;
/**
* The placement constraints to use for tasks in the service. For more information, see
* [Amazon ECS Task Placement Constraints](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html).
*
* @default - No constraints.
*/
readonly placementConstraints?: PlacementConstraint[];
/**
* The placement strategies to use for tasks in the service. For more information, see
* [Amazon ECS Task Placement Strategies](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html).
*
* @default - No strategies.
*/
readonly placementStrategies?: PlacementStrategy[];
/**
* Specifies whether the service will use the daemon scheduling strategy.
* If true, the service scheduler deploys exactly one task on each container instance in your cluster.
*
* When you are using this strategy, do not specify a desired number of tasks orany task placement strategies.
*
* @default false
*/
readonly daemon?: boolean;
/**
* Specifies whether to propagate the tags from the task definition or the service to the tasks in the service.
* Tags can only be propagated to the tasks within the service during service creation.
*
* @deprecated Use `propagateTags` instead.
* @default PropagatedTagSource.NONE
*/
readonly propagateTaskTagsFrom?: PropagatedTagSource;
}
/**
* The interface for a service using the EC2 launch type on an ECS cluster.
*/
export interface IEc2Service extends IService {
}
/**
* This creates a service using the EC2 launch type on an ECS cluster.
*
* @resource AWS::ECS::Service
*/
export class Ec2Service extends BaseService implements IEc2Service {
/**
* Imports from the specified service ARN.
*/
public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service {
class Import extends Resource implements IEc2Service {
public readonly serviceArn = ec2ServiceArn;
}
return new Import(scope, id);
}
private readonly constraints: CfnService.PlacementConstraintProperty[];
private readonly strategies: CfnService.PlacementStrategyProperty[];
private readonly daemon: boolean;
/**
* Constructs a new instance of the Ec2Service class.
*/
constructor(scope: Construct, id: string, props: Ec2ServiceProps) {
if (props.daemon && props.desiredCount !== undefined) {
throw new Error('Daemon mode launches one task on every instance. Don\'t supply desiredCount.');
}
if (props.daemon && props.maxHealthyPercent !== undefined && props.maxHealthyPercent !== 100) {
throw new Error('Maximum percent must be 100 for daemon mode.');
}
if (props.daemon && props.minHealthyPercent !== undefined && props.minHealthyPercent !== 0) {
throw new Error('Minimum healthy percent must be 0 for daemon mode.');
}
if (!props.taskDefinition.isEc2Compatible) {
throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2');
}
if (props.propagateTags && props.propagateTaskTagsFrom) {
throw new Error('You can only specify either propagateTags or propagateTaskTagsFrom. Alternatively, you can leave both blank');
}
const propagateTagsFromSource = props.propagateTaskTagsFrom !== undefined ? props.propagateTaskTagsFrom
: (props.propagateTags !== undefined ? props.propagateTags : PropagatedTagSource.NONE);
super(scope, id, {
...props,
// If daemon, desiredCount must be undefined and that's what we want. Otherwise, default to 1.
desiredCount: props.daemon || props.desiredCount !== undefined ? props.desiredCount : 1,
maxHealthyPercent: props.daemon && props.maxHealthyPercent === undefined ? 100 : props.maxHealthyPercent,
minHealthyPercent: props.daemon && props.minHealthyPercent === undefined ? 0 : props.minHealthyPercent,
launchType: LaunchType.EC2,
propagateTags: propagateTagsFromSource,
enableECSManagedTags: props.enableECSManagedTags,
},
{
cluster: props.cluster.clusterName,
taskDefinition: props.taskDefinition.taskDefinitionArn,
placementConstraints: Lazy.anyValue({ produce: () => this.constraints }, { omitEmptyArray: true }),
placementStrategies: Lazy.anyValue({ produce: () => this.strategies }, { omitEmptyArray: true }),
schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA',
}, props.taskDefinition);
this.constraints = [];
this.strategies = [];
this.daemon = props.daemon || false;
if (props.taskDefinition.networkMode === NetworkMode.AWS_VPC) {
this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcSubnets, props.securityGroup);
} else {
// Either None, Bridge or Host networking. Copy SecurityGroups from ASG.
// We have to be smart here -- by default future Security Group rules would be created
// in the Cluster stack. However, if the Cluster is in a different stack than us,
// that will lead to a cyclic reference (we point to that stack for the cluster name,
// but that stack will point to the ALB probably created right next to us).
//
// In that case, reference the same security groups but make sure new rules are
// created in the current scope (i.e., this stack)
validateNoNetworkingProps(props);
this.connections.addSecurityGroup(...securityGroupsInThisStack(this, props.cluster.connections.securityGroups));
}
this.addPlacementConstraints(...props.placementConstraints || []);
this.addPlacementStrategies(...props.placementStrategies || []);
if (!this.taskDefinition.defaultContainer) {
throw new Error('A TaskDefinition must have at least one essential container');
}
}
/**
* Adds one or more placement strategies to use for tasks in the service. For more information, see
* [Amazon ECS Task Placement Strategies](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html).
*/
public addPlacementStrategies(...strategies: PlacementStrategy[]) {
if (strategies.length > 0 && this.daemon) {
throw new Error("Can't configure placement strategies when daemon=true");
}
for (const strategy of strategies) {
this.strategies.push(...strategy.toJson());
}
}
/**
* Adds one or more placement strategies to use for tasks in the service. For more information, see
* [Amazon ECS Task Placement Constraints](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html).
*/
public addPlacementConstraints(...constraints: PlacementConstraint[]) {
for (const constraint of constraints) {
this.constraints.push(...constraint.toJson());
}
}
/**
* Validates this Ec2Service.
*/
protected validate(): string[] {
const ret = super.validate();
if (!this.cluster.hasEc2Capacity) {
ret.push('Cluster for this service needs Ec2 capacity. Call addXxxCapacity() on the cluster.');
}
return ret;
}
}
/**
* Validate combinations of networking arguments
*/
function validateNoNetworkingProps(props: Ec2ServiceProps) {
if (props.vpcSubnets !== undefined || props.securityGroup !== undefined || props.assignPublicIp) {
throw new Error('vpcSubnets, securityGroup and assignPublicIp can only be used in AwsVpc networking mode');
}
}
/**
* Force security group rules to be created in this stack.
*
* For every security group, if the scope and the group are in different stacks, return
* a fake "imported" security group instead. This will behave as the original security group,
* but new Ingress and Egress rule resources will be added in the current stack instead of the
* other one.
*/
function securityGroupsInThisStack(scope: Construct, groups: ec2.ISecurityGroup[]): ec2.ISecurityGroup[] {
const thisStack = Stack.of(scope);
let i = 1;
return groups.map(group => {
if (thisStack === Stack.of(group)) { return group; } // Simple case, just return the original one
return ec2.SecurityGroup.fromSecurityGroupId(scope, `SecurityGroup${i++}`, group.securityGroupId, {
allowAllOutbound: group.allowAllOutbound,
mutable: true,
});
});
}
/**
* The built-in container instance attributes
*/
export class BuiltInAttributes {
/**
* The id of the instance.
*/
public static readonly INSTANCE_ID = 'instanceId';
/**
* The AvailabilityZone where the instance is running in.
*/
public static readonly AVAILABILITY_ZONE = 'attribute:ecs.availability-zone';
/**
* The AMI id the instance is using.
*/
public static readonly AMI_ID = 'attribute:ecs.ami-id';
/**
* The EC2 instance type.
*/
public static readonly INSTANCE_TYPE = 'attribute:ecs.instance-type';
/**
* The operating system of the instance.
*
* Either 'linux' or 'windows'.
*/
public static readonly OS_TYPE = 'attribute:ecs.os-type';
}