import pulumi import pulumi_aws as aws import conf as config import iam import ecr import json def deploy_app(config_ecs_app, app_ecs_cluster, alb_security_group, app_load_balancer_arn): lb_config = config_ecs_app["lb_config"] target_group = aws.lb.TargetGroup(f"app-target-group-{lb_config['listener_port']}", port=lb_config["target_port"], protocol="HTTP", vpc_id=config.network["vpc_id"], target_type="ip", health_check=aws.lb.TargetGroupHealthCheckArgs( path="/", # TODO if it doesn't work, can use /docs for fastapi protocol="HTTP", port="traffic-port", healthy_threshold=2, unhealthy_threshold=2, timeout=5, interval=30, matcher="200-499", ), ) aws.lb.Listener(f"app-listener-{lb_config['listener_port']}", load_balancer_arn=app_load_balancer_arn, port=lb_config["listener_port"], protocol="HTTP", default_actions=[aws.lb.ListenerDefaultActionArgs( type="forward", target_group_arn=target_group.arn, )], ) # target_groups.append(target_group) # Build and Push ECR # ecr_repos = ecr.create_ecr_repo(config_ecs_app['ecr_repo_name']) # assert ('ecr_image_tag' in config_ecs_app.keys()) != ('ecr_image_digest' in config_ecs_app.keys()), 'User must provide either tag or image_digest, but not both, to identify image version' if 'ecr_image_tag' in config_ecs_app.keys(): ecr_repo_image = ecr.get_image(config_ecs_app['ecr_repo_name'], image_tag=config_ecs_app['ecr_image_tag']) elif 'ecr_image_digest' in config_ecs_app.keys(): ecr_repo_image = ecr.get_image(config_ecs_app['ecr_repo_name'], image_digest=config_ecs_app['ecr_image_digest']) # Log Group Setup #TODO move into ecs app_log_group = aws.cloudwatch.LogGroup(f"{config.project_name}-{config_ecs_app['task_name']}-log-group", retention_in_days=7) # if key # ssm_parameter, key = kms.setup_kms() # iam.create_execution_role_with_keys(ssm_parameter, key) # IAM Roles Setup app_execution_role = iam.create_execution_role() app_task_role = iam.create_task_role() # summarization_repo_image = config_ecs_app['ecr_image'] if config_ecs_app['use_load_balancer']: environemnt_variables = [dict(name=k, value=v) for k,v in config_ecs_app['env_variables'].items()] print(environemnt_variables) # ECS Task Definition Setup app_task_definition = aws.ecs.TaskDefinition(f"{config.project_name}-{config_ecs_app['task_name']}-task-definition", family=f"{config.project_name}-{config_ecs_app['task_name']}-{config.environment}", cpu=config_ecs_app["cpu"], memory=config_ecs_app["memory"], network_mode="awsvpc", execution_role_arn=app_execution_role.arn, task_role_arn=app_task_role.arn, requires_compatibilities=["FARGATE"], container_definitions=pulumi.Output.all(ecr_repo_image.image_uri, # ecr_repo_image.image_digest, app_log_group.name, environemnt_variables, # config_ecs_app["secret_name"], ).apply(lambda args: json.dumps([{ "name": f"{config.project_name}-{config_ecs_app['task_name']}-{config.environment}-service", "image": args[0], "cpu": 0, "portMappings": [ { "name": "api", "containerPort": lb_config["container_port"], "hostPort": lb_config["target_port"], "protocol": "tcp", } # } for lb_config_item in lb_configs ], "essential": True, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": args[1], "awslogs-region": config.aws_region, "awslogs-stream-prefix": "pulumi-langserve", }, }, "environment": args[2], }])), ) # ECS Security Group Setup app_ecs_security_group = aws.ec2.SecurityGroup(f"{config.project_name}-{config_ecs_app['task_name']}-ecs-security-group", vpc_id=config.network["vpc_id"], ingress=[aws.ec2.SecurityGroupIngressArgs( protocol="-1", from_port=0, to_port=0, security_groups=[alb_security_group.id], )], egress=[aws.ec2.SecurityGroupEgressArgs( protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"], )], ) # Security Group Rules for Ingress for sg_name, sg_id in config_ecs_app["sgs_allowing_ingress"].items(): aws.ec2.SecurityGroupRule(f"sgr-{sg_name}-allow_in_from-{config.project_name}", type="ingress", from_port=0, to_port=0, protocol="-1", security_group_id=sg_id, source_security_group_id=app_ecs_security_group.id, description=f"Allow from {config.project_name} ECS SG", ) # Service Discovery Namespace Setup app_service_discovery_namespace = aws.servicediscovery.PrivateDnsNamespace(f"{config.project_name}-{config_ecs_app['task_name']}-service-discovery-namespace", name=f"{config.environment}.{config.project_name}.local", vpc=config.network["vpc_id"], ) # ECS Service Setup load_balancer = aws.ecs.ServiceLoadBalancerArgs( target_group_arn=target_group.arn, container_name=f"{config.project_name}-{config_ecs_app['task_name']}-{config.environment}-service", container_port=lb_config["target_port"], ) app_service = aws.ecs.Service(f"{config.project_name}-{config_ecs_app['task_name']}-service", cluster=app_ecs_cluster.arn, task_definition=app_task_definition.arn, desired_count=config_ecs_app["desired_count"], launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( assign_public_ip=True, security_groups=[app_ecs_security_group.id], subnets=config.network["ecs_subnet_ids"], ), load_balancers=[load_balancer], scheduling_strategy="REPLICA", service_connect_configuration=aws.ecs.ServiceServiceConnectConfigurationArgs( enabled=True, namespace=app_service_discovery_namespace.arn, ), tags={"Name": f"{config.project_name}-{config_ecs_app['task_name']}-{config.environment}"}, ) #defining an auto-scaling for summarization scalable_target = aws.appautoscaling.Target("app-svc-target", max_capacity=config_ecs_app['auto_scaling']['max_capacity'], min_capacity=config_ecs_app['auto_scaling']['min_capacity'], resource_id=pulumi.Output.all(app_ecs_cluster.name, app_service.name).apply(lambda args: f"service/{args[0]}/{args[1]}"), scalable_dimension="ecs:service:DesiredCount", service_namespace="ecs", ) # Define an auto-scaling policy on CPU utilization scaling_policy = aws.appautoscaling.Policy("app-svc-policy", policy_type="TargetTrackingScaling", resource_id=scalable_target.resource_id, scalable_dimension="ecs:service:DesiredCount", service_namespace="ecs", target_tracking_scaling_policy_configuration={ "target_value": config_ecs_app['auto_scaling']['target_value'], # Target CPU utilization (30%) "predefined_metric_specification": { "predefined_metric_type": "ECSServiceAverageCPUUtilization" }, }, ) else: # classification_repo_image = ecr_repo_images['ai-med-exam-classification']['ecr_image'] # ECS without Load Balancer new_ecs_task_definition = aws.ecs.TaskDefinition("classification-theia-poc-task-definition", family="classification-theia-poc", cpu=config_ecs_app["cpu"], memory=config_ecs_app["memory"], network_mode="awsvpc", execution_role_arn=app_execution_role.arn, task_role_arn=app_task_role.arn, requires_compatibilities=["FARGATE"], container_definitions=pulumi.Output.all(ecr_repo_image.image_uri, # classification_repo_image.image_digest, app_log_group.name).apply(lambda args: json.dumps([{ "name": "classification-theia-poc-service", "image": args[0], "cpu": 0, "portMappings": [ { "name": "api", "containerPort": 80, # Define the container port without Load Balancer "hostPort": 80, "protocol": "tcp", } ], "essential": True, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": args[1], "awslogs-region": config.aws_region, "awslogs-stream-prefix": "pulumi-classification-theia-poc", }, }, }])), ) # ecs without load balancer new_ecs_service = aws.ecs.Service("classification-theia-poc-service", cluster=app_ecs_cluster.arn, task_definition=new_ecs_task_definition.arn, desired_count=config_ecs_app["desired_count"], launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( assign_public_ip=True, security_groups=[app_ecs_security_group.id], subnets=config.network["ecs_subnet_ids"], ), scheduling_strategy="REPLICA", service_connect_configuration=aws.ecs.ServiceServiceConnectConfigurationArgs( enabled=True, namespace=app_service_discovery_namespace.arn, ), tags={"Name": "classification-theia-poc"}, )