Created
March 18, 2019 08:27
-
-
Save faermanj/2a1676979712ae3818d03e1e2db315be to your computer and use it in GitHub Desktop.
Multi-region serverless backend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -e | |
# Script for creating a multi-region active-active deployment | |
# usgin the AWS CLI based on the post from @adhorn: | |
# https://medium.com/@adhorn/multi-region-serverless-backend-reloaded-1b887bc615c0 | |
RID="$RANDOM" | |
PREFIX="globalapp" | |
REGIONS="us-east-1 us-west-2 eu-west-1" | |
TABLE_NAME="${PREFIX}table${RID}" | |
PK="item_id" | |
LB_NAME="${PREFIX}alb${RID}" | |
ROLE_NAME="${PREFIX}fnrole${RID}" | |
FN_NAME="${PREFIX}fnget${RID}" | |
AXEL_NAME="${PREFIX}axel${RID}" | |
ALB_GROUP_NAME="${PREFIX}axelgrp${RID}" | |
echo "[RID=$RID] Global Application Deployment Script \o/" | |
echo "[RID=$RID] Prepare local resources" | |
rm -rf globalapp | |
mkdir globalapp | |
pushd globalapp | |
echo "[RID=$RID] Create lambda package" | |
echo 'simplejson==3.16.0' > requirements.txt | |
wget http://julio.cloud/pub/get.py | |
docker run -v $PWD:/var/task -it lambci/lambda:build-python3.6 /bin/bash -c "pip install -r requirements.txt -t vendor" | |
zip -r function.zip . | |
echo "[RID=$RID] Creating IAM Role for Lambda Functions" | |
wget http://julio.cloud/pub/trust-lambda.json | |
LAMBDA_ROLE=$(aws iam create-role \ | |
--role-name $ROLE_NAME \ | |
--assume-role-policy-document file://trust-lambda.json \ | |
--query Role.Arn \ | |
--output text) | |
echo $LAMBDA_ROLE | |
sleep 5 | |
aws iam attach-role-policy \ | |
--role-name "$ROLE_NAME" \ | |
--policy-arn 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' | |
aws iam attach-role-policy \ | |
--role-name "$ROLE_NAME" \ | |
--policy-arn 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess' | |
echo "[RID=$RID] Creating Global Accelerator [$AXEL_NAME]" | |
AXEL_ARN=$(aws globalaccelerator create-accelerator \ | |
--region us-west-2 \ | |
--name $AXEL_NAME \ | |
--enabled \ | |
--idempotency-token "globalappxceltoken" \ | |
--query "Accelerator.AcceleratorArn" \ | |
--output text) | |
echo "[AXEL] AXEL_ARN=$AXEL_ARN" | |
AXEL_LIST_ARN=$(aws globalaccelerator create-listener \ | |
--region us-west-2 \ | |
--accelerator-arn $AXEL_ARN \ | |
--port-ranges FromPort=80,ToPort=80 \ | |
--protocol TCP \ | |
--idempotency-token "globalappxceltoken" \ | |
--query "Listener.ListenerArn" \ | |
--output text) | |
echo "[AXEL] AXEL_LIST_ARN=$AXEL_LIST_ARN" | |
echo "Creating per-region resources" | |
for REGION in $REGIONS; do | |
echo "[$REGION] Creating resources" | |
echo "[$REGION] Creating Network Infrastructre" | |
echo "[$REGION] [VPC] Creating VPC" | |
VPC_ID=$(aws ec2 create-vpc \ | |
--cidr-block "10.0.0.0/16" \ | |
--query "Vpc.VpcId" \ | |
--region "$REGION" \ | |
--output text) | |
echo "[$REGION] [VPC] $VPC_ID" | |
AZ_A=$(aws ec2 describe-availability-zones \ | |
--region $REGION \ | |
--query "AvailabilityZones[0].ZoneName" \ | |
--output text) | |
AZ_B=$(aws ec2 describe-availability-zones \ | |
--region $REGION \ | |
--query "AvailabilityZones[1].ZoneName" \ | |
--output text) | |
echo "[$REGION] [VPC] AZs=$AZ_A,$AZ_B" | |
SUBNET_A=$(aws ec2 create-subnet \ | |
--vpc-id $VPC_ID \ | |
--cidr-block 10.0.10.0/24 \ | |
--availability-zone $AZ_A \ | |
--region $REGION \ | |
--query "Subnet.SubnetId" \ | |
--output text) | |
SUBNET_B=$(aws ec2 create-subnet \ | |
--vpc-id $VPC_ID \ | |
--cidr-block 10.0.20.0/24 \ | |
--availability-zone $AZ_B \ | |
--region $REGION \ | |
--query "Subnet.SubnetId" \ | |
--output text) | |
echo "[$REGION] [VPC] Subnets $SUBNET_A,$SUBNET_B" | |
IGW=$(aws ec2 create-internet-gateway \ | |
--region $REGION \ | |
--query "InternetGateway.InternetGatewayId" \ | |
--output text) | |
aws ec2 attach-internet-gateway \ | |
--region $REGION \ | |
--vpc-id $VPC_ID \ | |
--internet-gateway-id $IGW | |
echo "[$REGION] [VPC] IGW $IGW" | |
ROUTES=$(aws ec2 create-route-table --vpc-id $VPC_ID \ | |
--region $REGION \ | |
--query "RouteTable.RouteTableId" \ | |
--output text) | |
echo "[$REGION] [VPC] ROUTES=$ROUTES" | |
aws ec2 create-route \ | |
--region $REGION \ | |
--route-table-id $ROUTES \ | |
--destination-cidr-block "0.0.0.0/0" \ | |
--gateway-id $IGW | |
echo "[$REGION] [VPC] Associating routing tables" | |
aws ec2 associate-route-table \ | |
--region $REGION \ | |
--subnet-id $SUBNET_A \ | |
--route-table-id $ROUTES | |
aws ec2 associate-route-table \ | |
--region $REGION \ | |
--subnet-id $SUBNET_B \ | |
--route-table-id $ROUTES | |
echo "[$REGION] [VPC] Setting public-ip-on-launch" | |
aws ec2 modify-subnet-attribute \ | |
--region $REGION \ | |
--subnet-id $SUBNET_A \ | |
--map-public-ip-on-launch | |
aws ec2 modify-subnet-attribute \ | |
--region $REGION \ | |
--subnet-id $SUBNET_B \ | |
--map-public-ip-on-launch | |
GROUP_ID=$(aws ec2 create-security-group \ | |
--region $REGION \ | |
--group-name globalappsg --description "Global app security group" \ | |
--vpc-id $VPC_ID \ | |
--query "GroupId" \ | |
--output text) | |
echo "[$REGION] [VPC] Security Group $GROUP_ID" | |
aws ec2 authorize-security-group-ingress \ | |
--region $REGION \ | |
--group-id $GROUP_ID \ | |
--protocol tcp \ | |
--port 80 \ | |
--cidr "0.0.0.0/0" | |
echo "[$REGION] [DDB] Creating DynamoDB Table" | |
aws dynamodb create-table \ | |
--table-name $TABLE_NAME \ | |
--attribute-definitions AttributeName=$PK,AttributeType=S \ | |
--key-schema AttributeName=$PK,KeyType=HASH \ | |
--billing-mode PAY_PER_REQUEST \ | |
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \ | |
--region $REGION | |
echo "[$REGION] [LAMDA] Creating Lambda Function" | |
LAMBDA_ARN=$(aws lambda create-function \ | |
--function-name "$FN_NAME" \ | |
--zip-file "fileb://function.zip" \ | |
--role "$LAMBDA_ROLE" \ | |
--handler get.get_item \ | |
--runtime python3.6 \ | |
--timeout 30 \ | |
--memory-size 128 \ | |
--query FunctionArn \ | |
--environment "Variables={tablename=GlobalApp,STATUS=200}" \ | |
--output text \ | |
--region $REGION) | |
echo "[$REGION] [ALB] Creating Load Balancer" | |
LB=$(aws elbv2 create-load-balancer \ | |
--name $LB_NAME \ | |
--region $REGION \ | |
--subnets $SUBNET_A $SUBNET_B \ | |
--security-groups $GROUP_ID \ | |
--query "LoadBalancers[0].LoadBalancerArn" \ | |
--output text) | |
TARGETS=$(aws elbv2 create-target-group \ | |
--name "$ALB_GROUP_NAME" \ | |
--region $REGION \ | |
--target-type lambda \ | |
--health-check-enabled \ | |
--health-check-path "/health" \ | |
--health-check-interval-seconds 15 \ | |
--health-check-timeout-seconds 5 \ | |
--healthy-threshold-count 3 \ | |
--unhealthy-threshold-count 3 \ | |
--query "TargetGroups[0].TargetGroupArn" \ | |
--output text) | |
echo "[$REGION] [LAMBDA] Add Permission" | |
aws lambda add-permission \ | |
--region $REGION \ | |
--function-name "$FN_NAME" \ | |
--statement-id alb-lambda \ | |
--action "lambda:InvokeFunction" \ | |
--source-arn "$TARGETS" \ | |
--principal elasticloadbalancing.amazonaws.com | |
echo "[$REGION] [ALB] Register Target" | |
aws elbv2 register-targets \ | |
--region $REGION \ | |
--target-group-arn "$TARGETS" \ | |
--targets "Id=$LAMBDA_ARN" | |
echo "[$REGION] [ALB] Create Listener" | |
aws elbv2 create-listener \ | |
--region $REGION \ | |
--load-balancer-arn $LB \ | |
--protocol HTTP \ | |
--port 80 \ | |
--default-actions Type=forward,TargetGroupArn="$TARGETS" | |
ENDPOINT=$(aws elbv2 describe-load-balancers \ | |
--region $REGION \ | |
--load-balancer-arns $LB \ | |
--query "LoadBalancers[0].DNSName" \ | |
--output text) | |
echo "[$REGION] [ALB] $ENDPOINT" | |
echo "[$REGION] [ALB] Waiting $LB" | |
aws elbv2 wait load-balancer-available \ | |
--region $REGION \ | |
--load-balancer-arns $LB | |
echo "[$REGION] [AXEL] Creating Endpoint Group on Listener [$AXEL_LIST_ARN]" | |
aws globalaccelerator create-endpoint-group \ | |
--region us-west-2 \ | |
--idempotency-token "globalappxceltoken" \ | |
--listener-arn "$AXEL_LIST_ARN" \ | |
--endpoint-group-region $REGION \ | |
--endpoint-configurations EndpointId=$LB,Weight=10 | |
echo "[$REGION] [AXEL] $ENDPOINT" | |
echo "[$REGION] done" | |
done | |
echo "[DDB] Creating global table" | |
aws dynamodb create-global-table \ | |
--global-table-name $TABLE_NAME \ | |
--replication-group RegionName=us-east-1 RegionName=us-west-2 RegionName=eu-west-1 \ | |
--region us-east-1 | |
echo "[$RID] Done and global!" | |
echo "Try This:" | |
echo aws dynamodb put-item \ | |
--table-name $TABLE_NAME \ | |
--item \'{\"item_id\": {\"S\":\"toy\"}}\' \ | |
--region us-east-1 | |
echo aws dynamodb get-item \ | |
--table-name $TABLE_NAME \ | |
--key \'{\"item_id\": {\"S\":\"toy\"}}\' \ | |
--region eu-west-1 | |
IP_A=$(aws globalaccelerator describe-accelerator \ | |
--region us-west-2 \ | |
--accelerator-arn $AXEL_ARN \ | |
--query "Accelerator.IpSets[].IpAddresses[0]" \ | |
--output text) | |
echo "Endpoint for Global Accelerator [$AXEL_NAME]" | |
echo curl "http://$IP_A/health" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment