Skip to content

Commit

Permalink
feat(ecs): allow selection of container and port for SRV service disc…
Browse files Browse the repository at this point in the history
…overy records (aws#12798)

Adds `container` and `containerPort` as optional properties of `CloudMapOptions`. This change allows the user to select which container and container port the `SRV` record points to.

Closes aws#12796

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
misterjoshua authored and cornerwings committed Mar 8, 2021
1 parent 53d5802 commit e7428fe
Show file tree
Hide file tree
Showing 6 changed files with 1,288 additions and 6 deletions.
43 changes: 43 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,49 @@ taskDefinition.addContainer('TheContainer', {
});
```

## CloudMap Service Discovery

To register your ECS service with a CloudMap Service Registry, you may add the
`cloudMapOptions` property to your service:

```ts
const service = new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition,
cloudMapOptions: {
// Create A records - useful for AWSVPC network mode.
dnsRecordType: cloudmap.DnsRecordType.A,
},
});
```

With `bridge` or `host` network modes, only `SRV` DNS record types are supported.
By default, `SRV` DNS record types will target the default container and default
port. However, you may target a different container and port on the same ECS task:

```ts
// Add a container to the task definition
const specificContainer = taskDefinition.addContainer(...);

// Add a port mapping
specificContainer.addPortMappings({
containerPort: 7600,
protocol: ecs.Protocol.TCP,
});

new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition,
cloudMapOptions: {
// Create SRV records - useful for bridge networking
dnsRecordType: cloudmap.DnsRecordType.SRV,
// Targets port TCP port 7600 `specificContainer`
container: specificContainer,
containerPort: 7600,
},
});
```

## Capacity Providers

Currently, only `FARGATE` and `FARGATE_SPOT` capacity providers are supported.
Expand Down
65 changes: 59 additions & 6 deletions packages/@aws-cdk/aws-ecs/lib/base/base-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery';
import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition';
import { ContainerDefinition, Protocol } from '../container-definition';
import { ICluster, CapacityProviderStrategy } from '../cluster';
import { Protocol } from '../container-definition';
import { CfnService } from '../ecs.generated';
import { ScalableTaskCount } from './scalable-task-count';

Expand Down Expand Up @@ -572,10 +572,12 @@ export abstract class BaseService extends Resource
}
}

// If the task definition that your service task specifies uses the AWSVPC network mode and a type SRV DNS record is
// used, you must specify a containerName and containerPort combination
const containerName = dnsRecordType === cloudmap.DnsRecordType.SRV ? this.taskDefinition.defaultContainer!.containerName : undefined;
const containerPort = dnsRecordType === cloudmap.DnsRecordType.SRV ? this.taskDefinition.defaultContainer!.containerPort : undefined;
const { containerName, containerPort } = determineContainerNameAndPort({
taskDefinition: this.taskDefinition,
dnsRecordType: dnsRecordType!,
container: options.container,
containerPort: options.containerPort,
});

const cloudmapService = new cloudmap.Service(this, 'CloudmapService', {
namespace: sdNamespace,
Expand Down Expand Up @@ -799,7 +801,19 @@ export interface CloudMapOptions {
*
* NOTE: This is used for HealthCheckCustomConfig
*/
readonly failureThreshold?: number,
readonly failureThreshold?: number;

/**
* The container to point to for a SRV record.
* @default - the task definition's default container
*/
readonly container?: ContainerDefinition;

/**
* The port to point to for a SRV record.
* @default - the default port of the task definition's default container
*/
readonly containerPort?: number;
}

/**
Expand Down Expand Up @@ -885,3 +899,42 @@ export enum PropagatedTagSource {
*/
NONE = 'NONE'
}

/**
* Options for `determineContainerNameAndPort`
*/
interface DetermineContainerNameAndPortOptions {
dnsRecordType: cloudmap.DnsRecordType;
taskDefinition: TaskDefinition;
container?: ContainerDefinition;
containerPort?: number;
}

/**
* Determine the name of the container and port to target for the service registry.
*/
function determineContainerNameAndPort(options: DetermineContainerNameAndPortOptions) {
// If the record type is SRV, then provide the containerName and containerPort to target.
// We use the name of the default container and the default port of the default container
// unless the user specifies otherwise.
if (options.dnsRecordType === cloudmap.DnsRecordType.SRV) {
// Ensure the user-provided container is from the right task definition.
if (options.container && options.container.taskDefinition != options.taskDefinition) {
throw new Error('Cannot add discovery for a container from another task definition');
}

const container = options.container ?? options.taskDefinition.defaultContainer!;

// Ensure that any port given by the user is mapped.
if (options.containerPort && !container.portMappings.some(mapping => mapping.containerPort === options.containerPort)) {
throw new Error('Cannot add discovery for a container port that has not been mapped');
}

return {
containerName: container.containerName,
containerPort: options.containerPort ?? options.taskDefinition.defaultContainer!.containerPort,
};
}

return {};
}
Loading

0 comments on commit e7428fe

Please sign in to comment.