mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
feat(AWS ALB): Support health check configuration target groups (#7947)
This commit is contained in:
parent
6a7fd44efc
commit
a2f977c8ce
@ -195,3 +195,50 @@ functions:
|
||||
conditions:
|
||||
path: /hello
|
||||
```
|
||||
|
||||
## Configuring Health Checks
|
||||
|
||||
Health checks for target groups with a _lambda_ target type are disabled by default.
|
||||
|
||||
To enable the health check on a target group associated with an alb event, set the alb event's `healthCheck` property to `true`.
|
||||
|
||||
```yml
|
||||
functions:
|
||||
albEventConsumer:
|
||||
handler: handler.hello
|
||||
events:
|
||||
- alb:
|
||||
listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/
|
||||
priority: 1
|
||||
conditions:
|
||||
path: /hello
|
||||
healthCheck: true
|
||||
```
|
||||
|
||||
If you need to configure advanced health check settings, you can provide additional health check configuration.
|
||||
|
||||
```yml
|
||||
functions:
|
||||
albEventConsumer:
|
||||
handler: handler.hello
|
||||
events:
|
||||
- alb:
|
||||
listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/
|
||||
priority: 1
|
||||
conditions:
|
||||
path: /hello
|
||||
healthCheck:
|
||||
path: /health
|
||||
intervalSeconds: 35
|
||||
timeoutSeconds: 30
|
||||
healthyThresholdCount: 2
|
||||
unhealthyThresholdCount: 2
|
||||
matcher:
|
||||
httpCode: 200,201
|
||||
```
|
||||
|
||||
All advanced health check settings are optional. If any advanced health check settings are present, the target group's health check will be enabled.
|
||||
The target group's health check will use default values for any undefined settings.
|
||||
|
||||
Read the [AWS target group health checks documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-health-checks.html)
|
||||
for setting descriptions, constraints, and default values.
|
||||
|
||||
@ -408,6 +408,14 @@ functions:
|
||||
conditions:
|
||||
host: example.com
|
||||
path: /hello
|
||||
healthCheck: # optional, can also be set using a boolean value
|
||||
path: / # optional
|
||||
intervalSeconds: 35 # optional
|
||||
timeoutSeconds: 30 # optional
|
||||
healthyThresholdCount: 5 # optional
|
||||
unhealthyThresholdCount: 5 # optional
|
||||
matcher: # optional
|
||||
httpCode: '200'
|
||||
- eventBridge:
|
||||
# using the default AWS event bus
|
||||
schedule: rate(10 minutes)
|
||||
|
||||
@ -0,0 +1,228 @@
|
||||
'use strict';
|
||||
|
||||
const chai = require('chai');
|
||||
const runServerless = require('../../../../../../../../tests/utils/run-serverless');
|
||||
const fixtures = require('../../../../../../../../tests/fixtures');
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
const healthCheckDefaults = {
|
||||
HealthCheckEnabled: false,
|
||||
HealthCheckPath: '/',
|
||||
HealthCheckIntervalSeconds: 35,
|
||||
HealthCheckTimeoutSeconds: 30,
|
||||
HealthyThresholdCount: 5,
|
||||
UnhealthyThresholdCount: 5,
|
||||
Matcher: { HttpCode: '200' },
|
||||
};
|
||||
|
||||
const serverlessConfigurationExtension = {
|
||||
functions: {
|
||||
default: {
|
||||
handler: 'index.handler',
|
||||
events: [
|
||||
{
|
||||
alb: {
|
||||
listenerArn:
|
||||
'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/f2f7dc8efc522ab2',
|
||||
conditions: {
|
||||
path: '/',
|
||||
},
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
enabledTrue: {
|
||||
handler: 'index.handler',
|
||||
events: [
|
||||
{
|
||||
alb: {
|
||||
listenerArn:
|
||||
'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/f2f7dc8efc522ab2',
|
||||
conditions: {
|
||||
path: '/',
|
||||
},
|
||||
priority: 2,
|
||||
healthCheck: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
enabledFalse: {
|
||||
handler: 'index.handler',
|
||||
events: [
|
||||
{
|
||||
alb: {
|
||||
listenerArn:
|
||||
'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/f2f7dc8efc522ab2',
|
||||
conditions: {
|
||||
path: '/',
|
||||
},
|
||||
priority: 3,
|
||||
healthCheck: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
enabledAdvanced: {
|
||||
handler: 'index.handler',
|
||||
events: [
|
||||
{
|
||||
alb: {
|
||||
listenerArn:
|
||||
'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/f2f7dc8efc522ab2',
|
||||
conditions: {
|
||||
path: '/',
|
||||
},
|
||||
priority: 4,
|
||||
healthCheck: {
|
||||
path: '/health',
|
||||
intervalSeconds: 70,
|
||||
timeoutSeconds: 50,
|
||||
healthyThresholdCount: 7,
|
||||
unhealthyThresholdCount: 7,
|
||||
matcher: {
|
||||
httpCode: '200-299',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
enabledAdvancedPartial: {
|
||||
handler: 'index.handler',
|
||||
events: [
|
||||
{
|
||||
alb: {
|
||||
listenerArn:
|
||||
'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/f2f7dc8efc522ab2',
|
||||
conditions: {
|
||||
path: '/',
|
||||
},
|
||||
priority: 5,
|
||||
healthCheck: {
|
||||
path: '/health',
|
||||
intervalSeconds: 70,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('ALB TargetGroup Health Checks', () => {
|
||||
let cfResources;
|
||||
let naming;
|
||||
|
||||
after(fixtures.cleanup);
|
||||
|
||||
before(() =>
|
||||
fixtures
|
||||
.extend('function', serverlessConfigurationExtension)
|
||||
.then(fixturePath =>
|
||||
runServerless({
|
||||
cwd: fixturePath,
|
||||
cliArgs: ['package'],
|
||||
})
|
||||
)
|
||||
.then(({ cfTemplate, awsNaming }) => {
|
||||
({ Resources: cfResources } = cfTemplate);
|
||||
naming = awsNaming;
|
||||
})
|
||||
);
|
||||
|
||||
it('should be forcibly reverted to its default state (disabled) if healthCheck is not set', () => {
|
||||
const albTargetGroupName = naming.getAlbTargetGroupLogicalId('default', '50dc6c495c0c9188');
|
||||
|
||||
const targetGroup = cfResources[albTargetGroupName];
|
||||
expect(targetGroup.Type).to.equal('AWS::ElasticLoadBalancingV2::TargetGroup');
|
||||
|
||||
const properties = targetGroup.Properties;
|
||||
expect(properties.HealthCheckEnabled).to.equal(healthCheckDefaults.HealthCheckEnabled);
|
||||
expect(properties.HealthCheckPath).to.be.undefined;
|
||||
expect(properties.HealthCheckIntervalSeconds).to.be.undefined;
|
||||
expect(properties.HealthCheckTimeoutSeconds).to.be.undefined;
|
||||
expect(properties.HealthyThresholdCount).to.be.undefined;
|
||||
expect(properties.UnhealthyThresholdCount).to.be.undefined;
|
||||
expect(properties.Matcher).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should be disabled when healthCheck is explicitly false', () => {
|
||||
const albTargetGroupName = naming.getAlbTargetGroupLogicalId(
|
||||
'enabledFalse',
|
||||
'50dc6c495c0c9188'
|
||||
);
|
||||
|
||||
const targetGroup = cfResources[albTargetGroupName];
|
||||
expect(targetGroup.Type).to.equal('AWS::ElasticLoadBalancingV2::TargetGroup');
|
||||
|
||||
const properties = targetGroup.Properties;
|
||||
expect(properties.HealthCheckEnabled).to.be.false;
|
||||
});
|
||||
|
||||
it('should be enabled with default settings when healthCheck is explicitly true', () => {
|
||||
const albTargetGroupName = naming.getAlbTargetGroupLogicalId('enabledTrue', '50dc6c495c0c9188');
|
||||
|
||||
const targetGroup = cfResources[albTargetGroupName];
|
||||
expect(targetGroup.Type).to.equal('AWS::ElasticLoadBalancingV2::TargetGroup');
|
||||
|
||||
const properties = targetGroup.Properties;
|
||||
expect(properties.HealthCheckEnabled).to.be.true;
|
||||
expect(properties.HealthCheckPath).to.equal(healthCheckDefaults.HealthCheckPath);
|
||||
expect(properties.HealthCheckIntervalSeconds).to.equal(
|
||||
healthCheckDefaults.HealthCheckIntervalSeconds
|
||||
);
|
||||
expect(properties.HealthCheckTimeoutSeconds).to.equal(
|
||||
healthCheckDefaults.HealthCheckTimeoutSeconds
|
||||
);
|
||||
expect(properties.HealthyThresholdCount).to.equal(healthCheckDefaults.HealthyThresholdCount);
|
||||
expect(properties.UnhealthyThresholdCount).to.equal(
|
||||
healthCheckDefaults.UnhealthyThresholdCount
|
||||
);
|
||||
expect(properties.Matcher).to.deep.equal(healthCheckDefaults.Matcher);
|
||||
});
|
||||
|
||||
it('should be enabled with custom settings when healthCheck value is an object', () => {
|
||||
const albTargetGroupName = naming.getAlbTargetGroupLogicalId(
|
||||
'enabledAdvanced',
|
||||
'50dc6c495c0c9188'
|
||||
);
|
||||
|
||||
const targetGroup = cfResources[albTargetGroupName];
|
||||
expect(targetGroup.Type).to.equal('AWS::ElasticLoadBalancingV2::TargetGroup');
|
||||
|
||||
const properties = targetGroup.Properties;
|
||||
expect(properties.HealthCheckEnabled).to.be.true;
|
||||
expect(properties.HealthCheckPath).to.equal('/health');
|
||||
expect(properties.HealthCheckIntervalSeconds).to.equal(70);
|
||||
expect(properties.HealthCheckTimeoutSeconds).to.equal(50);
|
||||
expect(properties.HealthyThresholdCount).to.equal(7);
|
||||
expect(properties.UnhealthyThresholdCount).to.equal(7);
|
||||
expect(properties.Matcher.HttpCode).to.equal('200-299');
|
||||
});
|
||||
|
||||
it('should use defaults for any undefined advanced settings', () => {
|
||||
const albTargetGroupName = naming.getAlbTargetGroupLogicalId(
|
||||
'enabledAdvancedPartial',
|
||||
'50dc6c495c0c9188'
|
||||
);
|
||||
|
||||
const targetGroup = cfResources[albTargetGroupName];
|
||||
expect(targetGroup.Type).to.equal('AWS::ElasticLoadBalancingV2::TargetGroup');
|
||||
|
||||
const properties = targetGroup.Properties;
|
||||
expect(properties.HealthCheckEnabled).to.be.true;
|
||||
expect(properties.HealthCheckPath).to.equal('/health');
|
||||
expect(properties.HealthCheckIntervalSeconds).to.equal(70);
|
||||
expect(properties.HealthCheckTimeoutSeconds).to.equal(
|
||||
healthCheckDefaults.HealthCheckTimeoutSeconds
|
||||
);
|
||||
expect(properties.HealthyThresholdCount).to.equal(healthCheckDefaults.HealthyThresholdCount);
|
||||
expect(properties.UnhealthyThresholdCount).to.equal(
|
||||
healthCheckDefaults.UnhealthyThresholdCount
|
||||
);
|
||||
expect(properties.Matcher).to.deep.equal(healthCheckDefaults.Matcher);
|
||||
});
|
||||
});
|
||||
@ -2,10 +2,20 @@
|
||||
|
||||
const resolveLambdaTarget = require('../../../../../utils/resolveLambdaTarget');
|
||||
|
||||
const healthCheckDefaults = {
|
||||
HealthCheckEnabled: false,
|
||||
HealthCheckPath: '/',
|
||||
HealthCheckIntervalSeconds: 35,
|
||||
HealthCheckTimeoutSeconds: 30,
|
||||
HealthyThresholdCount: 5,
|
||||
UnhealthyThresholdCount: 5,
|
||||
Matcher: { HttpCode: '200' },
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
compileTargetGroups() {
|
||||
this.validated.events.forEach(event => {
|
||||
const { functionName, albId, multiValueHeaders = false } = event;
|
||||
const { functionName, albId, multiValueHeaders = false, healthCheck } = event;
|
||||
|
||||
const targetGroupLogicalId = this.provider.naming.getAlbTargetGroupLogicalId(
|
||||
functionName,
|
||||
@ -16,6 +26,32 @@ module.exports = {
|
||||
functionName
|
||||
);
|
||||
|
||||
const healthCheckProperties = { HealthCheckEnabled: healthCheckDefaults.HealthCheckEnabled };
|
||||
if (healthCheck) {
|
||||
Object.assign(healthCheckProperties, healthCheckDefaults);
|
||||
if (healthCheck.enabled != null) {
|
||||
healthCheckProperties.HealthCheckEnabled = healthCheck.enabled;
|
||||
}
|
||||
if (healthCheck.intervalSeconds != null) {
|
||||
healthCheckProperties.HealthCheckIntervalSeconds = healthCheck.intervalSeconds;
|
||||
}
|
||||
if (healthCheck.path != null) {
|
||||
healthCheckProperties.HealthCheckPath = healthCheck.path;
|
||||
}
|
||||
if (healthCheck.timeoutSeconds != null) {
|
||||
healthCheckProperties.HealthCheckTimeoutSeconds = healthCheck.timeoutSeconds;
|
||||
}
|
||||
if (healthCheck.healthyThresholdCount != null) {
|
||||
healthCheckProperties.HealthyThresholdCount = healthCheck.healthyThresholdCount;
|
||||
}
|
||||
if (healthCheck.unhealthyThresholdCount) {
|
||||
healthCheckProperties.UnhealthyThresholdCount = healthCheck.unhealthyThresholdCount;
|
||||
}
|
||||
if (healthCheck.matcher && healthCheck.matcher.httpCode) {
|
||||
healthCheckProperties.Matcher = { HttpCode: healthCheck.matcher.httpCode };
|
||||
}
|
||||
}
|
||||
|
||||
const functionObj = this.serverless.service.getFunction(functionName);
|
||||
const TargetGroup = {
|
||||
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup',
|
||||
@ -42,6 +78,7 @@ module.exports = {
|
||||
},
|
||||
DependsOn: [registerTargetPermissionLogicalId],
|
||||
};
|
||||
Object.assign(TargetGroup.Properties, healthCheckProperties);
|
||||
Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
|
||||
[targetGroupLogicalId]: TargetGroup,
|
||||
});
|
||||
|
||||
@ -93,6 +93,7 @@ describe('#compileTargetGroups()', () => {
|
||||
Value: 'some-service-first-50dc6c495c0c9188-dev',
|
||||
},
|
||||
],
|
||||
HealthCheckEnabled: false,
|
||||
},
|
||||
DependsOn: ['FirstLambdaPermissionRegisterTarget'],
|
||||
});
|
||||
@ -120,6 +121,7 @@ describe('#compileTargetGroups()', () => {
|
||||
Value: 'some-service-second-50dc6c495c0c9188-dev',
|
||||
},
|
||||
],
|
||||
HealthCheckEnabled: false,
|
||||
},
|
||||
DependsOn: ['SecondLambdaPermissionRegisterTarget'],
|
||||
});
|
||||
|
||||
@ -72,6 +72,9 @@ module.exports = {
|
||||
if (event.alb.authorizer) {
|
||||
albObj.authorizers = this.validateEventAuthorizers(event, authorizers, functionName);
|
||||
}
|
||||
if (event.alb.healthCheck) {
|
||||
albObj.healthCheck = this.validateAlbHealthCheck(event);
|
||||
}
|
||||
events.push(albObj);
|
||||
}
|
||||
}
|
||||
@ -221,4 +224,12 @@ module.exports = {
|
||||
|
||||
return auth;
|
||||
},
|
||||
|
||||
validateAlbHealthCheck(event) {
|
||||
const eventHealthCheck = event.alb.healthCheck;
|
||||
if (_.isObject(eventHealthCheck)) {
|
||||
return Object.assign(eventHealthCheck, { enabled: true });
|
||||
}
|
||||
return { enabled: true };
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user