Getting Started with OpenTofu on Azure
OpenTofu is an infrastructure-as-code tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share. It is hosted by the Linux Foundation.
Update (Sept 18, 2024): Want to learn OpenTofu from the ground up? We're hosting a free 10-part hands-on instructor-led workshop on adopting infrastructure-as-code with OpenTofu. Seats are limited, sign up here.
This repo aims to help you get started using OpenTofu to manage your Azure resources.
- Setup OpenTofu and Azure
- Set up state in Azure Storage
- Review IaC best practices
- Import existing resources into OpenTofu
This is a guide for setting up the Azure CLI and OpenTofu to deploy a resource group in Azure.
Prerequisites
- An Azure account
- Azure CLI
Create Azure Service Principal
If you want to follow the portal steps, feel free. I will be covering the CLI steps.
- Log into Azure CLI and get your subscription ID (save
subscription_id
for step 3)
az login
az account list --output table
- Create a service principal (save
appId
,password
, andtenant
for step 3)
az ad sp create-for-rbac --name "tofu-on-azure" --role owner --scopes /subscriptions/<azure_subscription_id>
- Set your environment variables
export ARM_SUBSCRIPTION_ID="<azure_subscription_id>"
export ARM_TENANT_ID="<azure_subscription_tenant_id>"
export ARM_CLIENT_ID="<service_principal_appid>"
export ARM_CLIENT_SECRET="<service_principal_password>"
Install OpenTofu
Brew
brew update
brew install opentofu
Standalone
## Download the installer script:
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
## Alternatively: wget --secure-protocol=TLSv1_2 --https-only https://get.opentofu.org/install-opentofu.sh -O install-opentofu.sh
## Grant execution permissions:
chmod +x install-opentofu.sh
## Please inspect the downloaded script at this point.
## Run the installer:
./install-opentofu.sh --install-method standalone
## Remove the installer:
rm -f install-opentofu.sh
PowerShell
## Download the installer script:
Invoke-WebRequest -outfile "install-opentofu.ps1" -uri "https://get.opentofu.org/install-opentofu.ps1"
## Please inspect the downloaded script at this point.
## Run the installer:
& .\install-opentofu.ps1 -installMethod standalone
## Remove the installer:
Remove-Item install-opentofu.ps1
Verify version:
tofu -version
[!TIP] If you already have Terraform and want to migrate to OpenTofu, you can find out more about that here.
Deploy a resource group
- Create a new directory to deploy a new resource group and
cd
into it - Create a file named
providers.tf
and paste the following:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
random = {
source = "hashicorp/random"
version = "~>3.0"
}
}
}
provider "azurerm" {
features {}
}
- Create a new file named
main.tf
and paste the following:
resource "random_pet" "rg_name" {
prefix = var.resource_group_name_prefix
}
resource "azurerm_resource_group" "rg" {
name = random_pet.rg_name.id
location = var.resource_group_location
}
- Create a new file named
variables.tf
and paste the following:
variable "resource_group_location" {
type = string
default = "westus"
description = "Location of the resource group."
}
variable "resource_group_name_prefix" {
type = string
default = "tofu-on-azure"
description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."
}
- Create a new file named
outputs.tf
and paste the following:
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
- Run
tofu init -upgrade
tofu init
is used to initialize the OpenTofu environment and download the AzureRM provider-upgrade
parameter upgrades the provider plugins to the newest version
- Run
tofu plan -out main.tfplan
tofu plan
creates an execution plan, but doesn't execute it. It determines which actions are necessary to create the configuration specified. Important for comparing changes.-out
is an optional parameter that allows you to specify an output file for the plan, otherwise it prints to console
- Run
tofu apply main.tfplan
tofu apply
is used to deploy the configurationtofu apply
can be ran without specifying a plan file, and it'll run on your detected.tf
files
Verify results
Portal method.
CLI
- Get the Azure resource group name
resource_group_name=$(tofu output -raw resource_group_name)
- Run
az group show
to display the resource group
az group show --name $resource_group_name
Cleanup resources
- Run
tofu plan
and specify-destroy
tag
tofu plan -destroy -out main.destroy.tfplan
- Run
tofu apply
to apply the execution plan
tofu apply main.destroy.tfplan
Alternatively, you can run tofu destroy
without tofu plan/tofu apply
OpenTofu state is used to reconcile deployed resources with the desired state. State allows OpenTofu to know what Azure resources to add, update, or delete.
By default, OpenTofu state is stored locally, which isn't ideal for a few reasons:
- Local state doesn't work well in a team or collaborative environment
- OpenTofu state can include sensitive information
- Storing state locally increases chance of accidental deletion
Prerequisites
- Completed setup
Configure remote state storage account
Assuming you have OpenTofu configured at this point, let's configure our remote state storage account using it!
- Create a new directory for your remote state storage account and
cd
into it - Create a new file named
providers.tf
in the directory and paste the following:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
}
}
provider "azurerm" {
features {}
}
- Create a new file named
main.tf
in the directory and paste the following:
resource "random_string" "resource_code" {
length = 5
special = false
upper = false
}
resource "azurerm_resource_group" "tfstate" {
name = "tfstate"
location = "westus"
}
resource "azurerm_storage_account" "tfstate" {
name = "tfstate${random_string.resource_code.result}"
resource_group_name = azurerm_resource_group.tfstate.name
location = azurerm_resource_group.tfstate.location
account_tier = "Standard"
account_replication_type = "LRS"
allow_nested_items_to_be_public = false
}
resource "azurerm_storage_container" "tfstate" {
name = "tfstate"
storage_account_name = azurerm_storage_account.tfstate.name
container_access_type = "private"
}
- Run
tofu init
thentofu apply
to configure the Azure storage account and container
Configure remote state backend
To configure the backend state, you need the following Azure storage information:
storage_account_name
: The name of the storage accountcontainer_name
: The name of the containerkey
: The name of the state store file to be created
- Fetch storage account name with the following command:
az storage account list --resource-group tfstate --query "[].name" --output tsv
- Once you have the required information, create a new directory and a new file named
backend.tf
and paste the following:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
}
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "<storage_account_name>"
container_name = "tfstate"
key = "terraform.tfstate"
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "state-demo-secure" {
name = "state-demo"
location = "westus"
}
-
Run
tofu init
thentofu apply
to configure the backend state. -
Confirm the state is being stored by running the following:
az storage blob list --account-name <storage_account_name> --container-name tfstate
[!NOTE] Azure blobs are automatically locked when any operation writes to state. This pattern prevents concurrent state operations, which can cause corruption. For more info, see state locking
[!NOTE] Data stored in an Azure blob is encrypted before being persisted. When needed, the OpenTofu retrieves the state from the backend and stores it in local memory. If this pattern is used, state is never written to your local disk. For more info about Azure storage encryption, see Azure storage encryption for data at rest
Cleanup
- Cleanup the backend state by running
tofu destroy
- Cleanup the storage account by running
tofu destroy
The guide below outlines recommended best practices to follow when using OpenTofu in Azure.
Code
- Use Modules: Organize your OpenTofu code using modules promote reusability and maintainability. Modules help to encapsulate logic, reduce duplication, and simplify code.
- Use Variables and Parameters: Implement variables and parameters to make your code more flexible and reusable. This customization reduces duplication and eases code maintenance.
- Use Consistent Naming Conventions: Adopt a consistent naming convention for resources, variables, and outputs to enhance readability and manageability of your code. This helps avoid confusion and ensures a uniform structure.
Example naming
Resource Type | Naming Convention Example |
---|---|
Resource Group | rg-<project>-<env> |
Virtual Network | vnet-<project>-<env> |
Storage Account | store<project><env> |
- Implement Input Validation: Validate input variables to ensure they meet expected criteria and prevent misconfigurations. Use validation blocks to enforce constraints on variables. Richer validation can be achieved using JSON Schema.
Example
variable "environment" {
description = "The environment to deploy to"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Invalid environment. Must be one of: dev, staging, production"
}
}
variable "resource_name" {
description = "The name of the resource"
type = string
validation {
condition = length(var.resource_name) <= 24
error_message = "Resource name must be 24 characters or less"
}
}
Example
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"environment": {
"description": "The environment to deploy to",
"type": "string",
"enum": ["dev", "staging", "production"],
"errorMessage": {
"enum": "The environment must be one of 'dev', 'staging', or 'production'."
}
},
"resource_name": {
"description": "The name of the resource",
"type": "string",
"maxLength": 24,
"errorMessage": {
"maxLength": "The resource name must be 24 characters or less."
}
}
},
"required": ["environment", "resource_name"],
"additionalProperties": false
}
- Use Locals: Utilize locals to encapsulate complex expressions around variable manipulation, string formatting, and conditional logic. Makes your code more readable, maintainable, and reusable.
Example locals
locals {
max_length = 24
alphanumeric_name = substr(replace(var.name, "/[^a-z0-9]/", ""), 0, local.max_length)
}
resource "azurerm_storage_account" "storage" {
name = local.alphanumeric_name
location = var.location
}
- Manage Dependencies: Ensure proper sequencing and avoid circular dependencies by using
depends_on
to manage resource dependencies. Terraform providers typically handle dependencies, butdepends_on
can enforce the correct order of operations when needed.
Example depends_on
depends_on
resource "azurerm_virtual_network" "vnet" {
name = "my-vnet"
}
module "subnet" {
source = "./modules/subnet"
name = "my-subnet"
virtual_network_name = azurerm_virtual_network.vnet.name
depends_on = [azurerm_virtual_network.vnet]
}
- Use Azure's Terraform Providers: Leverage Azure's Terraform providers (AzureRM, AzureAD, AzureDevops, AzAPI, AzureStack) to interact with Azure's services and APIs, enabling seamless integration and management.
- Use Workspaces: Manage multiple environments (e.g., dev, staging, production) within a single OpenTofu configuration using workspaces. This segregation helps manage configurations and maintain consistency across environments.
VCS & CI/CD
- Use Version Control: Track changes to code and collaborate with others using a version control system like Git. This is essential for managing changes, tracking the history of OpenTofu configurations, and effective collaboration.
- Branching Strategy: Implement a branching strategy (e.g. GitFlow) to manage feature development, bug fixes, and releases. This ensures that code changes are organized and integrated smoothly.
- Plan and Review Changes: Always run
tofu plan
to review the changes before applying them. This helps to identify potential issues and understand the impact of your changes. - Integrate with CI/CD Pipelines: Automate testing, building, and deployment of infrastructure by integrating the OpenTofu GitHub Action with your CI/CD pipelines. This ensures consistency, reliability, and repeatability in your deployments.
Example CI/CD
name: OpenTofu CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
opentofu:
name: "OpenTofu"
runs-on: ubuntu-latest
steps:
- name: "Checkout Code"
uses: actions/checkout@v2
- name: "Set up Terraform"
uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
- name: "OpenTofu Format"
run: tofu fmt -check
- name: "OpenTofu Init"
run: tofu init
- name: "OpenTofu Plan"
run: tofu plan -no-color
- name: "OpenTofu Apply"
run: tofu apply tfplan
State management
- Use Cloud Provisioning and Orchestration Platforms: Leverage platforms that offer features like remote state management, collaborative tools, deployment histories, user-friendly GUI, security & scalability. This simplifies managing infrastructure as code, automating deployments, and collaborating effectively.
- Implement State Locking: Prevent concurrent operations that could lead to conflicts and inconsistencies by implementing state locking mechanisms.
- Establish Backup and Restore Procedures: Ensure data integrity and recover from failures by establishing backup and restore procedures for your state files.
- Use Data Lookups Instead of Remote State References: Avoid using potentially stale data and adding dependencies on your state files by opting for data lookups instead of remote state references.
- State File Security: Encrypt your state files to protect sensitive information. Ensure that state files are stored securely and access is restricted to authorized users.
- Periodic State File Cleanup: Regularly clean up- old state file versions to reduce clutter and improve performance. This helps in maintaining a clean state management system
Documentation
-
Tag Resources: Enhance visibility, organization, and management by tagging your resources. This helps in identifying resources, tracking costs, and managing resources effectively.
Example
resource "azurerm_resource_group" "rg" { name = "my-rg" location = "East US" tags = { environment = "dev" owner = "John Doe" } }
-
Maintain Clear Documentation: Ensure your OpenTofu configurations are well-documented and up-to-date. This helps onboard new team members, troubleshoot issues, and manage infrastructure effectively.
-
Include Architecture Diagrams: Provide architecture diagrams that illustrate the infrastructure setup. This helps new users to visualize the deployment and understand the relationship between resources.
-
Review OpenTofu and Azure Provider Changelogs: Stay informed about new features, bug fixes, and improvements by regularly reviewing the OpenTofu changelog and changelog for each Azure Terraform provider used.
Testing
- Include Automated Tests: Validate your IaC to ensure correctness and prevent errors by including automated tests. This helps identify issues early, reduce risks, and improves reliability and consistency.
- Define Rollback Strategies: Plan for failures by defining rollback strategies to revert changes in case of errors or issues during deployments.
- Test Infrastructure Changes in Isolation: Test changes in a separate environment before applying them in production. This minimizes the risk of disruptions and allows for thorough testing.
Security
- Adopt Security Scanning Tools: Integrate tools like Checkov or Terrascan into your CI/CD pipelines to scan for security vulnerabilities and compliance violations.
- Regular Security Audits: Conduct regular security audits to identify and address vulnerabilities in your OpenTofu configurations. This ensures that your infrastructure remains secure over time.
- Secure Sensitive Information: Manage and store sensitive information such as API keys, passwords, and certificates using dedicated secrets management services like Azure Key Vault.
- Least Privilege Principle: Apply the principal of least privilege by granting minimal permissions required for resources. This reduces the risk of unauthorized access and enhances security.
- Isolate Environments: Improve security by isolating environments and using separate subscriptions, resource groups, and networks to prevent unauthorized access and reduce risks.
- Use Sensitive Keyword: Prevent sensitive information from being displayed in logs, state files, and other outputs by using the sensitive keyword on sensitive variables and outputs.
Monitoring
-
Enable Logging: Enable logging for your infrastructure resources to track changes, monitor performance, and troubleshoot issues effectively. Logs provide valuable insights into the health and behavior of your infrastructure.
-
Monitor Infrastructure: Track performance, detect issues, and ensure availability by monitoring your infrastructure. This is crucial for identifying problems, troubleshooting issues, and optimizing performance.
Monitoring
Monitoring Tool Description Configuration Documentation Azure Monitor Comprehensive monitoring solution Azure Monitor Log Analytics Log data collection and analysis Log Analytics Application Insights Application performance monitoring Application Insights -
Conduct Regular Reviews: Ensure compliance, security, quality, and reliability by regularly reviewing code, monitoring infrastructure changes, and tracking deployments.
In this guide, we're going to import some resources from Azure into OpenTofu using Azure Export for Terraform. Azure Export for Terraform (aztfexport
) is a tool that allows you to export your Azure resources into Terraform configuration files (.tf
). aztfexport
enables you to:
- Simplify migration to OpenTofu on Azure by using a single command to migrate your resources.
- Export user-specified resources into HCL code and state.
aztfexport
enables you to specify a predetermined scope, which can be a subscription, resource group, or resource. - Inspect your preexisting resources with all properties exposed using
aztfexport
's read-only option to expose all configurable properties. - Follow plan/apply workflow to migrate resources to OpenTofu. Export the HCL code and state, inspect the resources, and effortlessly integrate them into your production environment and remote backends.
Prerequisites
- Setup completed
- Azure Export for Terraform
Configuration and usage
By default, aztfexport
collects telemetry data, which Microsoft aggregates to identify trends and usage patterns. To disable telemtry, run:
aztfexport config set telemetry_enabled false
The basic commands for aztfexport
are:
Command | Description |
---|---|
aztfexport resource [option] <resource id> | Export a single resource using the Azure resource ID |
aztfexport resource-group [option] <resource group name> | Export all resources in a resource group using the resource group name (not ID) |
aztfexport query True | Exports all resources in the subscription |
aztfexport query [option] <ARG where predicate> | Export resources using an Azure Resource Graph query |
Test run
aztfexport
supports interactive and non-interactive modes. This guide will be using interactive mode. For non-interactive mode, append --non-interactive
to the command.
- Create a new test directory to store the imported HCL and state file and
cd
into it. - Create a test resource to import using the Azure CLI:
az group create --name tofu-export-test --location westus
az vm create --resource-group tofu-export-test --name tofu-export-test-vm --image UbuntuLTS --admin-username azureuser --generate-ssh-keys --image Debian11 --public-ip-sku Standard
- Import the resource group using
aztfexport
:
aztfexport resource-group tofu-export-test
After the tool initializes (may take a few minutes), a list of resources to be imported is displayed. Each line will have an Azure resource ID matched to the resource type. There is a list of commands on the bottom of the screen.
- ↑ and ↓ arrow keys to navigate the list
delete
key will skip the resource so it is not exportedw
to run the imports
to saveq
to quit
- Press
w
to run the import. - Once the import is finished, exit the tool by pressing any key.
- View the imported resources in your new directory using
ls
. You should see the following files:
aztfexportResourceMapping.json
- A mapping of Azure resource IDs to Terraform resource namesmain.tf
- Contains all of your Azure resources that were importedprovider.tf
- Contains the provider configuration forazurerm
terraform.tf
- Initializes theazurerm
provider and local state backend, and pins the versionterraform.tfstate
- Contains the state of the imported resources
- Review the imported resources and make any necessary changes.
- Run
tofu init --upgrade
thentofu plan
. If the terminal outputsNo changes. Your infrastructure matches the configuration.
then congratulations! You have successfully imported your resources and its state to Terraform.
Cleanup
- Navigate to your test directory.
- Run
tofu destroy
then enteryes
to the prompt to delete the resources from Azure.