Files
Airflow-on-Fargate/app/constructs/service-construct.ts
2020-08-12 16:34:16 -07:00

116 lines
3.6 KiB
TypeScript

import { Construct, CfnOutput, Duration } from "@aws-cdk/core";
import { IVpc } from "@aws-cdk/aws-ec2";
import ecs = require('@aws-cdk/aws-ecs');
import ec2 = require("@aws-cdk/aws-ec2");
import elbv2 = require("@aws-cdk/aws-elasticloadbalancingv2");
import { FargateTaskDefinition } from '@aws-cdk/aws-ecs';
import { PolicyConstruct } from "../policies";
import { workerAutoScalingConfig } from "../config";
export interface ServiceConstructProps {
readonly vpc: IVpc;
readonly cluster: ecs.ICluster;
readonly defaultVpcSecurityGroup: ec2.ISecurityGroup;
readonly taskDefinition: FargateTaskDefinition;
readonly isWorkerService?: boolean;
}
export class ServiceConstruct extends Construct {
private readonly fargateService: ecs.FargateService;
public readonly loadBalancerDnsName?: CfnOutput;
constructor(parent: Construct, name: string, props: ServiceConstructProps) {
super(parent, name);
// Attach required policies to Task Role
let policies = new PolicyConstruct(this, "AIrflowTaskPolicies");
if (policies.managedPolicies) {
policies.managedPolicies.forEach(managedPolicy => props.taskDefinition.taskRole.addManagedPolicy(managedPolicy));
}
if (policies.policyStatements) {
policies.policyStatements.forEach(policyStatement => props.taskDefinition.taskRole.addToPolicy(policyStatement));
}
// Create Fargate Service for Airflow
this.fargateService = new ecs.FargateService(this, name, {
cluster: props.cluster,
taskDefinition: props.taskDefinition,
securityGroup: props.defaultVpcSecurityGroup
});
const allowedPorts = new ec2.Port({
protocol: ec2.Protocol.TCP,
fromPort: 0,
toPort: 65535,
stringRepresentation: "All"
});
this.fargateService.connections.allowFromAnyIpv4(allowedPorts);
if (props.isWorkerService) {
this.configureAutoScaling();
}
else {
// Export Load Balancer DNS Name, which will be used to access Airflow UI
this.loadBalancerDnsName = new CfnOutput(this, 'LoadBalanceDNSName', {
value: this.attachLoadBalancer(props.vpc),
});
}
}
private attachLoadBalancer(vpc: IVpc): string {
let loadBalancer = new elbv2.NetworkLoadBalancer(
this,
"NetworkLoadBalancer",
{
vpc: vpc,
internetFacing: true,
crossZoneEnabled: true
}
);
const listener = loadBalancer.addListener("Listener", {
port: 80
});
const targetGroup = listener.addTargets(
"AirflowFargateServiceTargetGroup",
{
healthCheck: {
port: "traffic-port",
protocol: elbv2.Protocol.HTTP,
path: "/health"
},
port: 80,
targets: [this.fargateService]
}
);
targetGroup.setAttribute("deregistration_delay.timeout_seconds", "60");
return loadBalancer.loadBalancerDnsName;
}
private configureAutoScaling(): void {
const scaling = this.fargateService.autoScaleTaskCount({
maxCapacity: workerAutoScalingConfig.maxTaskCount,
minCapacity: workerAutoScalingConfig.minTaskCount
});
if (workerAutoScalingConfig.cpuUsagePercent) {
scaling.scaleOnCpuUtilization("CpuScaling", {
targetUtilizationPercent: workerAutoScalingConfig.cpuUsagePercent,
scaleInCooldown: Duration.seconds(60),
scaleOutCooldown: Duration.seconds(60)
});
}
if (workerAutoScalingConfig.memUsagePercent) {
scaling.scaleOnMemoryUtilization("MemoryScaling", {
targetUtilizationPercent: workerAutoScalingConfig.memUsagePercent,
scaleInCooldown: Duration.seconds(60),
scaleOutCooldown: Duration.seconds(60)
});
}
}
}