Infrastructure as Code: Provisioning Secure WordPress Clusters on Google Cloud Using Terraform
Terraform Project Structure and Provider Configuration
We’ll begin by establishing a robust Terraform project structure. This modular approach enhances maintainability and reusability. Our core configuration will reside in the main.tf file, defining the Google Cloud provider and essential resources. For security, we’ll leverage Google Cloud’s Identity and Access Management (IAM) for service account authentication.
First, ensure you have authenticated Terraform with Google Cloud. The recommended method is to create a dedicated service account with appropriate permissions (e.g., Compute Admin, Storage Admin, IAM User) and download its JSON key file. Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of this key file.
main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
}
}
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
variable "gcp_project_id" {
description = "The GCP project ID to deploy resources into."
type = string
}
variable "gcp_region" {
description = "The GCP region to deploy resources into."
type = string
default = "us-central1"
}
variable "gcp_zone" {
description = "The GCP zone to deploy resources into."
type = string
default = "us-central1-a"
}
variable "network_name" {
description = "Name for the VPC network."
type = string
default = "wordpress-vpc"
}
variable "subnet_name" {
description = "Name for the subnet."
type = string
default = "wordpress-subnet"
}
variable "subnet_cidr" {
description = "CIDR block for the subnet."
type = string
default = "10.0.1.0/24"
}
variable "wordpress_instance_name" {
description = "Name for the WordPress Compute Engine instance."
type = string
default = "wordpress-web"
}
variable "wordpress_machine_type" {
description = "Machine type for the WordPress Compute Engine instance."
type = string
default = "e2-medium"
}
variable "wordpress_disk_size" {
description = "Size of the boot disk for the WordPress instance in GB."
type = number
default = 20
}
variable "wordpress_image" {
description = "The boot disk image for the WordPress instance."
type = string
default = "debian-cloud/debian-11"
}
variable "db_instance_name" {
description = "Name for the Cloud SQL instance."
type = string
default = "wordpress-db"
}
variable "db_version" {
description = "The database version for Cloud SQL."
type = string
default = "POSTGRES_13" # Or MYSQL_8_0
}
variable "db_tier" {
description = "The machine type for the Cloud SQL instance."
type = string
default = "db-f1-micro"
}
variable "db_disk_size" {
description = "Size of the data disk for the Cloud SQL instance in GB."
type = number
default = 10
}
variable "db_user" {
description = "Username for the database."
type = string
default = "wp_user"
}
variable "db_password" {
description = "Password for the database."
type = string
sensitive = true
}
variable "db_name" {
description = "Name of the database."
type = string
default = "wordpress_db"
}
variable "firewall_rule_name" {
description = "Name for the firewall rule."
type = string
default = "wordpress-allow-http-https"
}
variable "ssh_user" {
description = "Username for SSH access to the WordPress instance."
type = string
default = "admin"
}
variable "ssh_public_key_path" {
description = "Path to the SSH public key file for instance access."
type = string
}
output "wordpress_instance_ip" {
description = "The external IP address of the WordPress instance."
value = google_compute_instance.wordpress.network_interface[0].access_config[0].nat_ip
}
output "db_instance_connection_name" {
description = "The connection name of the Cloud SQL instance."
value = google_sql_database_instance.wordpress_db.connection_name
}
output "db_instance_private_ip" {
description = "The private IP address of the Cloud SQL instance."
value = google_sql_database_instance.wordpress_db.private_ip_address
}
Networking and Security Group Configuration
A secure WordPress deployment necessitates a well-defined Virtual Private Cloud (VPC) network and appropriate firewall rules. We will create a custom VPC network and a subnet to isolate our WordPress resources. Subsequently, we’ll configure a firewall rule to allow inbound HTTP and HTTPS traffic to our WordPress instance.
VPC Network and Subnet
resource "google_compute_network" "wordpress_vpc" {
name = var.network_name
auto_create_subnetworks = false
routing_mode = "REGIONAL"
}
resource "google_compute_subnetwork" "wordpress_subnet" {
name = var.subnet_name
ip_cidr_range = var.subnet_cidr
region = var.gcp_region
network = google_compute_network.wordpress_vpc.id
}
Firewall Rule
resource "google_compute_firewall" "wordpress_firewall" {
name = var.firewall_rule_name
network = google_compute_network.wordpress_vpc.name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
# Allow SSH access from a specific IP range for management.
# Consider using a more restrictive range or bastion host for production.
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["0.0.0.0/0"] # WARNING: This allows SSH from anywhere. Restrict this in production.
target_tags = ["wordpress"] # Apply this tag to the WordPress instance
}
Security Note: The source_ranges = ["0.0.0.0/0"] for SSH is a significant security risk in production environments. It’s crucial to restrict this to known IP addresses or implement a bastion host for secure SSH access. For this example, we’ll use it for simplicity, but it must be hardened.
Cloud SQL Instance for WordPress Database
A managed database service like Google Cloud SQL is essential for a production-ready WordPress setup. It handles patching, backups, and high availability. We’ll provision a Cloud SQL instance, configure it with a specific version, and create a database and user for WordPress.
resource "google_sql_database_instance" "wordpress_db" {
name = var.db_instance_name
region = var.gcp_region
database_version = var.db_version
settings {
tier = var.db_tier
ip_configuration {
ipv4_enabled = true
private_network = google_compute_network.wordpress_vpc.id
}
backup_configuration {
enabled = true
binary_log_enabled = (var.db_version == "MYSQL_8_0" || var.db_version == "MYSQL_5_7") # Required for point-in-time recovery for MySQL
}
# For higher availability, consider:
# availability_type = "REGIONAL"
}
# Prevent accidental deletion of the database instance
deletion_protection = true
}
resource "google_sql_database" "wordpress_db_name" {
name = var.db_name
instance = google_sql_database_instance.wordpress_db.name
charset = "UTF8"
}
resource "google_sql_user" "wordpress_db_user" {
name = var.db_user
instance = google_sql_database_instance.wordpress_db.name
host = "%" # Allows connection from any host within the authorized network
password = var.db_password
}
Important: The private_network setting ensures the Cloud SQL instance is only accessible from within your VPC. This is a critical security measure. For MySQL, enabling binary_log_enabled is necessary for point-in-time recovery.
WordPress Compute Engine Instance
The heart of our WordPress deployment is a Compute Engine instance. We’ll configure it with a suitable machine type, boot disk, and importantly, a startup script to automate the installation and configuration of WordPress, Apache/Nginx, PHP, and the database connection.
Startup Script for WordPress Installation
#!/bin/bash
# Update package list and install necessary packages
sudo apt-get update -y
sudo apt-get install -y apache2 php libapache2-mod-php php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip unzip wget
# Download WordPress
cd /var/www/html
wget https://wordpress.org/latest.zip
unzip latest.zip
mv wordpress/* .
rm -rf wordpress latest.zip
# Set ownership and permissions for WordPress files
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
# Configure WordPress wp-config.php
sudo cp wp-config-sample.php wp-config.php
# Replace database credentials in wp-config.php
# IMPORTANT: Ensure the DB_PASSWORD variable is securely passed or managed.
sudo sed -i "s/database_name_here/${DB_NAME}/" wp-config.php
sudo sed -i "s/username_here/${DB_USER}/" wp-config.php
sudo sed -i "s/password_here/${DB_PASSWORD}/" wp-config.php
sudo sed -i "s/localhost/${DB_HOST}/" wp-config.php # Use Cloud SQL Proxy or private IP
# Generate unique security keys and salts
curl -s https://api.wordpress.org/secret-key/1.1/salt/ >> wp-config.php
# Restart Apache
sudo systemctl restart apache2
# Install Cloud SQL Auth Proxy (for connecting to Cloud SQL)
# This requires the instance connection name to be passed.
# For simplicity, we'll assume direct private IP access is configured.
# If using Cloud SQL Auth Proxy, you'd need to download and run it as a service.
# Example for direct private IP connection (if configured in wp-config.php)
# Ensure DB_HOST is set to the private IP of the Cloud SQL instance.
# If using Cloud SQL Auth Proxy, DB_HOST would be '127.0.0.1:3306' (for MySQL) or similar.
echo "WordPress installation complete."
Compute Engine Instance Resource
resource "google_compute_instance" "wordpress" {
name = var.wordpress_instance_name
machine_type = var.wordpress_machine_type
zone = var.gcp_zone
tags = ["wordpress"]
boot_disk {
initialize_params {
image = var.wordpress_image
size = var.wordpress_disk_size
}
}
network_interface {
subnetwork = google_compute_subnetwork.wordpress_subnet.id
access_config {
// Ephemeral IP, remove for static IP
}
}
metadata_startup_script = templatefile("${path.module}/scripts/startup.sh", {
DB_NAME = var.db_name
DB_USER = var.db_user
DB_PASSWORD = var.db_password
DB_HOST = google_sql_database_instance.wordpress_db.private_ip_address # Use private IP for direct connection
})
# SSH Key configuration
metadata {
ssh-keys = "${var.ssh_user}:${file(var.ssh_public_key_path)}"
}
# Ensure the database is created before the instance starts up
depends_on = [
google_sql_database.wordpress_db_name,
google_sql_user.wordpress_db_user
]
}
The metadata_startup_script uses templatefile to inject sensitive variables like database credentials and the Cloud SQL private IP address directly into the startup script. This avoids hardcoding them. The depends_on block ensures that the Cloud SQL resources are provisioned and ready before the Compute Engine instance attempts to start.
Deployment and Management
With the Terraform configuration in place, deploying your secure WordPress cluster is straightforward. Ensure you have a terraform.tfvars file or provide variables via the command line.
terraform.tfvars Example
gcp_project_id = "your-gcp-project-id" gcp_region = "us-central1" gcp_zone = "us-central1-a" db_password = "your-strong-db-password" ssh_public_key_path = "~/.ssh/id_rsa.pub" # Path to your SSH public key # Add any other variables you wish to override from defaults
Terraform Commands
# Initialize Terraform terraform init # Review the execution plan terraform plan # Apply the configuration to provision resources terraform apply # Destroy the resources when no longer needed terraform destroy
After running terraform apply, Terraform will output the external IP address of your WordPress instance. You can then access your WordPress site by navigating to this IP address in your web browser. The database will be accessible via its private IP address from within the VPC, or through the Cloud SQL Auth Proxy if you choose to implement that for enhanced security and easier management.
Further Enhancements and Security Considerations
This setup provides a foundational secure WordPress cluster. For production environments, consider the following enhancements:
- Load Balancing: Deploy multiple WordPress instances behind a Google Cloud Load Balancer for high availability and scalability.
- Managed Instance Groups: Use Managed Instance Groups (MIGs) to automatically scale your WordPress instances based on load.
- Static IP Address: Assign a static external IP address to your WordPress instance for a stable endpoint.
- SSL/TLS: Configure SSL certificates for HTTPS. This can be managed at the load balancer level or on the instance itself.
- Cloud SQL High Availability: Configure the Cloud SQL instance for regional availability.
- Secrets Management: For production, avoid passing secrets directly in
.tfvars. Use a secrets manager like Google Secret Manager or HashiCorp Vault. - SSH Access: Implement a bastion host or use Identity-Aware Proxy (IAP) for secure SSH access instead of opening port 22 to the internet.
- Monitoring and Logging: Integrate with Google Cloud’s operations suite (formerly Stackdriver) for comprehensive monitoring and logging.
- Content Delivery Network (CDN): Utilize Cloud CDN for caching static assets and improving performance.