From 64ff154ba044c6fd5fa72e774fb1591744852742 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Tue, 20 Dec 2022 09:33:24 -0700 Subject: [PATCH 1/8] WIP: Add reverse proxy example This is a version of `howto-alb` that assumes one of the backends runs HTTPS. HTTPS is not a supported listener protocol (see **Listener configuration** in the [Virtual nodes][1] section of the user guide), but we can work around this by using a reverse proxy to hide it behind an HTTP endpoint exposed via an additional container within the same task. TODO: * [ ] Drop the unneeded v1 backend, load balancer, etc. * [ ] Drop the v1 backend from the readme * [ ] Document how the reverse proxy works in the readme [1]: https://docs.aws.amazon.com/app-mesh/latest/userguide/virtual_nodes.html --- walkthroughs/howto-proxy/README.md | 35 + walkthroughs/howto-proxy/app.yaml | 742 ++++++++++++++++++ walkthroughs/howto-proxy/colorapp/Dockerfile | 17 + walkthroughs/howto-proxy/colorapp/app.py | 29 + walkthroughs/howto-proxy/colorapp/config.py | 12 + .../howto-proxy/colorapp/requirements.txt | 3 + .../howto-proxy/colorappssl/Dockerfile | 23 + walkthroughs/howto-proxy/colorappssl/app.py | 29 + .../howto-proxy/colorappssl/config.py | 12 + .../howto-proxy/colorappssl/requirements.txt | 3 + .../howto-proxy/colornginx/Dockerfile | 5 + .../howto-proxy/colornginx/default.conf | 12 + .../howto-proxy/colorproxy/Dockerfile | 5 + .../howto-proxy/colorproxy/default.conf | 13 + walkthroughs/howto-proxy/deploy.sh | 131 ++++ walkthroughs/howto-proxy/feapp/Dockerfile | 17 + walkthroughs/howto-proxy/feapp/app.py | 28 + walkthroughs/howto-proxy/feapp/config.py | 12 + .../howto-proxy/feapp/requirements.txt | 4 + walkthroughs/howto-proxy/howto-alb.png | Bin 0 -> 29618 bytes walkthroughs/howto-proxy/infra.yaml | 289 +++++++ 21 files changed, 1421 insertions(+) create mode 100644 walkthroughs/howto-proxy/README.md create mode 100644 walkthroughs/howto-proxy/app.yaml create mode 100644 walkthroughs/howto-proxy/colorapp/Dockerfile create mode 100755 walkthroughs/howto-proxy/colorapp/app.py create mode 100644 walkthroughs/howto-proxy/colorapp/config.py create mode 100644 walkthroughs/howto-proxy/colorapp/requirements.txt create mode 100644 walkthroughs/howto-proxy/colorappssl/Dockerfile create mode 100755 walkthroughs/howto-proxy/colorappssl/app.py create mode 100644 walkthroughs/howto-proxy/colorappssl/config.py create mode 100644 walkthroughs/howto-proxy/colorappssl/requirements.txt create mode 100644 walkthroughs/howto-proxy/colornginx/Dockerfile create mode 100644 walkthroughs/howto-proxy/colornginx/default.conf create mode 100644 walkthroughs/howto-proxy/colorproxy/Dockerfile create mode 100644 walkthroughs/howto-proxy/colorproxy/default.conf create mode 100755 walkthroughs/howto-proxy/deploy.sh create mode 100644 walkthroughs/howto-proxy/feapp/Dockerfile create mode 100755 walkthroughs/howto-proxy/feapp/app.py create mode 100644 walkthroughs/howto-proxy/feapp/config.py create mode 100644 walkthroughs/howto-proxy/feapp/requirements.txt create mode 100644 walkthroughs/howto-proxy/howto-alb.png create mode 100644 walkthroughs/howto-proxy/infra.yaml diff --git a/walkthroughs/howto-proxy/README.md b/walkthroughs/howto-proxy/README.md new file mode 100644 index 00000000..907310c6 --- /dev/null +++ b/walkthroughs/howto-proxy/README.md @@ -0,0 +1,35 @@ +## Overview +This example shows how services that are behind ALB can be accessed by clients using Envoy with the help of App Mesh. + +![System Diagram](./howto-alb.png "System Diagram") + +### Backend +There are two versions of Backend, V1 that is registered behind internal ALB and V2 that is registered under a CloudMap service (backend-v2.howto-alb.pvt.local). Additionally, there is a Route53 hosted zone (howto-alb.hosted.local) with a alias target to V1 ALB's DNS name (backend.howto-alb.hosted.local). + +V1 is registered as a virtual-node with service-discovery set to DNS (ALB's DNS). V2 on the otherhand is registered as a virtual-node with CloudMap service-discovery. Backend is registered as a virtual-service (name: backend.howto-alb.hosted.local) with router that routes to V1 and V2. + +### Frontend +Frontend app is ECS service that runs in private subnet behind internet-facing ALB. Frontend is registered with App Mesh as virtual-node with backends set to Backend's virtual-service. Frontend is deployed with Envoy sidecar that communicates with Backend. + +## Prerequisites +1. Install Docker. It is needed to build the demo application images. + +## Setup + +1. Clone this repository and navigate to the walkthrough/howto-alb folder, all commands will be ran from this location +2. **Your** account id: + ``` + export AWS_ACCOUNT_ID= + ``` +3. **Region** e.g. us-west-2 + ``` + export AWS_DEFAULT_REGION=us-west-2 + ``` +4. **ENVOY_IMAGE** environment variable is not set to App Mesh Envoy, see https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html + ``` + export ENVOY_IMAGE=... + ``` +5. Setup using cloudformation + ``` + ./deploy.sh + ``` diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml new file mode 100644 index 00000000..ee0bf66a --- /dev/null +++ b/walkthroughs/howto-proxy/app.yaml @@ -0,0 +1,742 @@ +Parameters: + ProjectName: + Type: String + Description: Project name to link stacks + + AppMeshXdsEndpoint: + Type: String + Description: App Mesh XDS Endpoint Override + Default: "" + + EnvoyImage: + Type: String + Description: Envoy container image + + FrontAppImage: + Type: String + Description: Front app container image + + ColorAppImage: + Type: String + Description: Color app container image + + ContainerPort: + Type: Number + Description: Port number to use for applications + Default: 8080 + + ColorProxyImage: + Type: String + Description: Color proxy container image + + ColorAppSslImage: + Type: String + Description: Color app ssl container image + +Resources: + + TaskSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "Security group for the tasks" + VpcId: + Fn::ImportValue: !Sub '${ProjectName}:VPC' + SecurityGroupIngress: + - CidrIp: + Fn::ImportValue: !Sub '${ProjectName}:VpcCIDR' + IpProtocol: -1 + + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub '${ProjectName}-log-group' + RetentionInDays: 30 + + TaskIamRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: | + { + "Statement": [{ + "Effect": "Allow", + "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ]}, + "Action": [ "sts:AssumeRole" ] + }] + } + ManagedPolicyArns: + - arn:aws:iam::aws:policy/CloudWatchFullAccess + - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess + - arn:aws:iam::aws:policy/AWSAppMeshEnvoyAccess + + TaskExecutionIamRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: | + { + "Statement": [{ + "Effect": "Allow", + "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ]}, + "Action": [ "sts:AssumeRole" ] + }] + } + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + + BackendRecordSet: + Type: AWS::Route53::RecordSet + DependsOn: + - BackendV1LoadBalancer + Properties: + AliasTarget: + HostedZoneId: !GetAtt 'BackendV1LoadBalancer.CanonicalHostedZoneID' + DNSName: !GetAtt 'BackendV1LoadBalancer.DNSName' + HostedZoneId: + Fn::ImportValue: + !Sub '${ProjectName}:DnsHostedZoneId' + Name: + Fn::Join: + - '.' + - + - backend + - Fn::ImportValue: + !Sub '${ProjectName}:DnsHostedZoneName' + Type: A + + # Backend V1 behind ALB + BackendV1LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internal + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: '30' + Subnets: + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet2' + SecurityGroups: + - !Ref TaskSecurityGroup + + BackendV1TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 60 + HealthCheckPath: '/ping' + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + TargetType: ip + Name: !Sub '${ProjectName}-backendtarget' + Port: !Ref ContainerPort + Protocol: HTTP + UnhealthyThresholdCount: 2 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 120 + VpcId: + Fn::ImportValue: + !Sub "${ProjectName}:VPC" + + BackendV1LoadBalancerListener: + DependsOn: + - BackendV1LoadBalancer + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref BackendV1TargetGroup + Type: 'forward' + LoadBalancerArn: !Ref BackendV1LoadBalancer + Port: !Ref ContainerPort + Protocol: HTTP + + BackendV1LoadBalancerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref BackendV1TargetGroup + Type: 'forward' + Conditions: + - Field: path-pattern + Values: + - '*' + ListenerArn: !Ref BackendV1LoadBalancerListener + Priority: 1 + + BackendV1TaskDef: + Type: AWS::ECS::TaskDefinition + Properties: + RequiresCompatibilities: + - 'FARGATE' + Family: 'blue' + NetworkMode: 'awsvpc' + Cpu: 256 + Memory: 512 + TaskRoleArn: !Ref TaskIamRole + ExecutionRoleArn: !Ref TaskExecutionIamRole + ContainerDefinitions: + - Name: 'app' + Image: !Ref ColorAppImage + Essential: true + DependsOn: + - ContainerName: 'xray' + Condition: 'START' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${ProjectName}-log-group + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'backend-v1' + PortMappings: + - ContainerPort: !Ref ContainerPort + HostPort: !Ref ContainerPort + Protocol: 'tcp' + Environment: + - Name: COLOR + Value: 'blue' + - Name: PORT + Value: !Sub '${ContainerPort}' + - Name: XRAY_APP_NAME + Value: + Fn::Join: + - '' + - + - Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + - '/' + - !GetAtt 'BackendV1VirtualNode.VirtualNodeName' + - Name: 'xray' + Image: "public.ecr.aws/xray/aws-xray-daemon" + Essential: true + User: '1337' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'front' + PortMappings: + - ContainerPort: 2000 + Protocol: 'udp' + + BackendV1Service: + Type: AWS::ECS::Service + DependsOn: + - BackendV1LoadBalancerListener + Properties: + Cluster: + Fn::ImportValue: + !Sub "${ProjectName}:ECSCluster" + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DesiredCount: 1 + LaunchType: 'FARGATE' + LoadBalancers: + - ContainerName: app + ContainerPort: !Ref ContainerPort + TargetGroupArn: !Ref BackendV1TargetGroup + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: DISABLED + SecurityGroups: + - !Ref TaskSecurityGroup + Subnets: + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet2' + TaskDefinition: !Ref BackendV1TaskDef + + # Backend V2 with CloudMap service registry + BackendV2ServiceRegistry: + Type: AWS::ServiceDiscovery::Service + Properties: + Name: 'backend-v2' + DnsConfig: + NamespaceId: + Fn::ImportValue: + !Sub '${ProjectName}:DnsNamespaceId' + DnsRecords: + - Type: A + TTL: 300 + HealthCheckCustomConfig: + FailureThreshold: 1 + + BackendV2TaskDef: + Type: AWS::ECS::TaskDefinition + Properties: + RequiresCompatibilities: + - 'FARGATE' + Family: 'green' + NetworkMode: 'awsvpc' + Cpu: 256 + Memory: 512 + TaskRoleArn: !Ref TaskIamRole + ExecutionRoleArn: !Ref TaskExecutionIamRole + ContainerDefinitions: + - Name: 'app' + Image: !Ref ColorProxyImage + Essential: true + DependsOn: + - ContainerName: 'envoy' + Condition: 'HEALTHY' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${ProjectName}-log-group + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'backend-v2' + PortMappings: + - ContainerPort: !Ref ContainerPort + HostPort: !Ref ContainerPort + Protocol: 'tcp' + Environment: + - Name: COLOR + Value: 'green' + - Name: PORT + Value: !Sub '${ContainerPort}' + - Name: 'colorappssl' + Image: !Ref ColorAppSslImage + Essential: false + DependsOn: + - ContainerName: 'envoy' + Condition: 'HEALTHY' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${ProjectName}-log-group + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'backend-v2' + PortMappings: + - ContainerPort: 8443 + HostPort: 8443 + Protocol: 'tcp' + Environment: + - Name: COLOR + Value: 'green' + - Name: PORT + Value: 8443 + - Name: envoy + Image: !Ref EnvoyImage + Essential: true + User: '1337' + Ulimits: + - Name: "nofile" + HardLimit: 15000 + SoftLimit: 15000 + PortMappings: + - ContainerPort: 9901 + Protocol: 'tcp' + - ContainerPort: 15000 + Protocol: 'tcp' + - ContainerPort: 15001 + Protocol: 'tcp' + HealthCheck: + Command: + - 'CMD-SHELL' + - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' + Interval: 5 + Timeout: 10 + Retries: 10 + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'backend-v2' + Environment: + - Name: 'ENVOY_LOG_LEVEL' + Value: 'debug' + - Name: 'ENABLE_ENVOY_STATS_TAGS' + Value: '1' + - Name: 'APPMESH_VIRTUAL_NODE_NAME' + Value: + Fn::Join: + - '' + - + - 'mesh/' + - Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + - '/virtualNode/' + - !GetAtt 'BackendV2VirtualNode.VirtualNodeName' + + BackendV2Service: + Type: AWS::ECS::Service + DependsOn: + - BackendV2ServiceRegistry + Properties: + Cluster: + Fn::ImportValue: + !Sub "${ProjectName}:ECSCluster" + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DesiredCount: 1 + LaunchType: 'FARGATE' + ServiceRegistries: + - RegistryArn: !GetAtt 'BackendV2ServiceRegistry.Arn' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: DISABLED + SecurityGroups: + - !Ref TaskSecurityGroup + Subnets: + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet2' + TaskDefinition: !Ref BackendV2TaskDef + + # Backend exposed in mesh as virtual-service + BackendV1VirtualNode: + Type: AWS::AppMesh::VirtualNode + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualNodeName: !Sub '${ProjectName}-backend-v1-node' + Spec: + Listeners: + - PortMapping: + Port: !Ref ContainerPort + Protocol: http + ServiceDiscovery: + DNS: + Hostname: !GetAtt BackendV1LoadBalancer.DNSName + + BackendV2VirtualNode: + Type: AWS::AppMesh::VirtualNode + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualNodeName: !Sub '${ProjectName}-backend-v2-node' + Spec: + Listeners: + - PortMapping: + Port: !Ref ContainerPort + Protocol: http + ServiceDiscovery: + AWSCloudMap: + NamespaceName: + Fn::ImportValue: + !Sub '${ProjectName}:DnsNamespaceName' + ServiceName: !GetAtt BackendV2ServiceRegistry.Name + Attributes: + - Key: 'ECS_TASK_DEFINITION_FAMILY' + Value: 'green' + + BackendVirtualService: + Type: AWS::AppMesh::VirtualService + DependsOn: + - BackendVirtualRouter + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualServiceName: !Ref BackendRecordSet + Spec: + Provider: + VirtualRouter: + VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName + + BackendVirtualRouter: + Type: AWS::AppMesh::VirtualRouter + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualRouterName: !Sub '${ProjectName}-backend-router' + Spec: + Listeners: + - PortMapping: + Port: !Ref ContainerPort + Protocol: http + + BackendRoute: + Type: AWS::AppMesh::Route + DependsOn: + - BackendVirtualRouter + - BackendV1VirtualNode + - BackendV2VirtualNode + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName + RouteName: !Sub '${ProjectName}-backend-route' + Spec: + HttpRoute: + Match: + Prefix: '/' + Action: + WeightedTargets: + - VirtualNode: !GetAtt BackendV1VirtualNode.VirtualNodeName + Weight: 0 + - VirtualNode: !GetAtt BackendV2VirtualNode.VirtualNodeName + Weight: 100 + + # Frontend + SecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Ingress from the public ALB + GroupId: !Ref TaskSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref PublicLoadBalancerSecurityGroup + + PublicLoadBalancerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: 'Access to the public facing load balancer' + VpcId: + Fn::ImportValue: + !Sub "${ProjectName}:VPC" + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + IpProtocol: tcp + FromPort: 80 + ToPort: 80 + + PublicLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internet-facing + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: '30' + Subnets: + - Fn::ImportValue: + !Sub '${ProjectName}:PublicSubnet1' + - Fn::ImportValue: + !Sub '${ProjectName}:PublicSubnet2' + SecurityGroups: + - !Ref PublicLoadBalancerSecurityGroup + + WebTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 60 + HealthCheckPath: '/ping' + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + TargetType: ip + Name: !Sub '${ProjectName}-webtarget' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: 2 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 120 + VpcId: + Fn::ImportValue: + !Sub "${ProjectName}:VPC" + + PublicLoadBalancerListener: + DependsOn: + - PublicLoadBalancer + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref WebTargetGroup + Type: 'forward' + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 80 + Protocol: HTTP + + WebLoadBalancerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref WebTargetGroup + Type: 'forward' + Conditions: + - Field: path-pattern + Values: + - '*' + ListenerArn: !Ref PublicLoadBalancerListener + Priority: 1 + + FrontVirtualNode: + Type: AWS::AppMesh::VirtualNode + DependsOn: + - BackendVirtualService + Properties: + MeshName: + Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + VirtualNodeName: !Sub "${ProjectName}-front-node" + Spec: + Listeners: + - PortMapping: + Port: !Sub '${ContainerPort}' + Protocol: http + ServiceDiscovery: + DNS: + Hostname: !GetAtt PublicLoadBalancer.DNSName + Backends: + - VirtualService: + VirtualServiceName: !GetAtt 'BackendVirtualService.VirtualServiceName' + + FrontTaskDef: + Type: AWS::ECS::TaskDefinition + DependsOn: + - BackendVirtualService + - FrontVirtualNode + Properties: + RequiresCompatibilities: + - 'FARGATE' + Family: 'front' + NetworkMode: 'awsvpc' + Cpu: 256 + Memory: 512 + TaskRoleArn: !Ref TaskIamRole + ExecutionRoleArn: !Ref TaskExecutionIamRole + ProxyConfiguration: + Type: 'APPMESH' + ContainerName: 'envoy' + ProxyConfigurationProperties: + - Name: 'IgnoredUID' + Value: '1337' + - Name: 'ProxyIngressPort' + Value: '15000' + - Name: 'ProxyEgressPort' + Value: '15001' + - Name: 'AppPorts' + Value: !Sub '${ContainerPort}' + - Name: 'EgressIgnoredIPs' + Value: '169.254.170.2,169.254.169.254' + ContainerDefinitions: + - Name: 'app' + Image: !Ref FrontAppImage + Essential: true + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'front' + PortMappings: + - ContainerPort: !Ref ContainerPort + Protocol: 'tcp' + DependsOn: + - ContainerName: 'envoy' + Condition: 'HEALTHY' + - ContainerName: 'xray' + Condition: 'START' + Environment: + - Name: 'COLOR_HOST' + Value: !Join ['', [!GetAtt 'BackendVirtualService.VirtualServiceName', ':', !Sub '${ContainerPort}']] + - Name: PORT + Value: !Sub '${ContainerPort}' + - Name: XRAY_APP_NAME + Value: + Fn::Join: + - '' + - + - Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + - '/' + - !GetAtt 'FrontVirtualNode.VirtualNodeName' + - Name: 'xray' + Image: "public.ecr.aws/xray/aws-xray-daemon" + Essential: true + User: '1337' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'front' + PortMappings: + - ContainerPort: 2000 + Protocol: 'udp' + - Name: envoy + Image: !Ref EnvoyImage + Essential: true + User: '1337' + DependsOn: + - ContainerName: 'xray' + Condition: 'START' + Ulimits: + - Name: "nofile" + HardLimit: 15000 + SoftLimit: 15000 + PortMappings: + - ContainerPort: 9901 + Protocol: 'tcp' + - ContainerPort: 15000 + Protocol: 'tcp' + - ContainerPort: 15001 + Protocol: 'tcp' + HealthCheck: + Command: + - 'CMD-SHELL' + - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' + Interval: 5 + Timeout: 10 + Retries: 10 + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'front' + Environment: + - Name: 'ENVOY_LOG_LEVEL' + Value: 'debug' + - Name: 'ENABLE_ENVOY_XRAY_TRACING' + Value: '1' + - Name: 'ENABLE_ENVOY_STATS_TAGS' + Value: '1' + - Name: 'APPMESH_VIRTUAL_NODE_NAME' + Value: + Fn::Join: + - '' + - + - 'mesh/' + - Fn::ImportValue: + !Sub '${ProjectName}:Mesh' + - '/virtualNode/' + - !GetAtt 'FrontVirtualNode.VirtualNodeName' + + FrontService: + Type: AWS::ECS::Service + DependsOn: + - WebLoadBalancerRule + Properties: + Cluster: + Fn::ImportValue: + !Sub "${ProjectName}:ECSCluster" + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DesiredCount: 1 + LaunchType: 'FARGATE' + TaskDefinition: !Ref FrontTaskDef + LoadBalancers: + - ContainerName: app + ContainerPort: !Ref ContainerPort + TargetGroupArn: !Ref WebTargetGroup + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: DISABLED + SecurityGroups: + - !Ref TaskSecurityGroup + Subnets: + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: + !Sub '${ProjectName}:PrivateSubnet2' + +Outputs: + FrontEndpoint: + Description: 'Public endpoint for Front service' + Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']] + Export: + Name: !Sub "${ProjectName}:FrontEndpoint" diff --git a/walkthroughs/howto-proxy/colorapp/Dockerfile b/walkthroughs/howto-proxy/colorapp/Dockerfile new file mode 100644 index 00000000..e25684b5 --- /dev/null +++ b/walkthroughs/howto-proxy/colorapp/Dockerfile @@ -0,0 +1,17 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2 + +COPY requirements.txt ./ + +RUN yum update -y && \ + yum install -y python3 && \ + pip3 install --no-cache-dir -r requirements.txt && \ + yum clean all && \ + rm -rf /var/cache/yum + +WORKDIR /usr/src/app + +COPY . . + +ENV PORT 8080 + +CMD ["gunicorn", "app:app", "--config=config.py"] \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/app.py b/walkthroughs/howto-proxy/colorapp/app.py new file mode 100755 index 00000000..e8ae3807 --- /dev/null +++ b/walkthroughs/howto-proxy/colorapp/app.py @@ -0,0 +1,29 @@ +import os +import config +from flask import Flask, request +from aws_xray_sdk.core import patch_all, xray_recorder +from aws_xray_sdk.ext.flask.middleware import XRayMiddleware + +app = Flask(__name__) + +xray_recorder.configure( + context_missing='LOG_ERROR', + service=config.XRAY_APP_NAME, +) +patch_all() + +XRayMiddleware(app, xray_recorder) + +@app.route('/ping') +def ping(): + return 'Pong' + +@app.route('/') +def color(): + print('----------------') + print(request.headers) + print('----------------') + return config.COLOR + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=config.PORT, debug=config.DEBUG_MODE) \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/config.py b/walkthroughs/howto-proxy/colorapp/config.py new file mode 100644 index 00000000..d8606440 --- /dev/null +++ b/walkthroughs/howto-proxy/colorapp/config.py @@ -0,0 +1,12 @@ +from os import environ as env +import multiprocessing + +PORT = int(env.get("PORT", 8080)) +DEBUG_MODE = int(env.get("DEBUG_MODE", 0)) +XRAY_APP_NAME = env.get('XRAY_APP_NAME', 'feapp') +COLOR = env.get('COLOR', 'n/a') + +# Gunicorn config +bind = ":" + str(PORT) +workers = multiprocessing.cpu_count() * 2 + 1 +threads = 2 * multiprocessing.cpu_count() \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/requirements.txt b/walkthroughs/howto-proxy/colorapp/requirements.txt new file mode 100644 index 00000000..47f05a04 --- /dev/null +++ b/walkthroughs/howto-proxy/colorapp/requirements.txt @@ -0,0 +1,3 @@ +flask +aws-xray-sdk +gunicorn==19.9.0 diff --git a/walkthroughs/howto-proxy/colorappssl/Dockerfile b/walkthroughs/howto-proxy/colorappssl/Dockerfile new file mode 100644 index 00000000..ff8c4792 --- /dev/null +++ b/walkthroughs/howto-proxy/colorappssl/Dockerfile @@ -0,0 +1,23 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2 + +COPY requirements.txt ./ + +RUN yum update -y && \ + yum install -y python3 && \ + pip3 install --no-cache-dir -r requirements.txt && \ + yum clean all && \ + rm -rf /var/cache/yum + +WORKDIR /usr/src/app + +RUN yum install -y openssl && \ + openssl req -x509 -nodes -days 365 \ + -subj "/C=CA/ST=QC/O=Company Inc/CN=example.com" \ + -newkey rsa:2048 -keyout key.pem \ + -out cert.pem; + +COPY . . + +ENV PORT 8443 + +CMD ["gunicorn", "--certfile", "cert.pem", "--keyfile", "key.pem", "app:app", "--config=config.py"] diff --git a/walkthroughs/howto-proxy/colorappssl/app.py b/walkthroughs/howto-proxy/colorappssl/app.py new file mode 100755 index 00000000..b28497dc --- /dev/null +++ b/walkthroughs/howto-proxy/colorappssl/app.py @@ -0,0 +1,29 @@ +import os +import config +from flask import Flask, request +from aws_xray_sdk.core import patch_all, xray_recorder +from aws_xray_sdk.ext.flask.middleware import XRayMiddleware + +app = Flask(__name__) + +xray_recorder.configure( + context_missing='LOG_ERROR', + service=config.XRAY_APP_NAME, +) +patch_all() + +XRayMiddleware(app, xray_recorder) + +@app.route('/ping') +def ping(): + return 'Pong' + +@app.route('/') +def color(): + print('----------------') + print(request.headers) + print('----------------') + return config.COLOR + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=config.PORT, debug=config.DEBUG_MODE) diff --git a/walkthroughs/howto-proxy/colorappssl/config.py b/walkthroughs/howto-proxy/colorappssl/config.py new file mode 100644 index 00000000..68c7a81d --- /dev/null +++ b/walkthroughs/howto-proxy/colorappssl/config.py @@ -0,0 +1,12 @@ +from os import environ as env +import multiprocessing + +PORT = int(env.get("PORT", 8443)) +DEBUG_MODE = int(env.get("DEBUG_MODE", 0)) +XRAY_APP_NAME = env.get('XRAY_APP_NAME', 'feapp') +COLOR = env.get('COLOR', 'n/a') + +# Gunicorn config +bind = ":" + str(PORT) +workers = multiprocessing.cpu_count() * 2 + 1 +threads = 2 * multiprocessing.cpu_count() diff --git a/walkthroughs/howto-proxy/colorappssl/requirements.txt b/walkthroughs/howto-proxy/colorappssl/requirements.txt new file mode 100644 index 00000000..47f05a04 --- /dev/null +++ b/walkthroughs/howto-proxy/colorappssl/requirements.txt @@ -0,0 +1,3 @@ +flask +aws-xray-sdk +gunicorn==19.9.0 diff --git a/walkthroughs/howto-proxy/colornginx/Dockerfile b/walkthroughs/howto-proxy/colornginx/Dockerfile new file mode 100644 index 00000000..6f52e280 --- /dev/null +++ b/walkthroughs/howto-proxy/colornginx/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:latest + +EXPOSE 8081 + +COPY default.conf /etc/nginx/conf.d/ diff --git a/walkthroughs/howto-proxy/colornginx/default.conf b/walkthroughs/howto-proxy/colornginx/default.conf new file mode 100644 index 00000000..a58e9028 --- /dev/null +++ b/walkthroughs/howto-proxy/colornginx/default.conf @@ -0,0 +1,12 @@ +server { + listen 8081; + server_name colornginx; + + location /ping { + return 200 'Pong'; + } + + location / { + return 200 'colornginx'; + } +} diff --git a/walkthroughs/howto-proxy/colorproxy/Dockerfile b/walkthroughs/howto-proxy/colorproxy/Dockerfile new file mode 100644 index 00000000..70144c5f --- /dev/null +++ b/walkthroughs/howto-proxy/colorproxy/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:latest + +EXPOSE 8080 + +COPY default.conf /etc/nginx/conf.d/ diff --git a/walkthroughs/howto-proxy/colorproxy/default.conf b/walkthroughs/howto-proxy/colorproxy/default.conf new file mode 100644 index 00000000..e533f912 --- /dev/null +++ b/walkthroughs/howto-proxy/colorproxy/default.conf @@ -0,0 +1,13 @@ +server { + listen 8080; + server_name colorproxy; + + location /ping { + return 200 'Pong'; + } + + location / { + # proxy_pass http://localhost:8081; + proxy_pass https://localhost:8443; + } +} diff --git a/walkthroughs/howto-proxy/deploy.sh b/walkthroughs/howto-proxy/deploy.sh new file mode 100755 index 00000000..65c7d926 --- /dev/null +++ b/walkthroughs/howto-proxy/deploy.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +set -e + +if [ -z $AWS_ACCOUNT_ID ]; then + echo "AWS_ACCOUNT_ID environment variable is not set." + exit 1 +fi + +if [ -z $AWS_DEFAULT_REGION ]; then + echo "AWS_DEFAULT_REGION environment variable is not set." + exit 1 +fi + +if [ -z $ENVOY_IMAGE ]; then + echo "ENVOY_IMAGE environment variable is not set to App Mesh Envoy, see https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html" + exit 1 +fi + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +PROJECT_NAME="howto-proxy" +ECR_URL="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com" +ECR_IMAGE_PREFIX="${ECR_URL}/${PROJECT_NAME}" +AWS_CLI_VERSION=$(aws --version 2>&1 | cut -d/ -f2 | cut -d. -f1) + +ecr_login() { + if [ $AWS_CLI_VERSION -gt 1 ]; then + aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | \ + docker login --username AWS --password-stdin ${ECR_URL} + else + $(aws ecr get-login --no-include-email) + fi +} + +deploy_images() { + ecr_login + for app in colorproxy colorapp colorappssl feapp; do + aws ecr describe-repositories --repository-name $PROJECT_NAME/$app >/dev/null 2>&1 || aws ecr create-repository --repository-name $PROJECT_NAME/$app >/dev/null + docker build -t ${ECR_IMAGE_PREFIX}/${app} ${DIR}/${app} + docker push ${ECR_IMAGE_PREFIX}/${app} + done +} + +deploy_infra() { + stack_name="${PROJECT_NAME}-infra" + aws cloudformation deploy \ + --no-fail-on-empty-changeset \ + --stack-name $stack_name\ + --template-file "${DIR}/infra.yaml" \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides "ProjectName=${PROJECT_NAME}" +} + +deploy_app() { + aws cloudformation deploy \ + --no-fail-on-empty-changeset \ + --stack-name "${PROJECT_NAME}-app" \ + --template-file "${DIR}/app.yaml" \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides "ProjectName=${PROJECT_NAME}" "EnvoyImage=${ENVOY_IMAGE}" "ColorAppSslImage=${ECR_IMAGE_PREFIX}/colorappssl" "ColorAppImage=${ECR_IMAGE_PREFIX}/colorapp" "ColorProxyImage=${ECR_IMAGE_PREFIX}/colorproxy" "FrontAppImage=${ECR_IMAGE_PREFIX}/feapp" +} + +delete_cfn_stack() { + stack_name=$1 + aws cloudformation delete-stack --stack-name $stack_name + echo 'Waiting for the stack to be deleted, this may take a few minutes...' + aws cloudformation wait stack-delete-complete --stack-name $stack_name + echo 'Done' +} + +confirm_service_linked_role() { + aws iam get-role --role-name AWSServiceRoleForAppMesh >/dev/null + [[ $? -eq 0 ]] || + (echo "Error: no service linked role for App Mesh" && exit 1) +} + +print_endpoint() { + echo "Public endpoint:" + prefix=$(aws cloudformation describe-stacks \ + --stack-name="${PROJECT_NAME}-app" \ + --query="Stacks[0].Outputs[?OutputKey=='FrontEndpoint'].OutputValue" \ + --output=text) + echo "${prefix}/color" +} + +deploy_resources() { + + if [ -z $SKIP_IMAGES ]; then + echo "deploy images..." + deploy_images + fi + + echo "deploy infra..." + deploy_infra + + echo "deploy app..." + deploy_app + + confirm_service_linked_role + print_endpoint +} + +delete_images() { + for app in colorproxy colorapp colorappssl feapp; do + echo "deleting repository..." + aws ecr delete-repository \ + --repository-name $PROJECT_NAME/$app \ + --force >/dev/null + done +} + +delete_resources() { + echo "delete app..." + delete_cfn_stack "${PROJECT_NAME}-app" + + echo "delete infra..." + delete_cfn_stack "${PROJECT_NAME}-infra" + + echo "delete images..." + delete_images + + echo "all resources from this tutorial have been removed" +} + +action=${1:-"deploy"} +if [ "$action" == "delete" ]; then + delete_resources + exit 0 +fi + +deploy_resources diff --git a/walkthroughs/howto-proxy/feapp/Dockerfile b/walkthroughs/howto-proxy/feapp/Dockerfile new file mode 100644 index 00000000..e25684b5 --- /dev/null +++ b/walkthroughs/howto-proxy/feapp/Dockerfile @@ -0,0 +1,17 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2 + +COPY requirements.txt ./ + +RUN yum update -y && \ + yum install -y python3 && \ + pip3 install --no-cache-dir -r requirements.txt && \ + yum clean all && \ + rm -rf /var/cache/yum + +WORKDIR /usr/src/app + +COPY . . + +ENV PORT 8080 + +CMD ["gunicorn", "app:app", "--config=config.py"] \ No newline at end of file diff --git a/walkthroughs/howto-proxy/feapp/app.py b/walkthroughs/howto-proxy/feapp/app.py new file mode 100755 index 00000000..b0014a3f --- /dev/null +++ b/walkthroughs/howto-proxy/feapp/app.py @@ -0,0 +1,28 @@ +import os +import requests +import config +from flask import Flask, request +from aws_xray_sdk.core import xray_recorder, patch_all +from aws_xray_sdk.ext.flask.middleware import XRayMiddleware + +app = Flask(__name__) + +xray_recorder.configure( + context_missing='LOG_ERROR', + service=config.XRAY_APP_NAME, +) +patch_all() +XRayMiddleware(app, xray_recorder) + +@app.route('/ping') +def ping(): + return 'Pong' + +@app.route('/color') +def color(): + print(request.headers) + response = requests.get(f'http://{config.COLOR_HOST}') + return response.text + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=config.PORT, debug=config.DEBUG_MODE) \ No newline at end of file diff --git a/walkthroughs/howto-proxy/feapp/config.py b/walkthroughs/howto-proxy/feapp/config.py new file mode 100644 index 00000000..a8018e01 --- /dev/null +++ b/walkthroughs/howto-proxy/feapp/config.py @@ -0,0 +1,12 @@ +from os import environ as env +import multiprocessing + +PORT = int(env.get("PORT", 8080)) +DEBUG_MODE = int(env.get("DEBUG_MODE", 0)) +XRAY_APP_NAME = env.get('XRAY_APP_NAME', 'feapp') +COLOR_HOST = env.get('COLOR_HOST') + +# Gunicorn config +bind = ":" + str(PORT) +workers = multiprocessing.cpu_count() * 2 + 1 +threads = 2 * multiprocessing.cpu_count() \ No newline at end of file diff --git a/walkthroughs/howto-proxy/feapp/requirements.txt b/walkthroughs/howto-proxy/feapp/requirements.txt new file mode 100644 index 00000000..56d7f135 --- /dev/null +++ b/walkthroughs/howto-proxy/feapp/requirements.txt @@ -0,0 +1,4 @@ +flask +requests +aws-xray-sdk +gunicorn==19.9.0 diff --git a/walkthroughs/howto-proxy/howto-alb.png b/walkthroughs/howto-proxy/howto-alb.png new file mode 100644 index 0000000000000000000000000000000000000000..be28b9abcac3398e50893e74a56c8bf2c1fe40fe GIT binary patch literal 29618 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 z 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 Date: Tue, 20 Dec 2022 18:52:45 +0000 Subject: [PATCH 2/8] Drop backend v1 VN, VS, and task definition --- walkthroughs/howto-proxy/app.yaml | 105 ------------------------------ 1 file changed, 105 deletions(-) diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml index ee0bf66a..b329216f 100644 --- a/walkthroughs/howto-proxy/app.yaml +++ b/walkthroughs/howto-proxy/app.yaml @@ -166,91 +166,6 @@ Resources: ListenerArn: !Ref BackendV1LoadBalancerListener Priority: 1 - BackendV1TaskDef: - Type: AWS::ECS::TaskDefinition - Properties: - RequiresCompatibilities: - - 'FARGATE' - Family: 'blue' - NetworkMode: 'awsvpc' - Cpu: 256 - Memory: 512 - TaskRoleArn: !Ref TaskIamRole - ExecutionRoleArn: !Ref TaskExecutionIamRole - ContainerDefinitions: - - Name: 'app' - Image: !Ref ColorAppImage - Essential: true - DependsOn: - - ContainerName: 'xray' - Condition: 'START' - LogConfiguration: - LogDriver: 'awslogs' - Options: - awslogs-group: !Sub ${ProjectName}-log-group - awslogs-region: !Ref AWS::Region - awslogs-stream-prefix: 'backend-v1' - PortMappings: - - ContainerPort: !Ref ContainerPort - HostPort: !Ref ContainerPort - Protocol: 'tcp' - Environment: - - Name: COLOR - Value: 'blue' - - Name: PORT - Value: !Sub '${ContainerPort}' - - Name: XRAY_APP_NAME - Value: - Fn::Join: - - '' - - - - Fn::ImportValue: - !Sub '${ProjectName}:Mesh' - - '/' - - !GetAtt 'BackendV1VirtualNode.VirtualNodeName' - - Name: 'xray' - Image: "public.ecr.aws/xray/aws-xray-daemon" - Essential: true - User: '1337' - LogConfiguration: - LogDriver: 'awslogs' - Options: - awslogs-group: !Sub '${ProjectName}-log-group' - awslogs-region: !Ref AWS::Region - awslogs-stream-prefix: 'front' - PortMappings: - - ContainerPort: 2000 - Protocol: 'udp' - - BackendV1Service: - Type: AWS::ECS::Service - DependsOn: - - BackendV1LoadBalancerListener - Properties: - Cluster: - Fn::ImportValue: - !Sub "${ProjectName}:ECSCluster" - DeploymentConfiguration: - MaximumPercent: 200 - MinimumHealthyPercent: 100 - DesiredCount: 1 - LaunchType: 'FARGATE' - LoadBalancers: - - ContainerName: app - ContainerPort: !Ref ContainerPort - TargetGroupArn: !Ref BackendV1TargetGroup - NetworkConfiguration: - AwsvpcConfiguration: - AssignPublicIp: DISABLED - SecurityGroups: - - !Ref TaskSecurityGroup - Subnets: - - Fn::ImportValue: - !Sub '${ProjectName}:PrivateSubnet1' - - Fn::ImportValue: - !Sub '${ProjectName}:PrivateSubnet2' - TaskDefinition: !Ref BackendV1TaskDef - # Backend V2 with CloudMap service registry BackendV2ServiceRegistry: Type: AWS::ServiceDiscovery::Service @@ -391,23 +306,6 @@ Resources: !Sub '${ProjectName}:PrivateSubnet2' TaskDefinition: !Ref BackendV2TaskDef - # Backend exposed in mesh as virtual-service - BackendV1VirtualNode: - Type: AWS::AppMesh::VirtualNode - Properties: - MeshName: - Fn::ImportValue: - !Sub '${ProjectName}:Mesh' - VirtualNodeName: !Sub '${ProjectName}-backend-v1-node' - Spec: - Listeners: - - PortMapping: - Port: !Ref ContainerPort - Protocol: http - ServiceDiscovery: - DNS: - Hostname: !GetAtt BackendV1LoadBalancer.DNSName - BackendV2VirtualNode: Type: AWS::AppMesh::VirtualNode Properties: @@ -461,7 +359,6 @@ Resources: Type: AWS::AppMesh::Route DependsOn: - BackendVirtualRouter - - BackendV1VirtualNode - BackendV2VirtualNode Properties: MeshName: @@ -475,8 +372,6 @@ Resources: Prefix: '/' Action: WeightedTargets: - - VirtualNode: !GetAtt BackendV1VirtualNode.VirtualNodeName - Weight: 0 - VirtualNode: !GetAtt BackendV2VirtualNode.VirtualNodeName Weight: 100 From 51ac4291d8d056d686482ddd61934625f5a4ff50 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Tue, 20 Dec 2022 18:58:12 +0000 Subject: [PATCH 3/8] Change backend from VR to VN --- walkthroughs/howto-proxy/app.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml index b329216f..8c1bf5bf 100644 --- a/walkthroughs/howto-proxy/app.yaml +++ b/walkthroughs/howto-proxy/app.yaml @@ -339,8 +339,8 @@ Resources: VirtualServiceName: !Ref BackendRecordSet Spec: Provider: - VirtualRouter: - VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName + VirtualNode: + VirtualNodeName: !GetAtt BackendV2VirtualNode.VirtualNodeName BackendVirtualRouter: Type: AWS::AppMesh::VirtualRouter From b86a8fcb399d84d58b7792582581407d0a27bb48 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Tue, 20 Dec 2022 19:01:20 +0000 Subject: [PATCH 4/8] Drop backend VR and route --- walkthroughs/howto-proxy/app.yaml | 35 ------------------------------- 1 file changed, 35 deletions(-) diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml index 8c1bf5bf..1fd03ae2 100644 --- a/walkthroughs/howto-proxy/app.yaml +++ b/walkthroughs/howto-proxy/app.yaml @@ -330,8 +330,6 @@ Resources: BackendVirtualService: Type: AWS::AppMesh::VirtualService - DependsOn: - - BackendVirtualRouter Properties: MeshName: Fn::ImportValue: @@ -342,39 +340,6 @@ Resources: VirtualNode: VirtualNodeName: !GetAtt BackendV2VirtualNode.VirtualNodeName - BackendVirtualRouter: - Type: AWS::AppMesh::VirtualRouter - Properties: - MeshName: - Fn::ImportValue: - !Sub '${ProjectName}:Mesh' - VirtualRouterName: !Sub '${ProjectName}-backend-router' - Spec: - Listeners: - - PortMapping: - Port: !Ref ContainerPort - Protocol: http - - BackendRoute: - Type: AWS::AppMesh::Route - DependsOn: - - BackendVirtualRouter - - BackendV2VirtualNode - Properties: - MeshName: - Fn::ImportValue: - !Sub '${ProjectName}:Mesh' - VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName - RouteName: !Sub '${ProjectName}-backend-route' - Spec: - HttpRoute: - Match: - Prefix: '/' - Action: - WeightedTargets: - - VirtualNode: !GetAtt BackendV2VirtualNode.VirtualNodeName - Weight: 100 - # Frontend SecurityGroupIngressFromPublicALB: Type: AWS::EC2::SecurityGroupIngress From 25e622c38b7c91e154c978678313ebf81d58d7d9 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Tue, 20 Dec 2022 20:02:13 +0000 Subject: [PATCH 5/8] Drop LB TG, listener, and rule --- walkthroughs/howto-proxy/app.yaml | 45 ------------------------------- 1 file changed, 45 deletions(-) diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml index 1fd03ae2..bad058e1 100644 --- a/walkthroughs/howto-proxy/app.yaml +++ b/walkthroughs/howto-proxy/app.yaml @@ -121,51 +121,6 @@ Resources: SecurityGroups: - !Ref TaskSecurityGroup - BackendV1TargetGroup: - Type: AWS::ElasticLoadBalancingV2::TargetGroup - Properties: - HealthCheckIntervalSeconds: 60 - HealthCheckPath: '/ping' - HealthCheckProtocol: HTTP - HealthCheckTimeoutSeconds: 5 - HealthyThresholdCount: 2 - TargetType: ip - Name: !Sub '${ProjectName}-backendtarget' - Port: !Ref ContainerPort - Protocol: HTTP - UnhealthyThresholdCount: 2 - TargetGroupAttributes: - - Key: deregistration_delay.timeout_seconds - Value: 120 - VpcId: - Fn::ImportValue: - !Sub "${ProjectName}:VPC" - - BackendV1LoadBalancerListener: - DependsOn: - - BackendV1LoadBalancer - Type: AWS::ElasticLoadBalancingV2::Listener - Properties: - DefaultActions: - - TargetGroupArn: !Ref BackendV1TargetGroup - Type: 'forward' - LoadBalancerArn: !Ref BackendV1LoadBalancer - Port: !Ref ContainerPort - Protocol: HTTP - - BackendV1LoadBalancerRule: - Type: AWS::ElasticLoadBalancingV2::ListenerRule - Properties: - Actions: - - TargetGroupArn: !Ref BackendV1TargetGroup - Type: 'forward' - Conditions: - - Field: path-pattern - Values: - - '*' - ListenerArn: !Ref BackendV1LoadBalancerListener - Priority: 1 - # Backend V2 with CloudMap service registry BackendV2ServiceRegistry: Type: AWS::ServiceDiscovery::Service From 0e00b543d18cd59e824642d80f2033d3c5fe778c Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:17:45 +0000 Subject: [PATCH 6/8] Drop unused colornginx image --- walkthroughs/howto-proxy/colornginx/Dockerfile | 5 ----- walkthroughs/howto-proxy/colornginx/default.conf | 12 ------------ 2 files changed, 17 deletions(-) delete mode 100644 walkthroughs/howto-proxy/colornginx/Dockerfile delete mode 100644 walkthroughs/howto-proxy/colornginx/default.conf diff --git a/walkthroughs/howto-proxy/colornginx/Dockerfile b/walkthroughs/howto-proxy/colornginx/Dockerfile deleted file mode 100644 index 6f52e280..00000000 --- a/walkthroughs/howto-proxy/colornginx/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM nginx:latest - -EXPOSE 8081 - -COPY default.conf /etc/nginx/conf.d/ diff --git a/walkthroughs/howto-proxy/colornginx/default.conf b/walkthroughs/howto-proxy/colornginx/default.conf deleted file mode 100644 index a58e9028..00000000 --- a/walkthroughs/howto-proxy/colornginx/default.conf +++ /dev/null @@ -1,12 +0,0 @@ -server { - listen 8081; - server_name colornginx; - - location /ping { - return 200 'Pong'; - } - - location / { - return 200 'colornginx'; - } -} From d3bbc16b18cbeb21cef124600de93e12cd0a3fc9 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:18:23 +0000 Subject: [PATCH 7/8] Clean up unneeded config --- walkthroughs/howto-proxy/colorproxy/default.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/walkthroughs/howto-proxy/colorproxy/default.conf b/walkthroughs/howto-proxy/colorproxy/default.conf index e533f912..43fb151c 100644 --- a/walkthroughs/howto-proxy/colorproxy/default.conf +++ b/walkthroughs/howto-proxy/colorproxy/default.conf @@ -1,13 +1,7 @@ server { listen 8080; server_name colorproxy; - - location /ping { - return 200 'Pong'; - } - location / { - # proxy_pass http://localhost:8081; proxy_pass https://localhost:8443; } } From 41ed8afd1655ddc92f6e3d5c830d4d97e5e52bd0 Mon Sep 17 00:00:00 2001 From: James Douglas <102178210+awsjdgls@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:28:35 +0000 Subject: [PATCH 8/8] Drop unused colorapp image --- walkthroughs/howto-proxy/app.yaml | 4 --- walkthroughs/howto-proxy/colorapp/Dockerfile | 17 ----------- walkthroughs/howto-proxy/colorapp/app.py | 29 ------------------- walkthroughs/howto-proxy/colorapp/config.py | 12 -------- .../howto-proxy/colorapp/requirements.txt | 3 -- walkthroughs/howto-proxy/deploy.sh | 6 ++-- 6 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 walkthroughs/howto-proxy/colorapp/Dockerfile delete mode 100755 walkthroughs/howto-proxy/colorapp/app.py delete mode 100644 walkthroughs/howto-proxy/colorapp/config.py delete mode 100644 walkthroughs/howto-proxy/colorapp/requirements.txt diff --git a/walkthroughs/howto-proxy/app.yaml b/walkthroughs/howto-proxy/app.yaml index bad058e1..d454b93b 100644 --- a/walkthroughs/howto-proxy/app.yaml +++ b/walkthroughs/howto-proxy/app.yaml @@ -16,10 +16,6 @@ Parameters: Type: String Description: Front app container image - ColorAppImage: - Type: String - Description: Color app container image - ContainerPort: Type: Number Description: Port number to use for applications diff --git a/walkthroughs/howto-proxy/colorapp/Dockerfile b/walkthroughs/howto-proxy/colorapp/Dockerfile deleted file mode 100644 index e25684b5..00000000 --- a/walkthroughs/howto-proxy/colorapp/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM public.ecr.aws/amazonlinux/amazonlinux:2 - -COPY requirements.txt ./ - -RUN yum update -y && \ - yum install -y python3 && \ - pip3 install --no-cache-dir -r requirements.txt && \ - yum clean all && \ - rm -rf /var/cache/yum - -WORKDIR /usr/src/app - -COPY . . - -ENV PORT 8080 - -CMD ["gunicorn", "app:app", "--config=config.py"] \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/app.py b/walkthroughs/howto-proxy/colorapp/app.py deleted file mode 100755 index e8ae3807..00000000 --- a/walkthroughs/howto-proxy/colorapp/app.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import config -from flask import Flask, request -from aws_xray_sdk.core import patch_all, xray_recorder -from aws_xray_sdk.ext.flask.middleware import XRayMiddleware - -app = Flask(__name__) - -xray_recorder.configure( - context_missing='LOG_ERROR', - service=config.XRAY_APP_NAME, -) -patch_all() - -XRayMiddleware(app, xray_recorder) - -@app.route('/ping') -def ping(): - return 'Pong' - -@app.route('/') -def color(): - print('----------------') - print(request.headers) - print('----------------') - return config.COLOR - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=config.PORT, debug=config.DEBUG_MODE) \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/config.py b/walkthroughs/howto-proxy/colorapp/config.py deleted file mode 100644 index d8606440..00000000 --- a/walkthroughs/howto-proxy/colorapp/config.py +++ /dev/null @@ -1,12 +0,0 @@ -from os import environ as env -import multiprocessing - -PORT = int(env.get("PORT", 8080)) -DEBUG_MODE = int(env.get("DEBUG_MODE", 0)) -XRAY_APP_NAME = env.get('XRAY_APP_NAME', 'feapp') -COLOR = env.get('COLOR', 'n/a') - -# Gunicorn config -bind = ":" + str(PORT) -workers = multiprocessing.cpu_count() * 2 + 1 -threads = 2 * multiprocessing.cpu_count() \ No newline at end of file diff --git a/walkthroughs/howto-proxy/colorapp/requirements.txt b/walkthroughs/howto-proxy/colorapp/requirements.txt deleted file mode 100644 index 47f05a04..00000000 --- a/walkthroughs/howto-proxy/colorapp/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask -aws-xray-sdk -gunicorn==19.9.0 diff --git a/walkthroughs/howto-proxy/deploy.sh b/walkthroughs/howto-proxy/deploy.sh index 65c7d926..d60f8e7c 100755 --- a/walkthroughs/howto-proxy/deploy.sh +++ b/walkthroughs/howto-proxy/deploy.sh @@ -34,7 +34,7 @@ ecr_login() { deploy_images() { ecr_login - for app in colorproxy colorapp colorappssl feapp; do + for app in colorproxy colorappssl feapp; do aws ecr describe-repositories --repository-name $PROJECT_NAME/$app >/dev/null 2>&1 || aws ecr create-repository --repository-name $PROJECT_NAME/$app >/dev/null docker build -t ${ECR_IMAGE_PREFIX}/${app} ${DIR}/${app} docker push ${ECR_IMAGE_PREFIX}/${app} @@ -57,7 +57,7 @@ deploy_app() { --stack-name "${PROJECT_NAME}-app" \ --template-file "${DIR}/app.yaml" \ --capabilities CAPABILITY_IAM \ - --parameter-overrides "ProjectName=${PROJECT_NAME}" "EnvoyImage=${ENVOY_IMAGE}" "ColorAppSslImage=${ECR_IMAGE_PREFIX}/colorappssl" "ColorAppImage=${ECR_IMAGE_PREFIX}/colorapp" "ColorProxyImage=${ECR_IMAGE_PREFIX}/colorproxy" "FrontAppImage=${ECR_IMAGE_PREFIX}/feapp" + --parameter-overrides "ProjectName=${PROJECT_NAME}" "EnvoyImage=${ENVOY_IMAGE}" "ColorAppSslImage=${ECR_IMAGE_PREFIX}/colorappssl" "ColorProxyImage=${ECR_IMAGE_PREFIX}/colorproxy" "FrontAppImage=${ECR_IMAGE_PREFIX}/feapp" } delete_cfn_stack() { @@ -101,7 +101,7 @@ deploy_resources() { } delete_images() { - for app in colorproxy colorapp colorappssl feapp; do + for app in colorproxy colorappssl feapp; do echo "deleting repository..." aws ecr delete-repository \ --repository-name $PROJECT_NAME/$app \