Created
September 6, 2024 17:09
-
-
Save ADelRosarioH/b1a8861e2a1fdbb7812e79b8b3300301 to your computer and use it in GitHub Desktop.
Typesense Provisioning using AWS CDK, ECS, EFS, ALB wth CloudFront Proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { StackContext } from "sst/constructs"; | |
import * as ec2 from "aws-cdk-lib/aws-ec2"; | |
import * as ecs from "aws-cdk-lib/aws-ecs"; | |
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"; | |
import * as efs from "aws-cdk-lib/aws-efs"; | |
import * as iam from "aws-cdk-lib/aws-iam"; | |
import * as cloudfront from "aws-cdk-lib/aws-cloudfront"; | |
import * as origins from "aws-cdk-lib/aws-cloudfront-origins"; | |
import { RemovalPolicy, Duration } from "aws-cdk-lib"; | |
import { nanoid } from "nanoid"; | |
export function SearchStack({ stack }: StackContext) { | |
// Create VPC | |
const vpc = new ec2.Vpc(stack, "Vpc", { | |
maxAzs: 2, | |
}); | |
// Create ECS Cluster | |
const cluster = new ecs.Cluster(stack, "EcsCluster", { | |
vpc, | |
}); | |
// Create security group for EFS to allow NFS (port 2049) traffic | |
const efsSecurityGroup = new ec2.SecurityGroup(stack, "EfsSecurityGroup", { | |
vpc, | |
allowAllOutbound: true, | |
}); | |
efsSecurityGroup.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(2049), "Allow NFS traffic"); | |
// Create EFS file system for Typesense data with security group | |
const fileSystem = new efs.FileSystem(stack, "EfsFileSystem", { | |
vpc, | |
lifecyclePolicy: efs.LifecyclePolicy.AFTER_7_DAYS, | |
performanceMode: efs.PerformanceMode.GENERAL_PURPOSE, | |
removalPolicy: RemovalPolicy.DESTROY, | |
securityGroup: efsSecurityGroup, // Associate the security group with the EFS Mount Targets | |
enableAutomaticBackups: true, | |
}); | |
// Create EFS Access Point | |
const accessPoint = new efs.AccessPoint(stack, "EfsAccessPoint", { | |
fileSystem, | |
path: "/data", | |
posixUser: { | |
uid: "1000", | |
gid: "1000", | |
}, | |
createAcl: { | |
ownerGid: "1000", | |
ownerUid: "1000", | |
permissions: "755", | |
}, | |
}); | |
// Security group for ECS service (Typesense) | |
const serviceSecurityGroup = new ec2.SecurityGroup(stack, "ServiceSecurityGroup", { | |
vpc, | |
allowAllOutbound: true, | |
}); | |
serviceSecurityGroup.addIngressRule(efsSecurityGroup, ec2.Port.tcp(2049), "Allow NFS traffic to EFS"); | |
// Security group for ALB | |
const albSecurityGroup = new ec2.SecurityGroup(stack, "AlbSecurityGroup", { | |
vpc, | |
allowAllOutbound: true, | |
}); | |
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), "Allow HTTP traffic"); | |
// IAM Role for ECS Task to access EFS | |
const taskRole = new iam.Role(stack, "TaskRole", { | |
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"), | |
}); | |
taskRole.addToPolicy( | |
new iam.PolicyStatement({ | |
actions: ["elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite"], | |
resources: [fileSystem.fileSystemArn], | |
}) | |
); | |
// Create Fargate Task Definition | |
const taskDefinition = new ecs.FargateTaskDefinition(stack, "TaskDef", { | |
memoryLimitMiB: 2048, | |
cpu: 1024, | |
taskRole, | |
}); | |
// Add Typesense container to the task definition | |
const typesenseApiKey = nanoid(); | |
const container = taskDefinition.addContainer("TypesenseContainer", { | |
image: ecs.ContainerImage.fromRegistry("typesense/typesense:27.0"), | |
logging: ecs.LogDriver.awsLogs({ streamPrefix: "typesense" }), | |
environment: { | |
TYPESENSE_API_KEY: typesenseApiKey, | |
TYPESENSE_DATA_DIR: "/data", | |
TYPESENSE_ENABLE_CORS: "true", | |
TYPESENSE_NUM_THREADS: "4", | |
}, | |
}); | |
// Add EFS volume to task definition, including the Access Point | |
taskDefinition.addVolume({ | |
name: "efs-volume", | |
efsVolumeConfiguration: { | |
fileSystemId: fileSystem.fileSystemId, | |
transitEncryption: "ENABLED", | |
authorizationConfig: { | |
accessPointId: accessPoint.accessPointId, | |
iam: "ENABLED", | |
}, | |
}, | |
}); | |
// Mount EFS volume to Typesense container | |
container.addMountPoints({ | |
containerPath: "/data", | |
sourceVolume: "efs-volume", | |
readOnly: false, | |
}); | |
// Add port mapping for Typesense container (port 8108) | |
container.addPortMappings({ | |
containerPort: 8108, | |
protocol: ecs.Protocol.TCP, | |
}); | |
// Create Fargate Service | |
const service = new ecs.FargateService(stack, "FargateService", { | |
cluster, | |
taskDefinition, | |
assignPublicIp: true, | |
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, | |
securityGroups: [serviceSecurityGroup], | |
}); | |
// Create Internet-facing ALB | |
const alb = new elbv2.ApplicationLoadBalancer(stack, "Alb", { | |
vpc, | |
internetFacing: true, | |
securityGroup: albSecurityGroup, | |
}); | |
// Add HTTP listener to ALB | |
const listener = alb.addListener("HttpListener", { | |
port: 80, | |
open: true, | |
}); | |
// Forward traffic from ALB to ECS service (port 8108) | |
listener.addTargets("EcsTarget", { | |
port: 8108, | |
protocol: elbv2.ApplicationProtocol.HTTP, // Specify the protocol | |
targets: [service], | |
healthCheck: { | |
path: "/health", | |
interval: Duration.seconds(30), | |
timeout: Duration.seconds(5), | |
}, | |
}); | |
// Create CloudFront Cache Policy for forwarding all headers | |
const cachePolicy = new cloudfront.CachePolicy(stack, "CachePolicy", { | |
headerBehavior: cloudfront.CacheHeaderBehavior.allowList("X-TYPESENSE-API-KEY"), // Forward all headers | |
}); | |
// Create CloudFront Distribution (without custom domain) | |
const distribution = new cloudfront.Distribution(stack, "CloudFrontDistribution", { | |
defaultBehavior: { | |
origin: new origins.LoadBalancerV2Origin(alb, { | |
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Use HTTP between CloudFront and ALB | |
}), | |
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, // Enforce HTTPS for viewers | |
cachePolicy, // Attach cache policy that forwards all headers | |
}, | |
}); | |
// Output the CloudFront URL and ALB DNS name | |
stack.addOutputs({ | |
CloudFrontURL: distribution.distributionDomainName, // CloudFront URL | |
AlbDns: alb.loadBalancerDnsName, // ALB DNS (can be used for direct access, HTTP only) | |
// Typesense API Key | |
TypesenseApiKey: typesenseApiKey, | |
}); | |
return { | |
distribution, | |
alb, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment