8.1 Infrastructure as Code with Terraform and the OCI Provider
Key Takeaways
Terraform describes infrastructure declaratively in HCL (.tf) or JSON (.tf.json) files, and the OCI provider translates those files into OCI API calls.
The core Terraform workflow is terraform init, terraform plan, terraform apply, and terraform destroy.
Terraform records what it created in a state file, so the state must be stored safely and protected from concurrent changes.
Modules package reusable configurations, such as a standard VCN, so teams build environments from tested components.
The OCI provider can authenticate with an API signing key, an instance principal, a resource principal, or a security token.
Why Infrastructure as Code
Clicking through the Console works for a lab but not for an enterprise. Infrastructure as Code (IaC) describes infrastructure in text files that are versioned, reviewed, and executed by automation. Architects care about IaC because it delivers:
- Repeatability: the same configuration builds development, test, production, and disaster recovery environments.
- Consistency: fewer configuration differences between regions and environments.
- Reviewability: changes go through pull requests, so security and network teams can approve them before they happen.
- Speed of recovery: a destroyed environment can be rebuilt from code, which complements backups and replication.
- Auditability: the repository history shows who changed what and why.
OCI's IaC tooling is built on Terraform, an open-source tool from HashiCorp, together with the OCI Terraform provider. OCI Resource Manager (next section) runs Terraform as a managed service.
Terraform Building Blocks
Terraform configurations are written in HashiCorp Configuration Language (HCL) files ending in .tf, or in JSON files ending in .tf.json. Terraform reads all files in the working directory together.
| Block | Purpose | Example |
|---|---|---|
| provider | Configures the plugin that talks to a platform's API | provider "oci" { region = var.region } |
| resource | Something Terraform creates and manages | resource "oci_core_vcn" "main" { ... } |
| data | Reads information that already exists | data "oci_identity_availability_domains" "ads" { ... } |
| variable | An input value | variable "compartment_ocid" {} |
| output | A value shown after apply or passed to other configurations | output "vcn_id" { value = oci_core_vcn.main.id } |
| module | A reusable package of configuration | module "network" { source = "./modules/vcn" } |
Example: A VCN and a Subnet
variable "compartment_ocid" {}
variable "region" { default = "us-ashburn-1" }
provider "oci" {
region = var.region
}
resource "oci_core_vcn" "main" {
compartment_id = var.compartment_ocid
cidr_blocks = ["10.0.0.0/16"]
display_name = "app-vcn"
}
resource "oci_core_subnet" "private_app" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
cidr_block = "10.0.1.0/24"
prohibit_public_ip_on_vnic = true
display_name = "private-app"
}
output "subnet_id" {
value = oci_core_subnet.private_app.id
}
The subnet refers to oci_core_vcn.main.id, so Terraform knows to create the VCN first. Terraform builds a dependency graph from these references and creates independent resources in parallel.
Variables
Variables make one configuration usable in several environments. Values can come from defaults, .tfvars files, command-line flags, or environment variables named TF_VAR_<name>. Mark sensitive values, such as passwords, as sensitive = true so Terraform hides them in output, and prefer pulling secrets from OCI Vault at run time.
Modules
A module is any directory of Terraform files. A root configuration calls child modules and passes variables to them. Platform teams publish standard modules, such as a hub VCN with a Network Firewall or a hardened compute instance, so application teams compose environments from approved building blocks. Oracle also publishes OCI landing zone and reference architecture configurations that follow the same idea.
State
Terraform stores a state file (terraform.tfstate, in JSON) that maps each resource in the configuration to the real resource OCID. Terraform compares the configuration, the state, and the real infrastructure to decide what to change. Important practices:
- Store state remotely and securely, because it can contain sensitive values.
- Lock state during runs so two people cannot apply changes at the same time.
- Never edit state by hand. Use
terraform statecommands orterraform importto bring existing resources under management.
The Terraform Workflow
| Command | What it does |
|---|---|
terraform init | Initializes the working directory and downloads providers and modules |
terraform validate | Checks the configuration for syntax and internal consistency |
terraform fmt | Formats files in the canonical style |
terraform plan | Shows the actions Terraform would take, without changing anything |
terraform apply | Executes the actions in the plan |
terraform destroy | Deletes the resources managed by the configuration |
terraform output | Shows output values |
Changing Infrastructure
To change infrastructure, you edit the configuration and run plan again. The plan marks each resource as create, update in place, destroy, or destroy and then create (replace). Reviewing the plan is critical: some attribute changes, such as a subnet CIDR block, force replacement, which would delete resources that depend on it. Lifecycle settings such as prevent_destroy protect critical resources, and create_before_destroy reduces downtime when a replacement is unavoidable.
Scaling a Configuration: count, for_each, and Environments
Two meta-arguments create several similar resources from one block:
count = 3creates three copies addressed asresource_type.name[0],[1], and[2]. It suits identical resources.for_eachiterates over a map or set, addressing each copy by key, such asoci_core_subnet.tier["web"]. Removing one key deletes only that resource, sofor_eachis safer when items are added and removed over time.
For separate environments, most teams keep one set of modules and a small root configuration per environment (development, test, production, and DR), each with its own variables and its own state. This keeps a mistake in development from touching production and makes the DR region a second instance of the same code rather than a hand-built copy.
Authenticating the OCI Provider
When you run Terraform yourself, the OCI provider supports several authentication methods:
| Method | Typical use |
|---|---|
| API signing key (user OCID, fingerprint, private key, tenancy OCID) | A developer's workstation |
| Instance principal | Terraform running on a compute instance in a dynamic group |
| Resource principal | Terraform running inside an OCI service that has its own identity |
| Security token (session authentication) | Short-lived interactive sessions created with oci session authenticate |
When Terraform runs inside Resource Manager, the service handles authentication for you, and the provider "oci" block only needs the region.
A platform team wants application teams to see exactly which OCI resources a change will create, modify, or delete before anything is changed, so the network team can approve it. Which Terraform command produces this output?
terraform init
terraform plan
terraform apply with the -auto-approve flag
terraform fmt
Two engineers run terraform apply against the same environment at the same time from their laptops, and the second run corrupts the record of which OCIDs belong to the configuration. Which practice prevents this?
Copy terraform.tfstate into each engineer's working directory before every run
Delete the state file after every apply so Terraform rebuilds it each time
Store all OCIDs as hard-coded variables instead of keeping a state file
Keep state in a shared, secured location with locking for each run
Sections you finish are checked off in the contents.