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

Walkthrough for Multiple Listeners #467

Merged
merged 2 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions walkthroughs/howto-multiple-listeners/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
## Fruit and Color App Setup
For this walkthrough, an NLB is used to forward traffic to the Virtual Gateway (running a set of Envoys).
We configure 3 Gateway Routes - 2 food routes and 1 color route each pointing to Virtual Services (backed by a Virtual Node each).
Virtual Service `foodteller.default.svc.cluster.local` points to `foodteller-vn` which has 2 applications. `fruit-app` listens on `5555` and `vegetable-app` which listens on `6666`. A Virtual Router is responsible for correctly matching the ports from the Virtual Gateway to the Virtual Nodes.
The Virtual Service `colorteller.default.svc.cluster.local` points to `colorteller-vn` which listens on port `7777`.

Note: Normally customers would likely use the same port from end to end, but we're using a different port per listener to show they are each independent values.

Let's deploy a mesh with a multiple listener gateway, router and node.

![System Diagram](./howto-multiple-listeners.png "System Diagram")

## Step 1: Prerequisites

1. Download the App Mesh Preview CLI

You will need the latest version of the App Mesh Preview CLI for this walkthrough. You can download and use the latest version using the commands below.

```bash
curl https://raw.githubusercontent.com/aws/aws-app-mesh-roadmap/main/appmesh-preview/service-model.json \
-o $HOME/appmesh-preview-model.json
aws configure add-model \
--service-name appmesh-preview \
--service-model file://$HOME/appmesh-preview-model.json
```

Additionally, this walkthrough makes use of the unix command line utility `jq`. If you don't already have it, [you can install it from here](https://stedolan.github.io/jq/).

2. Clone this repository and navigate to the `walkthroughs/howto-multiple-listeners` folder, all the commands henceforth are assumed to be run from the same directory as this README.

3. Make sure you have version 1.18.172 or higher of the [AWS CLI v1](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html) installed or you have version 2.0.62 or higher of the [AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed.

4. You'll need a keypair stored in AWS to access a bastion host.
If you do not already have one, you can create a keypair using the command below if you don't have one. See [Amazon EC2 Key Pairs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html).

```bash
aws ec2 create-key-pair --key-name color-app | jq -r .KeyMaterial > ~/.ssh/color-app.pem
chmod 400 ~/.ssh/color-app.pem
```

This command creates an Amazon EC2 Key Pair with name `color-app` and saves the private key at `~/.ssh/color-app.pem`.

5. Install Docker. It is needed to build the demo application images.

## Step 2: Set Environment Variables
We need to set a few environment variables before provisioning the infrastructure.
Please change the value for `AWS_ACCOUNT_ID`, `KEY_PAIR_NAME`, and `ENVOY_IMAGE` below.

```bash
export AWS_ACCOUNT_ID=<your account id>
export KEY_PAIR_NAME=<color-app or your SSH key pair stored in AWS>
export AWS_DEFAULT_REGION=us-west-2
export ENVIRONMENT_NAME=MultipleListeners
export MESH_NAME=multiple-listeners-mesh
export ENVOY_IMAGE=<get the latest from https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html>
export SERVICES_DOMAIN="default.svc.cluster.local"
export TELLER_IMAGE_NAME="howto-multiple-listeners/teller"
```

You can change these ENV variables in `vars.env` file and then apply it using:
`source ./vars.env`


## Step 3: Deploy Color App Infrastructure

We'll start by setting up the basic infrastructure for our services.

First, create the VPC.

```bash
./infrastructure/vpc.sh
```

Next, create the ECS cluster.

```bash
./infrastructure/ecs-cluster.sh
```

And the ECR repositories.

```bash
./infrastructure/ecr-repositories.sh
```

Next, build and deploy the teller image.

```bash
./src/teller/deploy.sh
```

**_Note_** that the example app uses go modules. If you have trouble accessing https://proxy.golang.org during the deployment you can override the GOPROXY by setting `GO_PROXY=direct`

```bash
GO_PROXY=direct ./src/teller/deploy.sh
```

Finally, let's create the mesh.

```bash
./mesh/mesh.sh up
```


## Step 4: Deploy services and Verify

Our next step is to deploy the service in ECS and test it out.

```bash
./infrastructure/ecs-service.sh
```

1. After a few minutes, the applications should be deployed and you will see an output such as:

```bash
Successfully created/updated stack - ${ENVIRONMENT_NAME}-ecs-service
Bastion endpoint:
123.45.67.89
ColorApp endpoint:
http://howto-Publi-55555555.us-west-2.elb.amazonaws.com
```

Export the public endpoint to access the gateway

```bash
export COLORAPP_ENDPOINT=<your_https_tellerApp_endpoint e.g. https://howto-Publi-55555555.us-west-2.elb.amazonaws.com>
```
And export the bastion endpoint for later use.

```bash
export BASTION_IP=<your_bastion_endpoint e.g. 12.245.6.189>
```

2. Let's issue a request to the color gateway with gatewayRoute prefix as `/fruit` which will set the port for the backend service route and direct the request to the fruit port.

```bash
curl "${COLORAPP_ENDPOINT}:1111/fruit"
```
If you run the above command, you should see a successful response containing `blueberry` or another fruit.

Similarly, let's issue a request to the gateway with gatewayRoute prefix as `/vegetable`.

```bash
curl "${COLORAPP_ENDPOINT}:1111/vegetable"
```
In this case, you should receive a successful response containing `greenbean` or another vegetable

Finally, let's issue a request to the gateway to a different listener. Since there's only 1 gatewayRoute for the port and we didn't configure a prefix we don't need to include a prefix.

```bash
curl "${COLORAPP_ENDPOINT}:2222"
```
In this case, you should receive a successful response containing `red` or another color.

## Step 5: Clean Up

If you want to keep the application running, you can do so, but this is the end of this walkthrough.
Run the following commands to clean up and tear down the resources that we’ve created.

Delete the CloudFormation stacks:

```bash
aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecs-service
aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecs-cluster
aws ecr delete-repository --force --repository-name $TELLER_IMAGE_NAME
aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecr-repositories
aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-vpc
```

Delete the Mesh:

```bash
./mesh/mesh.sh down
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -ex

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"

aws --region "${AWS_DEFAULT_REGION}" \
cloudformation deploy \
--stack-name "${ENVIRONMENT_NAME}-ecr-repositories" \
--capabilities CAPABILITY_IAM \
--template-file "${DIR}/ecr-repositories.yaml" \
--parameter-overrides \
TellerImageName="${TELLER_IMAGE_NAME}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Parameters:

TellerImageName:
Description: The name for the color teller image
Type: String

Resources:

TellerRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref TellerImageName

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -ex

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"

aws --region "${AWS_DEFAULT_REGION}" \
cloudformation deploy \
--stack-name "${ENVIRONMENT_NAME}-ecs-cluster" \
--capabilities CAPABILITY_IAM \
--template-file "${DIR}/ecs-cluster.yaml" \
--parameter-overrides \
EnvironmentName="${ENVIRONMENT_NAME}" \
KeyName="${KEY_PAIR_NAME}" \
ECSServicesDomain="${SERVICES_DOMAIN}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
Description: >
This template deploys an ECS cluster to the provided VPC and subnets
using an Auto Scaling Group

Parameters:

EnvironmentName:
Description: An environment name that will be prefixed to resource names
Type: String

InstanceType:
Description: Which instance type should we use to build the ECS cluster?
Type: String
Default: c4.large

KeyName:
Description: The EC2 Key Pair to allow SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName

ECSServiceLogGroupRetentionInDays:
Type: Number
Default: 30

ECSServicesDomain:
Type: String
Description: "Domain name registerd under Route-53 that will be used for Service Discovery"

EC2Ami:
Description: EC2 AMI ID
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"

Resources:

ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Ref EnvironmentName

ECSInstancesSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Security group for the instances"
VpcId:
'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
SecurityGroupIngress:
- CidrIp:
'Fn::ImportValue': !Sub "${EnvironmentName}:VpcCIDR"
IpProtocol: -1

ECSServiceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Security group for the service"
VpcId:
'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
SecurityGroupIngress:
- CidrIp:
'Fn::ImportValue': !Sub "${EnvironmentName}:VpcCIDR"
IpProtocol: -1

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/AWSAppMeshPreviewEnvoyAccess
- 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

ECSServiceLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
RetentionInDays:
Ref: ECSServiceLogGroupRetentionInDays

ECSServiceDiscoveryNamespace:
Type: AWS::ServiceDiscovery::PrivateDnsNamespace
Properties:
Vpc:
'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
Name: { Ref: ECSServicesDomain }

BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow http to client host
VpcId:
'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0

BastionHost:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref EC2Ami
KeyName: !Ref KeyName
InstanceType: t2.micro
SecurityGroupIds:
- !Ref BastionSecurityGroup
SubnetId:
'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet1"
Tags:
- Key: Name
Value: bastion-host

Outputs:

Cluster:
Description: A reference to the ECS cluster
Value: !Ref ECSCluster
Export:
Name: !Sub "${EnvironmentName}:ECSCluster"

ECSServiceDiscoveryNamespace:
Description: A SDS namespace that will be used by all services in this cluster
Value: !Ref ECSServiceDiscoveryNamespace
Export:
Name: !Sub "${EnvironmentName}:ECSServiceDiscoveryNamespace"

ECSServiceLogGroup:
Description: Log group for services to publish logs
Value: !Ref ECSServiceLogGroup
Export:
Name: !Sub "${EnvironmentName}:ECSServiceLogGroup"

ECSServiceSecurityGroup:
Description: Security group to be used by all services in the cluster
Value: !Ref ECSServiceSecurityGroup
Export:
Name: !Sub "${EnvironmentName}:ECSServiceSecurityGroup"

TaskExecutionIamRoleArn:
Description: Task Executin IAM role used by ECS tasks
Value: { "Fn::GetAtt": TaskExecutionIamRole.Arn }
Export:
Name: !Sub "${EnvironmentName}:TaskExecutionIamRoleArn"

TaskIamRoleArn:
Description: IAM role to be used by ECS task
Value: { "Fn::GetAtt": TaskIamRole.Arn }
Export:
Name: !Sub "${EnvironmentName}:TaskIamRoleArn"

BastionIP:
Description: Public IP for ssh access to bastion host
Value:
'Fn::GetAtt': [ BastionHost, PublicIp ]

Loading