diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 9c9757912..3d0288dd6 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -17,7 +17,7 @@ ENV PIP_NO_CACHE_DIR off RUN apt-get update && apt-get install -y gcc postgresql-client ca-certificates jq \ && update-ca-certificates \ && pip install --upgrade pip \ - && pip install --no-cache-dir setuptools pdm~=2.5.2 gunicorn awscli + && pip install --no-cache-dir setuptools pdm~=2.5.2 awscli COPY --from=chamber /chamber /bin/chamber diff --git a/packages/backend/Makefile b/packages/backend/Makefile index 30ed1a0e5..3eedaea0c 100644 --- a/packages/backend/Makefile +++ b/packages/backend/Makefile @@ -57,5 +57,5 @@ secrets: $(MAKE) -C $(PROJECT_ROOT_DIR) secrets-editor SERVICE_NAME=backend remote-shell: - ./scripts/execute_remote.sh + chamber exec $(ENV_STAGE) -- ./scripts/execute_remote.sh diff --git a/packages/backend/gunicorn.py b/packages/backend/gunicorn.py index d5b24b8a2..dfe44404c 100644 --- a/packages/backend/gunicorn.py +++ b/packages/backend/gunicorn.py @@ -2,8 +2,19 @@ import logging from multiprocessing import cpu_count +import environ + +env = environ.Env( + # set casting, default value + DJANGO_DEBUG=(bool, False) +) + +DEBUG = env("DJANGO_DEBUG") + def max_workers(): + if DEBUG: + return 2 return cpu_count() * 2 + 1 diff --git a/packages/backend/infra/stacks/api/stack.ts b/packages/backend/infra/stacks/api/stack.ts index 156e9ead6..b10d2a85a 100644 --- a/packages/backend/infra/stacks/api/stack.ts +++ b/packages/backend/infra/stacks/api/stack.ts @@ -1,4 +1,4 @@ -import { App, Fn, Stack, StackProps } from 'aws-cdk-lib'; +import {App, Duration, Fn, Stack, StackProps} from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as sm from 'aws-cdk-lib/aws-secretsmanager'; @@ -94,14 +94,20 @@ export class ApiStack extends Stack { { securityGroup: resources.fargateContainerSecurityGroup, serviceName: getApiServiceName(props.envSettings), + healthCheckGracePeriod: Duration.minutes(2), cluster: resources.mainCluster, cpu: 512, - memoryLimitMiB: 2048, + memoryLimitMiB: 1024, desiredCount: 1, taskRole, taskImageOptions: [ { containerName: 'backend', + command: [ + "sh", + "-c", + "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run.sh", + ], image: ecs.ContainerImage.fromEcrRepository( resources.backendRepository, envSettings.version diff --git a/packages/backend/pdm.lock b/packages/backend/pdm.lock index 731ebb19f..dd177a982 100644 --- a/packages/backend/pdm.lock +++ b/packages/backend/pdm.lock @@ -519,6 +519,15 @@ version = "2.0.1" requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" summary = "Lightweight in-process concurrent programming" +[[package]] +name = "gunicorn" +version = "21.2.0" +requires_python = ">=3.5" +summary = "WSGI HTTP Server for UNIX" +dependencies = [ + "packaging", +] + [[package]] name = "hashids" version = "1.3.1" @@ -1315,7 +1324,7 @@ dependencies = [ [metadata] lock_version = "4.2" groups = ["default", "dev"] -content_hash = "sha256:6c6def2593b67c68f2b77ce8f286a15e1acdc2fe15f1ed690aa59a3fa1439763" +content_hash = "sha256:cf9ce847e6db798e36da7a5ca49c7ce6eb1c96cfb11d80bfab2394df54e828df" [metadata.files] "aiohttp 3.8.4" = [ @@ -1959,6 +1968,10 @@ content_hash = "sha256:6c6def2593b67c68f2b77ce8f286a15e1acdc2fe15f1ed690aa59a3fa {url = "https://files.pythonhosted.org/packages/fd/6a/f07b0028baff9bca61ecfcd9ee021e7e33369da8094f00eff409f2ff32be/greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"}, {url = "https://files.pythonhosted.org/packages/ff/ab/31c5327752c3381a9d3b2599245f4c2c21e9f3c73039fa4f34725562a7dd/greenlet-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39"}, ] +"gunicorn 21.2.0" = [ + {url = "https://files.pythonhosted.org/packages/06/89/acd9879fa6a5309b4bf16a5a8855f1e58f26d38e0c18ede9b3a70996b021/gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {url = "https://files.pythonhosted.org/packages/0e/2a/c3a878eccb100ccddf45c50b6b8db8cf3301a6adede6e31d48e8531cab13/gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, +] "hashids 1.3.1" = [ {url = "https://files.pythonhosted.org/packages/26/a2/6f38b1de47b41ad0420047ad03b0b8cd5d6572271a3e9c2ab70e373fe63a/hashids-1.3.1.tar.gz", hash = "sha256:6c3dc775e65efc2ce2c157a65acb776d634cb814598f406469abef00ae3f635c"}, {url = "https://files.pythonhosted.org/packages/6e/46/ffdf25b1f6dbb1ce588ccb818e983df9e3d30594679f5a08c865a59cead7/hashids-1.3.1-py2.py3-none-any.whl", hash = "sha256:8bddd1acba501bfc9306e7e5a99a1667f4f2cacdc20cbd70bcc5ddfa5147c94c"}, diff --git a/packages/backend/project.json b/packages/backend/project.json index 0c6560717..4233f4d01 100644 --- a/packages/backend/project.json +++ b/packages/backend/project.json @@ -50,6 +50,31 @@ }, "dependsOn": ["compose-build-image"] }, + "deploy:api": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/backend", + "color": true, + "commands": [ + "pnpm nx cdk:deploy:api", + "pnpm nx run tools:upload-service-version api \"url=https://${SB_DOMAIN_API}\"" + ], + "parallel": false + } + }, + "deploy:migrations": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/backend", + "color": true, + "commands": [ + "pnpm nx cdk:deploy:migrations", + "pnpm nx run trigger-migrations-job", + "pnpm nx run tools:upload-service-version migrations" + ], + "parallel": false + } + }, "deploy": { "executor": "nx:run-commands", "options": { @@ -57,23 +82,6 @@ "color": true, "commands": ["nx cdk:deploy:api", "nx cdk:deploy:migrations"], "parallel": false - }, - "configurations": { - "api": { - "commands": [ - "pnpm nx cdk:deploy:api", - "pnpm nx run tools:upload-service-version api \"url=https://${SB_DOMAIN_API}\"" - ], - "parallel": false - }, - "migrations": { - "commands": [ - "pnpm nx cdk:deploy:migrations", - "pnpm nx run trigger-migrations-job", - "pnpm nx run tools:upload-service-version migrations" - ], - "parallel": false - } } } }, diff --git a/packages/backend/pyproject.toml b/packages/backend/pyproject.toml index b51c5a1a1..2c6d4cf1f 100644 --- a/packages/backend/pyproject.toml +++ b/packages/backend/pyproject.toml @@ -113,7 +113,8 @@ dependencies = [ "pyotp>=2.8.0", "openai>=0.27.2", "pydantic>=1.10.7", - "pyyaml>=6.0.0" + "pyyaml>=6.0.0", + "gunicorn==21.2.0", ] requires-python = "~=3.11.0" license = {text = "MIT"} diff --git a/packages/backend/scripts/execute_remote.sh b/packages/backend/scripts/execute_remote.sh index ada47a057..369763a8e 100755 --- a/packages/backend/scripts/execute_remote.sh +++ b/packages/backend/scripts/execute_remote.sh @@ -13,20 +13,6 @@ then exit 1 fi -ENV_FILE="../../.env" -ENV_STAGE_FILE="${ENV_FILE}.${ENV_STAGE}" - -if [ ! -f "$ENV_FILE" ]; then - echo "$ENV_FILE cannot be found" - exit 1 -elif [ ! -f "$ENV_STAGE_FILE" ]; then - echo "$ENV_STAGE_FILE cannot be found" - exit 1 -else - export $(echo $(cat "$ENV_FILE" | sed 's/#.*//g'| xargs) | envsubst) - export $(echo $(cat "$ENV_STAGE_FILE" | sed 's/#.*//g'| xargs) | envsubst) -fi - PROJECT_ENV_NAME=${PROJECT_NAME}-${ENV_STAGE} CLUSTER_NAME="${PROJECT_ENV_NAME}-main" @@ -41,4 +27,4 @@ aws ecs execute-command \ --region "${AWS_DEFAULT_REGION//\"/}" \ --task "$TASK_ARN" \ --container backend \ - --command "/bin/chamber exec ${CHAMBER_SERVICE_NAME} -- /bin/bash" --interactive \ No newline at end of file + --command "/bin/bash" --interactive \ No newline at end of file diff --git a/packages/backend/scripts/run.sh b/packages/backend/scripts/run.sh index 527b1d1a6..6515516a3 100755 --- a/packages/backend/scripts/run.sh +++ b/packages/backend/scripts/run.sh @@ -3,4 +3,4 @@ set -e echo Starting app server... -gunicorn -c gunicorn.py config.wsgi:application +pdm run gunicorn -c gunicorn.py config.wsgi:application diff --git a/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateService.ts b/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateService.ts index 15be00676..55d866495 100644 --- a/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateService.ts +++ b/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateService.ts @@ -3,18 +3,18 @@ import { FargateService, FargateTaskDefinition, AwsLogDriver, -} from "aws-cdk-lib/aws-ecs"; -import { ApplicationTargetGroup } from "aws-cdk-lib/aws-elasticloadbalancingv2"; -import { FeatureFlags } from "aws-cdk-lib"; -import * as cxapi from "aws-cdk-lib/cx-api"; -import { Construct } from "constructs"; +} from 'aws-cdk-lib/aws-ecs'; +import { ApplicationTargetGroup } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { FeatureFlags } from 'aws-cdk-lib'; +import * as cxapi from 'aws-cdk-lib/cx-api'; +import { Construct } from 'constructs'; import { ApplicationMultipleTargetGroupsServiceBase, ApplicationMultipleTargetGroupsServiceBaseProps, -} from "./applicationMultipleTargetGroupsFargateServiceBase"; -import { ISecurityGroup, SubnetType } from "aws-cdk-lib/aws-ec2"; -import { IRole } from "aws-cdk-lib/aws-iam"; -import { ILogGroup, LogGroup } from "aws-cdk-lib/aws-logs"; +} from './applicationMultipleTargetGroupsFargateServiceBase'; +import { ISecurityGroup, SubnetType } from 'aws-cdk-lib/aws-ec2'; +import { IRole } from 'aws-cdk-lib/aws-iam'; +import { ILogGroup, LogGroup } from 'aws-cdk-lib/aws-logs'; /** * The properties for the ApplicationMultipleTargetGroupsFargateService service. @@ -158,13 +158,13 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu if (props.taskDefinition && props.taskImageOptions) { throw new Error( - "You must specify only one of TaskDefinition or TaskImageOptions." + 'You must specify only one of TaskDefinition or TaskImageOptions.' ); } else if (props.taskDefinition) { this.taskDefinition = props.taskDefinition; } else if (props.taskImageOptions) { const taskImageOptions = props.taskImageOptions; - this.taskDefinition = new FargateTaskDefinition(this, "TaskDef", { + this.taskDefinition = new FargateTaskDefinition(this, 'TaskDef', { memoryLimitMiB: props.memoryLimitMiB, cpu: props.cpu, executionRole: props.executionRole, @@ -173,22 +173,18 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu }); for (const taskImageOptionsProps of taskImageOptions) { - const containerName = taskImageOptionsProps.containerName ?? "web"; + const containerName = taskImageOptionsProps.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptionsProps.image, logging: taskImageOptionsProps.enableLogging === false ? undefined : taskImageOptionsProps.logDriver || - this.createAWSLogDriver(this.node.id), + this.createAWSLogDriver(`${this.node.id}-${containerName}`), environment: taskImageOptionsProps.environment, secrets: taskImageOptionsProps.secrets, dockerLabels: taskImageOptionsProps.dockerLabels, - command: [ - "sh", - "-c", - "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run.sh", - ], + command: taskImageOptionsProps.command, }); if (taskImageOptionsProps.containerPorts) { for (const containerPort of taskImageOptionsProps.containerPorts) { @@ -199,10 +195,10 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu } } } else { - throw new Error("You must specify one of: taskDefinition or image"); + throw new Error('You must specify one of: taskDefinition or image'); } if (!this.taskDefinition.defaultContainer) { - throw new Error("At least one essential container must be specified"); + throw new Error('At least one essential container must be specified'); } if (this.taskDefinition.defaultContainer.portMappings.length === 0) { this.taskDefinition.defaultContainer.addPortMappings({ @@ -225,7 +221,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu } protected createAWSLogDriver(prefix: string): AwsLogDriver { - const logGroup = new LogGroup(this, "LogGroup"); + const logGroup = new LogGroup(this, `${prefix}-LogGroup`); this.logGroups.push(logGroup); return new AwsLogDriver({ streamPrefix: prefix, @@ -242,7 +238,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu ? this.internalDesiredCount : this.desiredCount; - return new FargateService(this, "Service", { + return new FargateService(this, 'Service', { cluster: this.cluster, desiredCount: desiredCount, taskDefinition: this.taskDefinition, diff --git a/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateServiceBase.ts b/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateServiceBase.ts index 88a90a439..86e5515e9 100644 --- a/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateServiceBase.ts +++ b/packages/infra/infra-core/src/lib/patterns/applicationMultipleTargetGroupsFargateServiceBase.ts @@ -192,6 +192,8 @@ export interface ApplicationLoadBalancedTaskImageProps { * @default - No labels. */ readonly dockerLabels?: { [key: string]: string }; + + readonly command?: string[]; } /**