Deploy an EC2 within a VPC to run WordPress and MySQL on AWS

Introduction

AWS CloudFormation is a template that easy to create and manage a collection of related AWS resources. Also, when we want to update something, we could easily do it. This article will talk about how to create a stack using an AWS CloudFormation template.

Solution Diagram

Create a CloudFomration Stack

The below code is a template of wordpress, please save it as wordpress.template file.

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS CloudFormation Sample Template WordPress_Single_Instance: WordPress
  is web software you can use to create a beautiful website or blog. This template
  installs WordPress with a local MySQL database for storage. It demonstrates using
  the AWS CloudFormation bootstrap scripts to deploy WordPress. **WARNING** This template
  creates an Amazon EC2 instance. You will be billed for the AWS resources used if
  you create a stack from this template.'
Parameters:
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
      - t1.micro
      - t2.nano
      - t2.micro
    ConstraintDescription: must be a valid EC2 instance type.
  SSHLocation:
    Description: The IP address range that can be used to SSH to the EC2 instances
    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 IP CIDR range of the form x.x.x.x/x.
  DBName:
    Default: wordpressdb
    Description: The WordPress database name
    Type: String
    MinLength: '1'
    MaxLength: '64'
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: must begin with a letter and contain only alphanumeric
      characters.
  DBUser:
    NoEcho: 'true'
    Description: The WordPress database admin 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:
    NoEcho: 'true'
    Description: The WordPress database admin account password
    Type: String
    MinLength: '8'
    MaxLength: '41'
    AllowedPattern: '[a-zA-Z0-9]*'
    ConstraintDescription: must contain only alphanumeric characters.
  DBRootPassword:
    NoEcho: 'true'
    Description: MySQL root password
    Type: String
    MinLength: '8'
    MaxLength: '41'
    AllowedPattern: '[a-zA-Z0-9]*'
    ConstraintDescription: must contain only alphanumeric characters.
  VPCCIDR:
    Description: CIDR Block for VPC
    Type: String
    Default: 10.1.0.0/16
    AllowedValues:
      - 10.1.0.0/16
  PublicSubnet1Param:
    Description: Public Subnet 1
    Type: String
    Default: 10.1.11.0/24
    AllowedValues:
      - 10.1.11.0/24
  PublicSubnet2Param:
    Description: Public Subnet 2
    Type: String
    Default: 10.1.12.0/24
    AllowedValues:
      - 10.1.12.0/24
Mappings:
  AWSInstanceType2Arch:
    t1.micro:
      Arch: PV64
    t2.nano:
      Arch: HVM64
    t2.micro:
      Arch: HVM64
    t2.small:
      Arch: HVM64
    t2.medium:
      Arch: HVM64
    t2.large:
      Arch: HVM64
    m1.small:
      Arch: PV64
    m1.medium:
      Arch: PV64
    m1.large:
      Arch: PV64
    m1.xlarge:
      Arch: PV64
    m2.xlarge:
      Arch: PV64
    m2.2xlarge:
      Arch: PV64
    m2.4xlarge:
      Arch: PV64
    m3.medium:
      Arch: HVM64
    m3.large:
      Arch: HVM64
    m3.xlarge:
      Arch: HVM64
    m3.2xlarge:
      Arch: HVM64
    m4.large:
      Arch: HVM64
    m4.xlarge:
      Arch: HVM64
    m4.2xlarge:
      Arch: HVM64
    m4.4xlarge:
      Arch: HVM64
    m4.10xlarge:
      Arch: HVM64
    c1.medium:
      Arch: PV64
    c1.xlarge:
      Arch: PV64
    c3.large:
      Arch: HVM64
    c3.xlarge:
      Arch: HVM64
    c3.2xlarge:
      Arch: HVM64
    c3.4xlarge:
      Arch: HVM64
    c3.8xlarge:
      Arch: HVM64
    c4.large:
      Arch: HVM64
    c4.xlarge:
      Arch: HVM64
    c4.2xlarge:
      Arch: HVM64
    c4.4xlarge:
      Arch: HVM64
    c4.8xlarge:
      Arch: HVM64
    g2.2xlarge:
      Arch: HVMG2
    g2.8xlarge:
      Arch: HVMG2
    r3.large:
      Arch: HVM64
    r3.xlarge:
      Arch: HVM64
    r3.2xlarge:
      Arch: HVM64
    r3.4xlarge:
      Arch: HVM64
    r3.8xlarge:
      Arch: HVM64
    i2.xlarge:
      Arch: HVM64
    i2.2xlarge:
      Arch: HVM64
    i2.4xlarge:
      Arch: HVM64
    i2.8xlarge:
      Arch: HVM64
    d2.xlarge:
      Arch: HVM64
    d2.2xlarge:
      Arch: HVM64
    d2.4xlarge:
      Arch: HVM64
    d2.8xlarge:
      Arch: HVM64
    hi1.4xlarge:
      Arch: HVM64
    hs1.8xlarge:
      Arch: HVM64
    cr1.8xlarge:
      Arch: HVM64
    cc2.8xlarge:
      Arch: HVM64
  AWSRegionArch2AMI:
    us-east-1:
      PV64: ami-2a69aa47
      HVM64: ami-6869aa05
      HVMG2: ami-2e5e9c43
    us-west-2:
      PV64: ami-7f77b31f
      HVM64: ami-7172b611
      HVMG2: ami-83b770e3
    us-west-1:
      PV64: ami-a2490dc2
      HVM64: ami-31490d51
      HVMG2: ami-fd76329d
    eu-west-1:
      PV64: ami-4cdd453f
      HVM64: ami-f9dd458a
      HVMG2: ami-b9bd25ca
    eu-central-1:
      PV64: ami-6527cf0a
      HVM64: ami-ea26ce85
      HVMG2: ami-7f04ec10
    ap-northeast-1:
      PV64: ami-3e42b65f
      HVM64: ami-374db956
      HVMG2: ami-e0ee1981
    ap-northeast-2:
      PV64: NOT_SUPPORTED
      HVM64: ami-2b408b45
      HVMG2: NOT_SUPPORTED
    ap-southeast-1:
      PV64: ami-df9e4cbc
      HVM64: ami-a59b49c6
      HVMG2: ami-0cb5676f
    ap-southeast-2:
      PV64: ami-63351d00
      HVM64: ami-dc361ebf
      HVMG2: ami-a71c34c4
    sa-east-1:
      PV64: ami-1ad34676
      HVM64: ami-6dd04501
      HVMG2: NOT_SUPPORTED
    cn-north-1:
      PV64: ami-77559f1a
      HVM64: ami-8e6aa0e3
      HVMG2: NOT_SUPPORTED
Resources:
  WebServerSecurityGroup:
    DependsOn: AttachGateway
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable HTTP access via port 80 locked down to the load balancer
        + SSH access
      VpcId: !Ref 'VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: !Ref 'SSHLocation'
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref 'VPCCIDR'
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        - Key: Name
          Value: Lab VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DependsOn: VPC
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    DependsOn:
      - VPC
      - InternetGateway
    Properties:
      VpcId: !Ref 'VPC'
      InternetGatewayId: !Ref 'InternetGateway'
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref 'VPC'
      CidrBlock: !Ref 'PublicSubnet1Param'
      AvailabilityZone: !Select
        - '0'
        - !GetAZs ''
      Tags:
        - Key: Name
          Value: Public Subnet 1
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref 'VPC'
      CidrBlock: !Ref 'PublicSubnet2Param'
      AvailabilityZone: !Select
        - '1'
        - !GetAZs ''
      Tags:
        - Key: Name
          Value: Public Subnet 2
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn:
      - VPC
      - AttachGateway
    Properties:
      VpcId: !Ref 'VPC'
      Tags:
        - Key: Name
          Value: Public
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn:
      - PublicRouteTable
      - AttachGateway
    Properties:
      RouteTableId: !Ref 'PublicRouteTable'
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref 'InternetGateway'
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    DependsOn:
      - PublicRouteTable
      - PublicSubnet1
      - AttachGateway
    Properties:
      SubnetId: !Ref 'PublicSubnet1'
      RouteTableId: !Ref 'PublicRouteTable'
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    DependsOn:
      - PublicRouteTable
      - PublicSubnet2
    Properties:
      SubnetId: !Ref 'PublicSubnet2'
      RouteTableId: !Ref 'PublicRouteTable'
  WebServer:
    Type: AWS::EC2::Instance
    DependsOn: WebServerSecurityGroup
    Metadata:
      AWS::CloudFormation::Init:
        configSets:
          wordpress_install:
            - install_cfn
            - install_wordpress
            - configure_wordpress
        install_cfn:
          files:
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
              mode: '000400'
              owner: root
              group: root
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.WebServer.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v          --stack ${AWS::StackName}         --resource WebServer          --configsets wordpress_install          --region ${AWS::Region}
              mode: '000400'
              owner: root
              group: root
          services:
            sysvinit:
              cfn-hup:
                enabled: 'true'
                ensureRunning: 'true'
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
        install_wordpress:
          packages:
            yum:
              php: []
              php-mysql: []
              mysql: []
              mysql-server: []
              mysql-devel: []
              mysql-libs: []
              httpd: []
          sources:
            /var/www/html: http://wordpress.org/latest.tar.gz
          files:
            /tmp/setup.mysql:
              content: !Sub |
                CREATE DATABASE ${DBName};
                CREATE USER '${DBUser}'@'localhost' IDENTIFIED BY '${DBPassword}';
                GRANT ALL ON ${DBName}.* TO '${DBUser}'@'localhost';
                FLUSH PRIVILEGES;
              mode: '000400'
              owner: root
              group: root
            /tmp/create-wp-config:
              content: !Sub |
                #!/bin/bash -xe
                cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php
                sed -i "s/'database_name_here'/'${DBName}'/g" wp-config.php
                sed -i "s/'username_here'/'${DBUser}'/g" wp-config.php
                sed -i "s/'password_here'/'${DBPassword}'/g" wp-config.php
              mode: '000500'
              owner: root
              group: root
          services:
            sysvinit:
              httpd:
                enabled: 'true'
                ensureRunning: 'true'
              mysqld:
                enabled: 'true'
                ensureRunning: 'true'
        configure_wordpress:
          commands:
            01_set_mysql_root_password:
              command: !Sub 'mysqladmin -u root password ''${DBRootPassword}'''
              test: !Sub '$(mysql ${DBName} -u root --password=''${DBRootPassword}''
                >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
            02_create_database:
              command: !Sub 'mysql -u root --password=''${DBRootPassword}'' < /tmp/setup.mysql'
              test: !Sub '$(mysql ${DBName} -u root --password=''${DBRootPassword}''
                >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
            03_configure_wordpress:
              command: /tmp/create-wp-config
              cwd: /var/www/html/wordpress
    Properties:
      ImageId: !FindInMap
        - AWSRegionArch2AMI
        - !Ref 'AWS::Region'
        - !FindInMap
          - AWSInstanceType2Arch
          - !Ref 'InstanceType'
          - Arch
      InstanceType: !Ref 'InstanceType'
      NetworkInterfaces:
        - DeviceIndex: '0'
          AssociatePublicIpAddress: 'true'
          SubnetId: !Ref 'PublicSubnet2'
          GroupSet:
            - !Ref 'WebServerSecurityGroup'
      KeyName: !Ref 'KeyName'
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash -xe
          yum update -y
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServer --configsets wordpress_install --region ${AWS::Region}
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServer --region ${AWS::Region}
          service httpd stop
          yum remove php* -y
          yum remove httpd* -y
          yum clean all
          yum upgrade -y
          yum install httpd24 -y
          yum install php70 php70-mysqlnd php70-imap php70-pecl-memcache php70-pecl-apcu php70-gd -y
          yum install mod24_ssl -y
          service httpd start

    CreationPolicy:
      ResourceSignal:
        Timeout: PT10M
Outputs:
  WebsiteURL:
    Value: !Sub 'http://${WebServer.PublicDnsName}/wordpress'
    Description: WordPress Website
  EC2IPAddress:
    Value: !GetAtt WebServer.PublicIp

In CloudFormation page, click Create stack.

We select template is ready, upload a template file, then choose the template file we have just created, click next.

First of all, we give the stack name as WordPress.

We can see by default we define some values.

It is also our expectation since we set default values for those parameters.

Then we fill in all the blanks. If you have no preference, please follow me DBPassword=password1, DBRootPassword=password2, DBUser=admin. But for KeyName, please choose your own existing key.

In this page, we can simply keep the default values, click Next.

After reviewing all the configuration, click create stack.

When we see above figure, it means we created successfully.

Testing

We click on the Website URL and click the link.

We have to input some information, just follow my input if you have no preference. Then click install WordPress.

It shows success then Log In.

Well, we have created the wordpress successfully.

In EC2 page, we can also find the EC2 instance. Click into the instance.

Click connect, we can have multiple ways to access the isntance.

Cleanup

We just select the stack and then delete.

Leave a Reply