├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── cdk.json ├── cdk_python ├── __init__.py ├── app_stack.py └── vpc_stack.py ├── requirements-dev.txt ├── requirements.txt ├── source.bat └── tests ├── __init__.py └── unit ├── __init__.py └── test_cdk_python_stack.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .venv 6 | *.egg-info 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DailyDevOps Projects 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # three tier architecture with cdk 3 | 4 | This repo contains code for a three tier application consisting of web layer, app layer and data layer. 5 | The template is highly customizable with many features thanks to the CDK's flexibility, including custom IP's for the VPC and the optional provisioning of the database layer. 6 | 7 | ## Useful commands 8 | 9 | * `cdk ls` list all stacks in the app 10 | * `cdk synth` emits the synthesized CloudFormation template 11 | * `cdk deploy` deploy this stack to your default AWS account/region 12 | * `cdk diff` compare deployed stack with current state 13 | * `cdk docs` open CDK documentation 14 | 15 | Enjoy! 16 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | import aws_cdk as cdk 5 | 6 | from cdk_python.app_stack import AppStack 7 | 8 | 9 | app = cdk.App() 10 | AppStack(app, "CdkPythonStack", three_tier=False) 11 | 12 | app.synth() 13 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python app.py", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "requirements*.txt", 11 | "source.bat", 12 | "**/__init__.py", 13 | "python/__pycache__", 14 | "tests" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 19 | "@aws-cdk/core:stackRelativeExports": true, 20 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 21 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 22 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 23 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 24 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 25 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 26 | "@aws-cdk/core:checkSecretUsage": true, 27 | "@aws-cdk/aws-iam:minimizePolicies": true, 28 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 29 | "@aws-cdk/core:target-partitions": [ 30 | "aws", 31 | "aws-cn" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cdk_python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Devops-Projects/3tier-architecture-with-cdk/aa3cc6326d2c425297007aaa866f96a83feb9817/cdk_python/__init__.py -------------------------------------------------------------------------------- /cdk_python/app_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | Stack, 3 | aws_ec2 as ec2, 4 | aws_autoscaling as autoscaling, 5 | aws_elasticloadbalancingv2 as elbv2, 6 | aws_iam as iam, 7 | aws_rds as rds, 8 | Duration 9 | ) 10 | from constructs import Construct 11 | from .vpc_stack import vpcStack 12 | 13 | 14 | class AppStack(Stack): 15 | 16 | def __init__(self, scope: Construct, construct_id: str, three_tier=True, **kwargs) -> None: 17 | super().__init__(scope, construct_id, **kwargs) 18 | 19 | # Attempt to get custom cidr from context if provided 20 | context_cidr = self.node.try_get_context("cidr") 21 | 22 | # Creating the networking resources 23 | network = vpcStack(self, 'vpc', three_tier=three_tier, vpc_cidr=context_cidr if context_cidr else '10.0.0.0/16') 24 | 25 | 26 | asg_role = iam.Role(self, "MyRole", 27 | assumed_by=iam.ServicePrincipal("ec2.amazonaws.com") 28 | ) 29 | asg_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonEC2RoleforSSM")) 30 | 31 | asg = autoscaling.AutoScalingGroup(self, "ASG", 32 | vpc=network.vpc, 33 | instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO), 34 | machine_image=ec2.GenericLinuxImage({'us-east-1':'ami-0cff7528ff583bf9a'}), 35 | security_group=network.asg_sg, 36 | cooldown=Duration.minutes(1), 37 | key_name='n-virginia-nedranboss', 38 | min_capacity=2, 39 | max_capacity=8, 40 | role=asg_role, 41 | vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC), 42 | user_data=ec2.UserData.custom( 43 | ''' 44 | #!/bin/bash 45 | sudo yum update -y 46 | sudo yum install -y httpd.x86_64 47 | sudo yum install -y amazon-cloudwatch-agent 48 | sudo systemctl start httpd 49 | sudo systemctl enable httpd 50 | sudo systemctl start amazon-ssm-agent 51 | sudo su 52 | sudo chmod 755 -R /var/www/html 53 | sudo echo "hello world from $(hostname -f)" >> /var/www/html/index.html 54 | ''' 55 | ) 56 | ) 57 | 58 | bastion = ec2.BastionHostLinux(self, "BastionHost", 59 | vpc=network.vpc, 60 | key_name='n-virginia-nedranboss', 61 | security_group=network.bastion_sg, 62 | subnet_selection=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC) 63 | ) 64 | 65 | # Configure load balancer 66 | lb = elbv2.ApplicationLoadBalancer(self, "LB", 67 | vpc=network.vpc, 68 | internet_facing=True, 69 | security_group=network.lb_sg 70 | ) 71 | 72 | listener = lb.add_listener("Listener", 73 | port=80 74 | ) 75 | 76 | listener.add_targets("ApplicationFleet", 77 | port=80, 78 | targets=[asg] 79 | ) 80 | 81 | if three_tier: 82 | # in case a three tier architecture is specified, create a database and a 83 | # secrets manager secret, and grants get secret value role to the 84 | # application fleet 85 | db_secrets = rds.DatabaseSecret(self, 'postgres-secret', 86 | username='postgres', 87 | secret_name='postgres-credentials' 88 | ) 89 | 90 | db = rds.DatabaseInstance(self, "db", 91 | engine=rds.DatabaseInstanceEngine.postgres(version=rds.PostgresEngineVersion.VER_13_4), 92 | instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO), 93 | credentials=rds.Credentials.from_secret(db_secrets), 94 | vpc=network.vpc, 95 | multi_az=True, 96 | security_groups=[network.db_SG], 97 | vpc_subnets=ec2.SubnetSelection( 98 | subnet_type=ec2.SubnetType.PRIVATE_ISOLATED 99 | ) 100 | ) 101 | 102 | asg.add_to_role_policy(iam.PolicyStatement( 103 | resources=[db_secrets.secret_arn], 104 | actions=[ 105 | "secretsmanager:GetSecretValue"] 106 | )) 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /cdk_python/vpc_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | Stack, 3 | aws_ec2 as ec2 4 | ) 5 | from constructs import Construct 6 | 7 | 8 | class vpcStack(Construct): 9 | 10 | def __init__(self, scope: Construct, construct_id: str, vpc_cidr='10.0.0.0/16', three_tier=True,**kwargs) -> None: 11 | super().__init__(scope, construct_id, **kwargs) 12 | ''' AWS CDK Construct that creates a configurable VPC for hosting a three tier architecture''' 13 | if three_tier: 14 | self.vpc = ec2.Vpc( 15 | self, 'vpc', 16 | cidr=vpc_cidr, 17 | max_azs=2, 18 | nat_gateways=2, 19 | enable_dns_hostnames=True, 20 | enable_dns_support=True, 21 | subnet_configuration=[ 22 | ec2.SubnetConfiguration( 23 | name= 'load_balancer', 24 | subnet_type= ec2.SubnetType.PUBLIC, 25 | cidr_mask= 24 26 | ), 27 | ec2.SubnetConfiguration( 28 | name= 'application', 29 | subnet_type= ec2.SubnetType.PRIVATE_WITH_NAT, 30 | cidr_mask= 24 31 | ), 32 | ec2.SubnetConfiguration( 33 | name= 'database', 34 | subnet_type= ec2.SubnetType.PRIVATE_ISOLATED, 35 | cidr_mask= 24 36 | ) 37 | ] 38 | ) 39 | else: 40 | self.vpc = ec2.Vpc( 41 | self, 'vpc', 42 | cidr=vpc_cidr, 43 | max_azs=2, 44 | nat_gateways=2, 45 | enable_dns_hostnames=True, 46 | enable_dns_support=True, 47 | subnet_configuration=[ 48 | ec2.SubnetConfiguration( 49 | name= 'load_balancer', 50 | subnet_type= ec2.SubnetType.PUBLIC, 51 | cidr_mask= 24 52 | ), 53 | ec2.SubnetConfiguration( 54 | name= 'application', 55 | subnet_type= ec2.SubnetType.PRIVATE_WITH_NAT, 56 | cidr_mask= 24 57 | ) 58 | ] 59 | ) 60 | 61 | 62 | # Load balancer security group 63 | self.lb_sg = ec2.SecurityGroup( 64 | self, 'lb-SG', 65 | vpc=self.vpc, 66 | allow_all_outbound=True 67 | ) 68 | 69 | self.lb_sg.add_ingress_rule( 70 | ec2.Peer.any_ipv4(), 71 | ec2.Port.tcp(443) 72 | ) 73 | 74 | self.lb_sg.add_ingress_rule( 75 | ec2.Peer.any_ipv4(), 76 | ec2.Port.tcp(80) 77 | ) 78 | 79 | 80 | # Bastion security group 81 | self.bastion_sg = ec2.SecurityGroup( 82 | self, 'bastion-SG', 83 | vpc=self.vpc, 84 | allow_all_outbound=True 85 | ) 86 | 87 | self.bastion_sg.add_ingress_rule( 88 | ec2.Peer.any_ipv4(), 89 | ec2.Port.tcp(22) 90 | ) 91 | 92 | # Application layer security group 93 | self.asg_sg = ec2.SecurityGroup( 94 | self, 'asg-SG', 95 | vpc=self.vpc, 96 | allow_all_outbound=True 97 | ) 98 | 99 | self.asg_sg.add_ingress_rule( 100 | self.lb_sg, 101 | ec2.Port.tcp(443) 102 | ) 103 | 104 | self.asg_sg.add_ingress_rule( 105 | self.lb_sg, 106 | ec2.Port.tcp(80) 107 | ) 108 | 109 | self.asg_sg.add_ingress_rule( 110 | self.bastion_sg, 111 | ec2.Port.tcp(22) 112 | ) 113 | 114 | # Database security group if three tier is specified 115 | if three_tier: 116 | self.db_SG = ec2.SecurityGroup( 117 | self, 'db-SG', 118 | vpc=self.vpc, 119 | allow_all_outbound=True 120 | ) 121 | 122 | self.db_SG.add_ingress_rule( 123 | self.asg_sg, 124 | ec2.Port.tcp(5432) 125 | ) 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib==2.28.1 2 | constructs>=10.0.0,<11.0.0 3 | -------------------------------------------------------------------------------- /source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Devops-Projects/3tier-architecture-with-cdk/aa3cc6326d2c425297007aaa866f96a83feb9817/tests/__init__.py -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Devops-Projects/3tier-architecture-with-cdk/aa3cc6326d2c425297007aaa866f96a83feb9817/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_cdk_python_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as core 2 | import aws_cdk.assertions as assertions 3 | 4 | from cdk_python.cdk_python_stack import CdkPythonStack 5 | 6 | # example tests. To run these tests, uncomment this file along with the example 7 | # resource in cdk_python/cdk_python_stack.py 8 | def test_sqs_queue_created(): 9 | app = core.App() 10 | stack = CdkPythonStack(app, "cdk-python") 11 | template = assertions.Template.from_stack(stack) 12 | 13 | # template.has_resource_properties("AWS::SQS::Queue", { 14 | # "VisibilityTimeout": 300 15 | # }) 16 | --------------------------------------------------------------------------------