├── hosts ├── local_wordpress └── ansible │ ├── hosts │ ├── roles │ ├── php-fpm │ │ ├── handlers │ │ │ └── main.yml │ │ ├── templates │ │ │ └── wordpress.conf │ │ └── tasks │ │ │ └── main.yml │ ├── nginx │ │ ├── handlers │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ └── default.conf │ ├── common │ │ └── tasks │ │ │ └── main.yml │ └── wordpress │ │ ├── tasks │ │ └── main.yml │ │ └── templates │ │ └── wp-config.php │ └── wordpress.yml ├── ansible.cfg ├── LICENSE ├── roles └── provision-ec2 │ └── tasks │ └── main.yml ├── group_vars └── all.yml ├── stack-wordpress.yml ├── README.md └── cloudformation └── wordpress.json /hosts: -------------------------------------------------------------------------------- 1 | [localhost] 2 | 127.0.0.1 3 | -------------------------------------------------------------------------------- /local_wordpress/ansible/hosts: -------------------------------------------------------------------------------- 1 | [local] 2 | 127.0.0.1 ansible_connection=local 3 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/php-fpm/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart php-fpm 3 | service: name=php5-fpm state=restarted 4 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart nginx 3 | service: name=nginx state=restarted enabled=yes 4 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user=ubuntu 3 | host_key_checking=False 4 | hostfile=hosts 5 | 6 | [ssh_connection] 7 | #control_path=%(directory)s/%%h-%%r 8 | ssh_args=-o ControlMaster=auto -o ControlPersist=60s -o ForwardAgent=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 9 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install nginx 3 | apt: name=nginx state=present 4 | 5 | - name: Copy nginx configuration for wordpress 6 | template: src=default.conf dest=/etc/nginx/sites-available/default 7 | notify: restart nginx 8 | 9 | - name: Start nginx Service 10 | service: name=nginx state=started enabled=yes 11 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Update apt cache 2 | sudo: yes 3 | apt: update_cache=yes 4 | 5 | - name: Install various packages 6 | apt: 7 | pkg: "{{ item }}" 8 | state: latest 9 | sudo: yes 10 | with_items: 11 | - git 12 | - vim 13 | - ntpdate 14 | 15 | - name: NTP update using cron 16 | sudo: yes 17 | cron: name="ntpdate" special_time=hourly job="/usr/sbin/ntpdate pool.ntp.org ntp.ubuntu.com" 18 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/php-fpm/templates/wordpress.conf: -------------------------------------------------------------------------------- 1 | [wordpress] 2 | listen = /var/run/php5-fpm-wordpress.sock 3 | listen.owner = www-data 4 | listen.group = www-data 5 | listen.mode = 0660 6 | user = wordpress 7 | group = wordpress 8 | pm = dynamic 9 | pm.max_children = 10 10 | pm.start_servers = 1 11 | pm.min_spare_servers = 1 12 | pm.max_spare_servers = 3 13 | pm.max_requests = 500 14 | chdir = /srv/wordpress/ 15 | php_admin_value[open_basedir] = /srv/wordpress/:/tmp 16 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/php-fpm/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install php-fpm and deps 3 | apt: name={{ item }} state=present 4 | with_items: 5 | - php5 6 | - php5-fpm 7 | - php5-enchant 8 | - php5-mysql 9 | - python-mysqldb 10 | 11 | - name: Disable default pool 12 | command: mv /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/www.disabled creates=/etc/php5/fpm/pool.d/www.disabled 13 | notify: restart php-fpm 14 | 15 | - name: Copy php-fpm configuration 16 | template: src=wordpress.conf dest=/etc/php5/fpm/pool.d/ 17 | notify: restart php-fpm 18 | -------------------------------------------------------------------------------- /local_wordpress/ansible/wordpress.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: local 3 | gather_facts: yes 4 | sudo: yes 5 | remote_user: ubuntu 6 | vars: 7 | server_hostname: "{{ ansible_local.cloudformation.elb.hostname }}" 8 | wp_version: 4.2.4 9 | wp_sha256sum: 42ca594afc709cbef8528a6096f5a1efe96dcf3164e7ce321e87d57ae015cc82 10 | wp_db_host: "{{ ansible_local.cloudformation.database.hostname }}" 11 | wp_db_name: wordpress 12 | wp_db_user: root 13 | wp_db_password: "{{ ansible_local.cloudformation.database.password }}" 14 | mysql_port: 3306 15 | 16 | pre_tasks: 17 | - name: Update apt cache 18 | apt: update_cache=yes cache_valid_time=3600 19 | 20 | roles: 21 | - nginx 22 | - php-fpm 23 | - wordpress 24 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/nginx/templates/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name {{ server_hostname }}; 4 | root /srv/wordpress/ ; 5 | 6 | client_max_body_size 64M; 7 | 8 | # Deny access to any files with a .php extension in the uploads directory 9 | location ~* /(?:uploads|files)/.*\.php$ { 10 | deny all; 11 | } 12 | 13 | location / { 14 | index index.php index.html index.htm; 15 | try_files $uri $uri/ /index.php?$args; 16 | } 17 | 18 | location ~* \.(gif|jpg|jpeg|png|css|js)$ { 19 | expires max; 20 | } 21 | 22 | location ~ \.php$ { 23 | try_files $uri =404; 24 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 25 | fastcgi_index index.php; 26 | fastcgi_pass unix:/var/run/php5-fpm-wordpress.sock; 27 | fastcgi_param SCRIPT_FILENAME 28 | $document_root$fastcgi_script_name; 29 | include fastcgi_params; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Allan Denot 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 | 23 | -------------------------------------------------------------------------------- /roles/provision-ec2/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision EC2 Box 3 | local_action: 4 | module: ec2 5 | id: "{{ ec2_tag_Name }}" 6 | key_name: "{{ ec2_keypair }}" 7 | group_id: "{{ ec2_security_group }}" 8 | instance_type: "{{ ec2_instance_type }}" 9 | image: "{{ ec2_image }}" 10 | vpc_subnet_id: "{{ ec2_subnet_ids|random }}" 11 | region: "{{ ec2_region }}" 12 | instance_tags: '{"Name":"{{ec2_tag_Name}}", "Type":"{{ec2_tag_Type}}"}' 13 | assign_public_ip: yes 14 | wait: true 15 | count: 1 16 | volumes: 17 | - device_name: /dev/sda1 18 | device_type: gp2 19 | volume_size: "{{ ec2_volume_size }}" 20 | delete_on_termination: true 21 | register: ec2 22 | 23 | - debug: var=item 24 | with_items: ec2.instances 25 | 26 | - add_host: 27 | name: "{{ item.public_ip }}" 28 | groups: "{{ ec2_tag_Type }}" 29 | ec2_region: "{{ ec2_region }}" 30 | ec2_tag_Name: "{{ ec2_tag_Name }}" 31 | ec2_ip_address: "{{ item.public_ip }}" 32 | with_items: ec2.instances 33 | 34 | - name: Wait for the instances to boot by checking the ssh port 35 | wait_for: host={{item.public_ip}} port=22 delay=5 timeout=320 state=started 36 | with_items: ec2.instances 37 | -------------------------------------------------------------------------------- /group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | region: "ap-southeast-1" 3 | 4 | # Baking Server Variables 5 | 6 | ec2_keypair: "Allan-DEMO1" 7 | ansible_ssh_private_key_file: ~/{{ ec2_keypair }}.pem # File location for accessing host 8 | 9 | ec2_security_group: "sg-f7933792" 10 | ec2_instance_type: "t2.small" 11 | ec2_image: "ami-ca381398" # Ubuntu 12 | 13 | ec2_region: "{{ region }}" 14 | ec2_subnet_ids: ['subnet-ff934c9a','subnet-9b27d4ec'] 15 | ec2_tag_Name: "wordpress-bake-1" # Tag to identify the host 16 | # Change name when new baking need to be created 17 | 18 | ec2_tag_Type: "wordpress-bake" # Also the Ansible group name that host will be added. 19 | ec2_volume_size: 8 20 | 21 | # CloudFormation Stack Variables 22 | 23 | cf_keypair: "{{ ec2_keypair }}" 24 | cf_instance_type: "c3.large" 25 | cf_subnet_ids: "{{ ec2_subnet_ids }}" 26 | 27 | vpc_id: "vpc-e3b34886" 28 | 29 | region: "ap-southeast-1" 30 | 31 | version: "{{ lookup('pipe','date +%s') }}" # Using unixtime as version 32 | 33 | stack_name: "wordpress-staging-1" # Name of the stack, keep the same to prevent 34 | # creating new ones if same name exists 35 | # Use different names to set environments 36 | 37 | rds_class: "db.t2.micro" 38 | rds_password: "password123" 39 | rds_storage_size: 5 40 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/wordpress/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Download WordPress 3 | get_url: 4 | url: "http://wordpress.org/wordpress-{{ wp_version }}.tar.gz" 5 | dest: "/srv/wordpress-{{ wp_version }}.tar.gz" 6 | sha256sum: "{{ wp_sha256sum }}" 7 | 8 | - name: Extract archive 9 | command: chdir=/srv/ /bin/tar xvf wordpress-{{ wp_version }}.tar.gz creates=/srv/wordpress 10 | 11 | - name: Add group "wordpress" 12 | group: name=wordpress 13 | 14 | - name: Add user "wordpress" 15 | user: name=wordpress group=wordpress home=/srv/wordpress/ 16 | 17 | - name: Fetch random salts for WordPress config 18 | local_action: command curl https://api.wordpress.org/secret-key/1.1/salt/ 19 | register: "wp_salt" 20 | sudo: no 21 | changed_when: false 22 | 23 | - name: Create WordPress database 24 | mysql_db: 25 | login_host: "{{ wp_db_host }}" 26 | login_user: "{{ wp_db_user }}" 27 | login_password: "{{ wp_db_password }}" 28 | login_port: "{{ mysql_port }}" 29 | name: "{{ wp_db_name }}" 30 | state: present 31 | 32 | - name: Copy WordPress config file 33 | template: src=wp-config.php dest=/srv/wordpress/ 34 | 35 | - name: Change ownership of WordPress installation 36 | file: path=/srv/wordpress/ owner=wordpress group=wordpress state=directory recurse=yes 37 | 38 | - name: Start php-fpm Service 39 | service: name=php5-fpm state=started enabled=yes 40 | -------------------------------------------------------------------------------- /stack-wordpress.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provisioning Baking Server 3 | # Baking server is the template for the AMI that will be used in 4 | # the CloudFormation stack 5 | hosts: localhost 6 | connection: local 7 | gather_facts: false 8 | roles: 9 | - provision-ec2 10 | 11 | - name: Configuring Baking Server 12 | # Playbooks are copied into baking server and are run 13 | # by server's userdata upon first boot 14 | hosts: wordpress-bake 15 | sudo: yes 16 | gather_facts: false 17 | tasks: 18 | - name: Upload local playbooks 19 | copy: src=local_wordpress/ansible dest=/etc 20 | 21 | - hosts: localhost 22 | connection: local 23 | gather_facts: false 24 | tasks: 25 | - name: Registering new AMI from baking server 26 | ec2_ami: 27 | instance_id: "{{ ec2.instances[0].id }}" 28 | region: "{{ region }}" 29 | wait: yes 30 | no_reboot: yes 31 | name: "wordpress-{{ version }}" 32 | description: "wordpress-{{ version }}" 33 | register: ami 34 | 35 | - name: Creating/Updating Stack with latest AMI 36 | cloudformation: 37 | stack_name: "{{ stack_name }}" 38 | state: "present" 39 | region: "{{ region }}" 40 | disable_rollback: true 41 | template: "cloudformation/wordpress.json" 42 | template_parameters: 43 | KeyName: "{{ cf_keypair }}" 44 | InstanceType: "{{ cf_instance_type }}" 45 | DBClass: "{{ rds_class }}" 46 | SSHLocation: "0.0.0.0/0" 47 | DBPassword: "{{ rds_password }}" 48 | DBAllocatedStorage: "{{ rds_storage_size }}" 49 | AMIId: "{{ ami.image_id }}" 50 | VPCId: "{{ vpc_id }}" 51 | SubnetIds: "{{ cf_subnet_ids|join(',') }}" 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autoscaling Wordpress with Ansible and CloudFormation 2 | 3 | [http://allandenot.com/devops/2015/12/24/autoscaling-wordpress-with-ansible-and-cloudformation.html](http://allandenot.com/devops/2015/12/24/autoscaling-wordpress-with-ansible-and-cloudformation.html) 4 | 5 | ## The Goal 6 | 7 | - A WordPress site running in EC2 hosts and an RDS database. 8 | - EC2 hosts inside an autoscaling group 9 | - Easily deploy configuration changes 10 | 11 | ## How it works 12 | 13 | User runs Ansible playbook: 14 | 15 | ![blog-ansible-autoscaling-wordpress.png](https://allandenot.com/images/blog-ansible-autoscaling-wordpress.png) 16 | 17 | Notes: 18 | 19 | - Baking server exists as a template for the AMI that will be used in CloudFormation. 20 | - Baking server is an Ubuntu with Ansible and local playbooks are copied into it. 21 | - We choose to use baked AMIs to speedup the bootup process when autoscaling is creating new instances. Upon boot, these instances run their local Ansible playbooks and the webserver is configured. 22 | 23 | ## How can I use it 24 | 25 | Fork my repository: [https://github.com/adenot/blog-ansible-autoscaling-wordpress](https://github.com/adenot/blog-ansible-autoscaling-wordpress) 26 | 27 | Edit file: 28 | 29 | group_vars/all 30 | 31 | Set variables from your EC2 account. 32 | 33 | Make sure to create the required AWS resources for the baking server: 34 | 35 | - EC2 Keypair 36 | - VPC, Subnet (if not exists) 37 | - Security group - Port 22/tcp must be open 38 | 39 | Running it: 40 | 41 | ansible-playbook -vv stack-wordpress.yml 42 | 43 | ### What is happening? 44 | 45 | First time it runs: 46 | 47 | - a baking server is created 48 | - local playbooks are copied into baking server (from local_wordpress/ansible) 49 | - an AMI is created from the baking server 50 | - a CloudFormation stack is created 51 | 52 | Second time it runs: 53 | 54 | - local playbooks are copied into baking server (from local_wordpress/ansible) 55 | - an AMI is created from the baking server 56 | - CloudFormation stack is updated with new AMI 57 | - Autoscaling group replaces EC2 instances with new ones using new AMI, one by one 58 | -------------------------------------------------------------------------------- /local_wordpress/ansible/roles/wordpress/templates/wp-config.php: -------------------------------------------------------------------------------- 1 | ", 74 | "ConstraintDescription" : "must be a valid list of subnet ids." 75 | } 76 | 77 | }, 78 | 79 | "Resources" : { 80 | "ELBSecurityGroup" : { 81 | "Type" : "AWS::EC2::SecurityGroup", 82 | "Properties" : { 83 | "GroupDescription" : "Enable HTTP access via port 80", 84 | "SecurityGroupIngress" : [ 85 | {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"} 86 | ], 87 | "VpcId": { "Ref": "VPCId" } 88 | } 89 | }, 90 | 91 | "ElasticLoadBalancer" : { 92 | "Type" : "AWS::ElasticLoadBalancing::LoadBalancer", 93 | "Properties" : { 94 | "CrossZone" : "true", 95 | "SecurityGroups" : [ { "Fn::GetAtt": [ "ELBSecurityGroup", "GroupId" ] } ], 96 | "Subnets" : { "Ref": "SubnetIds" }, 97 | "LBCookieStickinessPolicy" : [ { 98 | "PolicyName" : "CookieBasedPolicy", 99 | "CookieExpirationPeriod" : "30" 100 | } ], 101 | "Listeners" : [ { 102 | "LoadBalancerPort" : "80", 103 | "InstancePort" : "80", 104 | "Protocol" : "HTTP", 105 | "PolicyNames" : [ "CookieBasedPolicy" ] 106 | } ], 107 | "HealthCheck" : { 108 | "Target" : "HTTP:80/wp-admin/install.php", 109 | "HealthyThreshold" : "2", 110 | "UnhealthyThreshold" : "5", 111 | "Interval" : "10", 112 | "Timeout" : "5" 113 | } 114 | } 115 | }, 116 | 117 | "WebServerSecurityGroup" : { 118 | "Type" : "AWS::EC2::SecurityGroup", 119 | "Properties" : { 120 | "GroupDescription" : "Enable HTTP access via port 80 locked down to the load balancer + SSH access", 121 | "VpcId": { "Ref": "VPCId" }, 122 | "SecurityGroupIngress" : [{ 123 | "IpProtocol" : "tcp", 124 | "FromPort" : "80", 125 | "ToPort" : "80", 126 | "SourceSecurityGroupId" : { 127 | "Ref" : "ELBSecurityGroup" 128 | } 129 | },{ 130 | "IpProtocol" : "tcp", 131 | "FromPort" : "22", 132 | "ToPort" : "22", 133 | "CidrIp" : { "Ref" : "SSHLocation"} 134 | }] 135 | } 136 | }, 137 | 138 | "WebServerGroup" : { 139 | "Type" : "AWS::AutoScaling::AutoScalingGroup", 140 | "Properties" : { 141 | "AvailabilityZones" : { "Fn::GetAZs" : "" }, 142 | "VPCZoneIdentifier": { "Ref": "SubnetIds" }, 143 | "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, 144 | "MinSize" : "1", 145 | "MaxSize" : "4", 146 | "DesiredCapacity" : "2", 147 | "LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ] 148 | }, 149 | "UpdatePolicy" : { 150 | "AutoScalingRollingUpdate" : { 151 | "MinInstancesInService" : "1", 152 | "PauseTime" : "PT5M" 153 | } 154 | } 155 | }, 156 | 157 | "LaunchConfig": { 158 | "Type" : "AWS::AutoScaling::LaunchConfiguration", 159 | "Properties": { 160 | "ImageId" : { "Ref" : "AMIId" }, 161 | "InstanceType" : { "Ref" : "InstanceType" }, 162 | "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ], 163 | "KeyName" : { "Ref" : "KeyName" }, 164 | "UserData" : { "Fn::Base64" : { 165 | "Fn::Join" : [ 166 | "", 167 | [ 168 | "#!/bin/bash -v\n", 169 | "apt-get -y update\n", 170 | "apt-get -y install software-properties-common awscli\n", 171 | "apt-add-repository -y ppa:ansible/ansible\n", 172 | "apt-get -y update\n", 173 | "apt-get -y install ansible\n", 174 | "\n", 175 | "mkdir -p /etc/ansible/facts.d\n", 176 | "echo '[database]' >> /etc/ansible/facts.d/cloudformation.fact\n", 177 | "echo 'hostname=", { "Fn::GetAtt": [ "DBInstance", "Endpoint.Address" ] }, "' >> /etc/ansible/facts.d/cloudformation.fact\n", 178 | "echo 'password=", { "Ref" : "DBPassword" }, "' >> /etc/ansible/facts.d/cloudformation.fact\n", 179 | "echo '[elb]' >> /etc/ansible/facts.d/cloudformation.fact\n", 180 | "echo 'hostname=", { "Fn::GetAtt": [ "ElasticLoadBalancer", "DNSName" ] }, "' >> /etc/ansible/facts.d/cloudformation.fact\n", 181 | "\n", 182 | "cd /etc/ansible\n", 183 | "ansible-playbook wordpress.yml\n" 184 | ] 185 | ] 186 | } 187 | } 188 | } 189 | }, 190 | 191 | "DBEC2SecurityGroup": { 192 | "Type": "AWS::EC2::SecurityGroup", 193 | "Properties" : { 194 | "GroupDescription": "Open database for access", 195 | "VpcId": { "Ref": "VPCId" }, 196 | "SecurityGroupIngress" : [{ 197 | "IpProtocol" : "tcp", 198 | "FromPort" : "3306", 199 | "ToPort" : "3306", 200 | "SourceSecurityGroupId" : { "Ref" : "WebServerSecurityGroup" } 201 | }] 202 | } 203 | }, 204 | 205 | "DBSubnetGroup": { 206 | "Type" : "AWS::RDS::DBSubnetGroup", 207 | "Properties" : { 208 | "DBSubnetGroupDescription": "Subnet Group for Wordpress database", 209 | "SubnetIds" : { "Ref": "SubnetIds" } 210 | } 211 | }, 212 | 213 | "DBInstance" : { 214 | "Type": "AWS::RDS::DBInstance", 215 | "Properties": { 216 | "DBName" : "wordpress", 217 | "Engine" : "MySQL", 218 | "MasterUsername" : "root", 219 | "MasterUserPassword": { "Ref" : "DBPassword" }, 220 | "DBInstanceClass" : { "Ref" : "DBClass" }, 221 | "AllocatedStorage" : { "Ref" : "DBAllocatedStorage" }, 222 | "VPCSecurityGroups" : [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], 223 | "DBSubnetGroupName" : { "Ref" : "DBSubnetGroup" } 224 | } 225 | } 226 | }, 227 | 228 | "Outputs" : { 229 | "WebsiteURL" : { 230 | "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "ElasticLoadBalancer", "DNSName" ]} ]]}, 231 | "Description" : "WordPress Website" 232 | } 233 | } 234 | } 235 | --------------------------------------------------------------------------------