Skip to content
This repository has been archived by the owner on Oct 31, 2019. It is now read-only.

save wp-content data to a separate volume #16

Merged
merged 10 commits into from
Sep 1, 2017
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ All configuration is code, and [all setup steps are documented](#setup). New env

The code follows the [Don't Repeat Yourself (DRY)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. Values that need to be shared are passed around as variables, rather than being hard-coded in multiple places. This ensures configuration stays in sync.

## Immutable deployment

Once deployed, immutable resources should not be modified. The goal is to have as much of the infrastructure be immutable as possible, with regular backups of the mutable parts. EBS volumes where state is stored (like `wp-content/` for WordPress) and database are common mutable resources, EC2 instances and their root volumes should not be.

For mutable resources, enable [deletion protection](https://www.terraform.io/docs/configuration/resources.html#prevent_destroy).

### Configuration travels "down"

_Related to [DRY](#dry)._
Expand All @@ -44,6 +50,10 @@ WordPress has no idea that it's running on AWS, Ansible doesn't know it's runnin

Give (internal) custom domains to services, rather than using the hostnames/IPs provided by AWS by default. In this repository, a custom DNS record is added in a [Private Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html) in Route 53, pointing to the database - see [`terraform/dns.tf`](terraform/dns.tf). This helps with [service discovery](https://en.wikipedia.org/wiki/Service_discovery), because references to services (the database, in this case) can remain constant while the database can be recreated with a new IP, load-balanced, etc. See [this article](https://www.infoq.com/articles/rest-discovery-dns) for more information on this approach.

### Disk

* [Encryption](https://www.terraform.io/docs/providers/aws/r/ebs_volume.html#encrypted) is enabled

## Setup

1. Set up the AWS CLI.
Expand Down Expand Up @@ -111,6 +121,8 @@ For initial or subsequent deployment:
terraform apply
```

Note that if the public IP address changes after you set up the site initially, you will need to [change the site URL](https://codex.wordpress.org/Changing_The_Site_URL#Changing_the_Site_URL) in WordPress.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learned the hard way that WordPress saves the URL where it's accessed for the initial setup in the database, and therefore having the public IP change frequently was making parts of the site inaccessible. While a custom domain is what you'd actually use, an Elastic IP is good enough for now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to look at installing the wp-cli to make changes like this easier to do from the machine itself in case you can't get in to the web interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! Added as follow-up task: #22.


## Troubleshooting

To SSH into the running instance:
Expand Down
13 changes: 13 additions & 0 deletions ansible/wordpress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- hosts: all
become: true
vars:
apache_user: www-data
apache_group: "{{ apache_user }}"
wp_install_dir: /usr/share/wordpress
wp_content_dir: "{{ wp_install_dir }}/wp-content"
tasks:
Expand Down Expand Up @@ -33,6 +35,17 @@
src: config.php
# https://superuser.com/a/559371/102684
dest: /etc/wordpress/config-default.php
owner: "{{ apache_user }}"
group: "{{ apache_group }}"

# https://help.ubuntu.com/community/WordPress#Install_WordPress
- name: Fix permissions on WordPress directory
file:
path: "{{ wp_install_dir }}"
owner: "{{ apache_user }}"
group: "{{ apache_group }}"
state: directory
recurse: true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not positive that we want to allow automatic updates...? That would mean WordPress itself wouldn't be deployed immutably. Happy to take the conversation to an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the upstream source that deploys Wordpress on the instance is in sync with the updates, it may not be too bad. Wordpress updates download into /wp-content/updates and then deploy into the main directory. Sometimes those updates contain schema updates for the database as well. Lots to consider.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the way that I did this for our modified version of cf-ex-wordpress (deploying in cloud.gov) was to do some json in a particular schema, and feed that to wp-cli.

Relevant python bits here: cloud-gov/cf-ex-wordpress@25be580#diff-81085e34fc3ba4cf38cba76d477219bdR59 . I'm sure that could be turned into some ansible code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's worry about it separately, if that's ok with you both: #23.


- name: Configure Apache
template:
Expand Down
11 changes: 10 additions & 1 deletion terraform/ec2.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ data "aws_ami" "wordpress" {
owners = ["self"]
}

data "aws_subnet" "public" {
id = "${module.vpc.public_subnets[0]}"
}

resource "aws_instance" "wordpress" {
ami = "${data.aws_ami.wordpress.id}"
instance_type = "t2.micro"
subnet_id = "${module.vpc.public_subnets[0]}"
subnet_id = "${data.aws_subnet.public.id}"
vpc_security_group_ids = ["${aws_security_group.wordpress_ec2.id}"]
key_name = "${aws_key_pair.deployer.key_name}"

Expand All @@ -33,3 +37,8 @@ resource "aws_instance" "wordpress" {
}
}
}

resource "aws_eip" "public" {
instance = "${aws_instance.wordpress.id}"
vpc = true
}
40 changes: 40 additions & 0 deletions terraform/files/attach-data-volume.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash

# adapted from
# https://github.com/hashicorp/terraform/issues/2740#issuecomment-288549352

set -e
set -x

# note the name doesn't match the device_name in Terraform
DEVICE=/dev/xvdf
OWNER=www-data
DEST=/usr/share/wordpress/wp-content
OLDDEST=$DEST-old

devpath=$(readlink -f $DEVICE)

if [[ $(sudo file -s $devpath) != *ext4* && -b $devpath ]]; then
# Filesystem has not been created. Create it!
sudo mkfs -t ext4 $devpath
fi

sudo mv $DEST $OLDDEST
sudo mkdir -p $DEST

echo "$devpath $DEST ext4 defaults,nofail,noatime,nodiratime,barrier=0,data=writeback 0 2" | sudo tee -a /etc/fstab > /dev/null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably check that it doesn't exist before appending to fstab, yeah?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will look into the best way to do that. This script got complex enough that I'm considering changing it to Ansible (as a follow-up), which takes care of checks like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(but let me know if you know of an example)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to do with an egrep and regex:

if ! egrep "^${devpath}" /etc/fstab; then
  echo "$devpath $DEST ... etc
fi

sudo mount $DEST

sudo chown $OWNER:$OWNER $DEST
sudo chmod 0775 $DEST

# TODO: /etc/rc3.d/S99local to maintain on reboot
echo deadline | sudo tee /sys/block/$(basename "$devpath")/queue/scheduler
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

# if themes directory doesn't exist...
if [ ! -d "$DEST/themes" ]; then
# ...volume doesn't contain initial data. Copy initial content in.
# https://askubuntu.com/a/86891/501568
sudo cp -a $OLDDEST/. $DEST/
fi
4 changes: 2 additions & 2 deletions terraform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ output "ssh_user" {
}

output "public_ip" {
value = "${aws_instance.wordpress.public_ip}"
value = "${aws_eip.public.public_ip}"
}

output "public_subnet_id" {
Expand All @@ -27,5 +27,5 @@ output "db_pass" {
}

output "url" {
value = "http://${aws_instance.wordpress.public_ip}/blog/"
value = "http://${aws_eip.public.public_ip}/blog/"
}
5 changes: 5 additions & 0 deletions terraform/rds.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resource "aws_db_instance" "wordpress" {
engine = "mysql"
engine_version = "5.7.17"
instance_class = "db.t2.micro"
copy_tags_to_snapshot = true
# just for development
skip_final_snapshot = true

Expand All @@ -13,4 +14,8 @@ resource "aws_db_instance" "wordpress" {

db_subnet_group_name = "${module.vpc.database_subnet_group}"
vpc_security_group_ids = ["${aws_security_group.wordpress_db.id}"]

lifecycle {
prevent_destroy = true
}
}
26 changes: 26 additions & 0 deletions terraform/volume.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
resource "aws_ebs_volume" "wp_content" {
availability_zone = "${data.aws_subnet.public.availability_zone}"
type = "gp2"
size = 10
encrypted = true

lifecycle {
prevent_destroy = true
}
}

resource "aws_volume_attachment" "wp_content" {
device_name = "/dev/sdf"
volume_id = "${aws_ebs_volume.wp_content.id}"
instance_id = "${aws_instance.wordpress.id}"

# https://github.com/hashicorp/terraform/issues/2740#issuecomment-288549352
skip_destroy = true
provisioner "remote-exec" {
script = "files/attach-data-volume.sh"
connection {
user = "${var.ssh_user}"
host = "${aws_eip.public.public_ip}"
}
}
}