Skip to content

Commit

Permalink
Merge pull request #17 from hallieswan/AG-1591_AG-1592
Browse files Browse the repository at this point in the history
AG-1591, AG-1592, IT-4241: don't deploy unused containers, create DocumentDB cluster, allow connection from API container
  • Loading branch information
hallieswan authored Jan 27, 2025
2 parents 277e7dc + 526d54c commit fe042da
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @Sage-Bionetworks-IT/sagebio-it @Sage-Bionetworks-IT/infra-oversight-committee @Sage-Bionetworks-IT/agora-admin
* @Sage-Bionetworks-IT/infra-oversight-committee @Sage-Bionetworks-IT/agora-admin
107 changes: 36 additions & 71 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from os import environ

import aws_cdk as cdk
from aws_cdk import aws_ec2 as ec2

from src.ecs_stack import EcsStack
from src.load_balancer_stack import LoadBalancerStack
from src.network_stack import NetworkStack
from src.service_props import ServiceProps, ContainerVolume
from src.service_props import ServiceProps, ServiceSecret
from src.service_stack import LoadBalancedServiceStack, ServiceStack
from src.docdb_props import DocdbProps
from src.docdb_stack import DocdbStack

# get the environment and set environment specific variables
VALID_ENVIRONMENTS = ["dev", "stage", "prod"]
Expand Down Expand Up @@ -43,6 +46,8 @@
fully_qualified_domain_name = environment_variables["FQDN"]
environment_tags = environment_variables["TAGS"]
agora_version = "4.0.0-rc1"
docdb_master_username = "master"
mongodb_port = 27017

# Define stacks
cdk_app = cdk.App()
Expand All @@ -58,6 +63,20 @@
vpc_cidr=environment_variables["VPC_CIDR"],
)

docdb_props = DocdbProps(
instance_type=ec2.InstanceType.of(
ec2.InstanceClass.MEMORY5, ec2.InstanceSize.LARGE
),
master_username=docdb_master_username,
port=mongodb_port,
)
docdb_stack = DocdbStack(
scope=cdk_app,
construct_id=f"{stack_name_prefix}-docdb",
vpc=network_stack.vpc,
props=docdb_props,
)

ecs_stack = EcsStack(
scope=cdk_app,
construct_id=f"{stack_name_prefix}-ecs",
Expand All @@ -75,82 +94,24 @@
vpc=network_stack.vpc,
)

api_docs_props = ServiceProps(
container_name="agora-api-docs",
container_location=f"ghcr.io/sage-bionetworks/agora-api-docs:{agora_version}",
container_port=8010,
container_memory=200,
container_env_vars={"PORT": "8010"},
)
api_docs_stack = ServiceStack(
scope=cdk_app,
construct_id=f"{stack_name_prefix}-api-docs",
vpc=network_stack.vpc,
cluster=ecs_stack.cluster,
props=api_docs_props,
)

mongo_props = ServiceProps(
container_name="agora-mongo",
container_location=f"ghcr.io/sage-bionetworks/agora-mongo:{agora_version}",
container_port=27017,
container_memory=500,
container_env_vars={
"MONGO_INITDB_ROOT_USERNAME": "root",
"MONGO_INITDB_ROOT_PASSWORD": "changeme",
"MONGO_INITDB_DATABASE": "agora",
},
container_volumes=[
ContainerVolume(
path="/data/db",
size=30,
)
],
)
mongo_stack = ServiceStack(
scope=cdk_app,
construct_id=f"{stack_name_prefix}-mongo",
vpc=network_stack.vpc,
cluster=ecs_stack.cluster,
props=mongo_props,
)

# It is probably not appropriate host this container in ECS
# data_props = ServiceProps(
# container_name="agora-data",
# container_location=f"ghcr.io/sage-bionetworks/agora-data:{agora_version}",
# container_port=9999, # Not used
# container_memory=2048,
# )
# data_stack = ServiceStack(
# scope=cdk_app,
# construct_id=f"{stack_name_prefix}-data",
# vpc=network_stack.vpc,
# cluster=ecs_stack.cluster,
# props=data_props,
# container_env_vars={
# "DB_USER": "root",
# "DB_PASS": "changeme",
# "DB_NAME": "agora",
# "DB_PORT": "27017",
# "DB_HOST": "agora-mongo",
# "DATA_FILE": "syn13363290",
# "DATA_VERSION": "68",
# "TEAM_IMAGES_ID": "syn12861877",
# "SYNAPSE_AUTH_TOKEN": "agora-service-user-pat-here",
# },
# )
# data_stack.add_dependency(mongo_stack)

api_props = ServiceProps(
container_name="agora-api",
container_location=f"ghcr.io/sage-bionetworks/agora-api:{agora_version}",
container_port=3333,
container_memory=1024,
container_env_vars={
"MONGODB_URI": "mongodb://root:changeme@agora-mongo:27017/agora?authSource=admin",
"NODE_ENV": "development",
"MONGODB_PORT": f"{mongodb_port}",
"MONGODB_NAME": "agora",
"MONDODB_USER": docdb_master_username,
"MONGODB_HOST": docdb_stack.cluster.cluster_endpoint.hostname,
},
container_secrets=[
ServiceSecret(
secret_name=docdb_stack.master_password_secret.secret_name,
environment_key="MONGODB_PASS",
)
],
)
api_stack = ServiceStack(
scope=cdk_app,
Expand All @@ -159,7 +120,11 @@
cluster=ecs_stack.cluster,
props=api_props,
)
api_stack.add_dependency(mongo_stack)
api_stack.add_dependency(docdb_stack)
api_stack.service.connections.allow_to_default_port(
docdb_stack.cluster,
"Allow API container to connect to DocumentDB cluster",
)

app_props = ServiceProps(
container_name="agora-app",
Expand All @@ -171,6 +136,7 @@
"APP_VERSION": f"{agora_version}",
"CSR_API_URL": f"http://{fully_qualified_domain_name}/api/v1",
"SSR_API_URL": "http://agora-api:3333/v1",
"TAG_NAME": f"agora/v${agora_version}",
},
)
app_stack = ServiceStack(
Expand Down Expand Up @@ -207,7 +173,6 @@
health_check_path="/health",
)
apex_stack.add_dependency(app_stack)
apex_stack.add_dependency(api_docs_stack)
apex_stack.add_dependency(api_stack)

cdk_app.synth()
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
aws-cdk-lib~=2.139
aws-cdk-lib~=2.176
constructs~=10.0
boto3~=1.34
18 changes: 18 additions & 0 deletions src/docdb_props.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from aws_cdk import aws_ec2 as ec2


class DocdbProps:
"""
DocumentDB properties
instance_type: What type of instance to start for the replicas
master_username: The database admin account username
port: The MongoDB port
"""

def __init__(
self, instance_type: ec2.InstanceType, master_username: str, port: int
) -> None:
self.instance_type = instance_type
self.master_username = master_username
self.port = port
69 changes: 69 additions & 0 deletions src/docdb_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import aws_cdk as cdk
from aws_cdk import (
aws_docdb as docdb,
aws_ec2 as ec2,
aws_secretsmanager as sm,
)
from src.docdb_props import DocdbProps

from constructs import Construct


class DocdbStack(cdk.Stack):
"""
DocumentDB cluster
"""

def __init__(
self,
scope: Construct,
construct_id: str,
vpc: ec2.Vpc,
props: DocdbProps,
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)

self.master_password_secret = sm.Secret(
self,
"DocDbMasterPassword",
generate_secret_string=sm.SecretStringGenerator(
password_length=32, exclude_punctuation=True
),
)

cluster_parameter_group = docdb.ClusterParameterGroup(
self,
"DocDbClusterParameterGroup",
family="docdb5.0",
parameters={
"audit_logs": "disabled",
"profiler": "enabled",
"profiler_sampling_rate": "1.0",
"profiler_threshold_ms": "50",
"change_stream_log_retention_duration": "10800",
"tls": "disabled",
"ttl_monitor": "disabled",
},
)

# https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_docdb/DatabaseCluster.html
self.cluster = docdb.DatabaseCluster(
self,
"DocDbCluster",
master_user=docdb.Login(
username=props.master_username,
password=self.master_password_secret.secret_value,
),
instance_type=props.instance_type,
vpc=vpc,
vpc_subnets=ec2.SubnetSelection(
subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS
),
parameter_group=cluster_parameter_group,
removal_policy=cdk.RemovalPolicy.DESTROY,
storage_encrypted=True,
preferred_maintenance_window="sat:06:54-sat:07:24",
port=props.port,
export_profiler_logs_to_cloud_watch=True,
)
8 changes: 1 addition & 7 deletions src/service_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,6 @@ def _get_secret(scope: Construct, id: str, name: str) -> sm.Secret:
health_check=props.container_healthcheck,
)

self.security_group = ec2.SecurityGroup(self, "SecurityGroup", vpc=vpc)
self.security_group.add_ingress_rule(
peer=ec2.Peer.ipv4("0.0.0.0/0"),
connection=ec2.Port.tcp(props.container_port),
)

# attach ECS task to ECS cluster
self.service = ecs.FargateService(
self,
Expand All @@ -141,7 +135,6 @@ def _get_secret(scope: Construct, id: str, name: str) -> sm.Secret:
task_definition=self.task_definition,
enable_execute_command=True,
circuit_breaker=ecs.DeploymentCircuitBreaker(enable=True, rollback=True),
security_groups=([self.security_group]),
service_connect_configuration=ecs.ServiceConnectProps(
log_driver=ecs.LogDrivers.aws_logs(stream_prefix=f"{construct_id}"),
services=[
Expand All @@ -164,6 +157,7 @@ def _get_secret(scope: Construct, id: str, name: str) -> sm.Secret:
),
],
)
self.service.connections.allow_from_any_ipv4(ec2.Port.tcp(props.container_port))

# Setup AutoScaling policy
scaling = self.service.auto_scale_task_count(
Expand Down
63 changes: 63 additions & 0 deletions tests/unit/test_docdb_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import aws_cdk as cdk
from aws_cdk import aws_ec2 as ec2, assertions as assertions

from src.network_stack import NetworkStack
from src.docdb_stack import DocdbStack
from src.docdb_props import DocdbProps


def test_docdb_created():
cdk_app = cdk.App()
master_username = "myuser"
port = 27017
vpc_cidr = "10.254.192.0/24"
network_stack = NetworkStack(cdk_app, "NetworkStack", vpc_cidr=vpc_cidr)

docdb_props = DocdbProps(
instance_type=ec2.InstanceType.of(
ec2.InstanceClass.MEMORY5, ec2.InstanceSize.LARGE
),
master_username=master_username,
port=port,
)
docdb_stack = DocdbStack(
scope=cdk_app,
construct_id="docdb",
vpc=network_stack.vpc,
props=docdb_props,
)

template = assertions.Template.from_stack(docdb_stack)
template.has_resource_properties(
"AWS::DocDB::DBClusterParameterGroup",
{
"Parameters": {
"audit_logs": "disabled",
"profiler": "enabled",
"profiler_sampling_rate": "1.0",
"profiler_threshold_ms": "50",
"change_stream_log_retention_duration": "10800",
"tls": "disabled",
"ttl_monitor": "disabled",
}
},
)
template.has_resource_properties(
"AWS::DocDB::DBCluster",
{
"MasterUsername": master_username,
"MasterUserPassword": assertions.Match.any_value(),
"DBSubnetGroupName": assertions.Match.any_value(),
"DBClusterParameterGroupName": assertions.Match.any_value(),
"StorageEncrypted": True,
"PreferredMaintenanceWindow": "sat:06:54-sat:07:24",
"Port": port,
"EnableCloudwatchLogsExports": ["profiler"],
"VpcSecurityGroupIds": [
assertions.Match.any_value(),
],
},
)
template.resource_properties_count_is(
"AWS::EC2::SecurityGroup", assertions.Match.any_value(), 1
)
2 changes: 1 addition & 1 deletion tools/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
set -euxo pipefail

# Install Node.js dependencies
npm install -g aws-cdk@2.151.0
npm install -g aws-cdk@2.176.0

# Install Python dependencies
python -m venv .venv
Expand Down

0 comments on commit fe042da

Please sign in to comment.