WordPress

Running WordPress on App Engine flexible environment (Long Version)

This post is Deprecated, please refer to the shorter version.

This is the archived version of the fifth post of “Running WordPress on Managed VMs” series. In this series, I have been doing some experiments to run WordPress on App Engine Managed VMs. I think this is a good time to summarize them and show you how to configure WordPress from scratch for running on App Engine flexible environment. This is a long post, and also there are some redundant parts in it, but I’d like to make this post a complete standalone guide for running WordPress on App Engine flexible environment and I’ll update this guide and try to make it shorter overtime, so please bare with me.

In this post, we’ll configure WordPress as follows:

  • Leverage App Engine’s autoscaler
  • Use Cloud SQL 2nd generation
  • Use read-only file system for better security
  • Use Google Cloud Storage for media upload
  • Use Memcached service for caching pages

Prerequisites

Configure Google Cloud SDK

Configure Google Cloud SDK with your account and the project.

$ gcloud auth login
...
...
$ gcloud config set project YOUR_PROJECT_ID

Let’s configure the GCS bucket for later use. The default App Engine bucket looks like YOUR_PROJECT_ID.appspot.com. Change the default acl of that bucket as follows:

 $ gsutil defacl ch -u AllUsers:R gs://YOUR_PROJECT_ID.appspot.com

Create and configure a Cloud SQL second generation instance

Let’s say we will use wp for various resource names; in particular for the instance name, the database name, and the user name.

You can create a new Cloud SQL second generation instance with the following command:

$ gcloud sql instances create wp \
  --activation-policy=ALWAYS \
  --tier=db-n1-standard-1

Then change the root password for your instance:

$ gcloud sql instances set-root-password wp \
  --password YOUR_INSTANCE_ROOT_PASSWORD # Don't use this password!

To access this MySQL instance, we’ll use Cloud SQL Proxy. Please download an appropriate binary from the download page, make it executable.

If you haven’t created a service account for the project, please create it(Choose a new service account). Download the JSON key file and save it in a secure place.

Run the proxy by the following command:

$ cloud_sql_proxy \
  -dir /tmp/cloudsql \
  -instances=YOUR_PROJECT_ID:us-central1:wp=tcp:3306 \
  -credential_file=PATH_TO_YOUR_SERVICE_ACCOUNT_JSON

Now you can access to the Cloud SQL instance with the normal MySQL client. Please create a new database and a user as follows:

$ mysql -h 127.0.0.1 -u root -p
mysql> create database wp;
mysql> create user 'wp'@'%' identified by 'PASSWORD'; // Don't use this password!
mysql> grant all on wp.* to 'wp'@'%';
mysql> exit
Bye

In the above example, I created a new database wp and a new user wp.

Download and configure WordPress

First create a directory for our project. Let’s say we’ll have wp-mvm directory.

$ mkdir wp-mvm
$ cd wp-mvm

Then download the latest WordPress and untar it:

$ wget https://wordpress.org/latest.tar.gz -O /tmp/latest.tar.gz
$ tar zxvf /tmp/latest.tar.gz

Now you have wordpress directory in your project.

If you have not install Composer, please install it. Then run the following:

$ composer require google/appengine-php-sdk

Then create the following files.

app.yaml:

runtime: custom
vm: true

beta_settings:
 cloud_sql_instances: YOUR_PROJECT:us-central1:wp

env_variables:
  DOCUMENT_ROOT: /app/wordpress

Dockerfile:

# This file will go away once gcloud implements fingerprinting.
FROM gcr.io/php-mvm-a/php-nginx:latest

nginx-app.conf:

location / {
    try_files $uri $uri/ /index.php?q=$uri&$args;
}

location /_ah/health {
    return 200 'ok';
}

php.ini:

suhosin.executor.func.blacklist=""
extension=bcmath.so
zend_extension=opcache.so

Copy the template config file.

$ cp wordpress/wp-config-sample.php wordpress/wp-config.php

Then edit the file, first add the following lines at the top (use actual project id for MY_BUCKET):

// Register GCS stream wrapper
set_include_path(__DIR__ . '/../vendor/google/appengine-php-sdk');
require_once(__DIR__ . '/../vendor/autoload.php');
stream_wrapper_register(
    'gs',
    '\google\appengine\ext\cloud_storage_streams\CloudStorageStreamWrapper',
    0);

// Bucket name for the media upload
define('MY_BUCKET', 'PROJECT_ID.appspot.com');

// true on production.
define('ON_MVM', filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN));

// Cache settings
define('WP_CACHE', ON_MVM);

$memcached_servers = array(
    'default' => array(
        getenv('MEMCACHE_PORT_11211_TCP_ADDR')
        . ':' . getenv('MEMCACHE_PORT_11211_TCP_PORT')
    )
);

// Auto detect the URL fails.
define('WP_HOME', ON_MVM ? 'https://'.$_SERVER['HTTP_HOST']
       : 'http://'.$_SERVER['HTTP_HOST']);
define('WP_SITEURL', ON_MVM ? 'https://'.$_SERVER['HTTP_HOST']
       : 'http://'.$_SERVER['HTTP_HOST']);

// Need this for cookies.
define('FORCE_SSL_ADMIN', ON_MVM);

// Get HTTPS value from the App Engine specific header.
$_SERVER['HTTPS'] = ON_MVM ? $_SERVER['HTTP_X_APPENGINE_HTTPS'] : false;

Then the MySQL settings as follows:

/** The name of the database for WordPress */
define('DB_NAME', 'wp');

/** MySQL database username */
define('DB_USER', 'wp');

/** MySQL database password */
define('DB_PASSWORD', 'MYSQL_USER_PASSWORD');

/** MySQL hostname */
define('DB_HOST', ON_MVM
       ? 'localhost:/cloudsql/PROJECT_ID:us-central1:wp'
       : '127.0.0.1');

Don’t forget replacing Authentication Unique Keys and Salts. You can generate those Keys and Salts at the following URL:
https://api.wordpress.org/secret-key/1.1/salt/

Phew, configuration is done!

Install plugins

Download Batcache and Memcached into wordpress/wp-content/plugins directory.

Create a new file `wordpress/wp-content/plugins/gcs-media.php` with the following:

<?php
/**
 * @package GCS media
 * @version 0.1
 */
/*
Plugin Name: GCS media
Plugin URI: https://wp.gaeflex.ninja/
Description: Use Google Cloud Storage for media upload.
Author: Takashi Matsuo
Version: 0.1
Author URI: https://wp.gaeflex.ninja/
License: Apache 2.0
*/

namespace GCS\Media;

defined( 'ABSPATH' ) or die('No direct access!');

add_filter('upload_dir', 'GCS\Media\filter_upload_dir');
function filter_upload_dir( $values ) {
    $basedir = 'gs://' . MY_BUCKET;
    $baseurl = 'https://storage.googleapis.com/' . MY_BUCKET;
    $values = array(
        'path' => $basedir . $values['subdir'],
        'subdir' => $values['subdir'],
        'error' => false,
    );
    $values['url'] = rtrim($baseurl . $values['subdir'], '/');
    $values['basedir'] = $basedir;
    $values['baseurl'] = $baseurl;
    return $values;
}

Create shell wrapper for deployment/local run

deploy-wrapper.sh:

#!/bin/sh

set -x

# Temporary copy the dropins

cp wordpress/wp-content/plugins/batcache/advanced-cache.php \
    wordpress/wp-content/advanced-cache.php
cp wordpress/wp-content/plugins/memcached/object-cache.php \
    wordpress/wp-content/object-cache.php

$@

# Remove the file for local run
rm wordpress/wp-content/advanced-cache.php \
    wordpress/wp-content/object-cache.php

wp.php:

Copy the content from Run WordPress Locally Using PHP’s Built-In Web Server.

Run WordPress locally and set username and password

Run the following command to run WordPress locally:

$ php -S localhost:8000 -t wordpress wp.php

Then access http://localhost:8000/. Follow the installation steps, create the first user and its password. Login to the Dashboard and update if any of the plugins have update. I’m assuming that the local network is secure here. If you think differently, maybe you can first deploy the app, and access the live site immediately, then create the first username and its password there.

Now it’s ready for the first deployment.

Deployment

Use the shell script wrapper for deployment as follows:

$ gcloud app deploy --promote --stop-previous-version app.yaml

Then access your site, use the username and the password you created locally.
https://PROJECT_ID.appspot.com/

Go to the Dashboard, and in the Plugins page, activate the GCS media plugin. Try uploading a media and confirm the image is uploaded to the GCS bucket.

Check if Batcache is working

In the plugin page, you should see these 2 drop-ins are activated as follows:

WP-dropins

To make sure it’s really working, you can open an incognito window and visit the site because the cache plugin only serves from cache to anonymous users. Also you should access the site several times because the plugin only caches pages which are considered popular. You will see the following Batcache stats in the HTML source:

<!--
	generated 31 seconds ago
	generated in 0.165 seconds
	served from batcache in 0.009 seconds
	expires in 269 seconds
-->

Various workflows

Install/Update plugins/themes

Because the wp-content directory on the server is read-only, you have to do this locally. Run WordPress locally and update plugins/themes in the local Dashboard, then deploy, then activate them in the production Dashboard.

Remove plugins/themes

First Deactivate them in the production Dashboard, then remove them completely locally. The next deployment will remove those files from the production environment.

Update WordPress itself

Most of the case, just download the newest WordPress and overwrite the existing wordpress directory. It is still possible that the existing config files are not compatible with the newest WordPress, so please update the config file manually in that case.

Update the image

We sometimes release the security update for the php-docker image. Then you’ll have to re-deploy your WordPress instance to get the security update.

Conclusion

We walked though how to run WordPress on App Engine flexible environment. Because the flexible environment is securely configured and we’re using Cloud SQL proxy for SQL access, so you can run your WordPress site in a secure manner. It performs well because we’re leveraging Memcached service provided by App Engine. Also thanks to the App Engine autoscaler, Google will automatically spin up new instances when your site is getting lots of traffic.

If you need to run new WordPress site, please give this guide a try and let us know what you think.

Leave a Reply

Your email address will not be published. Required fields are marked *

By submitting this form, you accept the Mollom privacy policy.