Skip to content

Commit

Permalink
Implement Outlier Detection for Virtual Nodes
Browse files Browse the repository at this point in the history
issue aws#11648
  • Loading branch information
Alexander Johnson committed Apr 2, 2021
1 parent b2d4548 commit 5e0e90e
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 6 deletions.
27 changes: 27 additions & 0 deletions packages/@aws-cdk/aws-appmesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,33 @@ const gateway = new appmesh.VirtualGateway(this, 'gateway', {
});
```

## Adding outlier detection to a Virtual Node listener

The `outlierDetection` property can be added to a Virtual Node listener to add outlier detection. The 4 parameters
(`baseEjectionDuration`, `interval`, `maxEjectionPercent`, `maxServerErrors`) are required.

```typescript
// Cloud Map service discovery is currently required for host ejection by outlier detection
const vpc = new ec2.Vpc(stack, 'vpc');
const namespace = new servicediscovery.PrivateDnsNamespace(this, 'test-namespace', {
vpc,
name: 'domain.local',
});
const service = namespace.createService('Svc');

const node = mesh.addVirtualNode('virtual-node', {
serviceDiscovery: appmesh.ServiceDiscovery.cloudMap({
service: service,
}),
outlierDetection: {
baseEjectionDuration: cdk.Duration.seconds(10),
interval: cdk.Duration.seconds(30),
maxEjectionPercent: 50,
maxServerErrors: 5,
},
});
```

## Adding a Route

A `route` is associated with a virtual router, and it's used to match requests for a virtual router and distribute traffic accordingly to its associated virtual nodes.
Expand Down
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,32 @@ export interface HealthCheck {
readonly unhealthyThreshold?: number;
}

/**
* Represents the outlier detection for a listener.
*/
export interface OutlierDetection {
/**
* The base amount of time for which a host is ejected.
*/
readonly baseEjectionDuration: cdk.Duration;

/**
* The time interval between ejection sweep analysis.
*/
readonly interval: cdk.Duration;

/**
* Maximum percentage of hosts in load balancing pool for upstream service that can be ejected. Will eject at
* least one host regardless of the value.
*/
readonly maxEjectionPercent: number;

/**
* Number of consecutive 5xx errors required for ejection.
*/
readonly maxServerErrors: number;
}

/**
* All Properties for Envoy Access logs for mesh endpoints
*/
Expand Down
36 changes: 30 additions & 6 deletions packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cdk from '@aws-cdk/core';
import { CfnVirtualNode } from './appmesh.generated';
import { validateHealthChecks } from './private/utils';
import { HealthCheck, Protocol, HttpTimeout, GrpcTimeout, TcpTimeout } from './shared-interfaces';
import { HealthCheck, Protocol, HttpTimeout, GrpcTimeout, TcpTimeout, OutlierDetection } from './shared-interfaces';
import { TlsCertificate, TlsCertificateConfig } from './tls-certificate';

// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
Expand Down Expand Up @@ -42,6 +42,13 @@ interface VirtualNodeListenerCommonOptions {
* @default - none
*/
readonly tlsCertificate?: TlsCertificate;

/**
* Represents the configuration for enabling outlier detection
*
* @default - none
*/
readonly outlierDetection?: OutlierDetection;
}

/**
Expand Down Expand Up @@ -88,28 +95,28 @@ export abstract class VirtualNodeListener {
* Returns an HTTP Listener for a VirtualNode
*/
public static http(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener {
return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port, props.tlsCertificate);
return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection);
}

/**
* Returns an HTTP2 Listener for a VirtualNode
*/
public static http2(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener {
return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate);
return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection);
}

/**
* Returns an GRPC Listener for a VirtualNode
*/
public static grpc(props: GrpcVirtualNodeListenerOptions = {}): VirtualNodeListener {
return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate);
return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection);
}

/**
* Returns an TCP Listener for a VirtualNode
*/
public static tcp(props: TcpVirtualNodeListenerOptions = {}): VirtualNodeListener {
return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate);
return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection);
}

/**
Expand All @@ -124,7 +131,8 @@ class VirtualNodeListenerImpl extends VirtualNodeListener {
private readonly healthCheck: HealthCheck | undefined,
private readonly timeout: HttpTimeout | undefined,
private readonly port: number = 8080,
private readonly tlsCertificate: TlsCertificate | undefined) { super(); }
private readonly tlsCertificate: TlsCertificate | undefined,
private readonly outlierDetection: OutlierDetection | undefined) { super(); }

public bind(scope: Construct): VirtualNodeListenerConfig {
const tlsConfig = this.tlsCertificate?.bind(scope);
Expand All @@ -137,6 +145,7 @@ class VirtualNodeListenerImpl extends VirtualNodeListener {
healthCheck: this.healthCheck ? this.renderHealthCheck(this.healthCheck) : undefined,
timeout: this.timeout ? this.renderTimeout(this.timeout) : undefined,
tls: tlsConfig ? this.renderTls(tlsConfig) : undefined,
outlierDetection: this.outlierDetection ? this.renderOutlierDetection(this.outlierDetection) : undefined,
},
};
}
Expand Down Expand Up @@ -191,5 +200,20 @@ class VirtualNodeListenerImpl extends VirtualNodeListener {
},
});
}

private renderOutlierDetection(outlierDetection: OutlierDetection): CfnVirtualNode.OutlierDetectionProperty {
return {
baseEjectionDuration: {
unit: 'ms',
value: outlierDetection.baseEjectionDuration.toMilliseconds(),
},
interval: {
unit: 'ms',
value: outlierDetection.interval.toMilliseconds(),
},
maxEjectionPercent: outlierDetection.maxEjectionPercent,
maxServerErrors: outlierDetection.maxServerErrors,
};
}
}

55 changes: 55 additions & 0 deletions packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,61 @@ export = {
},
},

'when a listener is added with outlier detection with user defined props': {
'should add a listener outlier detection to the resource'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const mesh = new appmesh.Mesh(stack, 'mesh', {
meshName: 'test-mesh',
});

const node = new appmesh.VirtualNode(stack, 'test-node', {
mesh,
serviceDiscovery: appmesh.ServiceDiscovery.dns('test'),
});

node.addListener(appmesh.VirtualNodeListener.tcp({
port: 80,
outlierDetection: {
baseEjectionDuration: cdk.Duration.seconds(10),
interval: cdk.Duration.seconds(30),
maxEjectionPercent: 50,
maxServerErrors: 5,
},
}));

// THEN
expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', {
Spec: {
Listeners: [
{
OutlierDetection: {
BaseEjectionDuration: {
Unit: 'ms',
Value: 10000,
},
Interval: {
Unit: 'ms',
Value: 30000,
},
MaxEjectionPercent: 50,
MaxServerErrors: 5,
},
PortMapping: {
Port: 80,
Protocol: 'tcp',
},
},
],
},
}));

test.done();
},
},

'when a default backend is added': {
'should add a backend default to the resource'(test: Test) {
// GIVEN
Expand Down

0 comments on commit 5e0e90e

Please sign in to comment.