Skip to content
This repository has been archived by the owner on Apr 16, 2022. It is now read-only.

Production Infrastructure

Ruslan Talpa edited this page Aug 28, 2018 · 4 revisions

!!! This documentation is outdated. New version here.

Overview

Every component (apart from the database) in this stack is packaged as a docker container. Because of this, it can be deployed to any infrastructure capable of running containers. In this section we'll explain how to setup Amazon EC2 Container Service (ECS) to run your containers and setup a PostgreSQL database in Amazon Relational Database Service (RDS) that will hold your data (and most of the code).

Familiarize yourself with ECS concepts and AWS

If you feel overwelmed by all the AWS services and their names, check out Amazon Web Services in Plain English

Install AWS CLI

We'll be interacting with AWS mostly using it's command line interface. You will need to install and configure it.

ECS Cluster

Create your cluster using the wizard (start with one ec2 instance) In this example the name used is mycluster Creating a Cluster - Guide Important On the guide page, in the first paragraph, you will be directed to Setting Up with Amazon ECS. DO NOT follow those step, teh docs are a bit outdated. You probably already have a AWS account (#1) and we already installed the CLI (#7). The only step taht you could follow is # Create a Key Pair if you want to ssh into instances fro your cluster.

Also note, for the Create Cluster form to appear the you must ignore the first-run wizard (it appears with two checkboxes), if not it will be presented with unneeded forms for the completion of the tutorial.

This is how the form should look

From this step forward, we'll use the command line.

Save the cluster name in a env var.

export CLUSTER_NAME=mycluster

Get the cluster's cloudformation stack name:

aws cloudformation list-stacks --output table --query 'StackSummaries[*].[StackName,TemplateDescription]'

Result should look something like this

--------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                      ListStacks                                                                      |
+-------------------------------+----------------------------------------------------------------------------------------------------------------------+
|  EC2ContainerService-mycluster|  AWS CloudFormation template to create a new VPC or use an existing VPC for ECS deployment in Create Cluster Wizard  |
+-------------------------------+----------------------------------------------------------------------------------------------------------------------+

Save the stack name to env

export STACK_NAME=EC2ContainerService-mycluster

Extract stack configuration info

while read k v ; do export Cluster_Resource_$k=$v; done < <( \
    aws cloudformation describe-stack-resources \
        --stack-name $STACK_NAME \
        --output text \
        --query 'StackResources[*].[LogicalResourceId, PhysicalResourceId]'\
)

while read k v ; do export Cluster_Param_$k=$v; done < <( \
    aws cloudformation describe-stacks \
        --stack-name $STACK_NAME \
        --output text \
        --query 'Stacks[*].Parameters[*][ParameterKey,ParameterValue]'\
)

#check if it worked with

env | grep Cluster

### sample output below
Cluster_Param_SubnetCidr2=10.0.1.0/24
Cluster_Param_EbsVolumeSize=22
Cluster_Param_SubnetCidr3=
Cluster_Param_EcsEndpoint=
Cluster_Resource_EcsInstanceAsg=EC2ContainerService-mycluster-EcsInstanceAsg-XXXXXXXX
Cluster_Resource_PubSubnet2RouteTableAssociation=rtbassoc-0000000
Cluster_Param_AsgMaxSize=2
Cluster_Param_SubnetCidr1=10.0.0.0/24
Cluster_Param_SecurityIngressToPort=80
Cluster_Param_EcsClusterName=mycluster
Cluster_Param_EbsVolumeType=gp2
Cluster_Resource_RouteViaIgw=rtb-aaaaaaa
Cluster_Resource_Vpc=vpc-00000000
Cluster_Param_VpcCidr=10.0.0.0/16
Cluster_Param_VpcId=
Cluster_Resource_PubSubnet1RouteTableAssociation=rtbassoc-0000000
Cluster_Resource_AttachGateway=EC2Co-Attac-AAAAAAAAAA
Cluster_Resource_PubSubnetAz1=subnet-aaaaaaa
Cluster_Param_KeyName=mycluster-cluster
Cluster_Resource_PubSubnetAz2=subnet-bbbbbbb
Cluster_Resource_InternetGateway=igw-aaaaaaa
Cluster_Param_IamRoleInstanceProfile=ecsInstanceRole
Cluster_Param_SecurityIngressFromPort=80
Cluster_Param_DeviceName=/dev/xvdcz
Cluster_Param_VpcAvailabilityZones=us-east-1a,us-east-1d,us-east-1e,us-east-1c,us-east-1b
Cluster_Param_SecurityGroupId=
Cluster_Resource_PublicRouteViaIgw=EC2Co-Publi-AAAAAAAAAAA
Cluster_Param_SubnetIds=
Cluster_Resource_EcsSecurityGroup=sg-0000000
Cluster_Resource_EcsInstanceLc=EC2ContainerService-mycluster-EcsInstanceLc-AAAAAAAAAA
Cluster_Param_EcsAmiId=ami-04351e12
Cluster_Param_EcsInstanceType=t2.medium
Cluster_Param_SecurityIngressCidrIp=0.0.0.0/0

Save the cluster reagion in a env var

export AWS_REGION=`echo $Cluster_Param_VpcAvailabilityZones | cut -d',' -f1 | head --bytes -2`
echo $AWS_REGION

SSL Certificate

If you want to use HTTPS, you will need a SSL certificate and you will need to complete this step before the next one (creating the loadbalancer). You can create (or upload) a certificate in AWS Certificate Manager. You must request (or configure) your certificate in the same region as your cluster, you can not use them across regions.

# List the certificates
aws acm list-certificates

# Save the ARN
export CERTIFICATE_ARN="arn:aws:acm:us-east-1:CHANGE-WITH-YOURS:certificate/CHANGE-WITH-YOURS"

Result should look like this

------------------------------------------------------------------------------------------------------------
|                                             ListCertificates                                             |
+----------------------------------------------------------------------------------------------------------+
||                                         CertificateSummaryList                                         ||
|+---------------------------------------------------------------------------------------+----------------+|
||                                    CertificateArn                                     |  DomainName    ||
|+---------------------------------------------------------------------------------------+----------------+|
||  arn:aws:acm:us-east-1:000000000000:certificate/00000000-0000-0000-0000-000000000000  |  mydomain.com  ||
|+---------------------------------------------------------------------------------------+----------------+|

Loadbalancer

The loadbalancer will route traffic to our containers (just like the cluster, it can be used for multiple applications). If you do not need HTTPS, and you skipped the previous step, make sure you set CERTIFICATE_ARN to empty string

export CERTIFICATE_ARN=""

Create the loadbalancer (make sure you are in the root folder of your starter kit project in order for aws to find the loudformation/loadbalancer.yml template file )

curl -SLO https://raw.githubusercontent.com/wiki/subzerocloud/postgrest-starter-kit/cloudformation/loadbalancer.yml

aws cloudformation create-stack \
    --stack-name $CLUSTER_NAME-loadbalancer \
    --template-body file://loadbalancer.yml \
    --capabilities CAPABILITY_IAM \
    --parameters \
    ParameterKey=ClusterName,ParameterValue=$CLUSTER_NAME \
    ParameterKey=CertificateArn,ParameterValue=$CERTIFICATE_ARN \
    ParameterKey=Vpc,ParameterValue=$Cluster_Resource_Vpc \
    ParameterKey=EcsSecurityGroup,ParameterValue=$Cluster_Resource_EcsSecurityGroup \
    ParameterKey=PubSubnetAz1,ParameterValue=$Cluster_Resource_PubSubnetAz1 \
    ParameterKey=PubSubnetAz2,ParameterValue=$Cluster_Resource_PubSubnetAz2 \

Image Repository

We'll store our OpenResty image (the only one that changes) in Amazon EC2 Container Registry. You can use any docker image repository you like.

# read project env vars
source .env

# create the repository
aws ecr create-repository --repository-name $COMPOSE_PROJECT_NAME/openresty

# extract the uri in a separate env var
export OPENRESTY_REPO_URI=`aws ecr describe-repositories --repository-name $COMPOSE_PROJECT_NAME/openresty --output text --query 'repositories[0].repositoryUri'`

Database (PostgreSQL in RDS)

# read project env vars
source .env

# we will place the database in the same security group as our ECS cluster
export PRODUCTION_DB_SECURITY_GROUP=$Cluster_Resource_EcsSecurityGroup

# set the subnet to the same VPS as the cluster
aws rds create-db-subnet-group \
    --db-subnet-group-name $COMPOSE_PROJECT_NAME-db-subnet \
    --db-subnet-group-description $COMPOSE_PROJECT_NAME-db-subnet \
    --subnet-ids $Cluster_Resource_PubSubnetAz1 $Cluster_Resource_PubSubnetAz2


# allow ECS nodes to connect to this db's in the same security group as the cluster
aws ec2 authorize-security-group-ingress \
              --region $AWS_REGION \
              --group-id $PRODUCTION_DB_SECURITY_GROUP \
              --protocol tcp \
              --port 5432 \
              --source-group $PRODUCTION_DB_SECURITY_GROUP

# get your current IP
# option A
export MY_IP=`curl http://checkip.amazonaws.com/`
# option B
export MY_IP=`dig +short myip.opendns.com @resolver1.opendns.com`

# allow yourself to connect directly to the database
aws ec2 authorize-security-group-ingress \
              --region $AWS_REGION \
              --group-id $PRODUCTION_DB_SECURITY_GROUP \
              --protocol tcp \
              --port 5432 \
              --cidr $MY_IP/32

# create the database
aws rds create-db-instance \
    --db-instance-identifier $COMPOSE_PROJECT_NAME-db \
    --db-name $DB_NAME \
    --vpc-security-group-ids $PRODUCTION_DB_SECURITY_GROUP \
    --allocated-storage 20 \
    --db-instance-class db.t2.micro \
    --engine postgres \
    --publicly-accessible \
    --multi-az \
    --db-subnet-group-name $COMPOSE_PROJECT_NAME-db-subnet \
    --master-username $SUPER_USER \
    --master-user-password SET-YOUR-ROOT-PASSWORD-HERE

# check the AWS management pannel and wait until the database is "ready"

# export production db host
export PRODUCTION_DB_HOST=`aws rds describe-db-instances --db-instance-identifier $COMPOSE_PROJECT_NAME-db --output text --query 'DBInstances[0].Endpoint.Address'`

# check you can connect
psql -h $PRODUCTION_DB_HOST -U $SUPER_USER $DB_NAME

# create the authenticator role used by PostgREST to connect
psql \
    -h $PRODUCTION_DB_HOST \
    -U $SUPER_USER \
    -c "create role $DB_USER with login password 'SET-YOUR-AUTHENTICATOR-PASSWORD';" $DB_NAME

Application Stack

Create the application cloudformation stack.

Right now we will use DesiredCount=0 since our application is not deployed yet (db is empty and the OpenResty images are not uploaded, this will be done by CircleCI)

curl -SLO https://raw.githubusercontent.com/wiki/subzerocloud/postgrest-starter-kit/cloudformation/application.yml

aws cloudformation create-stack \
--stack-name $COMPOSE_PROJECT_NAME \
--template-body file://application.yml \
--capabilities CAPABILITY_IAM \
--parameters \
ParameterKey=ClusterName,ParameterValue=$CLUSTER_NAME \
ParameterKey=DesiredCount,ParameterValue=0 \
ParameterKey=Version,ParameterValue=v0.0.0 \
ParameterKey=OpenRestyImage,ParameterValue=$OPENRESTY_REPO_URI \
ParameterKey=ListenerHostNamePattern,ParameterValue=mydomain.com \
ParameterKey=HasHttpsListener,ParameterValue=Yes \
ParameterKey=DbHost,ParameterValue=$PRODUCTION_DB_HOST \
ParameterKey=DbPassword,ParameterValue=SET-YOUR-AUTHENTICATOR-PASSWORD \
ParameterKey=JwtSecret,ParameterValue=SET-YOUR-JWT-SECRET \

Github Repository

Your current application directory is a local git repository, we need to push it to to a remote one (GitHub)

Create an empty GitHub repository. On the next page, find the section that says or push an existing repository from the command line and execute those command in the root folder of your project. They should look something like this

git remote add origin https://github.com/<your-github-user>/<repo-name>.git
git push -u origin master

Now your code is in GitHub.

CircleCI build pipeline

The starter kit comes with a CircleCI configuration file that will build your code and push it to production.

mkdir .circleci
cd .circleci
curl -SLO https://raw.githubusercontent.com/wiki/subzerocloud/postgrest-starter-kit/circleci/config.yml
  • Signup to CircleCI using your Github account.
  • Add your project's Github repo to the list repos CircleCI will build when new code is pushed.

Once you add your project, CircleCI will immediately start building your project. This is ok, it will not push it to production yet. Note: When adding new repo in Circle 2 it does not build successfully the first time and it gives a: job "build" is not found, this cannot be fixed through a GUI 'rebuild', the build will be successful only on a new commit Issue #13549

  • In CircleCI interface, go to the configuration of your project and setup all the env variables listed at the top of the config file

With this last step, your production infrastructure is ready to receive the first version of your application.