Terraform_basics
Chapter 34: Terraform Basics
Section titled “Chapter 34: Terraform Basics”This chapter covers the fundamental building blocks of Terraform: providers, resources, and data sources.
Providers Deep Dive
Section titled “Providers Deep Dive”Providers are plugins that Terraform uses to interact with cloud platforms, SaaS providers, and other APIs.
┌─────────────────────────────────────────────────────────────────────────────┐│ Terraform Providers │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ Provider Block │ ││ │ │ ││ │ provider "aws" { │ ││ │ region = "us-east-1" │ ││ │ access_key = "..." # Or use AWS_ACCESS_KEY_ID env │ ││ │ secret_key = "..." # Or use AWS_SECRET_ACCESS_KEY │ ││ │ │ ││ │ # Optional: Alias for multi-region configurations │ ││ │ alias = "east" │ ││ │ } │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ ││ Common Providers: ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ ││ │ AWS │ │ Azure │ │ GCP │ │Kubernetes│ │ Docker │ ││ └─────────┘ └─────────┘ └─────────┘ └──────────┘ └────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Provider Configuration
Section titled “Provider Configuration”# AWS Provider with assume roleprovider "aws" { region = "us-east-1"
assume_role { role_arn = "arn:aws:iam::123456789012:role/AdminRole" }}
# Multiple AWS providers for different regionsprovider "aws" { region = "us-west-2" alias = "west"}
# Then reference with: provider = "aws.west"Resources
Section titled “Resources”Resources are the most important element in Terraform. They define the infrastructure components.
┌─────────────────────────────────────────────────────────────────────────────┐│ Resource Block Structure │├─────────────────────────────────────────────────────────────────────────────┤│ ││ resource "resource_type" "resource_name" { ││ # Configuration block ││ # ││ # Required arguments ││ # Optional arguments with defaults ││ # ││ # Arguments can reference: ││ # - Variables ││ # - Other resources ││ # - Data sources ││ } ││ ││ Example: ││ resource "aws_instance" "web" { ││ ami = "ami-0c55b159cbfafe1f0" ││ instance_type = "t2.micro" ││ tags = { Name = "WebServer" } ││ } ││ │└─────────────────────────────────────────────────────────────────────────────┘Common AWS Resources
Section titled “Common AWS Resources”# EC2 Instanceresource "aws_instance" "web_server" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"
# Subnet placement subnet_id = aws_subnet.main.id
# Security groups vpc_security_group_ids = [aws_security_group.web.id]
# User data (bootstrap script) user_data = <<-EOF #!/bin/bash yum update -y yum install -y httpd systemctl start httpd EOF
tags = { Name = "WebServer" Environment = "Production" }}
# S3 Bucketresource "aws_s3_bucket" "assets" { bucket = "my-app-assets-${var.environment}"
tags = { Name = "Assets Bucket" Environment = var.environment }}
# VPCresource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true
tags = { Name = "Main VPC" }}
# Security Groupresource "aws_security_group" "web" { name = "web-sg" description = "Security group for web servers" vpc_id = aws_vpc.main.id
# Ingress rules ingress { description = "HTTP" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
ingress { description = "HTTPS" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
# Egress rules egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
tags = { Name = "Web Security Group" }}Data Sources
Section titled “Data Sources”Data sources allow Terraform to read information from external sources without creating new resources.
┌─────────────────────────────────────────────────────────────────────────────┐│ Data Sources Concept │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Terraform │ │ External Data │ ││ │ Config │────────▶│ Sources │ ││ └─────────────────┘ └─────────────────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Resources │ │ Use in your │ ││ │ to Create │ │ config │ ││ └─────────────────┘ └─────────────────┘ ││ ││ Common Data Sources: ││ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ││ │ aws_ami │ │ aws_vpc │ │ aws_subnet │ ││ │ (Find AMIs) │ │ (Get VPC info) │ │ (Get Subnets) │ ││ └────────────────┘ └────────────────┘ └────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Using Data Sources
Section titled “Using Data Sources”# Get latest Amazon Linux 2 AMIdata "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"]
filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-gp2"] }}
# Use the data sourceresource "aws_instance" "app" { ami = data.aws_ami.amazon_linux.id instance_type = "t2.micro"
tags = { Name = "AppServer" }}
# Get existing VPC informationdata "aws_vpc" "existing" { default = true # Or use tags to filter}
# Get subnet informationdata "aws_subnet_ids" "public" { vpc_id = data.aws_vpc.existing.id
tags = { Type = "Public" }}
# Get account informationdata "aws_caller_identity" "current" {}
output "account_id" { value = data.aws_caller_identity.current.account_id}
# Get AWS regionsdata "aws_regions" "available" {}
output "all_regions" { value = data.aws_regions.available.names}Resource Dependencies
Section titled “Resource Dependencies”Resources can have explicit and implicit dependencies.
┌─────────────────────────────────────────────────────────────────────────────┐│ Resource Dependencies │├─────────────────────────────────────────────────────────────────────────────┤│ ││ Implicit Dependency (Automatic): ││ ┌──────────────┐ ││ │ aws_vpc.main │──────────┐ ││ └──────────────┘ │ ││ ▼ ││ ┌──────────────┐ ││ │aws_subnet.pub│ (Referenced by aws_vpc.main.id) ││ └──────────────┘ ││ ││ Explicit Dependency (manual): ││ ┌──────────────┐ ││ │ aws_instance │──────────┐ ││ └──────────────┘ │ ││ ▼ ││ ┌──────────────┐ depends_on = [ ││ │ aws_iam_role │ aws_s3_bucket.assets ││ └──────────────┘ ] ││ │└─────────────────────────────────────────────────────────────────────────────┘Dependency Examples
Section titled “Dependency Examples”# Implicit dependency - Terraform figures it out automaticallyresource "aws_subnet" "public" { vpc_id = aws_vpc.main.id # This creates implicit dependency}
# Explicit dependency - use when dependency is not in resource configresource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"
# This instance must be created after the DB is ready # (but doesn't directly reference db instance) depends_on = [aws_db_instance.mysql]}
# Lifecycle managementresource "aws_instance" "example" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"
lifecycle { # Create before destroy create_before_destroy = true
# Prevent destroy prevent_destroy = false
# Ignore changes to certain attributes ignore_changes = [tags]
# Replace instance when AMI changes replace_triggered_by = [data.aws_ami.amazon_linux.id] }}Meta-Arguments
Section titled “Meta-Arguments”Terraform provides several meta-arguments that work with any resource type.
# count - Create multiple instancesresource "aws_instance" "server" { count = 3
ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"
tags = { Name = "Server-${count.index}" }}
# for_each - Create instances from a map or setresource "aws_instance" "servers" { for_each = { web = "t2.micro" api = "t2.small" db = "t2.medium" }
ami = "ami-0c55b159cbfafe1f0" instance_type = each.value
tags = { Name = each.key }}
# for_each with setresource "aws_ebs_volume" "example" { for_each = toset(["data", "logs", "backup"])
size = 100 availability_zone = "us-east-1a"
tags = { Name = "${each.value}-volume" }}Provisioners
Section titled “Provisioners”Provisioners are used to execute scripts on local or remote machines after resource creation.
┌─────────────────────────────────────────────────────────────────────────────┐│ Provisioners Overview │├─────────────────────────────────────────────────────────────────────────────┤│ ││ Types: ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ local-exec │ │ remote-exec │ ││ │ (local machine)│ │ (remote target)│ ││ └─────────────────┘ └─────────────────┘ ││ ││ When to use: ││ ✓ Initial setup scripts ││ ✓ Configuration management ││ ✓ Software installation ││ ││ Better alternatives: ││ ✗ Use cloud-init for EC2 ││ ✗ Use startup scripts for Azure/GCP ││ ✗ Use configuration management tools (Ansible) ││ │└─────────────────────────────────────────────────────────────────────────────┘Provisioner Examples
Section titled “Provisioner Examples”# Local provisioner - runs on machine running Terraformresource "null_resource" "example" { # Trigger on every change triggers = { always = timestamp() }
provisioner "local-exec" { command = "echo 'Instance ${aws_instance.web.id} created' >> /tmp/instances.txt" }}
# Remote provisioner - runs on created resourceresource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"
connection { type = "ssh" user = "ec2-user" private_key = file("~/.ssh/id_rsa") host = self.public_ip }
provisioner "remote-exec" { inline = [ "sudo yum install -y nginx", "sudo systemctl start nginx", "sudo systemctl enable nginx", ] }}
# file provisioner - copy files to remoteprovisioner "file" { source = "scripts/init.sh" destination = "/tmp/init.sh"
connection { type = "ssh" user = "ec2-user" private_key = file("~/.ssh/id_rsa") host = self.public_ip }}Summary
Section titled “Summary”In this chapter, you learned:
- Providers: How to configure AWS, Azure, GCP, and other providers
- Resources: Creating EC2, S3, VPC, Security Groups
- Data Sources: Reading external information (AMIs, VPCs, accounts)
- Dependencies: Implicit and explicit resource dependencies
- Meta-arguments: Using count and for_each for multiple resources
- Provisioners: Running local and remote scripts (and when to avoid them)