Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add comments and docs to service connect structs #241

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,51 @@ new Service(this, 'Worker', {
});
```

## Aliased Port Extension
[Amazon ECS Service Connect](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-connect.html#service-connect-concepts) is a simple-to-use managed service mesh offering. It involves the creation of a CloudMap Namespace in a service's environment, then the creation of one or more Service Connect Services which route traffic to any named port mapping on the service's task definition.

The following example adds an `AliasedPortExtension` to a Service, allowing other services which have opted in to Service Connect to reach it through its terse DNS alias.

```ts
const environment = new Environment(this, 'production');

const serverDescription = new ServiceDescription() ;
serverDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serverDescription.add(new AliasedPortExtension({
alias: 'server',
}));

new Service(this, 'Server', {
environment,
serviceDescription: serverDescription
});

const clientDescription = new ServiceDescription();
clientDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/greeter'),
environment: {
PORT: '80',
NAME_URL: 'http://server'
},
}));

const clientService = new Service(this, 'client', {
environment,
serviceDescription: clientDescription,
});
clientService.enableServiceConnect();
```

In the example above, the `server` service advertises its port `80` via a terse DNS alias `server`. The client opts in to ECS Service Connect and uses the short URL and port to access the server service. The `AliasedPortExtension` creates the necessary named port mapping on the Task Definition, adds a default CloudMap namespace to the environment, and registers the Service Connect Service under the container's `alias` and `trafficPort`.

## Community Extensions

We encourage the development of Community Service Extensions that support
Expand Down
31 changes: 26 additions & 5 deletions src/extensions/aliased-port.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as cloudmap from 'aws-cdk-lib/aws-servicediscovery';
import { Construct } from 'constructs';
import { Service } from '../service';
import { Container } from './container';
import { ContainerMutatingHook, ServiceBuild, ServiceExtension } from './extension-interfaces';


/**
* AliasedPortProps defines the properties of an aliased port extension
* AliasedPortProps defines the properties of an aliased port extension.
*/
export interface AliasedPortProps {
/**
Expand All @@ -30,11 +31,24 @@ export interface AliasedPortProps {
readonly aliasPort?: number;
}

/**
* AliasedPortExtension allows services to opt in to Amazon ECS Service Connect using a terse DNS alias,
* an optional protocol, and a port over which the service will receive Service Connect traffic.
*
* @example
*
* declare const description: ServiceDescription;
* description.add(new AliasedPortExtension({
* alias: 'backend-api',
* appProtocol: ecs.AppProtocol.grpc,
* aliasPort: 80,
* }));
*/
export class AliasedPortExtension extends ServiceExtension {
protected alias: string;
protected aliasPort?: number;
protected appProtocol?: ecs.AppProtocol;
protected namespace?: string;
protected namespace?: cloudmap.INamespace;

constructor(props: AliasedPortProps) {
super('aliasedPort');
Expand All @@ -49,12 +63,12 @@ export class AliasedPortExtension extends ServiceExtension {
this.scope = scope;

// If there isn't a default cloudmap namespace on the cluster, create a private HTTP namespace for SC.
if (!this.parentService.cluster.defaultCloudMapNamespace) {
if (!this.parentService.environment.cluster.defaultCloudMapNamespace) {
this.parentService.environment.addDefaultCloudMapNamespace({
name: this.parentService.environment.id,
});
}
this.namespace = this.parentService.environment.cluster.defaultCloudMapNamespace?.namespaceName;
this.namespace = this.parentService.environment.cluster.defaultCloudMapNamespace as cloudmap.INamespace;
}

public addHooks(): void {
Expand All @@ -71,7 +85,7 @@ export class AliasedPortExtension extends ServiceExtension {
}

public modifyServiceProps(props: ServiceBuild): ServiceBuild {
if (props.serviceConnectConfiguration && props.serviceConnectConfiguration.namespace !== this.namespace) {
if (props.serviceConnectConfiguration && props.serviceConnectConfiguration.namespace !== this.namespace?.namespaceName) {
throw new Error('Service connect cannot be enabled with two different namespaces.');
}

Expand Down Expand Up @@ -118,6 +132,13 @@ export class AliasedPortExtension extends ServiceExtension {
},
};
}

public useService(service: ecs.Ec2Service | ecs.FargateService): void {
if (!this.namespace) {
throw new Error('Environment must have a default Cloudmap namespace to enable Service Connect.');
}
service.node.addDependency(this.namespace);
}
}

export interface AliasedPortMutatingHookProps {
Expand Down
27 changes: 27 additions & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export interface ServiceProps {
* @default none
*/
readonly autoScaleTaskCount?: AutoScalingOptions;

/**
* Whether to opt this service in to Service Connect as a client.
*
* @default - true if an AliasedPortExtension was added to the service description, otherwise false
*/
readonly enableServiceConnect?: boolean;
}

export interface AutoScalingOptions {
Expand Down Expand Up @@ -291,6 +298,11 @@ export class Service extends Construct {
throw new Error(`Unknown capacity type for service ${this.id}`);
}

// Enable service connect if requested and it has not been enabled by an extension.
if (props.enableServiceConnect && !serviceProps.serviceConnectConfiguration) {
this.enableServiceConnect();
}

// Create the auto scaling target and configure target tracking policies after the service is created
if (props.autoScaleTaskCount) {
this.scalableTaskCount = this.ecsService.autoScaleTaskCount({
Expand Down Expand Up @@ -375,4 +387,19 @@ export class Service extends Construct {
public enableAutoScalingPolicy() {
this.autoScalingPoliciesEnabled = true;
}

/**
* This method allows a service to opt in to ECS Service Connect as a client.
* If this method is not called, the service will not be able to reach other
* Service Connect enabled services via their terse DNS aliases.
*/
public enableServiceConnect() {
if (!this.environment.cluster.defaultCloudMapNamespace) {
throw new Error('Environment must have a default CloudMap namespace to enable Service Connect.');
}
this.ecsService.enableServiceConnect({
namespace: this.environment.cluster.defaultCloudMapNamespace.namespaceName,
});
this.ecsService.node.addDependency(this.environment.cluster.defaultCloudMapNamespace);
}
}
4 changes: 2 additions & 2 deletions test/aliased-port.integ.snapshot/aws-ecs-integ.assets.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "21.0.0",
"files": {
"1904e1ff90c4f297a8a8c9f4f5573101aa4cb632abb96486f141dc588c222614": {
"4ac410b83bf75ad3eee119980f62663b332db0d347a80e1c7a1493bf14772c05": {
"source": {
"path": "aws-ecs-integ.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "1904e1ff90c4f297a8a8c9f4f5573101aa4cb632abb96486f141dc588c222614.json",
"objectKey": "4ac410b83bf75ad3eee119980f62663b332db0d347a80e1c7a1493bf14772c05.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading