Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

csworkshop-frontend - FrontendServiceMesh error #14

Open
thomshib opened this issue Mar 14, 2023 · 1 comment
Open

csworkshop-frontend - FrontendServiceMesh error #14

thomshib opened this issue Mar 14, 2023 · 1 comment

Comments

@thomshib
Copy link

Initial errors in app.py

  1. line 190 - corrected to security_groups=[self.base_platform.services_sec_grp]
  2. line 208 - corrected to service_discovery=appmesh.ServiceDiscovery.cloud_map(self.fargate_service.cloud_map_service)

But, even after correcting the above errors, CDK synth fails with the error

File "app.py", line 353, in
app.synth()
File "/home/ec2-user/.local/lib/python3.7/site-packages/aws_cdk/init.py", line 19599, in synth
return typing.cast(_CloudAssembly_c693643e, jsii.invoke(self, "synth", [options]))
File "/home/ec2-user/.local/lib/python3.7/site-packages/jsii/_kernel/init.py", line 149, in wrapped
return _recursize_dereference(kernel, fn(kernel, *args, **kwargs))
File "/home/ec2-user/.local/lib/python3.7/site-packages/jsii/_kernel/init.py", line 405, in invoke
args=_make_reference_for_native(self, args),
File "/home/ec2-user/.local/lib/python3.7/site-packages/jsii/_kernel/providers/process.py", line 378, in invoke
return self._process.send(request, InvokeResponse)
File "/home/ec2-user/.local/lib/python3.7/site-packages/jsii/_kernel/providers/process.py", line 340, in send
raise RuntimeError(resp.error) from JavaScriptError(resp.stack)
RuntimeError: Error: Resolution error: Supplied properties not correct for "CfnVirtualNodeProps"
spec: supplied properties not correct for "VirtualNodeSpecProperty"
backends: element 0: supplied properties not correct for "BackendProperty"
virtualService: supplied properties not correct for "VirtualServiceBackendProperty"
virtualServiceName: required but missing.

@thomshib
Copy link
Author

Here is a working app.py file

#!/usr/bin/env python3

import os
import aws_cdk as cdk
from constructs import Construct
from aws_cdk import (
App, CfnOutput, Stack, Environment, Fn, Duration,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
aws_servicediscovery as servicediscovery,
aws_iam as iam,
aws_appmesh as appmesh,
aws_logs as logs)

Creating a construct that will populate the required objects created in the platform repo such as vpc, ecs cluster, and service discovery namespace

class BasePlatform(Construct):

def __init__(self, scope: Construct, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)
    environment_name = 'ecsworkshop'

    # The base platform stack is where the VPC was created, so all we need is the name to do a lookup and import it into this stack for use
    self.vpc = ec2.Vpc.from_lookup(
        self, "VPC",
        vpc_name='{}-base/BaseVPC'.format(environment_name)
    )
    
    self.sd_namespace = servicediscovery.PrivateDnsNamespace.from_private_dns_namespace_attributes(
        self, "SDNamespace",
        namespace_name=cdk.Fn.import_value('NSNAME'),
        namespace_arn=cdk.Fn.import_value('NSARN'),
        namespace_id=cdk.Fn.import_value('NSID')
    )
    
    self.ecs_cluster = ecs.Cluster.from_cluster_attributes(
        self, "ECSCluster",
        cluster_name=cdk.Fn.import_value('ECSClusterName'),
        security_groups=[],
        vpc=self.vpc,
        default_cloud_map_namespace=self.sd_namespace
    )
    
    self.services_sec_grp = ec2.SecurityGroup.from_security_group_id(
        self, "ServicesSecGrp",
        security_group_id=cdk.Fn.import_value('ServicesSecGrp')
    )

class FrontendService(Stack):

def __init__(self, scope: Stack, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)

base_platform = BasePlatform(self, stack_name)

    self.base_platform = BasePlatform(self, "BasePlatform") 

    self.fargate_task_image = ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
        image=ecs.ContainerImage.from_registry("public.ecr.aws/aws-containers/ecsdemo-frontend"),
        container_port=3000,
        environment={
            "CRYSTAL_URL": "http://ecsdemo-crystal.service.local:3000/crystal",
            "NODEJS_URL": "http://ecsdemo-nodejs.service.local:3000",
            "REGION": os.getenv('AWS_DEFAULT_REGION')
        },
    )

    self.fargate_load_balanced_service = ecs_patterns.ApplicationLoadBalancedFargateService(
        self, "FrontendFargateLBService",
        service_name='ecsdemo-frontend',
        cluster=self.base_platform.ecs_cluster,
        cpu=256,
        memory_limit_mib=512,
        desired_count=1,
        public_load_balancer=True,
        cloud_map_options=ecs.CloudMapOptions(
            cloud_map_namespace=self.base_platform.sd_namespace
            ),
        task_image_options=self.fargate_task_image
    )
    
    self.fargate_load_balanced_service.task_definition.add_to_task_role_policy(
        iam.PolicyStatement(
            actions=['ec2:DescribeSubnets'],
            resources=['*']
        )
    )
    
    self.fargate_load_balanced_service.service.connections.allow_to(
        self.base_platform.services_sec_grp,
        port_range=ec2.Port(protocol=ec2.Protocol.TCP, string_representation="frontendtobackend", from_port=3000, to_port=3000)
    )
    
    # Enable Service Autoscaling
    #self.autoscale = fargate_load_balanced_service.service.auto_scale_task_count(
    #    min_capacity=1,
    #    max_capacity=10
    #)
    
    #self.autoscale.scale_on_cpu_utilization(
    #    "CPUAutoscaling",
    #    target_utilization_percent=50,
    #    scale_in_cooldown=Duration.seconds(30),
    #    scale_out_cooldown=Duration.seconds(30)
    #)

class FrontendServiceMesh(Stack):

def __init__(self, scope: Stack, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)

    self.base_platform = BasePlatform(self, stack_name)
    
    self.mesh = appmesh.Mesh.from_mesh_arn(
        self,
        "EcsWorkShop-AppMesh",
        mesh_arn=cdk.Fn.import_value("MeshArn")
    )
    
    self.mesh_vgw = appmesh.VirtualGateway.from_virtual_gateway_attributes(
        self,
        "Mesh-VGW",
        mesh=self.mesh,
        virtual_gateway_name=cdk.Fn.import_value("MeshVGWName")
    )
    
    self.mesh_crystal_vs = appmesh.VirtualService.from_virtual_service_attributes(
        self,
        "mesh-crystal-vs",
        mesh=self.mesh,
        # virtual_service_name=cdk.Fn.import_value("MeshCrystalVSName")
        virtual_service_name="ecsdemo-crystal.service.local"
    )
    
    self.mesh_nodejs_vs = appmesh.VirtualService.from_virtual_service_attributes(
        self,
        "mesh-nodejs-vs",
        mesh=self.mesh,
        # virtual_service_name=cdk.Fn.import_value("MeshNodeJsVSName")
        virtual_service_name="ecsdemo-nodejs.service.local"
    )
    
    self.fargate_task_def = ecs.TaskDefinition(
        self, "FrontEndTaskDef",
        compatibility=ecs.Compatibility.EC2_AND_FARGATE,
        cpu='256',
        memory_mib='512',
        proxy_configuration=ecs.AppMeshProxyConfiguration( 
            container_name="envoy",
            properties=ecs.AppMeshProxyConfigurationProps(
                app_ports=[3000],
                proxy_ingress_port=15000,
                proxy_egress_port=15001,
                egress_ignored_i_ps=["169.254.170.2","169.254.169.254"],
                ignored_uid=1337
            )
        )
    )
    
    self.logGroup = logs.LogGroup(self,"ecsworkshopFrontendLogGroup",
        #log_group_name="ecsworkshop-frontend",
        retention=logs.RetentionDays.ONE_WEEK
    )
    
    self.app_container = self.fargate_task_def.add_container(
        "FrontendServiceContainerDef",
        image=ecs.ContainerImage.from_registry("public.ecr.aws/aws-containers/ecsdemo-frontend"),
        logging=ecs.LogDriver.aws_logs(
            stream_prefix='/frontend-container',
            log_group=self.logGroup
        ),
        essential=True,
        memory_reservation_mib=128,
        environment={
            "CRYSTAL_URL": "http://ecsdemo-crystal.service.local:3000/crystal",
            "NODEJS_URL": "http://ecsdemo-nodejs.service.local:3000",
            "REGION": os.getenv('AWS_DEFAULT_REGION')
        },
        container_name="frontend-app"
    )
    
    self.app_container.add_port_mappings(
        ecs.PortMapping(
            container_port=3000
        )
    )
    
    self.fargate_service = ecs.FargateService(
        self, "FrontEndFargateService",
        service_name='ecsdemo-frontend',
        task_definition=self.fargate_task_def,
        cluster=self.base_platform.ecs_cluster,
        security_groups=[self.base_platform.services_sec_grp],
        desired_count=1,
        cloud_map_options=ecs.CloudMapOptions(
            cloud_map_namespace=self.base_platform.sd_namespace,
            name='ecsdemo-frontend'
        ),
        #deployment_controller=aws_ecs.DeploymentController(type=aws_ecs.DeploymentControllerType.EXTERNAL)
    )
         
    ##################################################
    ###APP Mesh Configuration####
    
    self.mesh_frontend_vn = appmesh.VirtualNode(
        self,
        "MeshFrontEndNode",
        mesh=self.mesh,
        virtual_node_name="frontend",
        listeners=[appmesh.VirtualNodeListener.http(port=3000)],
        service_discovery=appmesh.ServiceDiscovery.cloud_map(self.fargate_service.cloud_map_service),
        # backends=[
        #     appmesh.Backend.virtual_service(self.mesh_crystal_vs),
        #     appmesh.Backend.virtual_service(self.mesh_nodejs_vs)
        #     ],
        access_log=appmesh.AccessLog.from_file_path("/dev/stdout")
        
    )
    
    self.mesh_frontend_vn.add_backend(appmesh.Backend.virtual_service(self.mesh_crystal_vs))
    self.mesh_frontend_vn.add_backend(appmesh.Backend.virtual_service(self.mesh_nodejs_vs))
    
    
   
    self.envoy_container = self.fargate_task_def.add_container(
        "FrontendServiceProxyContdef",
        image=ecs.ContainerImage.from_registry("public.ecr.aws/appmesh/aws-appmesh-envoy:v1.18.3.0-prod"),
        container_name="envoy",
        memory_reservation_mib=128,
        environment={
            "REGION": os.getenv('AWS_DEFAULT_REGION'),
            "ENVOY_LOG_LEVEL": "critical",
            "ENABLE_ENVOY_STATS_TAGS": "1",
            # "ENABLE_ENVOY_XRAY_TRACING": "1",
            "APPMESH_RESOURCE_ARN": self.mesh_frontend_vn.virtual_node_arn
        },
        essential=True,
        logging=ecs.LogDriver.aws_logs(
            stream_prefix='/mesh-envoy-container',
            log_group=self.logGroup
        ),
        health_check=ecs.HealthCheck(
            interval=cdk.Duration.seconds(5),
            timeout=cdk.Duration.seconds(10),
            retries=10,
            command=["CMD-SHELL","curl -s http://localhost:9901/server_info | grep state | grep -q LIVE"],
        ),
        user="1337"
    )
    
    self.envoy_container.add_ulimits(ecs.Ulimit(
        hard_limit=15000,
        name=ecs.UlimitName.NOFILE,
        soft_limit=15000
        )
    )
    
    self.app_container.add_container_dependencies(ecs.ContainerDependency(
          container=self.envoy_container,
          condition=ecs.ContainerDependencyCondition.HEALTHY
      )
    )
    
    #appmesh-xray-uncomment
    # self.xray_container = self.fargate_task_def.add_container(
    #     "FrontendServiceXrayContdef",
    #     image=ecs.ContainerImage.from_registry("amazon/aws-xray-daemon"),
    #     logging=ecs.LogDriver.aws_logs(
    #         stream_prefix='/xray-container',
    #         log_group=self.logGroup
    #     ),
    #     essential=True,
    #     container_name="xray",
    #     memory_reservation_mib=256,
    #     user="1337"
    # )
    
    # self.envoy_container.add_container_dependencies(ecs.ContainerDependency(
    #       container=xray_container,
    #       condition=ecs.ContainerDependencyCondition.START
    #   )
    # )
    #appmesh-xray-uncomment
    
    self.fargate_task_def.add_to_task_role_policy(
        iam.PolicyStatement(
            actions=['ec2:DescribeSubnets'],
            resources=['*']
        )
    )
    
    self.fargate_service.connections.allow_from_any_ipv4(
        port_range=ec2.Port(protocol=ec2.Protocol.TCP, string_representation="tcp_3000", from_port=3000, to_port=3000),
        description="Allow TCP connections on port 3000"
    )
    
    self.fargate_task_def.execution_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerRegistryReadOnly"))
    self.fargate_task_def.execution_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchLogsFullAccess"))
    
    self.fargate_task_def.task_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchFullAccess"))
    # fargate_task_def.task_role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AWSXRayDaemonWriteAccess"))
    self.fargate_task_def.task_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("AWSAppMeshEnvoyAccess"))
    
    # Creating a App Mesh virtual router
    meshVR=appmesh.VirtualRouter(
        self,
        "MeshVirtualRouter",
        mesh=self.mesh,
        listeners=[appmesh.VirtualRouterListener.http(3000)],
        virtual_router_name="FrontEnd"
    )
    
    meshVR.add_route(
        "MeshFrontEndVRRoute",
        route_spec=appmesh.RouteSpec.http(
            weighted_targets=[appmesh.WeightedTarget(virtual_node=self.mesh_frontend_vn,weight=1)]
        ),
        route_name="frontend-a"
    )
    
     # Asdding mesh virtual service 
    mesh_frontend_vs = appmesh.VirtualService(self,"mesh-frontend-vs",
        virtual_service_provider=appmesh.VirtualServiceProvider.virtual_router(meshVR),
        virtual_service_name="{}.{}".format(self.fargate_service.cloud_map_service.service_name,self.fargate_service.cloud_map_service.namespace.namespace_name)
    )
    
    # Adding Virtual Gateway Route
    self.mesh_gt_router = self.mesh_vgw.add_gateway_route(
        "MeshVGWRouter",
        gateway_route_name="frontend-router",
        route_spec=appmesh.GatewayRouteSpec.http(
            route_target=mesh_frontend_vs
        )
    )
    
    # Enable Service Autoscaling
    self.autoscale = self.fargate_service.auto_scale_task_count(
        min_capacity=3,
        max_capacity=10
    )
    
    self.autoscale.scale_on_cpu_utilization(
        "CPUAutoscaling",
        target_utilization_percent=50,
        scale_in_cooldown=Duration.seconds(30),
        scale_out_cooldown=Duration.seconds(30)
    )
     
    CfnOutput(self, "MeshFrontendVNARN",value=self.mesh_frontend_vn.virtual_node_arn,export_name="MeshFrontendVNARN")
    CfnOutput(self, "MeshFrontendVNName",value=self.mesh_frontend_vn.virtual_node_name,export_name="MeshFrontendVNName")
    CfnOutput(self, "MeshFrontendVGRARN",value=self.mesh_gt_router.gateway_route_arn,export_name="MeshFrontendVGRARN")

_env = Environment(account=os.getenv('AWS_ACCOUNT_ID'), region=os.getenv('AWS_DEFAULT_REGION'))
environment = "ecsworkshop"
stack_name = "{}-frontend".format(environment)
app = App()

FrontendService(app, stack_name, env=_env)

App Mesh workshop

FrontendServiceMesh(app, stack_name, env=_env)
app.synth()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant