Enhance your project Makefile
in the project directory with the following content:
.PHONY: app
app: guard-STAGE
aws cloudformation deploy \
--no-fail-on-empty-changeset \
--template-file cloudformation/app.yml \
--stack-name $(SERVICE)-$(STAGE)-app \
--parameter-overrides Stage=$(STAGE) \
--capabilities CAPABILITY_IAM \
--region $(AWS_REGION)
.PHONY: guard-%
guard-%:
$(if $(value ${*}),,$(error "Variable ${*} not set!"))
Create a new cloudformation template in cloudformation/app.yml
and define your application stack in there:
---
AWSTemplateFormatVersion: 2010-09-09
Description: Webapp for '...'
Parameters:
Stage:
Type: String
Mappings:
ELBHostedZoneId:
eu-central-1:
HostedZoneId: Z215JYRZR1TBD5
Resources:
Application:
Type: AWS::ElasticBeanstalk::Application
Properties:
ApplicationName: !Ref AWS::StackName
Environment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
ApplicationName: !Ref Application
EnvironmentName: !Sub '${AWS::StackName}-active'
SolutionStackName: "64bit Amazon Linux 2018.03 v2.12.14 running Docker 18.06.1-ce"
OptionSettings:
- Namespace: 'aws:autoscaling:launchconfiguration'
OptionName: IamInstanceProfile
Value: !Ref InstanceProfile
- Namespace: 'aws:autoscaling:launchconfiguration'
OptionName: InstanceType
Value: 't3.nano'
- Namespace: 'aws:autoscaling:asg'
OptionName: MinSize
Value: '2'
- Namespace: 'aws:autoscaling:asg'
OptionName: MaxSize
Value: '10'
- Namespace: 'aws:elasticbeanstalk:environment'
OptionName: ServiceRole
Value: !Ref ServiceRole
- Namespace: 'aws:elasticbeanstalk:command'
OptionName: DeploymentPolicy
Value: 'Rolling'
- Namespace: 'aws:elasticbeanstalk:environment'
OptionName: LoadBalancerType
Value: 'application'
- Namespace: 'aws:elbv2:listener:443'
OptionName: ListenerEnabled
Value: 'true'
- Namespace: 'aws:elbv2:listener:443'
OptionName: Protocol
Value: 'HTTPS'
- Namespace: 'aws:elbv2:listener:443'
OptionName: SSLCertificateArns
Value:
'Fn::ImportValue': !Sub 'certificates-${Stage}-CertificateArn'
- Namespace: 'aws:elasticbeanstalk:environment:process:default'
OptionName: HealthCheckPath
Value: '/counter/health'
- Namespace: 'aws:elb:healthcheck'
OptionName: Interval
Value: '5'
- Namespace: 'aws:elasticbeanstalk:healthreporting:system'
OptionName: SystemType
Value: 'enhanced'
- Namespace: 'aws:elasticbeanstalk:cloudwatch:logs:health'
OptionName: HealthStreamingEnabled
Value: 'true'
- Namespace: 'aws:ec2:vpc'
OptionName: VPCId
Value:
'Fn::ImportValue': 'vpc-VPC'
- Namespace: 'aws:ec2:vpc'
OptionName: Subnets
Value:
'Fn::ImportValue': 'vpc-SubnetsPublic'
- Namespace: 'aws:ec2:vpc'
OptionName: ELBSubnets
Value:
'Fn::ImportValue': 'vpc-SubnetsPublic'
- Namespace: 'aws:elasticbeanstalk:environment:proxy'
OptionName: ProxyServer
Value: 'none'
- Namespace: 'aws:ec2:vpc'
OptionName: AssociatePublicIpAddress
Value: 'true'
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: MYSQL_USER
Value: "{{resolve:secretsmanager:REPLACE_ME-db:SecretString:username}}"
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: MYSQL_PASSWORD
Value: "{{resolve:secretsmanager:REPLACE_ME-db:SecretString:password}}"
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: MYSQL_HOST
Value: "{{resolve:secretsmanager:REPLACE_ME-db:SecretString:host}}"
- Namespace: 'aws:elasticbeanstalk:application:environment'
OptionName: MYSQL_DATABASE
Value: "{{resolve:secretsmanager:REPLACE_ME-db:SecretString:dbname}}"
LoadBalancerRecordSet:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId:
'Fn::ImportValue': !Sub 'dns-${Stage}-HostedZoneId'
Name:
'Fn::ImportValue': !Sub 'dns-${Stage}-HostedZoneName'
Type: A
AliasTarget:
HostedZoneId:
'Fn::FindInMap': [ "ELBHostedZoneId", !Ref "AWS::Region", "HostedZoneId" ]
DNSName: !GetAtt Environment.EndpointURL
ServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Sid: ''
Effect: Allow
Principal:
Service: "elasticbeanstalk.amazonaws.com"
Action: 'sts:AssumeRole'
Condition:
StringEquals:
'sts:ExternalId': elasticbeanstalk
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth'
Path: /
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref InstanceProfileRole
InstanceProfileRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowEbAuth
Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource:
- "*"
- Sid: AllowPull
Effect: Allow
Resource:
- "*"
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:BatchGetImage
- Sid: BucketAccess
Action:
- 's3:Get*'
- 's3:List*'
- 's3:PutObject'
Effect: Allow
Resource:
- !Join
- ''
- - 'arn:'
- "aws"
- ':s3:::elasticbeanstalk-*-'
- !Ref 'AWS::AccountId'
- !Join
- ''
- - 'arn:'
- "aws"
- ':s3:::elasticbeanstalk-*-'
- !Ref 'AWS::AccountId'
- /*
- !Join
- ''
- - 'arn:'
- "aws"
- ':s3:::elasticbeanstalk-*-'
- !Ref 'AWS::AccountId'
- '-*'
- !Join
- ''
- - 'arn:'
- "aws"
- ':s3:::elasticbeanstalk-*-'
- !Ref 'AWS::AccountId'
- '-*/*'
- Sid: ECSAccess
Effect: Allow
Action:
- 'ecs:StartTask'
- 'ecs:StopTask'
- 'ecs:RegisterContainerInstance'
- 'ecs:DeregisterContainerInstance'
- 'ecs:DescribeContainerInstances'
- 'ecs:DiscoverPollEndpoint'
- 'ecs:Submit*'
- 'ecs:Poll'
Resource: '*'
- Sid: QueueAccess
Action:
- 'sqs:ChangeMessageVisibility'
- 'sqs:DeleteMessage'
- 'sqs:ReceiveMessage'
- 'sqs:SendMessage'
Effect: Allow
Resource: '*'
- Sid: DynamoPeriodicTasks
Action:
- 'dynamodb:BatchGetItem'
- 'dynamodb:BatchWriteItem'
- 'dynamodb:DeleteItem'
- 'dynamodb:GetItem'
- 'dynamodb:PutItem'
- 'dynamodb:Query'
- 'dynamodb:Scan'
- 'dynamodb:UpdateItem'
Effect: Allow
Resource:
- !Join
- ''
- - 'arn:'
- "aws"
- ':dynamodb:*:'
- !Ref 'AWS::AccountId'
- ':table/*-stack-AWSEBWorkerCronLeaderRegistry*'
- Sid: MetricsAccess
Action:
- 'cloudwatch:PutMetricData'
Effect: Allow
Resource: '*'
Create the Cloudformation stack with the following command in the app-staging AWS account
AWS_PROFILE=myorg-app-staging make app STAGE=dev