1. Home

Anil Lakhman

< Blog />

PHP stack template for AWS Cloudformation

in

This was a template I created with troposphere and launches a PHP stack on AWS via cloudformation.

It takes no parameters, but depends on the following Exports from another cloudformation stack.

ElasticIP
A export named ElasticIP (Domain Stack)
VPC
A export named VPC (VPC Stack) to add security groups
Subnet1
A export named Subnet1 (VPC Stack) to add our instance into

Note

  • I was testing this in the eu-west-2 (London) region, you may have to modify this to match your geographic region
  • To build this template use pip install troposphere and then run the file via python php-stack.py

Important

This template depends on the following stacks.

Hint

Checkout the following log files if you’re having any issues.

  • cat /var/log/cloud-init-output.log
  • cd /var/lib/cloud/instances/**/
# -*- coding: utf-8 -*-
from __future__ import print_function
import troposphere.ec2 as ec2
import os
from troposphere import Base64, Join, ImportValue
from troposphere import Parameter, Ref, Tags, Template
from troposphere.ec2 import SecurityGroupRule, SecurityGroup

#
# Troposphere template - https://github.com/cloudtools/troposphere
#

#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │                         Template & Parameters                             │
# └───────────────────────────────────────────────────────────────────────────┘
#
template = Template()
template.add_description("PHP stack | anil.io")


def make_name(component_name):
    return Join('', [component_name, ' - ', Ref('AWS::StackName')])


#
# Linux AMI ID - We build of a AWS AMI, create your own image and add it here
#
LinuxAmi = template.add_parameter(Parameter(
    "LinuxAmi",
    Type="AWS::EC2::Image::Id",
    Description="The AMI ID for our Linux Web server instance. (default: Amazon Linux AMI 2017 - ami-489f8e2c)",
    Default="ami-489f8e2c",
))

#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │                               SSH Key pair                                │
# │     1) add keypair via AWS Console        2) reference it here            │
# └───────────────────────────────────────────────────────────────────────────┘
#
keyname_param = template.add_parameter(Parameter(
    "KeyPairName",
    Description="Name of an existing EC2 KeyPair to enable SSH access to the instance",
    Type="AWS::EC2::KeyPair::KeyName",
    Default="nld_aws_london"
))


#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │                              Security Groups                              │
# │ When creating a Security Group, a default "Main" group is also created.   │
# │ We can ignore the empty group, we provide our own.                        │
# └───────────────────────────────────────────────────────────────────────────┘
#
instanceSecurityGroup = template.add_resource(
    SecurityGroup(
        'InstanceSecurityGroup',
        GroupDescription='Enable EC2 HTTP, HTTPS',
        SecurityGroupIngress=[
            # SSH
            SecurityGroupRule(IpProtocol='tcp', FromPort='22', ToPort='22', CidrIp='0.0.0.0/0'),

            # HTTP
            SecurityGroupRule(IpProtocol='tcp', FromPort='80', ToPort='80', CidrIp='0.0.0.0/0'),

            # HTTPS
            SecurityGroupRule(IpProtocol='tcp', FromPort='443', ToPort='443', CidrIp='0.0.0.0/0'),

            # Github - https://help.github.com/articles/what-ip-addresses-does-github-use-that-i-should-whitelist/
            # SecurityGroupRule(IpProtocol='tcp', FromPort='9418', ToPort='9418', CidrIp='192.30.252.0/22'),
        ],
        VpcId=ImportValue('VPC'),
        Tags=Tags(Name=make_name('Linux Web Server Sec-Group'))
    )
)


#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │                               EC2 Instance                                │
# └───────────────────────────────────────────────────────────────────────────┘
#
# We have 1 instance. Linux (Amazon Linux AMI) built from an AMI
# You should create and setup this instance manually and reference it here
#
# An Elastic IP (required to expose it publicly), must be launched within a
# Subnet with an IGW. To expose a server publicly we need to have one of:
#
#   a) An elastic IP
#   b) An Elastic Load Balancer serving traffic to our instance
#
# View UserData files and output here:
#   cd /var/lib/cloud/instances/**/
#   /var/log/cloud-init-output.log
#

user_data_v_host = """ cat << EOF > /etc/httpd/conf.d/virtualhosts.conf
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    <Directory "/var/www/html">
        AuthType Basic
        AuthName "Restricted Content"
        AuthUserFile /var/www/html/.htpasswd
        Require valid-user
    </Directory>
</VirtualHost>
EOF"""

user_data_bashrc = """ cat << EOF >> /home/ec2-user/.bashrc

# User
export PATH=./bin:$PATH
alias l='ls -alh'

# .pm2
export USER=ec2-user
export HOME=/home/ec2-user
export PM2_HOME=/home/ec2-user/.pm2
EOF"""

ec2_instance = template.add_resource(ec2.Instance(
    "Ec2Instance",
    ImageId=Ref(LinuxAmi),
    InstanceType="t2.small",
    KeyName=Ref(keyname_param),
    SecurityGroupIds=[Ref(instanceSecurityGroup)],
    SubnetId=ImportValue('Subnet1'),
    Tags=Tags(Name=make_name('Linux Web Server')),
    UserData=Base64(Join('', [
        "#!/bin/bash\n"

        # Set the timezone - picked up on next reboot
        "sed -i '/ZONE=\"UTC\"/c\ZONE=\"Europe/London\"' /etc/sysconfig/clock \n",
        "ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime\n",

        "echo [UserData] Updating Packages\n",
        "yum update -y\n",

        # Update bashrc with exports
        user_data_bashrc,
        ". /home/ec2-user/.bashrc\n",

        "echo [UserData] Installing LAMP\n",
        "yum install -y httpd24 php56 mysql56-server php56-mysqlnd php56-intl git\n",

        # Set PHP timezone
        "sed -i \'/date.timezone =.*/a date.timezone = Europe\/London\' /etc/php.ini\n",

        "service httpd start\n",
        "chkconfig httpd on\n",

        # Add a www group and add ec2-user to that group.
        "groupadd www\n",
        "usermod -a -G www ec2-user\n",
        "chown -R ec2-user:www /var/www\n",
        "chmod 2775 /var/www\n",
        "find /var/www -type d -exec chmod 2775 {} +\n",
        "find /var/www -type f -exec chmod 0664 {} +\n",
        "echo \"<?php phpinfo(); ?>\" > /var/www/html/phpinfo.php\n",

        # Add .htpasswd with user:pass demo:demo
        "echo 'demo:$apr1$SHoPBfcN$httsSshmBhTe2pTOybwl3.' > /var/www/html/.htpasswd\n"
        "touch /etc/httpd/conf.d/virtualhosts.conf\n"
        "mkdir /var/www/html/logs\n"
        "APACHE_LOG_DIR=/var/www/html/logs\n",

        user_data_v_host,

        "\nservice httpd restart\n"

        # # Install Composer
        # "curl -sS https://getcomposer.org/installer | php\n",
        # "mv composer.phar /usr/bin/composer\n",
        # "chmod +x /usr/bin/composer\n",
        # "mkdir -p /home/ec2-user/.composer\n",

        # # Install Redis
        # "sudo yum-config-manager --enable epel -y\n",
        # "sudo yum install redis -y\n",
        # "sudo yum-config-manager --disable epel -y\n",
        # "sudo service redis start -y\n",

        # # Install Node.js - v6.x - http://stackoverflow.com/a/35165401/711308
        # "curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -\n",
        # "sudo yum -y install nodejs npm\n\n",

        # Go to: http://dev.lakhman.com/phpinfo.php
        ]
     ))
))

#
# Associate our ElasticIP (from Domain-Stack) to our instance
#
eip_association = template.add_resource(ec2.EIPAssociation(
    "EIPEC2Association",
    EIP=ImportValue('ElasticIP'),
    InstanceId=Ref(ec2_instance),
))


#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │                                 JSON DUMP                                 │
# └───────────────────────────────────────────────────────────────────────────┘
#
with open(os.path.realpath(__file__)[:-2] + 'json', 'w') as the_file:
    print(template.to_json(), file=the_file)
    print('Generated: ' + __file__)
{
    "Description": "PHP stack | anil.io",
    "Metadata": {
        "AWS::CloudFormation::Interface": {
            "ParameterGroups": [
                {
                    "Label": {
                        "default": "Application Configuration"
                    },
                    "Parameters": [
                        "LinuxAmi"
                    ]
                },
                {
                    "Label": {
                        "default": "Amazon EC2 Configuration"
                    },
                    "Parameters": [
                        "KeyPairName",
                        "SSHLocation"
                    ]
                }
            ],
            "ParameterLabels": {
                "LinuxAmi": {
                    "default": "Linux Server AMI ID"
                }
            }
        }
    },
    "Parameters": {
        "KeyPairName": {
            "Default": "nld_aws_london",
            "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
            "Type": "AWS::EC2::KeyPair::KeyName"
        },
        "LinuxAmi": {
            "Default": "ami-489f8e2c",
            "Description": "The AMI ID for our Linux Web server instance. (default: Amazon Linux AMI 2017 - ami-489f8e2c)",
            "Type": "AWS::EC2::Image::Id"
        },
        "SSHLocation": {
            "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
            "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
            "Default": "52.56.84.136/32",
            "Description": " The IP address range that can be used to SSH to the EC2 instances",
            "MaxLength": "18",
            "MinLength": "9",
            "Type": "String"
        }
    },
    "Resources": {
        "EIPAssociation": {
            "Properties": {
                "EIP": {
                    "Fn::ImportValue": "ElasticIP"
                },
                "InstanceId": {
                    "Ref": "Ec2Instance"
                }
            },
            "Type": "AWS::EC2::EIPAssociation"
        },
        "Ec2Instance": {
            "Properties": {
                "ImageId": {
                    "Ref": "LinuxAmi"
                },
                "InstanceType": "t2.small",
                "KeyName": {
                    "Ref": "KeyPairName"
                },
                "SecurityGroupIds": [
                    {
                        "Ref": "InstanceSecurityGroup"
                    }
                ],
                "SubnetId": {
                    "Fn::ImportValue": "Subnet1"
                },
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": {
                            "Ref": "AWS::StackName"
                        }
                    }
                ],
                "UserData": {
                    "Fn::Base64": {
                        "Fn::Join": [
                            "",
                            [
                                "#!/bin/bash\nsed -i '/ZONE=\"UTC\"/c\\ZONE=\"Europe/London\"' /etc/sysconfig/clock \n",
                                "ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime\n",
                                "echo [UserData] Updating Packages\n",
                                "yum update -y\n",
                                "echo [UserData] Installing LAMP\n",
                                "yum install -y httpd24 php56 mysql56-server php56-mysqlnd php56-intl git\n",
                                "sed -i '/date.timezone =.*/a date.timezone = Europe\\/London' /etc/php.ini\n",
                                "service httpd start\n",
                                "chkconfig httpd on\n",
                                "groupadd www\n",
                                "usermod -a -G www ec2-user\n",
                                "chown -R root:www /var/www\n",
                                "chmod 2775 /var/www\n",
                                "find /var/www -type d -exec chmod 2775 {} +\n",
                                "find /var/www -type f -exec chmod 0664 {} +\n",
                                "echo \"<?php phpinfo(); ?>\" > /var/www/html/phpinfo.php\n",
                                "echo 'demo:$apr1$SHoPBfcN$httsSshmBhTe2pTOybwl3.' > /var/www/html/.htpasswd\ntouch /etc/httpd/conf.d/virtualhosts.conf\nmkdir /var/www/html/logs\nAPACHE_LOG_DIR=/var/www/html/logs\n",
                                " cat << EOF > /etc/httpd/conf.d/virtualhosts.conf\n<VirtualHost *:80>\n    ServerAdmin webmaster@localhost\n    DocumentRoot /var/www/html\n    ErrorLog ${APACHE_LOG_DIR}/error.log\n    CustomLog ${APACHE_LOG_DIR}/access.log combined\n\n    <Directory \"/var/www/html\">\n        AuthType Basic\n        AuthName \"Restricted Content\"\n        AuthUserFile /var/www/html/.htpasswd\n        Require valid-user\n    </Directory>\n</VirtualHost>\nEOF",
                                "\nservice httpd restart\necho 'export PATH=./bin:$PATH' | tee -a /home/ec2-user/.bash_profile /home/ec2-user/.bashrc\n",
                                "echo \"alias l='ls -alh'\" | tee -a /home/ec2-user/.bash_profile /home/ec2-user/.bashrc\n"
                            ]
                        ]
                    }
                }
            },
            "Type": "AWS::EC2::Instance"
        },
        "InstanceSecurityGroup": {
            "Properties": {
                "GroupDescription": "Enable EC2 HTTP, HTTPS",
                "SecurityGroupIngress": [
                    {
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "22",
                        "IpProtocol": "tcp",
                        "ToPort": "22"
                    },
                    {
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "80",
                        "IpProtocol": "tcp",
                        "ToPort": "80"
                    },
                    {
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "443",
                        "IpProtocol": "tcp",
                        "ToPort": "443"
                    }
                ],
                "Tags": [
                    {
                        "Key": "Application",
                        "Value": {
                            "Ref": "AWS::StackId"
                        }
                    },
                    {
                        "Key": "Name",
                        "Value": "Linux WebServer Security Group"
                    }
                ],
                "VpcId": {
                    "Fn::ImportValue": "VPC"
                }
            },
            "Type": "AWS::EC2::SecurityGroup"
        }
    }
}