• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Infrastructure as Code: Provisioning Secure Ruby Clusters on Google Cloud Using Terraform

Infrastructure as Code: Provisioning Secure Ruby Clusters on Google Cloud Using Terraform

Terraform Project Structure and Provider Configuration

We’ll begin by establishing a robust Terraform project structure. This organization is crucial for managing complexity, especially as your infrastructure grows. Our core configuration will reside in main.tf, variables in variables.tf, and outputs in outputs.tf. For this example, we’ll focus on provisioning a secure Ruby cluster on Google Cloud Platform (GCP). This involves setting up a Virtual Private Cloud (VPC), firewall rules, and Compute Engine instances.

First, let’s define the GCP provider and its authentication. It’s best practice to avoid hardcoding credentials. Instead, leverage environment variables or GCP’s built-in credential discovery mechanisms.

main.tf – Provider Block

# main.tf

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}

variables.tf – Input Variables

# variables.tf

variable "gcp_project_id" {
  description = "The GCP project ID to deploy resources into."
  type        = string
}

variable "gcp_region" {
  description = "The GCP region for resource deployment."
  type        = string
  default     = "us-central1"
}

variable "vpc_name" {
  description = "Name for the VPC network."
  type        = string
  default     = "ruby-cluster-vpc"
}

variable "subnet_name" {
  description = "Name for the subnet."
  type        = string
  default     = "ruby-cluster-subnet"
}

variable "subnet_cidr" {
  description = "CIDR block for the subnet."
  type        = string
  default     = "10.0.1.0/24"
}

variable "ruby_instance_count" {
  description = "Number of Ruby application instances."
  type        = number
  default     = 2
}

variable "ruby_instance_machine_type" {
  description = "Machine type for Ruby instances."
  type        = string
  default     = "e2-medium"
}

variable "ruby_instance_image" {
  description = "GCP image for Ruby instances."
  type        = string
  default     = "ubuntu-os-cloud/ubuntu-2004-lts"
}

variable "ssh_user" {
  description = "Username for SSH access to instances."
  type        = string
  default     = "deployer"
}

variable "ssh_public_key_path" {
  description = "Path to the public SSH key file."
  type        = string
}

Networking: VPC, Subnet, and Firewall Rules

A secure infrastructure starts with well-defined networking. We’ll create a custom VPC network and a subnet to isolate our Ruby cluster. Crucially, we’ll implement strict firewall rules to control ingress and egress traffic, allowing only necessary ports for application access and management.

main.tf – Network Resources

# main.tf (continued)

resource "google_compute_network" "vpc_network" {
  name                    = var.vpc_name
  auto_create_subnetworks = false
  routing_mode            = "REGIONAL"
}

resource "google_compute_subnetwork" "subnet" {
  name          = var.subnet_name
  ip_cidr_range = var.subnet_cidr
  region        = var.gcp_region
  network       = google_compute_network.vpc_network.id
}

# Allow SSH access from a specific IP range (e.g., your office or bastion host)
resource "google_compute_firewall" "allow_ssh" {
  name    = "${var.vpc_name}-allow-ssh"
  network = google_compute_network.vpc_network.name
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
  source_ranges = ["0.0.0.0/0"] # WARNING: Restrict this in production!
  target_tags   = ["ruby-cluster"]
}

# Allow HTTP/HTTPS access to the application
resource "google_compute_firewall" "allow_http_https" {
  name    = "${var.vpc_name}-allow-http-https"
  network = google_compute_network.vpc_network.name
  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }
  source_ranges = ["0.0.0.0/0"] # Allow from anywhere for public access
  target_tags   = ["ruby-cluster"]
}

# Allow internal communication within the subnet
resource "google_compute_firewall" "allow_internal" {
  name    = "${var.vpc_name}-allow-internal"
  network = google_compute_network.vpc_network.name
  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }
  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }
  allow {
    protocol = "icmp"
  }
  source_ranges = [var.subnet_cidr]
  target_tags   = ["ruby-cluster"]
}

# Deny all other egress traffic by default (requires explicit rules for outbound)
# This is a more advanced security posture. For simplicity, we'll omit a deny-all egress rule here,
# but it's highly recommended for production environments.

Compute Engine Instances for Ruby Cluster

We’ll provision multiple Compute Engine instances to form our Ruby cluster. Each instance will be configured with a startup script to install Ruby, necessary dependencies, and deploy a basic application. We’ll use instance templates and managed instance groups for scalability and resilience, though for this initial setup, we’ll focus on individual instances for clarity.

main.tf – Compute Engine Resources

# main.tf (continued)

data "google_compute_image" "ruby_image" {
  name = var.ruby_instance_image
}

resource "google_compute_instance" "ruby_app_instance" {
  count        = var.ruby_instance_count
  name         = "ruby-app-${count.index}"
  machine_type = var.ruby_instance_machine_type
  zone         = "${var.gcp_region}-a" # Example zone, consider making this configurable or using autoscaling

  tags = ["ruby-cluster", "ssh", "http", "https"]

  boot_disk {
    initialize_params {
      image = data.google_compute_image.ruby_image.self_link
      size  = 20
      type  = "pd-ssd"
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id
    access_config {
      // Ephemeral public IP, consider using static IPs or a Load Balancer
    }
  }

  metadata_startup_script = templatefile("${path.module}/scripts/startup.sh.tpl", {
    ssh_user = var.ssh_user
  })

  // SSH Key injection for initial access
  metadata = {
    ssh-keys = "${var.ssh_user}:${file(var.ssh_public_key_path)}"
  }

  service_account {
    scopes = ["cloud-platform"] // Adjust scopes as needed for your application
  }

  lifecycle {
    create_before_destroy = true
  }
}

scripts/startup.sh.tpl – Instance Startup Script

#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status.

# Update package lists and install essential packages
sudo apt-get update -y
sudo apt-get install -y ruby-full ruby-dev build-essential git curl software-properties-common

# Install Bundler
sudo gem install bundler

# Create a deployment directory
sudo mkdir -p /opt/ruby_app
sudo chown ${ssh_user}:${ssh_user} /opt/ruby_app
cd /opt/ruby_app

# --- Basic Ruby Application Deployment ---
# In a real-world scenario, you'd clone from a Git repository,
# manage secrets securely, and configure a web server (e.g., Puma, Unicorn).

# Example: Create a simple Sinatra app
cat << EOF > app.rb
require 'sinatra'

get '/' do
  "Hello from Ruby Cluster Instance: #{Socket.gethostname}!"
end
EOF

# Example: Create a Gemfile
cat << EOF > Gemfile
source 'https://rubygems.org'
gem 'sinatra'
EOF

# Install gems
bundle install --path vendor/bundle

# --- Systemd Service for Application ---
# This ensures the Ruby app runs as a service and restarts on failure.

cat << EOF | sudo tee /etc/systemd/system/ruby_app.service
[Unit]
Description=Ruby Application Service
After=network.target

[Service]
User=${ssh_user}
Group=${ssh_user}
WorkingDirectory=/opt/ruby_app
Environment="BUNDLE_GEMFILE=/opt/ruby_app/Gemfile"
ExecStart=/usr/bin/bundle exec ruby app.rb -o 0.0.0.0 -p 4567
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

# Reload systemd, enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable ruby_app.service
sudo systemctl start ruby_app.service

echo "Ruby application setup complete."

Outputs and Accessing the Cluster

Finally, we define outputs to easily retrieve important information about our deployed infrastructure, such as the public IP addresses of the instances. This makes it simple to connect to and manage the cluster.

outputs.tf – Output Values

# outputs.tf

output "instance_public_ips" {
  description = "Public IP addresses of the Ruby application instances."
  value       = google_compute_instance.ruby_app_instance[*].network_interface[0].access_config[0].nat_ip
}

output "ssh_command_template" {
  description = "Template command to SSH into an instance."
  value       = "ssh -i <path_to_your_private_key> ${var.ssh_user}@%s"
}

Deployment Workflow

To deploy this infrastructure, follow these steps:

  • Initialize Terraform: Navigate to your Terraform project directory and run terraform init. This downloads the necessary provider plugins.
  • Set GCP Credentials: Ensure your GCP credentials are set up. The easiest way is often by setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account key file, or by running gcloud auth application-default login.
  • Create a terraform.tfvars file: This file will hold your specific variable values.

terraform.tfvars – Example Values

# terraform.tfvars

gcp_project_id       = "your-gcp-project-id"
ssh_public_key_path  = "~/.ssh/id_rsa.pub" # Path to your public SSH key
# gcp_region         = "us-east1" # Optional: override default region
# ruby_instance_count = 3        # Optional: override default instance count
  • Plan the Deployment: Run terraform plan -var-file="terraform.tfvars" to see a preview of the resources Terraform will create.
  • Apply the Deployment: Execute terraform apply -var-file="terraform.tfvars". Terraform will prompt for confirmation before provisioning the resources.
  • Access Your Cluster: Once applied, Terraform will output the public IP addresses of your instances. You can then SSH into them using the provided command template and your private key. For example, if an IP is 34.123.45.67, the command would be: ssh -i ~/.ssh/id_rsa [email protected]. You can then access your Ruby application via a web browser at http://<instance_public_ip>:4567.
  • Destroy Resources: When you no longer need the infrastructure, run terraform destroy -var-file="terraform.tfvars" to clean up all provisioned resources.

Security Considerations and Next Steps

This setup provides a foundational secure Ruby cluster. However, for production environments, consider the following enhancements:

  • Restrict SSH Source Ranges: Replace 0.0.0.0/0 in the allow_ssh firewall rule with specific IP addresses or CIDR blocks of trusted networks (e.g., your office VPN, bastion host).
  • Managed Instance Groups (MIGs) and Load Balancing: For high availability and scalability, replace individual instances with a MIG and front it with a GCP Load Balancer. This also simplifies IP management and health checking.
  • Secrets Management: Integrate with GCP Secret Manager or HashiCorp Vault for managing application secrets instead of embedding them in startup scripts or code.
  • Immutable Infrastructure: Build Docker images for your Ruby application and deploy them using GKE or Compute Engine instance templates with Container-Optimized OS. This promotes consistency and simplifies rollbacks.
  • CI/CD Integration: Automate Terraform runs within your CI/CD pipeline (e.g., GitLab CI, GitHub Actions, Cloud Build) for consistent and repeatable deployments.
  • Monitoring and Logging: Set up Cloud Monitoring and Cloud Logging for your instances and applications.
  • Private IP Addressing: For internal services, avoid public IPs and rely on private networking and internal load balancers.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala