10.3 Terraform Workflow & GCP
This chapter covers the Terraform workflow and Google Cloud Platform-specific considerations for real-world deployments.
The Terraform Workflow
Terraform follows a consistent workflow that makes infrastructure changes predictable and safe:
1. Write
Define your infrastructure in configuration files (.tf files).
2. Initialize (terraform init)
Download provider plugins and prepare the working directory.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/google...
- Installing hashicorp/google v4.84.0...
Terraform has been successfully initialized!
3. Plan (terraform plan)
Preview the changes Terraform will make to match your configuration.
$ terraform plan
Terraform will perform the following actions:
# google_compute_instance.web_server will be created
+ resource "google_compute_instance" "web_server" {
+ name = "web-server"
+ machine_type = "e2-micro"
+ zone = "us-central1-a"
+ self_link = (known after apply)
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
4. Apply (terraform apply)
Execute the planned changes to create, update, or delete infrastructure.
$ terraform apply
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
google_compute_instance.web_server: Creating...
google_compute_instance.web_server: Creation complete after 45s
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
5. Destroy (terraform destroy)
Remove all infrastructure managed by the configuration (use with caution).
$ terraform destroy
# Destroy specific resources
$ terraform destroy -target=google_compute_instance.web_server
GCP-Specific Terraform Considerations
Google Cloud Platform has unique characteristics that affect how you use Terraform effectively.
1. GCP Authentication and Setup
# Authenticate with Google Cloud
gcloud auth login
gcloud auth application-default login
# Set default project
gcloud config set project my-gcp-project-id
# Enable required APIs (Terraform cannot enable these automatically)
gcloud services enable compute.googleapis.com
gcloud services enable storage.googleapis.com
gcloud services enable container.googleapis.com
2. Service Account Best Practices
# Create a service account for Terraform
resource "google_service_account" "terraform" {
account_id = "terraform-automation"
display_name = "Terraform Service Account"
description = "Service account for Terraform infrastructure automation"
}
# Grant minimal required permissions
resource "google_project_iam_member" "terraform_compute" {
project = var.project_id
role = "roles/compute.instanceAdmin.v1"
member = "serviceAccount:${google_service_account.terraform.email}"
}
resource "google_project_iam_member" "terraform_storage" {
project = var.project_id
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.terraform.email}"
}
Service Account Key Management:
# Create service account key
gcloud iam service-accounts keys create terraform-key.json \
--iam-account=terraform-automation@PROJECT_ID.iam.gserviceaccount.com
# Set environment variable
export GOOGLE_APPLICATION_CREDENTIALS="terraform-key.json"
# Or use in provider block
provider "google" {
credentials = file("terraform-key.json")
project = "my-project-id"
region = "us-central1"
}
3. GCP Resource Naming and Organization
# GCP has strict naming conventions
locals {
# Resource names must be lowercase, contain only letters, numbers, and hyphens
resource_prefix = lower("${var.environment}-${var.project_name}")
# GCP labels are different from AWS tags
common_labels = {
environment = var.environment
project = var.project_name
managed_by = "terraform"
team = var.team_name
}
}
resource "google_compute_instance" "app" {
name = "${local.resource_prefix}-app-server"
# Use labels, not tags (AWS terminology)
labels = local.common_labels
}
GCP Naming Rules:
Lowercase only: All GCP resource names must be lowercase
Length limits: Most resources have 63-character limits
Valid characters: Letters, numbers, hyphens (no underscores in names)
Start/end: Must start and end with letter or number
4. GCP-Specific State Backend Configuration
# Create a GCS bucket for Terraform state
resource "google_storage_bucket" "terraform_state" {
name = "my-terraform-state-bucket"
location = "US"
uniform_bucket_level_access = true
versioning {
enabled = true
}
lifecycle_rule {
condition {
age = 30
}
action {
type = "Delete"
}
}
}
# Configure backend in separate configuration
terraform {
backend "gcs" {
bucket = "my-terraform-state-bucket"
prefix = "production"
}
}
State Backend Security:
# Secure state bucket
resource "google_storage_bucket" "terraform_state" {
name = "${var.project_id}-terraform-state"
location = "US"
# Prevent public access
uniform_bucket_level_access = true
# Enable versioning for state history
versioning {
enabled = true
}
# Encrypt at rest
encryption {
default_kms_key_name = google_kms_crypto_key.terraform_state.id
}
# Lifecycle management
lifecycle_rule {
condition {
age = 90
matches_storage_class = ["COLDLINE"]
}
action {
type = "Delete"
}
}
}
5. GCP Quota and API Limitations
# Check current quotas
gcloud compute project-info describe --project=my-project
# Request quota increases through Console
# Plan for API rate limits in large deployments
Handling Quotas in Terraform:
# Use timeouts for slow operations
resource "google_compute_instance" "app" {
name = "app-server"
machine_type = "e2-micro"
timeouts {
create = "10m"
update = "10m"
delete = "10m"
}
}
# Use depends_on to control resource creation order
resource "google_compute_instance" "app" {
count = var.instance_count
depends_on = [
google_compute_network.main,
google_compute_subnetwork.public
]
name = "app-server-${count.index + 1}"
# Spread across zones to avoid single-zone limits
zone = data.google_compute_zones.available.names[count.index % length(data.google_compute_zones.available.names)]
}
6. GCP Multi-Region Deployments
# GCP regions and zones
variable "regions" {
description = "List of GCP regions for deployment"
type = list(string)
default = ["us-central1", "us-east1", "europe-west1"]
}
# Get available zones for each region
data "google_compute_zones" "available" {
for_each = toset(var.regions)
region = each.key
status = "UP"
}
# Deploy across multiple regions
resource "google_compute_instance" "app" {
for_each = toset(var.regions)
name = "app-server-${each.key}"
machine_type = "e2-micro"
zone = data.google_compute_zones.available[each.key].names[0]
boot_disk {
initialize_params {
image = data.google_compute_image.ubuntu.self_link
}
}
network_interface {
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.regional[each.key].id
access_config {
# Ephemeral public IP
}
}
labels = {
region = each.key
app = "web-server"
}
}
Advanced Terraform Workflows
1. Environment Management
# Directory structure for environments
environments/
├── dev/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── staging/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── production/
├── main.tf
├── terraform.tfvars
└── backend.tf
Environment-Specific Configuration:
# environments/production/terraform.tfvars
environment = "production"
instance_type = "e2-medium"
min_replicas = 3
max_replicas = 10
enable_backup = true
backup_schedule = "0 2 * * *"
# environments/dev/terraform.tfvars
environment = "development"
instance_type = "e2-micro"
min_replicas = 1
max_replicas = 2
enable_backup = false
2. Terraform Workspaces
# Create and switch to workspace
terraform workspace new production
terraform workspace new staging
terraform workspace new development
# List workspaces
terraform workspace list
# Switch workspace
terraform workspace select production
# Use workspace in configuration
locals {
environment = terraform.workspace
instance_count = terraform.workspace == "production" ? 3 : 1
}
3. State Management Strategies
Remote State Sharing:
# In networking project
output "vpc_id" {
value = google_compute_network.main.id
}
output "subnet_ids" {
value = {
for subnet in google_compute_subnetwork.subnets :
subnet.name => subnet.id
}
}
# In application project
data "terraform_remote_state" "networking" {
backend = "gcs"
config = {
bucket = "company-terraform-state"
prefix = "networking"
}
}
resource "google_compute_instance" "app" {
network = data.terraform_remote_state.networking.outputs.vpc_id
subnetwork = data.terraform_remote_state.networking.outputs.subnet_ids["public"]
}
4. Module Development
# modules/gcp-web-server/main.tf
resource "google_compute_instance" "web" {
name = var.instance_name
machine_type = var.machine_type
zone = var.zone
boot_disk {
initialize_params {
image = var.boot_disk_image
size = var.boot_disk_size
}
}
network_interface {
network = var.network
subnetwork = var.subnetwork
dynamic "access_config" {
for_each = var.assign_public_ip ? [1] : []
content {
# Ephemeral public IP
}
}
}
metadata_startup_script = var.startup_script
labels = var.labels
tags = var.network_tags
}
# Using the module
module "web_servers" {
source = "./modules/gcp-web-server"
instance_name = "web-server"
machine_type = "e2-micro"
zone = "us-central1-a"
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.public.id
assign_public_ip = true
startup_script = file("${path.module}/scripts/install-nginx.sh")
labels = {
environment = var.environment
component = "web-server"
}
network_tags = ["web-server", "http-allowed"]
}
5. CI/CD Integration
GitHub Actions Example:
# .github/workflows/terraform.yml
name: 'Terraform'
on:
push:
branches: [ main ]
paths: ['infrastructure/**']
pull_request:
branches: [ main ]
paths: ['infrastructure/**']
env:
TF_VAR_project_id: ${{ secrets.GCP_PROJECT_ID }}
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: ./infrastructure
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -no-color
if: github.event_name == 'pull_request'
- name: Terraform Apply
run: terraform apply -auto-approve
if: github.ref == 'refs/heads/main'
Terraform Best Practices
1. State File Security
# Never commit state files
echo "*.tfstate" >> .gitignore
echo "*.tfstate.*" >> .gitignore
echo ".terraform/" >> .gitignore
echo "terraform.tfvars" >> .gitignore
2. Resource Tagging/Labeling
locals {
common_labels = {
environment = var.environment
project = var.project_name
managed_by = "terraform"
team = var.team_name
cost_center = var.cost_center
created_by = "terraform"
created_on = formatdate("YYYY-MM-DD", timestamp())
}
}
# Apply to all resources
resource "google_compute_instance" "app" {
labels = merge(
local.common_labels,
{
component = "application-server"
tier = "web"
}
)
}
3. Security Practices
# Use Google Secret Manager for sensitive data
resource "google_secret_manager_secret" "db_password" {
secret_id = "database-password"
replication {
automatic = true
}
}
resource "google_secret_manager_secret_version" "db_password" {
secret = google_secret_manager_secret.db_password.id
secret_data = var.db_password
}
# Reference secrets in resources
resource "google_sql_database_instance" "main" {
settings {
user_labels = {
password_secret = google_secret_manager_secret.db_password.secret_id
}
}
}
4. Error Handling and Validation
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be one of: dev, staging, production."
}
}
variable "instance_count" {
description = "Number of instances"
type = number
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
Note
Always run terraform plan before terraform apply to review changes. Use terraform validate and terraform fmt to catch errors early and maintain consistent formatting.