Skip to content

Instantly share code, notes, and snippets.

@miyamoto-daisuke
Created March 14, 2014 09:35
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save miyamoto-daisuke/9544801 to your computer and use it in GitHub Desktop.
Save miyamoto-daisuke/9544801 to your computer and use it in GitHub Desktop.
AWS CloudFormation tempate for PHP Blue-Green Deployment environment
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "PHP Blue-Green Deployment environment template",
"Parameters" : {
"KeyName" : {
"Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type" : "String",
"MinLength" : "1",
"MaxLength" : "64",
"AllowedPattern" : "[-_ a-zA-Z0-9]*",
"ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores."
},
"DeveloperBlock" : {
"Description" : "Lockdown SSH access to the bastion host (default can be accessed from anywhere)",
"Type" : "String",
"MinLength" : "9",
"MaxLength" : "18",
"Default" : "0.0.0.0/0",
"AllowedPattern" : "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription" : "must be a valid CIDR range of the form x.x.x.x/x."
},
"DBUsername" : {
"Default" : "admin",
"Description" : "The database master account username",
"Type" : "String",
"MinLength" : "1",
"MaxLength" : "16",
"AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
"ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
},
"DBPassword" : {
"Description" : "Password of RDS master password",
"Type" : "String",
"MinLength" : "4"
},
"DBSnapshotName" : {
"Default" : "",
"Description" : "The name of a DB snapshot (optional)",
"Type" : "String"
},
"HostedZone" : {
"Description" : "The DNS name of an existing Amazon Route 53 hosted zone",
"Type" : "String"
},
"ProductionEnv" : {
"Description" : "Production Environment name",
"Default" : "blue",
"Type" : "String",
"AllowedValues" : [ "blue", "green" ],
"ConstraintDescription" : "must specify blue or green."
},
"BlueInstanceType" : {
"Description" : "EC2 instance type for the Blue environment",
"Default" : "t1.micro",
"Type" : "String"
},
"GreenInstanceType" : {
"Description" : "EC2 instance type for the Green environment",
"Default" : "t1.micro",
"Type" : "String"
},
"BlueFleetSize" : {
"Description" : "Number of EC2 instances to launch for the Blue server",
"Default" : "2",
"Type" : "Number",
"MaxValue" : "10",
"MinValue" : "0"
},
"GreenFleetSize" : {
"Description" : "Number of EC2 instances to launch for the Green server",
"Default" : "2",
"Type" : "Number",
"MaxValue" : "10",
"MinValue" : "0"
},
"BlueApplicationURL" : {
"Description" : "Package URL for the Blue environment",
"Type" : "String"
},
"GreenApplicationURL" : {
"Description" : "Package URL for the Green environment",
"Type" : "String"
},
"BlueDatabaseName" : {
"Description" : "DB name for the Blue environment",
"Default" : "production",
"Type" : "String"
},
"GreenDatabaseName" : {
"Description" : "DB name for the Green environment",
"Default" : "development",
"Type" : "String"
}
},
"Conditions" : {
"ProductionBlue" : { "Fn::Equals" : [ { "Ref" : "ProductionEnv" }, "blue" ] },
"ProductionGreen" : { "Fn::Equals" : [ { "Ref" : "ProductionEnv" }, "green" ] },
"UseDBSnapshot" : { "Fn::Not" : [ { "Fn::Equals" : [ { "Ref" : "DBSnapshotName" }, "" ] } ] }
},
"Mappings" : {
"AWSAmazonLinuxAMI" : {
"us-east-1" : { "name" :"Virginia", "201303" : "ami-3275ee5b", "201309" : "ami-35792c5c" },
"us-west-2" : { "name" :"Oregon", "201303" : "ami-ecbe2adc", "201309" : "ami-d03ea1e0" },
"us-west-1" : { "name" :"California", "201303" : "ami-66d1fc23", "201309" : "ami-687b4f2d" },
"eu-west-1" : { "name" :"Ireland", "201303" : "ami-44939930", "201309" : "ami-149f7863" },
"ap-southeast-1" : { "name" :"Singapole", "201303" : "ami-aa9ed2f8", "201309" : "ami-14f2b946" },
"ap-southeast-2" : { "name" :"Sydney", "201303" : "ami-363eaf0c", "201309" : "ami-a148d59b" },
"ap-northeast-1" : { "name" :"Tokyo", "201303" : "ami-173fbf16", "201309" : "ami-3561fe34" },
"sa-east-1" : { "name" :"SaoPaulo", "201303" : "ami-dd6bb0c0", "201309" : "ami-9f6ec982" }
},
"AZ" : {
"us-east-1" : { "primary" : "us-east-1b", "secondary" : "us-east-1c" },
"us-west-2" : { "primary" : "us-west-2a", "secondary" : "us-west-2b" },
"us-west-1" : { "primary" : "us-west-1a", "secondary" : "us-west-1b" },
"eu-west-1" : { "primary" : "eu-west-1a", "secondary" : "eu-west-1b" },
"ap-southeast-1" : { "primary" : "ap-southeast-1a", "secondary" : "ap-southeast-1b" },
"ap-southeast-2" : { "primary" : "ap-southeast-2a", "secondary" : "ap-southeast-2b" },
"ap-northeast-1" : { "primary" : "ap-northeast-1a", "secondary" : "ap-northeast-1c" },
"sa-east-1" : { "primary" : "sa-east-1a", "secondary" : "sa-east-1b" }
},
"StackConfig" : {
"VPC" : { "CIDR" : "10.0.0.0/16" },
"FrontendSubnet1" : { "CIDR" : "10.0.0.0/24" },
"FrontendSubnet2" : { "CIDR" : "10.0.1.0/24" },
"ApplicationSubnet1" : { "CIDR" : "10.0.2.0/24" },
"ApplicationSubnet2" : { "CIDR" : "10.0.3.0/24" },
"DatastoreSubnet1" : { "CIDR" : "10.0.4.0/24" },
"DatastoreSubnet2" : { "CIDR" : "10.0.5.0/24" },
"BastionServer" : { "InstanceType" : "t1.micro" },
"DBServer" : { "InstanceType" : "db.t1.micro", "AllocatedStorage" : "5" }
}
},
"Resources" : {
"PowerUserRole" : {
"Type" : "AWS::IAM::Role",
"Properties" : {
"AssumeRolePolicyDocument" : {
"Statement" : [ {
"Effect" : "Allow",
"Principal" : {
"Service" : [ "ec2.amazonaws.com" ]
},
"Action" : [ "sts:AssumeRole" ]
} ]
},
"Path" : "/",
"Policies" :[ {
"PolicyName" : "PowerUserPolicy",
"PolicyDocument" : {
"Statement" : [ {
"Sid" : "PowerUserStmt",
"Effect" : "Allow",
"NotAction" : "iam:*",
"Resource" : "*"
} ]
}
}]
}
},
"PowerUserProfile" : {
"Type" : "AWS::IAM::InstanceProfile",
"Properties" : {
"Path" : "/",
"Roles" : [ { "Ref" : "PowerUserRole" } ]
}
},
"VPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "VPC", "CIDR" ]},
"EnableDnsSupport" : "true",
"EnableDnsHostnames" : "true",
"InstanceTenancy" : "default",
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"InternetGateway" : {
"Type" : "AWS::EC2::InternetGateway",
"Properties" : {
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"AttachGateway" : {
"Type" : "AWS::EC2::VPCGatewayAttachment",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"InternetGatewayId" : { "Ref" : "InternetGateway" }
}
},
"PublicRouteTable" : {
"Type" : "AWS::EC2::RouteTable",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"PrivateRouteTable" : {
"Type" : "AWS::EC2::RouteTable",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Protected" }
]
}
},
"PublicRoute" : {
"Type" : "AWS::EC2::Route",
"DependsOn" : "AttachGateway",
"Properties" : {
"RouteTableId" : { "Ref" : "PublicRouteTable" },
"DestinationCidrBlock" : "0.0.0.0/0",
"GatewayId" : { "Ref" : "InternetGateway" }
}
},
"FrontendSubnet1" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "primary" ]},
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "FrontendSubnet1", "CIDR" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"FrontendSubnet2" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "secondary" ]},
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "FrontendSubnet2", "CIDR" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"ApplicationSubnet1" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "ApplicationSubnet1", "CIDR" ]},
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "primary" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"ApplicationSubnet2" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "ApplicationSubnet2", "CIDR" ]},
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "secondary" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Public" }
]
}
},
"DatastoreSubnet1" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "DatastoreSubnet1", "CIDR" ]},
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "primary" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Private" }
]
}
},
"DatastoreSubnet2" : {
"Type" : "AWS::EC2::Subnet",
"DependsOn" : "AttachGateway",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"CidrBlock" : { "Fn::FindInMap" : [ "StackConfig", "DatastoreSubnet2", "CIDR" ]},
"AvailabilityZone" : { "Fn::FindInMap" : [ "AZ", { "Ref" : "AWS::Region" }, "secondary" ]},
"Tags" : [
{ "Key" : "Application", "Value" : { "Ref" : "AWS::StackId" } },
{ "Key" : "Network", "Value" : "Private" }
]
}
},
"FrontendSubnet1RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "FrontendSubnet1" },
"RouteTableId" : { "Ref" : "PublicRouteTable" }
}
},
"FrontendSubnet2RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "FrontendSubnet2" },
"RouteTableId" : { "Ref" : "PublicRouteTable" }
}
},
"ApplicationSubnet1RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "ApplicationSubnet1" },
"RouteTableId" : { "Ref" : "PublicRouteTable" }
}
},
"ApplicationSubnet2RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "ApplicationSubnet2" },
"RouteTableId" : { "Ref" : "PublicRouteTable" }
}
},
"DatastoreSubnet1RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "DatastoreSubnet1" },
"RouteTableId" : { "Ref" : "PrivateRouteTable" }
}
},
"DatastoreSubnet2RouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : { "Ref" : "DatastoreSubnet2" },
"RouteTableId" : { "Ref" : "PrivateRouteTable" }
}
},
"VPCDefaultSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"GroupDescription" : "Allow all communications in VPC",
"SecurityGroupIngress" : [
{ "IpProtocol" : "tcp", "FromPort" : "0", "ToPort" : "65535", "CidrIp" : { "Fn::FindInMap" : [ "StackConfig", "VPC", "CIDR" ]} },
{ "IpProtocol" : "udp", "FromPort" : "0", "ToPort" : "65535", "CidrIp" : { "Fn::FindInMap" : [ "StackConfig", "VPC", "CIDR" ]} },
{ "IpProtocol" : "icmp", "FromPort" : "-1", "ToPort" : "-1", "CidrIp" : { "Fn::FindInMap" : [ "StackConfig", "VPC", "CIDR" ]} }
]
}
},
"SSHSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"GroupDescription" : "Enable SSH access via port 22",
"SecurityGroupIngress" : [{
"IpProtocol" : "tcp",
"FromPort" : "22",
"ToPort" : "22",
"CidrIp" : { "Ref" : "DeveloperBlock" }
}]
}
},
"PublicWebSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"GroupDescription" : "Public Security Group with HTTP access on port 443 from the internet",
"SecurityGroupIngress" : [
{ "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" },
{ "IpProtocol" : "tcp", "FromPort" : "443", "ToPort" : "443", "CidrIp" : "0.0.0.0/0" }
]
}
},
"ApplicationSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"GroupDescription" : "Marker security group for Application server."
}
},
"MySQLSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : { "Ref" : "VPC" },
"GroupDescription" : "Marker security group for MySQL server."
}
},
"BastionDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [{ "Ref" : "HostedZone" }, "." ]]},
"Comment" : "A record for the Bastion instance.",
"Name" : { "Fn::Join" : [ "", [ "bastion.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"TTL" : "300",
"ResourceRecords" : [
{ "Ref" :"BastionInstanceEIP" }
]
}
},
"BastionLocalDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [{ "Ref" : "HostedZone" }, "." ]]},
"Comment" : "A record for the private IP address of Bastion instance.",
"Name" : { "Fn::Join" : [ "", [ "bastion.local.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"TTL" : "300",
"ResourceRecords" : [
{ "Fn::GetAtt" : [ "BastionInstance", "PrivateIp" ] }
]
}
},
"BastionInstanceEIP" : {
"Type" : "AWS::EC2::EIP",
"DependsOn" : "AttachGateway",
"Properties" : {
"Domain" : "vpc",
"InstanceId" : { "Ref" : "BastionInstance" }
}
},
"BastionWaitHandle" : {
"Type" : "AWS::CloudFormation::WaitConditionHandle"
},
"BastionWaitCondition" : {
"Type" : "AWS::CloudFormation::WaitCondition",
"DependsOn" : "BastionInstance",
"Properties" : {
"Handle" : { "Ref" : "BastionWaitHandle" },
"Timeout" : "900"
}
},
"BastionInstance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"InstanceType" : { "Fn::FindInMap" : [ "StackConfig", "BastionServer", "InstanceType" ]},
"KeyName" : { "Ref" : "KeyName" },
"SubnetId" : { "Ref" : "FrontendSubnet1" },
"ImageId" : { "Fn::FindInMap" : [ "AWSAmazonLinuxAMI", { "Ref" : "AWS::Region" }, "201309" ]},
"IamInstanceProfile" : { "Ref" : "PowerUserProfile" },
"SecurityGroupIds" : [
{ "Ref" : "SSHSecurityGroup" },
{ "Ref" : "VPCDefaultSecurityGroup" }
],
"Tags" : [
{ "Key" : "Name", "Value" : "Bastion" }
],
"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [
"#! /bin/bash -v\n",
"yum update -y\n",
"# Helper function\n",
"function error_exit\n",
"{\n",
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "BastionWaitHandle" }, "'\n",
" exit 1\n",
"}\n",
"# Install packages\n",
"/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r BastionInstance ",
" --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n",
"# All is well so signal success\n",
"/opt/aws/bin/cfn-signal -e $? -r \"BastionInstance setup complete\" '", { "Ref" : "BastionWaitHandle" }, "'\n"
]]}}
},
"Metadata" : {
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"mysql55" : [],
"jq" : [],
"python-magic" : []
}
}
}
}
}
},
"PublicLoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSetGroup",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "Zone apex alias targeted to LoadBalancer.",
"RecordSets" : [
{
"Name" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : { "Fn::If" : [ "ProductionBlue",
{
"HostedZoneId" : { "Fn::GetAtt" : [ "BlueElasticLoadBalancer", "CanonicalHostedZoneNameID" ] },
"DNSName" : { "Fn::GetAtt" : [ "BlueElasticLoadBalancer","CanonicalHostedZoneName" ] }
},
{
"HostedZoneId" : { "Fn::GetAtt" : [ "GreenElasticLoadBalancer", "CanonicalHostedZoneNameID" ] },
"DNSName" : { "Fn::GetAtt" : [ "GreenElasticLoadBalancer","CanonicalHostedZoneName" ] }
}
]}
}
]
}
},
"BlueLoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSetGroup",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "Zone apex alias targeted to LoadBalancer.",
"RecordSets" : [
{
"Name" : { "Fn::Join" : [ "", [ "blue.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : { "Fn::GetAtt" : [ "BlueElasticLoadBalancer", "CanonicalHostedZoneNameID" ] },
"DNSName" : { "Fn::GetAtt" : [ "BlueElasticLoadBalancer","CanonicalHostedZoneName" ] }
}
}
]
}
},
"GreenLoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSetGroup",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "Zone apex alias targeted to LoadBalancer.",
"RecordSets" : [
{
"Name" : { "Fn::Join" : [ "", [ "green.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : { "Fn::GetAtt" : [ "GreenElasticLoadBalancer", "CanonicalHostedZoneNameID" ] },
"DNSName" : { "Fn::GetAtt" : [ "GreenElasticLoadBalancer","CanonicalHostedZoneName" ] }
}
}
]
}
},
"BlueElasticLoadBalancer" : {
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
"DependsOn" : "AttachGateway",
"Properties" : {
"Subnets" : [
{ "Ref" : "FrontendSubnet1" },
{ "Ref" : "FrontendSubnet2" }
],
"Listeners" : [
{ "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" }
],
"HealthCheck" : {
"Target" : "HTTP:80/healthcheck.php",
"HealthyThreshold" : "2",
"UnhealthyThreshold" : "2",
"Interval" : "6",
"Timeout" : "5"
},
"SecurityGroups" : [
{ "Ref" : "PublicWebSecurityGroup" }
]
}
},
"GreenElasticLoadBalancer" : {
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
"DependsOn" : "AttachGateway",
"Properties" : {
"Subnets" : [
{ "Ref" : "FrontendSubnet1" },
{ "Ref" : "FrontendSubnet2" }
],
"Listeners" : [
{ "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" }
],
"HealthCheck" : {
"Target" : "HTTP:80/healthcheck.php",
"HealthyThreshold" : "2",
"UnhealthyThreshold" : "2",
"Interval" : "6",
"Timeout" : "5"
},
"SecurityGroups" : [
{ "Ref" : "PublicWebSecurityGroup" }
]
}
},
"BlueApplicationFleet" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"DependsOn" : "PublicRoute",
"UpdatePolicy" : {
"AutoScalingRollingUpdate" : {
"MaxBatchSize" : "1",
"MinInstancesInService" : "1",
"PauseTime" : "PT2M30S"
}
},
"Properties" : {
"AvailabilityZones" : [
{ "Fn::GetAtt" : [ "ApplicationSubnet1", "AvailabilityZone" ] },
{ "Fn::GetAtt" : [ "ApplicationSubnet2", "AvailabilityZone" ] }
],
"VPCZoneIdentifier" : [
{ "Ref" : "ApplicationSubnet1" },
{ "Ref" : "ApplicationSubnet2" }
],
"LaunchConfigurationName" : { "Ref" : "BlueApplicationServerLaunchConfig" },
"MinSize" : "0",
"MaxSize" : "10",
"DesiredCapacity" : { "Ref" : "BlueFleetSize" },
"LoadBalancerNames" : [ { "Ref" : "BlueElasticLoadBalancer" } ],
"Tags" : [
{ "Key" : "Name", "Value" : "BlueApplication", "PropagateAtLaunch" : "true" }
]
}
},
"GreenApplicationFleet" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"DependsOn" : "PublicRoute",
"UpdatePolicy" : {
"AutoScalingRollingUpdate" : {
"MaxBatchSize" : "1",
"MinInstancesInService" : "1",
"PauseTime" : "PT2M30S"
}
},
"Properties" : {
"AvailabilityZones" : [
{ "Fn::GetAtt" : [ "ApplicationSubnet1", "AvailabilityZone" ] },
{ "Fn::GetAtt" : [ "ApplicationSubnet2", "AvailabilityZone" ] }
],
"VPCZoneIdentifier" : [
{ "Ref" : "ApplicationSubnet1" },
{ "Ref" : "ApplicationSubnet2" }
],
"LaunchConfigurationName" : { "Ref" : "GreenApplicationServerLaunchConfig" },
"MinSize" : "0",
"MaxSize" : "10",
"DesiredCapacity" : { "Ref" : "GreenFleetSize" },
"LoadBalancerNames" : [ { "Ref" : "GreenElasticLoadBalancer" } ],
"Tags" : [
{ "Key" : "Name", "Value" : "GreenApplication", "PropagateAtLaunch" : "true" }
]
}
},
"BlueApplicationServerLaunchConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Properties" : {
"InstanceType" : { "Ref" : "BlueInstanceType" },
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Fn::FindInMap" : [ "AWSAmazonLinuxAMI", { "Ref" : "AWS::Region" }, "201309" ]},
"SecurityGroups" : [
{ "Ref" : "VPCDefaultSecurityGroup" },
{ "Ref" : "ApplicationSecurityGroup" }
],
"IamInstanceProfile" : { "Ref" : "PowerUserProfile" },
"InstanceMonitoring" : "false",
"AssociatePublicIpAddress" : "true",
"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [
"#! /bin/bash -v\n",
"yum update -y\n",
"# Install packages\n",
"/opt/aws/bin/cfn-init -v",
" -s ", { "Ref" : "AWS::StackId" },
" -r BlueApplicationServerLaunchConfig",
" -c create",
" --region ", { "Ref" : "AWS::Region" }, " || exit 1\n",
"cat << _EOF_ >>/etc/rc.local\n",
"# Start up the cfn-hup daemon to listen for changes\n",
"/opt/aws/bin/cfn-hup\n",
"_EOF_"
]]}}
},
"Metadata" : {
"AWS::CloudFormation::Init" : {
"configSets" : {
"create" : [
"install_packages",
"configure_cfnhup",
"cleanup_application",
"install_application"
],
"update" : [
"install_packages",
"configure_cfnhup",
"cleanup_application",
"install_application"
]
},
"install_packages" : {
"packages" : {
"yum" : {
"httpd24" : [],
"mysql55" : [],
"php54" : [],
"php54-mbstring" : [],
"php54-mysql" : [],
"php54-pdo" : [],
"jq" : [],
"python-magic" : []
}
}
},
"configure_cfnhup" : {
"files" : {
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : [ "", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackName" }, "\n",
"interval=1\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content" : { "Fn::Join" : [ "", [
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.BlueApplicationServerLaunchConfig.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -v",
" -s ", { "Ref" : "AWS::StackName" },
" -r BlueApplicationServerLaunchConfig",
" -c update",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]}
}
}
},
"cleanup_application" : {
"commands" : {
"remove" : {
"command" : "rm -fr /var/www/html/*"
}
}
},
"install_application" : {
"sources" : {
"/var/www/html" : { "Ref" : "BlueApplicationURL" }
},
"files" : {
"/etc/httpd/conf.d/envs.conf" : {
"content" : { "Fn::Join" : [ "", [
"SetEnv RDS_HOSTNAME ", { "Fn::Join" : [ "", [ "rds.local.", { "Ref" : "HostedZone" } ]]}, "\n",
"SetEnv RDS_PORT ", { "Fn::GetAtt" : [ "DBInstance", "Endpoint.Port" ] }, "\n",
"SetEnv RDS_USERNAME ", { "Ref" : "DBUsername" }, "\n",
"SetEnv RDS_PASSWORD ", { "Ref" : "DBPassword" }, "\n",
"SetEnv RDS_DB_NAME ", { "Ref" : "BlueDatabaseName" }, "\n"
]]},
"mode" : "000644",
"owner" : "apache",
"group" : "apache"
}
},
"commands" : {
"chown" : {
"command" : "chown -R apache:root /var/www/html/* && chmod -R 644 /var/www/html/*"
}
},
"services" : {
"sysvinit" : {
"httpd" : {
"enabled" : "true",
"ensureRunning" : "true",
"files" : [ "/etc/httpd/conf.d/envs.conf" ],
"sources" : [ "/var/www/html" ]
}
}
}
}
}
}
},
"GreenApplicationServerLaunchConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Properties" : {
"InstanceType" : { "Ref" : "GreenInstanceType" },
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Fn::FindInMap" : [ "AWSAmazonLinuxAMI", { "Ref" : "AWS::Region" }, "201309" ]},
"SecurityGroups" : [
{ "Ref" : "VPCDefaultSecurityGroup" },
{ "Ref" : "ApplicationSecurityGroup" }
],
"IamInstanceProfile" : { "Ref" : "PowerUserProfile" },
"InstanceMonitoring" : "false",
"AssociatePublicIpAddress" : "true",
"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [
"#! /bin/bash -v\n",
"yum update -y\n",
"# Install packages\n",
"/opt/aws/bin/cfn-init -v",
" -s ", { "Ref" : "AWS::StackId" },
" -r GreenApplicationServerLaunchConfig",
" -c create",
" --region ", { "Ref" : "AWS::Region" }, " || exit 1\n",
"cat << _EOF_ >>/etc/rc.local\n",
"# Start up the cfn-hup daemon to listen for changes\n",
"/opt/aws/bin/cfn-hup\n",
"_EOF_"
]]}}
},
"Metadata" : {
"AWS::CloudFormation::Init" : {
"configSets" : {
"create" : [
"install_packages",
"configure_cfnhup",
"cleanup_application",
"install_application"
],
"update" : [
"install_packages",
"configure_cfnhup",
"cleanup_application",
"install_application"
]
},
"install_packages" : {
"packages" : {
"yum" : {
"httpd24" : [],
"mysql55" : [],
"php54" : [],
"php54-mbstring" : [],
"php54-mysql" : [],
"php54-pdo" : [],
"jq" : [],
"python-magic" : []
}
}
},
"configure_cfnhup" : {
"files" : {
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : [ "", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackName" }, "\n",
"interval=1\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content" : { "Fn::Join" : [ "", [
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.GreenApplicationServerLaunchConfig.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -v",
" -s ", { "Ref" : "AWS::StackName" },
" -r GreenApplicationServerLaunchConfig",
" -c update",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]}
}
}
},
"cleanup_application" : {
"commands" : {
"remove" : {
"command" : "rm -fr /var/www/html/*"
}
}
},
"install_application" : {
"sources" : {
"/var/www/html" : { "Ref" : "GreenApplicationURL" }
},
"files" : {
"/etc/httpd/conf.d/envs.conf" : {
"content" : { "Fn::Join" : [ "", [
"SetEnv RDS_HOSTNAME ", { "Fn::Join" : [ "", [ "rds.local.", { "Ref" : "HostedZone" } ]]}, "\n",
"SetEnv RDS_PORT ", { "Fn::GetAtt" : [ "DBInstance", "Endpoint.Port" ] }, "\n",
"SetEnv RDS_USERNAME ", { "Ref" : "DBUsername" }, "\n",
"SetEnv RDS_PASSWORD ", { "Ref" : "DBPassword" }, "\n",
"SetEnv RDS_DB_NAME ", { "Ref" : "GreenDatabaseName" }, "\n"
]]},
"mode" : "000644",
"owner" : "apache",
"group" : "apache"
}
},
"commands" : {
"chown" : {
"command" : "chown -R apache:root /var/www/html/* && chmod -R 644 /var/www/html/*"
}
},
"services" : {
"sysvinit" : {
"httpd" : {
"enabled" : "true",
"ensureRunning" : "true",
"files" : [ "/etc/httpd/conf.d/envs.conf" ],
"sources" : [ "/var/www/html" ]
}
}
}
}
}
}
},
"DatabaseDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "CNAME for the database.",
"Name" : { "Fn::Join" : [ "", [ "rds.local.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "CNAME",
"TTL" : "300",
"ResourceRecords" : [
{ "Fn::GetAtt" : [ "DBInstance", "Endpoint.Address" ] }
]
}
},
"DBParamGroup" : {
"Type" : "AWS::RDS::DBParameterGroup",
"Properties" : {
"Description" : "Default parameter group",
"Family" : "MySQL5.6",
"Parameters" : {
"character_set_database" : "utf8mb4",
"character_set_client" : "utf8mb4",
"character_set_connection" : "utf8mb4",
"character_set_results" : "utf8mb4",
"character_set_server" : "utf8mb4",
"skip-character-set-client-handshake" : "TRUE"
}
}
},
"DBSubnetGroup" : {
"Type" : "AWS::RDS::DBSubnetGroup",
"Properties" : {
"DBSubnetGroupDescription" : "Database subnets for RDS",
"SubnetIds" : [
{ "Ref" : "DatastoreSubnet1" },
{ "Ref" : "DatastoreSubnet2" }
]
}
},
"DBInstance" : {
"Type" : "AWS::RDS::DBInstance",
"DeletionPolicy" : "Snapshot",
"Properties" : {
"DBInstanceClass" : { "Fn::FindInMap" : [ "StackConfig", "DBServer", "InstanceType" ]},
"AllocatedStorage" : { "Fn::FindInMap" : [ "StackConfig", "DBServer", "AllocatedStorage" ]},
"Engine" : "MySQL",
"MultiAZ" : "true",
"EngineVersion" : "5.6.13",
"MasterUsername" : { "Ref" : "DBUsername" },
"MasterUserPassword" : { "Ref" : "DBPassword" },
"BackupRetentionPeriod" : "35",
"DBParameterGroupName" : { "Ref" : "DBParamGroup" },
"DBSubnetGroupName" : { "Ref" : "DBSubnetGroup" },
"DBSnapshotIdentifier" : { "Fn::If" : [
"UseDBSnapshot",
{ "Ref" : "DBSnapshotName" },
{ "Ref" : "AWS::NoValue" }
]},
"PreferredBackupWindow" : "19:00-19:30",
"PreferredMaintenanceWindow" : "sat:20:00-sat:20:30",
"VPCSecurityGroups" : [
{ "Ref" : "VPCDefaultSecurityGroup" },
{ "Ref" : "MySQLSecurityGroup" }
]
}
}
},
"Outputs" : {
"SSHToBackendServer" : {
"Value" : { "Fn::Join" :[ "", [
"ssh -i /path/to/", { "Ref" : "KeyName" }, ".pem",
" -oProxyCommand='ssh -i /path/to/", { "Ref" : "KeyName" }, ".pem",
" -W %h:%p ec2-user@", { "Ref" : "BastionDNSRecord" }, "'",
" ec2-user@<private-ip>"
]]},
"Description" : "SSH command to connect to the backend servers"
}
}
}
@deitch
Copy link

deitch commented Jul 7, 2014

This is nice, encapsulates and entire blue-green environment inside a VPC, bastion host and all. Two questions for you:

  1. How do you actually switch the prod env? Do you just modify the params of the running stack?
  2. How do you do software deployment?

@dai0304
Copy link

dai0304 commented Sep 19, 2014

  1. When the prod env is now blue, to switch the production env to green, you can set ProductionEnv parameter to 'green' and update stack.
    http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_UpdateStack.html
  2. This templete assume that your application bundle (archive) is placed at BlueApplicationURL and GreenApplicationURL. Each ec2 instances will download bundle and expand to /var/www/html/.

@stevenao
Copy link

stevenao commented Jan 4, 2016

Both Green and Blue instances application will be updated when you do a UpdateStack. I don't think that is the desire behaviour for blue green deployment. We want to update the group that is not currently in production only. Later, we will route traffic to the staging group.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment