diff --git a/deployment/aws_ecs_fargate/cloudformation/README.md b/deployment/aws_ecs_fargate/cloudformation/README.md new file mode 100644 index 00000000000..eb377e30fa7 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/README.md @@ -0,0 +1,68 @@ +# Onyx AWS ECS Fargate CloudFormation Deployment + +This directory contains CloudFormation templates and scripts to deploy Onyx on AWS ECS Fargate. + +## Configuration + +All configuration parameters are stored in a single JSON file: `onyx_config.json`. This file contains all the parameters needed for the different CloudFormation stacks. + +Example: +```json +{ + "OnyxNamespace": "onyx", + "Environment": "production", + "EFSName": "onyx-efs", + "AWSRegion": "us-east-2", + "VpcID": "YOUR_VPC_ID", + "SubnetIDs": "YOUR_SUBNET_ID1,YOUR_SUBNET_ID2", + "DomainName": "YOUR_DOMAIN e.g ecs.onyx.app", + "ValidationMethod": "DNS", + "HostedZoneId": "" +} +``` + +### Required Parameters + +- `Environment`: Used to prefix all stack names during deployment. This is required. +- `OnyxNamespace`: Namespace for the Onyx deployment. +- `EFSName`: Name for the Elastic File System. +- `AWSRegion`: AWS region where resources will be deployed. +- `VpcID`: ID of the VPC where Onyx will be deployed. +- `SubnetIDs`: Comma-separated list of subnet IDs for deployment. +- `DomainName`: Domain name for the Onyx deployment. +- `ValidationMethod`: Method for domain validation (typically "DNS"). +- [optional] `HostedZoneId`: Route 53 hosted zone ID (only if using Route 53 for DNS). + +The deployment script automatically extracts the needed parameters for each CloudFormation template based on the parameter names defined in the templates. + +## Deployment Order + +The deployment follows this order: + +1. Infrastructure stacks: + - EFS + - Cluster + - ACM + +2. Service stacks: + - Postgres + - Redis + - Vespa Engine + - Model Server (Indexing) + - Model Server (Inference) + - Backend API Server + - Backend Background Server + - Web Server + - Nginx + +## Usage + +To deploy: +```bash +./deploy.sh +``` + +To uninstall: +```bash +./uninstall.sh +``` diff --git a/deployment/aws_ecs_fargate/cloudformation/deploy.sh b/deployment/aws_ecs_fargate/cloudformation/deploy.sh new file mode 100755 index 00000000000..d59e31dd058 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/deploy.sh @@ -0,0 +1,194 @@ +#!/bin/bash + +# Function to remove comments from JSON and output valid JSON +remove_comments() { + sed 's/\/\/.*$//' "$1" | grep -v '^[[:space:]]*$' +} + +# Variables +TEMPLATE_DIR="$(pwd)" +SERVICE_DIR="$TEMPLATE_DIR/services" + +# Unified config file +CONFIG_FILE="onyx_config.jsonl" + +# Try to get AWS_REGION from config, fallback to default if not found +AWS_REGION_FROM_CONFIG=$(remove_comments "$CONFIG_FILE" | jq -r '.AWSRegion // empty') +if [ -n "$AWS_REGION_FROM_CONFIG" ]; then + AWS_REGION="$AWS_REGION_FROM_CONFIG" +else + AWS_REGION="${AWS_REGION:-us-east-2}" +fi + +# Get environment from config file +ENVIRONMENT=$(remove_comments "$CONFIG_FILE" | jq -r '.Environment') +if [ -z "$ENVIRONMENT" ] || [ "$ENVIRONMENT" == "null" ]; then + echo "Missing Environment in $CONFIG_FILE. Please add the Environment field." + exit 1 +fi + +# Try to get S3_BUCKET from config, fallback to default if not found +S3_BUCKET_FROM_CONFIG=$(remove_comments "$CONFIG_FILE" | jq -r '.S3Bucket // empty') +if [ -n "$S3_BUCKET_FROM_CONFIG" ]; then + S3_BUCKET="$S3_BUCKET_FROM_CONFIG" +else + S3_BUCKET="${S3_BUCKET:-onyx-ecs-fargate-configs}" +fi + +INFRA_ORDER=( + "onyx_efs_template.yaml" + "onyx_cluster_template.yaml" + "onyx_acm_template.yaml" +) + +# Deployment order for services +SERVICE_ORDER=( + "onyx_postgres_service_template.yaml" + "onyx_redis_service_template.yaml" + "onyx_vespaengine_service_template.yaml" + "onyx_model_server_indexing_service_template.yaml" + "onyx_model_server_inference_service_template.yaml" + "onyx_backend_api_server_service_template.yaml" + "onyx_backend_background_server_service_template.yaml" + "onyx_web_server_service_template.yaml" + "onyx_nginx_service_template.yaml" +) + +# Function to validate a CloudFormation template +validate_template() { + local template_file=$1 + echo "Validating template: $template_file..." + aws cloudformation validate-template --template-body file://"$template_file" --region "$AWS_REGION" > /dev/null + if [ $? -ne 0 ]; then + echo "Error: Validation failed for $template_file. Exiting." + exit 1 + fi + echo "Validation succeeded for $template_file." +} + +# Function to create CloudFormation parameters from JSON +create_parameters_from_json() { + local template_file=$1 + local temp_params_file="${template_file%.yaml}_parameters.json" + + # Convert the config file contents to CloudFormation parameter format + echo "[" > "$temp_params_file" + + # Process all key-value pairs from the config file + local first=true + remove_comments "$CONFIG_FILE" | jq -r 'to_entries[] | select(.value != null and .value != "") | "\(.key)|\(.value)"' | while IFS='|' read -r key value; do + if [ "$first" = true ]; then + first=false + else + echo "," >> "$temp_params_file" + fi + echo " {\"ParameterKey\": \"$key\", \"ParameterValue\": \"$value\"}" >> "$temp_params_file" + done + + echo "]" >> "$temp_params_file" + + # Debug output - display the created parameters file + echo "Generated parameters file: $temp_params_file" >&2 + echo "Contents:" >&2 + cat "$temp_params_file" >&2 + + # Return just the filename + echo "$temp_params_file" +} + +# Function to deploy a CloudFormation stack +deploy_stack() { + local stack_name=$1 + local template_file=$2 + + echo "Checking if stack $stack_name exists..." + if aws cloudformation describe-stacks --stack-name "$stack_name" --region "$AWS_REGION" > /dev/null 2>&1; then + echo "Stack $stack_name already exists. Skipping deployment." + return 0 + fi + + # Create temporary parameters file for this template + local temp_params_file=$(create_parameters_from_json "$template_file") + + # Special handling for SubnetIDs parameter if needed + if grep -q "SubnetIDs" "$template_file"; then + echo "Template uses SubnetIDs parameter, ensuring it's properly formatted..." + # Make sure we're passing SubnetIDs as a comma-separated list + local subnet_ids=$(remove_comments "$CONFIG_FILE" | jq -r '.SubnetIDs // empty') + if [ -n "$subnet_ids" ]; then + echo "Using SubnetIDs from config: $subnet_ids" + else + echo "Warning: SubnetIDs not found in config but template requires it." + fi + fi + + echo "Deploying stack: $stack_name with template: $template_file and generated config from: $CONFIG_FILE..." + aws cloudformation deploy \ + --stack-name "$stack_name" \ + --template-file "$template_file" \ + --parameter-overrides file://"$temp_params_file" \ + --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND \ + --region "$AWS_REGION" \ + --no-cli-auto-prompt > /dev/null + + if [ $? -ne 0 ]; then + echo "Error: Deployment failed for $stack_name. Exiting." + exit 1 + fi + + # Clean up temporary parameter file + rm "$temp_params_file" + + echo "Stack deployed successfully: $stack_name." +} + +convert_underscores_to_hyphens() { + local input_string="$1" + local converted_string="${input_string//_/-}" + echo "$converted_string" +} + +deploy_infra_stacks() { + for template_name in "${INFRA_ORDER[@]}"; do + # Skip ACM template if HostedZoneId is not set + if [[ "$template_name" == "onyx_acm_template.yaml" ]]; then + HOSTED_ZONE_ID=$(remove_comments "$CONFIG_FILE" | jq -r '.HostedZoneId') + if [ -z "$HOSTED_ZONE_ID" ] || [ "$HOSTED_ZONE_ID" == "" ] || [ "$HOSTED_ZONE_ID" == "null" ]; then + echo "Skipping ACM template deployment because HostedZoneId is not set in $CONFIG_FILE" + continue + fi + fi + + template_file="$template_name" + stack_name="$ENVIRONMENT-$(basename "$template_name" _template.yaml)" + stack_name=$(convert_underscores_to_hyphens "$stack_name") + + if [ -f "$template_file" ]; then + validate_template "$template_file" + deploy_stack "$stack_name" "$template_file" + else + echo "Warning: Template file $template_file not found. Skipping." + fi + done +} + +deploy_services_stacks() { + for template_name in "${SERVICE_ORDER[@]}"; do + template_file="$SERVICE_DIR/$template_name" + stack_name="$ENVIRONMENT-$(basename "$template_name" _template.yaml)" + stack_name=$(convert_underscores_to_hyphens "$stack_name") + + if [ -f "$template_file" ]; then + validate_template "$template_file" + deploy_stack "$stack_name" "$template_file" + else + echo "Warning: Template file $template_file not found. Skipping." + fi + done +} + +echo "Starting deployment of Onyx to ECS Fargate Cluster..." +deploy_infra_stacks +deploy_services_stacks + +echo "All templates validated and deployed successfully." diff --git a/deployment/aws_ecs_fargate/cloudformation/onyx_acm_template.yaml b/deployment/aws_ecs_fargate/cloudformation/onyx_acm_template.yaml new file mode 100644 index 00000000000..d48931f4471 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/onyx_acm_template.yaml @@ -0,0 +1,31 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: CloudFormation template to create an ACM Certificate. + +Parameters: + DomainName: + Type: String + Description: The primary domain name for the certificate (e.g., example.com). + Default: example.com + Environment: + Type: String + Default: production + ValidationMethod: + Type: String + Default: DNS + +Resources: + Certificate: + Type: AWS::CertificateManager::Certificate + Properties: + DomainName: !Ref DomainName + ValidationMethod: !Ref ValidationMethod + Tags: + - Key: env + Value: !Ref Environment + +Outputs: + OutputAcm: + Description: ACM Cert Id + Value: !Ref Certificate + Export: + Name: !Sub ${AWS::StackName}-OnyxCertificate diff --git a/deployment/aws_ecs_fargate/cloudformation/onyx_cluster_template.yaml b/deployment/aws_ecs_fargate/cloudformation/onyx_cluster_template.yaml new file mode 100644 index 00000000000..2b30a53165d --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/onyx_cluster_template.yaml @@ -0,0 +1,156 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: The template used to create an ECS Cluster from the ECS Console. + +Parameters: + Environment: + Type: String + Description: The environment that is used in the name of the cluster as well. + OnyxNamespace: + Type: String + Default: onyx + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + +Resources: + ECSCluster: + Type: AWS::ECS::Cluster + Properties: + ClusterName: !Sub ${Environment}-onyx-cluster + CapacityProviders: + - FARGATE + - FARGATE_SPOT + ClusterSettings: + - Name: containerInsights + Value: enhanced + ServiceConnectDefaults: + Namespace: !Sub ${Environment}-onyx-cluster + Tags: + - Key: env + Value: !Ref Environment + - Key: app + Value: onyx + + S3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Sub ${Environment}-onyx-ecs-fargate-configs + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + + PrivateDnsNamespace: + Type: AWS::ServiceDiscovery::PrivateDnsNamespace + Properties: + Description: AWS Cloud Map private DNS namespace for resources for onyx website. + Vpc: !Ref VpcID + Name: !Ref OnyxNamespace + Properties: + DnsProperties: + SOA: + TTL: 50 + + ECSTaskRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub ${Environment}-OnyxEcsTaskRole + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: "EFSPolicy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: "VisualEditor0" + Effect: Allow + Action: + - "elasticfilesystem:*" + Resource: + - !Sub "arn:aws:elasticfilesystem:*:${AWS::AccountId}:access-point/*" + - !Sub "arn:aws:elasticfilesystem:*:${AWS::AccountId}:file-system/*" + - Sid: "VisualEditor1" + Effect: Allow + Action: "elasticfilesystem:*" + Resource: "*" + - PolicyName: "S3Policy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: "VisualEditor0" + Effect: Allow + Action: + - "s3:GetObject" + - "s3:ListBucket" + Resource: + - !Sub "arn:aws:s3:::${Environment}-onyx-ecs-fargate-configs/*" + - !Sub "arn:aws:s3:::${Environment}-onyx-ecs-fargate-configs" + + ECSTaskExecutionRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub ${Environment}-OnyxECSTaskExecutionRole + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy + Policies: + - PolicyName: "CloudWatchLogsPolicy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: "VisualEditor0" + Effect: Allow + Action: "logs:CreateLogGroup" + Resource: !Sub "arn:aws:logs:*:${AWS::AccountId}:log-group:*" + - PolicyName: "SecretsManagerPolicy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${Environment}/postgres/user/password-* + +Outputs: + OutputEcsCluster: + Description: Onyx ECS Cluster + Value: !Ref ECSCluster + Export: + Name: !Sub ${AWS::StackName}-ECSClusterName + OutputECSTaskRole: + Description: Onyx ECS Task Role + Value: !Ref ECSTaskRole + Export: + Name: !Sub ${AWS::StackName}-ECSTaskRole + OutputECSTaskExecutionRole: + Description: Onyx ECS TaskExecutionRole + Value: !Ref ECSTaskExecutionRole + Export: + Name: !Sub ${AWS::StackName}-ECSTaskExecutionRole + OutputOnyxNamespace: + Description: Onyx CloudMap namespace ID for ECS service discvoery. + Value: !Ref PrivateDnsNamespace + Export: + Name: !Sub ${AWS::StackName}-OnyxNamespace + OutputOnyxNamespaceName: + Description: Onyx CloudMap namespace domain name for ECS service discvoery. + Value: !Ref OnyxNamespace + Export: + Name: !Sub ${AWS::StackName}-OnyxNamespaceName diff --git a/deployment/aws_ecs_fargate/cloudformation/onyx_config.jsonl b/deployment/aws_ecs_fargate/cloudformation/onyx_config.jsonl new file mode 100644 index 00000000000..cac6a210d47 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/onyx_config.jsonl @@ -0,0 +1,16 @@ +{ + // Naming, likely doesn't need to be changed + "OnyxNamespace": "onyx", + "Environment": "production-1", + "EFSName": "onyx-efs-1", + + // Region and VPC Stuff + "AWSRegion": "us-east-2", + "VpcID": "vpc-08822ab4927074015", + "SubnetIDs": "subnet-0c0c686bf71a9756e,subnet-0711d1a975b8a035c", + + // Domain and ACM Stuff + "DomainName": "ecs-chris.danswer.dev", + "ValidationMethod": "DNS", + "HostedZoneId": "" // Only specify if using Route 53 for DNS +} \ No newline at end of file diff --git a/deployment/aws_ecs_fargate/cloudformation/onyx_efs_template.yaml b/deployment/aws_ecs_fargate/cloudformation/onyx_efs_template.yaml new file mode 100644 index 00000000000..5d30e4874e5 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/onyx_efs_template.yaml @@ -0,0 +1,128 @@ +Parameters: + + EFSName: + Type: String + Default: onyx-efs + Environment: + Type: String + Default: production + VpcID: + Type: String + Default: vpc-0f230ca52bb04c722 + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + +Resources: + + OnyxEfs: + Type: AWS::EFS::FileSystem + Properties: + BackupPolicy: + Status: ENABLED + Encrypted: True + PerformanceMode: generalPurpose + FileSystemTags: + - Key: Name + Value: !Sub ${Environment}-${EFSName}-${AWS::Region}-${AWS::AccountId} + FileSystemProtection: + ReplicationOverwriteProtection: ENABLED + ThroughputMode: elastic + + VespaEngineTmpEfsAccessPoint: + Type: AWS::EFS::AccessPoint + Properties: + AccessPointTags: + - Key: Name + Value: vespaengine-tmp + FileSystemId: !Ref OnyxEfs + RootDirectory: + CreationInfo: + OwnerGid: "1000" + OwnerUid: "1000" + Permissions: "0755" + Path: /var/tmp + + VespaEngineDataEfsAccessPoint: + Type: AWS::EFS::AccessPoint + Properties: + AccessPointTags: + - Key: Name + Value: vespaengine-data + FileSystemId: !Ref OnyxEfs + RootDirectory: + CreationInfo: + OwnerGid: "1000" + OwnerUid: "1000" + Permissions: "0755" + Path: /opt/vespa/var + + PostgresDataEfsAccessPoint: + Type: AWS::EFS::AccessPoint + Properties: + AccessPointTags: + - Key: Name + Value: postgres-data + FileSystemId: !Ref OnyxEfs + RootDirectory: + CreationInfo: + OwnerGid: "1000" + OwnerUid: "1000" + Permissions: "0755" + Path: /var/lib/postgresql/data + + EFSMountTarget1: + DependsOn: OnyxEfs + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref OnyxEfs + SubnetId: !Select [0, !Ref SubnetIDs] + SecurityGroups: + - !Ref EFSSecurityGroupMountTargets + + EFSMountTarget2: + DependsOn: OnyxEfs + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref OnyxEfs + SubnetId: !Select [1, !Ref SubnetIDs] + SecurityGroups: + - !Ref EFSSecurityGroupMountTargets + + EFSSecurityGroupMountTargets: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security Group for EFS Mount Targets + VpcId: !Ref VpcID + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 2049 + ToPort: 2049 + CidrIp: 0.0.0.0/0 + +Outputs: + OutputOnyxEfsId: + Description: Onyx Filesystem Id + Value: !Ref OnyxEfs + Export: + Name: !Sub ${AWS::StackName}-OnyxEfsId + OutputVespaEngineTmpEfsAccessPoint: + Description: VespaEngine Tmp AP + Value: !Ref VespaEngineTmpEfsAccessPoint + Export: + Name: !Sub ${AWS::StackName}-VespaEngineTmpEfsAccessPoint + OutputVespaEngineDataEfsAccessPoint: + Description: VespaEngine Data Ap + Value: !Ref VespaEngineDataEfsAccessPoint + Export: + Name: !Sub ${AWS::StackName}-VespaEngineDataEfsAccessPoint + OutputPostgresDataEfsAccessPoint: + Description: Postgres Data AP + Value: !Ref PostgresDataEfsAccessPoint + Export: + Name: !Sub ${AWS::StackName}-PostgresDataEfsAccessPoint + OutputEFSSecurityGroupMountTargets: + Description: EFS Security Group + Value: !Ref EFSSecurityGroupMountTargets + Export: + Name: !Sub ${AWS::StackName}-EFSSecurityGroupMountTargets diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_api_server_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_api_server_service_template.yaml new file mode 100644 index 00000000000..d0d949f16b4 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_api_server_service_template.yaml @@ -0,0 +1,216 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Backend Api Server TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-backend-api-server + TaskCpu: + Type: String + Default: "2048" + TaskMemory: + Type: String + Default: "4096" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 8080 + ToPort: 8080 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 8080 + ToPort: 8080 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: onyx-backend + Image: onyxdotapp/onyx-backend:latest + Cpu: 0 + Essential: true + Command: + - "/bin/sh" + - "-c" + - | + alembic upgrade head && echo "Starting Onyx Api Server" && uvicorn onyx.main:app --host 0.0.0.0 --port 8080 + PortMappings: + - Name: backend + ContainerPort: 8080 + HostPort: 8080 + Protocol: tcp + AppProtocol: http + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + Environment: + - Name: REDIS_HOST + Value: !Sub + - "${Environment}-onyx-redis-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: MODEL_SERVER_HOST + Value: !Sub + - "${Environment}-onyx-model-server-inference-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: VESPA_HOST + Value: !Sub + - "${Environment}-onyx-vespaengine-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: POSTGRES_HOST + Value: !Sub + - "${Environment}-onyx-postgres-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: INDEXING_MODEL_SERVER_HOST + Value: !Sub + - "${Environment}-onyx-model-server-indexing-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: AUTH_TYPE + Value: disabled + Secrets: + - Name: POSTGRES_PASSWORD + ValueFrom: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${Environment}/postgres/user/password + VolumesFrom: [] + SystemControls: [] + + ECSAutoScalingTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: ECSService + Properties: + MaxCapacity: 5 + MinCapacity: 1 + ResourceId: !Sub + - "service/${ImportedCluster}/${Environment}-${ServiceName}-service" + - ImportedCluster: !ImportValue + 'Fn::Sub': "${Environment}-onyx-cluster-ECSClusterName" + ServiceName: !Ref ServiceName + Environment: !Ref Environment + ScalableDimension: ecs:service:DesiredCount + ServiceNamespace: ecs + + ECSAutoScalingPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-cpu-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 75 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageCPUUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 + + ECSAutoScalingPolicyMemory: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-mem-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 80 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageMemoryUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_background_server_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_background_server_service_template.yaml new file mode 100644 index 00000000000..34333bcc212 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_backend_background_server_service_template.yaml @@ -0,0 +1,174 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Backend Background Server TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-backend-background-server + TaskCpu: + Type: String + Default: "2048" + TaskMemory: + Type: String + Default: "4096" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 8080 + ToPort: 8080 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 8080 + ToPort: 8080 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: onyx-backend-background + Image: onyxdotapp/onyx-backend:latest + Cpu: 0 + Essential: true + Command: + - "/usr/bin/supervisord" + - "-c" + - "/etc/supervisor/conf.d/supervisord.conf" + PortMappings: + - Name: backend + ContainerPort: 8080 + HostPort: 8080 + Protocol: tcp + AppProtocol: http + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + Environment: + - Name: REDIS_HOST + Value: !Sub + - "${Environment}-onyx-redis-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: MODEL_SERVER_HOST + Value: !Sub + - "${Environment}-onyx-model-server-inference-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: VESPA_HOST + Value: !Sub + - "${Environment}-onyx-vespaengine-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: POSTGRES_HOST + Value: !Sub + - "${Environment}-onyx-postgres-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: INDEXING_MODEL_SERVER_HOST + Value: !Sub + - "${Environment}-onyx-model-server-indexing-service.${ImportedNamespace}" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + - Name: AUTH_TYPE + Value: disabled + Secrets: + - Name: POSTGRES_PASSWORD + ValueFrom: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${Environment}/postgres/user/password + VolumesFrom: [] + SystemControls: [] diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_indexing_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_indexing_service_template.yaml new file mode 100644 index 00000000000..51dac077076 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_indexing_service_template.yaml @@ -0,0 +1,163 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Model Server Indexing TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-model-server-indexing + TaskCpu: + Type: String + Default: "2048" + TaskMemory: + Type: String + Default: "4096" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 9000 + ToPort: 9000 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 9000 + ToPort: 9000 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: onyx-model-server-indexing + Image: onyxdotapp/onyx-model-server:latest + Cpu: 0 + Essential: true + Command: + - "/bin/sh" + - "-c" + - > + if [ "${DISABLE_MODEL_SERVER:-false}" = "True" ]; then echo 'Skipping service...'; + exit 0; else exec uvicorn model_server.main:app --host 0.0.0.0 --port 9000; fi + PortMappings: + - Name: model-server + ContainerPort: 9000 + HostPort: 9000 + Protocol: tcp + AppProtocol: http + Environment: + - Name: LOG_LEVEL + Value: info + - Name: INDEXING_ONLY + Value: True + - Name: VESPA_SEARCHER_THREADS + Value: "1" + MountPoints: + - SourceVolume: efs-volume + ContainerPath: /root/.cache/huggingface/ + ReadOnly: false + VolumesFrom: [] + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: "ecs" + SystemControls: [] + Volumes: + - Name: efs-volume + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: "/" diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_inference_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_inference_service_template.yaml new file mode 100644 index 00000000000..e395891f91a --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_model_server_inference_service_template.yaml @@ -0,0 +1,200 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Model Server Inference TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-model-server-inference + TaskCpu: + Type: String + Default: "2048" + TaskMemory: + Type: String + Default: "4096" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 9000 + ToPort: 9000 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 9000 + ToPort: 9000 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: onyx-model-server-inference + Image: onyxdotapp/onyx-model-server:latest + Cpu: 0 + Essential: true + Command: + - "/bin/sh" + - "-c" + - > + if [ "${DISABLE_MODEL_SERVER:-false}" = "True" ]; then echo 'Skipping service...'; + exit 0; else exec uvicorn model_server.main:app --host 0.0.0.0 --port 9000; fi + PortMappings: + - Name: model-server + ContainerPort: 9000 + HostPort: 9000 + Protocol: tcp + AppProtocol: http + Environment: + - Name: LOG_LEVEL + Value: info + MountPoints: + - SourceVolume: efs-volume + ContainerPath: /root/.cache/huggingface/ + ReadOnly: false + VolumesFrom: [] + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: "ecs" + SystemControls: [] + Volumes: + - Name: efs-volume + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: "/" + + ECSAutoScalingTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: ECSService + Properties: + MaxCapacity: 5 + MinCapacity: 1 + ResourceId: !Sub + - "service/${ImportedCluster}/${Environment}-${ServiceName}-service" + - ImportedCluster: !ImportValue + 'Fn::Sub': "${Environment}-onyx-cluster-ECSClusterName" + ServiceName: !Ref ServiceName + Environment: !Ref Environment + ScalableDimension: ecs:service:DesiredCount + ServiceNamespace: ecs + + ECSAutoScalingPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-cpu-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 75 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageCPUUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 + + ECSAutoScalingPolicyMemory: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-memory-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 80 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageMemoryUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_nginx_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_nginx_service_template.yaml new file mode 100644 index 00000000000..652a202955f --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_nginx_service_template.yaml @@ -0,0 +1,288 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "The template used to create an ECS Service from the ECS Console." + +Parameters: + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + HostedZoneId: + Type: String + Default: '' + DomainName: + Type: String + Default: demo.danswer.ai + Environment: + Type: String + ServiceName: + Type: String + Default: onyx-nginx + OnyxNamespace: + Type: String + Default: onyx + OnyxBackendApiServiceName: + Type: String + Default: onyx-backend-api-server-service + OnyxWebServerServiceName: + Type: String + Default: onyx-web-server-service + TaskCpu: + Type: String + Default: "512" + TaskMemory: + Type: String + Default: "1024" + TaskDesiredCount: + Type: Number + Default: 1 + GitHubConfigUrl: + Type: String + Default: "https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/data/nginx/app.conf.template.dev" + Description: "URL to the nginx configuration file on GitHub" + GitHubRunScriptUrl: + Type: String + Default: "https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/data/nginx/run-nginx.sh" + Description: "URL to the nginx run script on GitHub" + +Conditions: + CreateRoute53: !Not + - !Equals + - !Ref HostedZoneId + - '' + +Resources: + ECSService: + Type: "AWS::ECS::Service" + DependsOn: LoadBalancer + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: "FARGATE" + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName} + SchedulingStrategy: "REPLICA" + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: "ENABLED" + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: "ENABLED" + SecurityGroups: + - !Ref SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: "LATEST" + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: "ECS" + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt + - "ServiceDiscoveryService" + - "Arn" + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + LoadBalancers: + - ContainerName: nginx + ContainerPort: 80 + TargetGroupArn: !Ref TargetGroup + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + ContainerDefinitions: + - Name: nginx + Image: nginx:1.23.4-alpine + Cpu: 0 + PortMappings: + - Name: nginx-80-tcp + ContainerPort: 80 + HostPort: 80 + Protocol: tcp + Essential: true + Command: + - /bin/sh + - -c + - dos2unix /etc/nginx/conf.d/run-nginx.sh && /etc/nginx/conf.d/run-nginx.sh app.conf.template.dev + Environment: + - Name: EMAIL + Value: "" + - Name: DOMAIN + Value: !Ref DomainName + - Name: ONYX_BACKEND_API_HOST + Value: !Sub ${Environment}-${OnyxBackendApiServiceName}.${OnyxNamespace} + - Name: ONYX_WEB_SERVER_HOST + Value: !Sub ${Environment}-${OnyxWebServerServiceName}.${OnyxNamespace} + MountPoints: + - SourceVolume: efs-volume + ContainerPath: /etc/nginx/conf.d + VolumesFrom: [] + DependsOn: + - ContainerName: github-sync-container + Condition: SUCCESS + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-OnyxNginxTaskDefinition + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: 25m + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + SystemControls: [] + - Name: github-sync-container + Image: curlimages/curl:latest + Cpu: 128 + MemoryReservation: 256 + PortMappings: [] + Essential: false + Command: + - sh + - -c + - !Sub | + curl -L ${GitHubConfigUrl} -o /etc/nginx/conf.d/app.conf.template.dev && + curl -L ${GitHubRunScriptUrl} -o /etc/nginx/conf.d/run-nginx.sh && + chmod 644 /etc/nginx/conf.d/app.conf.template.dev && + chmod 755 /etc/nginx/conf.d/run-nginx.sh && + exit 0 || exit 1 + MountPoints: + - SourceVolume: efs-volume + ContainerPath: /etc/nginx/conf.d + VolumesFrom: [] + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-github-sync-configs-TaskDefinition + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: 25m + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + SystemControls: [] + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + Volumes: + - Name: efs-volume + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: / + PlacementConstraints: [] + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + EnableFaultInjection: false + + SecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: !Sub "Security group for ${ServiceName}" + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 80 + ToPort: 80 + IpProtocol: "tcp" + CidrIp: "0.0.0.0/0" + - FromPort: 80 + ToPort: 80 + IpProtocol: "tcp" + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Ref ServiceName + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + DependsOn: SecurityGroup + Properties: + Type: application + Scheme: internet-facing + Subnets: !Ref SubnetIDs + SecurityGroups: + - !Ref SecurityGroup + + LoadBalancerListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + LoadBalancerArn: !Ref LoadBalancer + Port: 80 + Protocol: HTTP + DefaultActions: + - Type: forward + TargetGroupArn: !Ref TargetGroup + + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckEnabled: True + HealthCheckIntervalSeconds: 30 + HealthCheckPort: 80 + HealthCheckPath: /api/health + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 20 + HealthyThresholdCount: 3 + Port: 80 + Protocol: HTTP + ProtocolVersion: HTTP1 + VpcId: !Ref VpcID + TargetType: ip + + Route53Record: + Type: AWS::Route53::RecordSet + Condition: CreateRoute53 + Properties: + HostedZoneId: !Ref HostedZoneId + Name: !Ref DomainName + Type: A + AliasTarget: + DNSName: !GetAtt LoadBalancer.DNSName + HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID + EvaluateTargetHealth: false + +Outputs: + ECSService: + Description: "The created service." + Value: !Ref "ECSService" + ServiceDiscoveryService: + Value: !Ref "ServiceDiscoveryService" + OutputOnyxLoadBalancerDNSName: + Description: LoadBalancer DNSName + Value: !GetAtt LoadBalancer.DNSName + Export: + Name: !Sub ${AWS::StackName}-OnyxLoadBalancerDNSName diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_postgres_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_postgres_service_template.yaml new file mode 100644 index 00000000000..12f83db8784 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_postgres_service_template.yaml @@ -0,0 +1,177 @@ +AWSTemplateFormatVersion: '2010-09-09' +Parameters: + Environment: + Type: String + Default: production + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-postgres + TaskCpu: + Type: String + Default: "1024" + TaskMemory: + Type: String + Default: "2048" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: DISABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - !Ref SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 100 + MinimumHealthyPercent: 0 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 5432 + ToPort: 5432 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 5432 + ToPort: 5432 + IpProtocol: tcp + CidrIpv6: "::/0" + - FromPort: 2049 + ToPort: 2049 + IpProtocol: tcp + SourceSecurityGroupId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-EFSSecurityGroupMountTargets" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + Volumes: + - Name: efs-volume-data + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: "/" + TransitEncryption: ENABLED + AuthorizationConfig: + AccessPointId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-PostgresDataEfsAccessPoint" + ContainerDefinitions: + - Name: !Ref ServiceName + Image: postgres:15.2-alpine + Cpu: 0 + Essential: true + StopTimeout: 30 + Command: + - "-c" + - "max_connections=250" + PortMappings: + - Name: postgres + ContainerPort: 5432 + HostPort: 5432 + Protocol: tcp + AppProtocol: http + Environment: + - Name: POSTGRES_USER + Value: postgres + - Name: PGSSLMODE + Value: require + - Name: POSTGRES_DB + Value: postgres + Secrets: + - Name: POSTGRES_PASSWORD + ValueFrom: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${Environment}/postgres/user/password + MountPoints: + - SourceVolume: efs-volume-data + ContainerPath: /var/lib/postgresql/data + ReadOnly: false + - SourceVolume: efs-volume-data + ContainerPath: /var/lib/postgresql + ReadOnly: false + User: "1000" + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: /ecs/OnyxPostgresTaskDefinition + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_redis_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_redis_service_template.yaml new file mode 100644 index 00000000000..9d0d9627c60 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_redis_service_template.yaml @@ -0,0 +1,146 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Redis TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-redis + TaskCpu: + Type: String + Default: "1024" + TaskMemory: + Type: String + Default: "2048" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 6379 + ToPort: 6379 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 6379 + ToPort: 6379 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: redis + Image: redis:7.4-alpine + Cpu: 0 + Essential: true + Command: + - "redis-server" + - "--save" + - "\"\"" + - "--appendonly" + - "no" + PortMappings: + - Name: redis_port + ContainerPort: 6379 + HostPort: 6379 + Protocol: tcp + AppProtocol: http + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + Environment: [] + VolumesFrom: [] + SystemControls: [] diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_vespaengine_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_vespaengine_service_template.yaml new file mode 100644 index 00000000000..6d5d3ada39f --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_vespaengine_service_template.yaml @@ -0,0 +1,190 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Vespa Engine TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-vespaengine + TaskCpu: + Type: String + Default: "4096" + TaskMemory: + Type: String + Default: "16384" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 19071 + ToPort: 19071 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 19071 + ToPort: 19071 + IpProtocol: tcp + CidrIpv6: "::/0" + - FromPort: 8081 + ToPort: 8081 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 8081 + ToPort: 8081 + IpProtocol: tcp + CidrIpv6: "::/0" + - FromPort: 2049 + ToPort: 2049 + IpProtocol: tcp + SourceSecurityGroupId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-EFSSecurityGroupMountTargets" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: vespaengine + Image: vespaengine/vespa:8.277.17 + Cpu: 0 + Essential: true + PortMappings: + - Name: vespaengine_port + ContainerPort: 19071 + HostPort: 19071 + Protocol: tcp + AppProtocol: http + - Name: vespaengine_port2 + ContainerPort: 8081 + HostPort: 8081 + Protocol: tcp + AppProtocol: http + MountPoints: + - SourceVolume: efs-volume-data + ContainerPath: /opt/vespa/var + ReadOnly: false + - SourceVolume: efs-volume-tmp + ContainerPath: /var/tmp + ReadOnly: false + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: /ecs/OnyxVespaEngineTaskDefinition + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + User: "1000" + Environment: [] + VolumesFrom: [] + SystemControls: [] + Volumes: + - Name: efs-volume-tmp + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: "/" + TransitEncryption: ENABLED + AuthorizationConfig: + AccessPointId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-VespaEngineTmpEfsAccessPoint" + - Name: efs-volume-data + EFSVolumeConfiguration: + FilesystemId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-OnyxEfsId" + RootDirectory: "/" + TransitEncryption: ENABLED + AuthorizationConfig: + AccessPointId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-efs-VespaEngineDataEfsAccessPoint" diff --git a/deployment/aws_ecs_fargate/cloudformation/services/onyx_web_server_service_template.yaml b/deployment/aws_ecs_fargate/cloudformation/services/onyx_web_server_service_template.yaml new file mode 100644 index 00000000000..b1c199eb81c --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/services/onyx_web_server_service_template.yaml @@ -0,0 +1,190 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: CloudFormation template for Onyx Web Server TaskDefinition +Parameters: + Environment: + Type: String + SubnetIDs: + Type: CommaDelimitedList + Description: "Comma-delimited list of at least two subnet IDs in different Availability Zones" + VpcID: + Type: String + Default: vpc-098cfa79d637dabff + ServiceName: + Type: String + Default: onyx-web-server + TaskCpu: + Type: String + Default: "1024" + TaskMemory: + Type: String + Default: "2048" + TaskDesiredCount: + Type: Number + Default: 1 + +Resources: + + ECSService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSClusterName" + CapacityProviderStrategy: + - CapacityProvider: FARGATE + Base: 0 + Weight: 1 + TaskDefinition: !Ref TaskDefinition + ServiceName: !Sub ${Environment}-${ServiceName}-service + SchedulingStrategy: REPLICA + DesiredCount: !Ref TaskDesiredCount + AvailabilityZoneRebalancing: ENABLED + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Ref: SecurityGroup + Subnets: !Ref SubnetIDs + PlatformVersion: LATEST + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DeploymentCircuitBreaker: + Enable: true + Rollback: true + DeploymentController: + Type: ECS + ServiceConnectConfiguration: + Enabled: false + ServiceRegistries: + - RegistryArn: !GetAtt ServiceDiscoveryService.Arn + Tags: + - Key: app + Value: onyx + - Key: service + Value: !Ref ServiceName + - Key: env + Value: !Ref Environment + EnableECSManagedTags: true + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub Onyx SecurityGroup access to EFS mount and ${ServiceName}. + GroupName: !Sub ${Environment}-ecs-${ServiceName} + VpcId: !Ref VpcID + SecurityGroupIngress: + - FromPort: 3000 + ToPort: 3000 + IpProtocol: tcp + CidrIp: 0.0.0.0/0 + - FromPort: 3000 + ToPort: 3000 + IpProtocol: tcp + CidrIpv6: "::/0" + + ServiceDiscoveryService: + Type: "AWS::ServiceDiscovery::Service" + Properties: + Name: !Sub ${Environment}-${ServiceName}-service + DnsConfig: + DnsRecords: + - Type: "A" + TTL: 15 + NamespaceId: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespace" + HealthCheckCustomConfig: + FailureThreshold: 1 + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Sub ${Environment}-${ServiceName}-TaskDefinition + TaskRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskRole" + ExecutionRoleArn: + Fn::ImportValue: + Fn::Sub: "${Environment}-onyx-cluster-ECSTaskExecutionRole" + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref TaskCpu + Memory: !Ref TaskMemory + RuntimePlatform: + CpuArchitecture: ARM64 + OperatingSystemFamily: LINUX + ContainerDefinitions: + - Name: onyx-webserver + Image: onyxdotapp/onyx-web-server:latest + Cpu: 0 + Essential: true + PortMappings: + - Name: webserver + ContainerPort: 3000 + HostPort: 3000 + Protocol: tcp + Environment: + - Name: NEXT_PUBLIC_DISABLE_STREAMING + Value: "false" + - Name: NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA + Value: "false" + - Name: INTERNAL_URL + Value: !Sub + - "http://${Environment}-onyx-backend-api-server-service.${ImportedNamespace}:8080" + - ImportedNamespace: !ImportValue + Fn::Sub: "${Environment}-onyx-cluster-OnyxNamespaceName" + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub /ecs/${Environment}-${ServiceName} + mode: non-blocking + awslogs-create-group: "true" + max-buffer-size: "25m" + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: ecs + User: "1000" + VolumesFrom: [] + SystemControls: [] + + ECSAutoScalingTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: ECSService + Properties: + MaxCapacity: 5 + MinCapacity: 1 + ResourceId: !Sub + - "service/${ImportedCluster}/${Environment}-${ServiceName}-service" + - ImportedCluster: !ImportValue + 'Fn::Sub': "${Environment}-onyx-cluster-ECSClusterName" + ServiceName: !Ref ServiceName + Environment: !Ref Environment + ScalableDimension: ecs:service:DesiredCount + ServiceNamespace: ecs + + ECSAutoScalingPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-cpu-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 75 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageCPUUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 + + ECSAutoScalingPolicyMemory: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Sub ${Environment}-${ServiceName}-service-memory-scaleout + ScalingTargetId: !Ref ECSAutoScalingTarget + PolicyType: TargetTrackingScaling + TargetTrackingScalingPolicyConfiguration: + TargetValue: 80 + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageMemoryUtilization + ScaleOutCooldown: 60 + ScaleInCooldown: 60 diff --git a/deployment/aws_ecs_fargate/cloudformation/uninstall.sh b/deployment/aws_ecs_fargate/cloudformation/uninstall.sh new file mode 100755 index 00000000000..bedc3a73c08 --- /dev/null +++ b/deployment/aws_ecs_fargate/cloudformation/uninstall.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +AWS_REGION="${AWS_REGION:-us-west-1}" + +# Reference to consolidated config +CONFIG_FILE="onyx_config.json" + +# Get environment from config file +ENVIRONMENT=$(jq -r '.Environment' "$CONFIG_FILE") +if [ -z "$ENVIRONMENT" ] || [ "$ENVIRONMENT" == "null" ]; then + echo "Missing Environment in $CONFIG_FILE. Please add the Environment field." + exit 1 +fi + +# Try to get S3_BUCKET from config, fallback to default if not found +S3_BUCKET_FROM_CONFIG=$(jq -r '.S3Bucket // empty' "$CONFIG_FILE") +if [ -n "$S3_BUCKET_FROM_CONFIG" ]; then + S3_BUCKET="$S3_BUCKET_FROM_CONFIG" +else + S3_BUCKET="${S3_BUCKET:-onyx-ecs-fargate-configs}" +fi + +STACK_NAMES=( + "${ENVIRONMENT}-onyx-nginx-service" + "${ENVIRONMENT}-onyx-web-server-service" + "${ENVIRONMENT}-onyx-backend-background-server-service" + "${ENVIRONMENT}-onyx-backend-api-server-service" + "${ENVIRONMENT}-onyx-model-server-inference-service" + "${ENVIRONMENT}-onyx-model-server-indexing-service" + "${ENVIRONMENT}-onyx-vespaengine-service" + "${ENVIRONMENT}-onyx-redis-service" + "${ENVIRONMENT}-onyx-postgres-service" + "${ENVIRONMENT}-onyx-cluster" + "${ENVIRONMENT}-onyx-acm" + "${ENVIRONMENT}-onyx-efs" + ) + +delete_stack() { + local stack_name=$1 + + if [ "$stack_name" == "${ENVIRONMENT}-onyx-cluster" ]; then + echo "Removing all objects and directories from the onyx config s3 bucket." + aws s3 rm "s3://${ENVIRONMENT}-${S3_BUCKET}" --recursive + sleep 5 + fi + + echo "Checking if stack $stack_name exists..." + if aws cloudformation describe-stacks --stack-name "$stack_name" --region "$AWS_REGION" > /dev/null 2>&1; then + echo "Deleting stack: $stack_name..." + aws cloudformation delete-stack \ + --stack-name "$stack_name" \ + --region "$AWS_REGION" + + echo "Waiting for stack $stack_name to be deleted..." + aws cloudformation wait stack-delete-complete \ + --stack-name "$stack_name" \ + --region "$AWS_REGION" + + if [ $? -eq 0 ]; then + echo "Stack $stack_name deleted successfully." + sleep 10 + else + echo "Failed to delete stack $stack_name. Exiting." + exit 1 + fi + else + echo "Stack $stack_name does not exist, skipping." + return 0 + fi +} + +for stack_name in "${STACK_NAMES[@]}"; do + delete_stack "$stack_name" +done + +echo "All stacks deleted successfully." diff --git a/deployment/data/nginx/app.conf.template b/deployment/data/nginx/app.conf.template index 83311b24052..036a13d293b 100644 --- a/deployment/data/nginx/app.conf.template +++ b/deployment/data/nginx/app.conf.template @@ -31,11 +31,11 @@ upstream api_server { # for a TCP configuration # TODO: use gunicorn to manage multiple processes - server api_server:8080 fail_timeout=0; + server ${ONYX_BACKEND_API_HOST}:8080 fail_timeout=0; } upstream web_server { - server web_server:3000 fail_timeout=0; + server ${ONYX_WEB_SERVER_HOST}:3000 fail_timeout=0; } server { diff --git a/deployment/data/nginx/app.conf.template.dev b/deployment/data/nginx/app.conf.template.dev index 0c1ed5f6a02..ba98bedc9e8 100644 --- a/deployment/data/nginx/app.conf.template.dev +++ b/deployment/data/nginx/app.conf.template.dev @@ -1,8 +1,8 @@ -# Override log format to include request latency +# Log format to include request latency log_format custom_main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time'; + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time'; upstream api_server { # fail_timeout=0 means we always retry an upstream even if it failed @@ -13,17 +13,17 @@ upstream api_server { # for a TCP configuration # TODO: use gunicorn to manage multiple processes - server api_server:8080 fail_timeout=0; + server ${ONYX_BACKEND_API_HOST}:8080 fail_timeout=0; } upstream web_server { - server web_server:3000 fail_timeout=0; + server ${ONYX_WEB_SERVER_HOST}:3000 fail_timeout=0; } server { listen 80 default_server; - client_max_body_size 5G; # Maximum upload size + client_max_body_size 5G; # Maximum upload size access_log /var/log/nginx/access.log custom_main; @@ -66,5 +66,5 @@ server { proxy_redirect off; proxy_pass http://web_server; } -} +} diff --git a/deployment/data/nginx/run-nginx.sh b/deployment/data/nginx/run-nginx.sh index 04346be6843..089e297d9de 100755 --- a/deployment/data/nginx/run-nginx.sh +++ b/deployment/data/nginx/run-nginx.sh @@ -1,5 +1,8 @@ # fill in the template -envsubst '$DOMAIN $SSL_CERT_FILE_NAME $SSL_CERT_KEY_FILE_NAME' < "/etc/nginx/conf.d/$1" > /etc/nginx/conf.d/app.conf +ONYX_BACKEND_API_HOST="${ONYX_BACKEND_API_HOST:-api_server}" +ONYX_WEB_SERVER_HOST="${ONYX_WEB_SERVER_HOST:-web_server}" + +envsubst '$DOMAIN $SSL_CERT_FILE_NAME $SSL_CERT_KEY_FILE_NAME $ONYX_BACKEND_API_HOST $ONYX_WEB_SERVER_HOST' < "/etc/nginx/conf.d/$1" > /etc/nginx/conf.d/app.conf # wait for the api_server to be ready echo "Waiting for API server to boot up; this may take a minute or two..." @@ -10,7 +13,7 @@ echo while true; do # Use curl to send a request and capture the HTTP status code - status_code=$(curl -o /dev/null -s -w "%{http_code}\n" "http://api_server:8080/health") + status_code=$(curl -o /dev/null -s -w "%{http_code}\n" "http://${ONYX_BACKEND_API_HOST}:8080/health") # Check if the status code is 200 if [ "$status_code" -eq 200 ]; then