diff --git a/README.md b/README.md index e2b49efb..12cf911f 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,20 @@ and run the migrations: uv run alembic upgrade head ``` +# Job scheduler + +We use the `apscheduler` library to schedule recurring jobs. Currently +this is used to synchronize user information from Auth0 to the AAI +database. You can run the job scheduler locally using: + +```shell +uv run python run_scheduler.py +``` + +Note that for small jobs that run on-demand, e.g. sending a notification email when a user +signs up, you can use FastAPI's built-in [background tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/) +within the FastAPI app, instead of using the dedicated job scheduler. + # Deployment Currently the service is deployed to AWS via the CDK scripts in `deploy/`, @@ -112,3 +126,8 @@ and updated on each commit to `main`. Secrets/configuration variables for the deployment are stored in the GitHub Secrets for the repository. + +The service deploys two containers (which both use the same image/Python environment): + +* The FastAPI app +* The `apscheduler` job scheduler diff --git a/deploy/aai_backend_deploy/aai_backend_deploy_stack.py b/deploy/aai_backend_deploy/aai_backend_deploy_stack.py index bdd3cd41..bbce030b 100644 --- a/deploy/aai_backend_deploy/aai_backend_deploy_stack.py +++ b/deploy/aai_backend_deploy/aai_backend_deploy_stack.py @@ -60,9 +60,9 @@ def __init__(self, scope: Construct, construct_id: str, config: dict, **kwargs) # Task definition for Fargate task_definition = ecs.FargateTaskDefinition(self, "AaiBackendTaskDef", - memory_limit_mib=1024, - cpu=512) - # Allow executing comands in the ECS container + memory_limit_mib=2048, + cpu=1024) + # Allow executing commands in the ECS container task_definition.task_role.add_managed_policy( iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore") ) @@ -85,6 +85,25 @@ def __init__(self, scope: Construct, construct_id: str, config: dict, **kwargs) }, logging=ecs.LogDrivers.aws_logs(stream_prefix="FastAPI"), ) + # Run the job scheduler as a separate container + task_definition.add_container( + "SchedulerContainer", + image=ecs.ContainerImage.from_ecr_repository( + ecr_repo, + tag="latest" + ), + # Override the default command to run the scheduler + command=["uv", "run", "python", "run_scheduler.py"], + environment={ + "FORCE_REDEPLOY": str(datetime.datetime.now()), + "DB_HOST": self.db_host, + }, + secrets={ + "DB_USER": ecs.Secret.from_secrets_manager(db_secret, field="username"), + "DB_PASSWORD": ecs.Secret.from_secrets_manager(db_secret, field="password"), + }, + logging=ecs.LogDrivers.aws_logs(stream_prefix="Scheduler"), + ) container.add_port_mappings( ecs.PortMapping(container_port=8000) @@ -112,4 +131,12 @@ def __init__(self, scope: Construct, construct_id: str, config: dict, **kwargs) service.target_group.configure_health_check(path="/", healthy_http_codes="200-399") + # Output relevant ARNs and IDs CfnOutput(self, "LoadBalancerDNS", value=service.load_balancer.load_balancer_dns_name) + CfnOutput(self, "ServiceArn", value=service.service.service_arn) + CfnOutput(self, "ServiceName", value=service.service.service_name) + CfnOutput(self, "ClusterArn", value=cluster.cluster_arn) + CfnOutput(self, "ClusterName", value=cluster.cluster_name) + CfnOutput(self, "TaskDefinitionArn", value=task_definition.task_definition_arn) + CfnOutput(self, "TaskDefinitionFamily", value=task_definition.family) + CfnOutput(self, "LoadBalancerArn", value=service.load_balancer.load_balancer_arn)