Infrastructure as Code: Provisioning Secure PHP Clusters on Google Cloud Using Terraform
Terraform Provider Configuration for Google Cloud
To begin provisioning resources on Google Cloud Platform (GCP) using Terraform, we need to configure the Google Cloud provider. This involves specifying the project ID and the region where our infrastructure will reside. For enhanced security and manageability, we’ll also define a service account with the necessary permissions. This service account should be created beforehand with roles like ‘Compute Admin’ and ‘Service Account User’.
Create a file named main.tf and add the following configuration. Replace your-gcp-project-id with your actual GCP project ID and your-gcp-region with your desired region (e.g., us-central1).
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
}
}
provider "google" {
project = "your-gcp-project-id"
region = "your-gcp-region"
}
# It's highly recommended to use a dedicated service account for Terraform.
# Ensure this service account has the necessary roles (e.g., Compute Admin, Service Account User).
# You can authenticate Terraform by setting the GOOGLE_APPLICATION_CREDENTIALS environment variable
# to the path of your service account key file.
# Example: export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account-key.json"
VPC Network and Subnet Configuration
A robust network infrastructure is the foundation of any secure deployment. We’ll define a custom Virtual Private Cloud (VPC) network and a subnet within it. This provides better control over network traffic and IP addressing. We’ll also configure a firewall rule to allow SSH access (port 22) from a specific IP range for administrative purposes. For production, consider restricting this further or using IAP (Identity-Aware Proxy).
resource "google_compute_network" "vpc_network" {
name = "php-cluster-vpc"
auto_create_subnetworks = false
routing_mode = "REGIONAL"
}
resource "google_compute_subnetwork" "subnet" {
name = "php-cluster-subnet"
ip_cidr_range = "10.0.1.0/24"
region = var.gcp_region # Assuming var.gcp_region is defined or hardcoded
network = google_compute_network.vpc_network.id
}
resource "google_compute_firewall" "ssh_firewall" {
name = "allow-ssh"
network = google_compute_network.vpc_network.name
allow {
protocol = "tcp"
ports = ["22"]
}
# Restrict source_ranges to your management IP or VPN subnet for security.
# For demonstration, using a broad range, but this should be tightened.
source_ranges = ["0.0.0.0/0"] # !! PRODUCTION: Restrict this to your specific IP/range !!
target_tags = ["ssh-enabled"] # Apply this tag to instances that should allow SSH
}
# Define a variable for region if not hardcoded in provider block
variable "gcp_region" {
description = "The GCP region to deploy resources in."
type = string
default = "us-central1" # Example default
}
Managed Instance Group (MIG) for PHP Application Servers
To ensure high availability and scalability for our PHP application, we’ll deploy a Managed Instance Group (MIG). This group will manage a set of identical Compute Engine instances. We’ll use a custom machine image that has PHP, a web server (like Nginx or Apache), and our application code pre-installed. Auto-scaling will be configured based on CPU utilization.
First, let’s define the instance template. This template specifies the machine type, boot disk image, network interfaces, and startup script for each instance in the MIG. The startup script is crucial for bootstrapping the application environment if not fully baked into the image.
resource "google_compute_instance_template" "php_app_template" {
name_prefix = "php-app-template-"
machine_type = "e2-medium" # Choose an appropriate machine type
tags = ["http-server", "ssh-enabled"]
disk {
source_image = "debian-cloud/debian-11" # Replace with your custom PHP image if available
auto_delete = true
boot = true
}
network_interface {
subnetwork = google_compute_subnetwork.subnet.id
# Access config for a public IP. For internal-only access, remove this.
access_config {
// Ephemeral IP
}
}
# Example startup script to install Nginx and PHP if not in image
# In a production scenario, a pre-built custom image is preferred for faster boot times
# and more consistent deployments.
metadata_startup_script = <<-EOT
#!/bin/bash
apt-get update -y
apt-get install -y nginx php php-fpm php-mysql # Add other PHP extensions as needed
# Configure Nginx to serve your PHP application
# This is a simplified example. You'll need to adapt it to your app's structure.
cat < /etc/nginx/sites-available/default
server {
listen 80 default_server;
root /var/www/html; # Your application's web root
index index.php index.html index.htm;
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version if necessary
}
}
EOF
systemctl restart nginx
systemctl enable nginx
# Deploy your application code here (e.g., via git clone, gsutil cp)
# Example:
# git clone https://your-repo.com/your-app.git /var/www/html
# chown -R www-data:www-data /var/www/html
EOT
lifecycle {
create_before_destroy = true
}
}
resource "google_compute_instance_group_manager" "php_app_mig" {
name = "php-app-mig"
base_instance_name = "php-app"
zone = "us-central1-a" # Should match a zone within your region
target_size = 2 # Initial number of instances
version {
instance_template = google_compute_instance_template.php_app_template.id
name = "primary"
}
# Auto-scaling configuration
auto_healing_policies {
health_check = google_compute_health_check.php_health_check.id
initial_delay_sec = 300
}
update_policy {
type = "PROACTIVE"
minimal_action = "REPLACE"
}
# Auto-scaling based on CPU utilization
autoscaling_policy {
max_num_replicas = 10
min_num_replicas = 2
cooldown_period_sec = 60
cpu_utilization {
target = 0.6 # Target 60% CPU utilization
}
}
}
# Health check for the MIG
resource "google_compute_health_check" "php_health_check" {
name = "php-app-health-check"
check_interval_sec = 5
timeout_sec = 5
healthy_threshold = 2
unhealthy_threshold = 2
http_health_check {
port = 80
request_path = "/" # Adjust to your application's health check endpoint
}
}
Load Balancer for Traffic Distribution
To distribute incoming traffic across the PHP application instances and provide a single point of access, we’ll set up a Google Cloud Load Balancer. We’ll use a Global External HTTP(S) Load Balancer, which is suitable for web applications. This involves creating a backend service that points to our MIG, a URL map, and a target proxy.
# Forwarding rule for the load balancer
resource "google_compute_forwarding_rule" "http_forwarding_rule" {
name = "php-app-http-forwarding-rule"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL"
port_range = "80"
target = google_compute_target_http_proxy.http_proxy.id
ip_address = "0.0.0.0" # Any IP address
}
# Target HTTP proxy
resource "google_compute_target_http_proxy" "http_proxy" {
name = "php-app-http-proxy"
url_map = google_compute_url_map.url_map.id
}
# URL map
resource "google_compute_url_map" "url_map" {
name = "php-app-url-map"
default_service = google_compute_backend_service.php_backend_service.id
}
# Backend service
resource "google_compute_backend_service" "php_backend_service" {
name = "php-app-backend-service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 10
enable_cdn = false
load_balancing_scheme = "EXTERNAL"
backend {
group = google_compute_instance_group_manager.php_app_mig.instance_group
}
health_checks = [google_compute_health_check.php_health_check.id]
}
# Output the load balancer IP address
output "load_balancer_ip" {
description = "The IP address of the HTTP load balancer."
value = google_compute_forwarding_rule.http_forwarding_rule.ip_address
}
Database Provisioning (e.g., Cloud SQL for MySQL)
A secure PHP application typically requires a database. For this example, we’ll provision a Cloud SQL instance for MySQL. It’s crucial to configure this securely, including setting strong passwords, enabling private IP, and restricting network access.
resource "google_sql_database_instance" "php_db" {
name = "php-app-db"
region = var.gcp_region
database_version = "MYSQL_8_0"
project = "your-gcp-project-id" # Ensure this is set
settings {
tier = "db-f1-micro" # Choose an appropriate tier
ip_configuration {
ipv4_enabled = false # Disable public IP for security
private_network = google_compute_network.vpc_network.id
}
backup_configuration {
enabled = true
}
# Consider enabling point-in-time recovery for production
# point_in_time_recovery_enabled = true
}
# Prevent accidental deletion of the database
deletion_protection = true
}
resource "google_sql_database" "app_db" {
name = "app_database"
instance = google_sql_database_instance.php_db.name
charset = "utf8mb4"
collation = "utf8mb4_unicode_ci"
}
resource "google_sql_user" "app_user" {
name = "app_user"
instance = google_sql_database_instance.php_db.name
host = "%" # Restrict this to specific IPs or localhost if possible
password = random_password.db_password.result
}
resource "random_password" "db_password" {
length = 16
special = true
override_special = "_%@"
}
# Output the database connection name and user for application configuration
output "db_connection_name" {
description = "The connection name of the Cloud SQL instance."
value = google_sql_database_instance.php_db.connection_name
}
output "db_user" {
description = "The database username."
value = google_sql_user.app_user.name
}
output "db_password" {
description = "The database password."
value = random_password.db_password.result
sensitive = true # Mark as sensitive to prevent accidental logging
}
Securing the Deployment
Security is paramount. Beyond the network configurations and private IP for the database, consider these additional measures:
- Secrets Management: Use GCP Secret Manager or HashiCorp Vault to store database credentials, API keys, and other sensitive information. Inject these secrets into your application environment securely, rather than hardcoding them or storing them in Terraform state.
- IAM Roles: Apply the principle of least privilege for the Terraform service account and any service accounts used by your Compute Engine instances.
- Firewall Rules: Continuously review and tighten firewall rules. For example, instead of allowing SSH from
0.0.0.0/0, use a bastion host or GCP’s Identity-Aware Proxy (IAP) for secure SSH access. - Instance Images: Use hardened, minimal OS images. Regularly patch and update your custom images to address security vulnerabilities.
- Application Security: Implement standard PHP security practices such as input validation, parameterized queries to prevent SQL injection, and secure session management.
- HTTPS: For production, configure the load balancer with an SSL certificate (e.g., Google-managed SSL certificates) to enable HTTPS.
Deployment Workflow
Once you have your Terraform configuration files (main.tf, variables.tf, etc.) in place:
- Initialize Terraform: Run
terraform initin your project directory. This downloads the necessary provider plugins. - Review the Plan: Execute
terraform plan. This command shows you exactly what Terraform will create, modify, or destroy in your GCP environment. Carefully review this output. - Apply the Configuration: Run
terraform apply. Terraform will prompt you to confirm the changes before provisioning the resources. - Destroy Resources (when needed): To tear down the entire infrastructure, use
terraform destroy.
This comprehensive Terraform setup provides a secure, scalable, and automated way to provision your PHP application clusters on Google Cloud. Remember to adapt the configurations, especially security settings like IP ranges and instance types, to your specific production requirements.