---
id: iac-terraform
title: "Terraform trên Azure"
track: cicd-iac
trackTitle: "CI/CD và Infrastructure as Code"
order: 2
level: core
status: published
estimatedMinutes: 45
prerequisites:
  - cicd-github-actions
next: iac-bicep
reviewedAgainst: 2026-10-12
sources:
  - title: "Terraform language"
    url: https://developer.hashicorp.com/terraform/language
  - title: "AzureRM provider"
    url: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
  - title: "azurerm_resource_group"
    url: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group
---

# Terraform trên Azure

Terraform khai báo hạ tầng trong file, rồi gọi API để Azure khớp file đó. Lúc viết bài, AzureRM mới nhất trên registry là 5.9.0. Mẫu ghim `~> 5.0`: nhận các bản 5.x, không tự nhảy sang major sau. Trước `terraform init`, mở lại trang provider vì bản vá vẫn ra tiếp.

## Để làm gì

Khai báo một resource group Azure trong file, xem diff, rồi tạo hoặc xóa resource đó bằng lệnh chạy lại được. Portal dùng để nhìn resource. File là bản đồng nghiệp review được.

## Gồm những gì

- Khối `terraform`, provider, variable, resource, output.
- `init`, `fmt`, `validate`, `plan`, `apply`, `destroy`.
- State local, và những file không commit.
- Auth bằng Azure CLI.

Bài này tạo một resource group trống. Virtual network, AKS, và remote backend để bài sau. Resource group không có phí theo giờ của riêng nó, nhưng nó nằm trong subscription thật. Resource tính phí về sau sẽ nằm trong group đó.

## Dùng khi nào

Sửa hạ tầng thì đọc diff trước khi nó có hiệu lực. `terraform plan` in diff đó. `terraform apply` mới gọi API Azure.

Lab chỉ chạy trên subscription học. Trước khi apply, đọc tên subscription trong output của `az account show`.

## Kiến thức cốt lõi

Terraform đọc config, refresh state, rồi plan những API cần gọi để Azure khớp file. State là sổ các object đã tạo. File state local mặc định là `terraform.tfstate`. File này có thể chứa ID và dữ liệu nhạy cảm của resource. Đừng commit. Làm nhóm thì cần remote backend có lock. Bước đó để sau lab này, mẫu chưa có.

Từ AzureRM 4.0, provider bắt buộc có subscription ID, qua tham số `subscription_id` hoặc biến môi trường `ARM_SUBSCRIPTION_ID`. Khối `features` bắt buộc có, dù bên trong chưa khai báo gì.

`~> 5.0` khóa major. Bản 6.x sau này có thể đổi schema. Nâng major là một thay đổi riêng: đọc upgrade guide của provider trước.

`terraform validate` kiểm tra cú pháp và reference nội bộ sau khi provider đã tải. `terraform plan` còn so với Azure. Plan chưa tạo resource group. Apply mới tạo. Destroy lên plan chiều ngược, rồi xóa resource mà state đang quản lý.

Lab trên máy cá nhân thì login bằng Azure CLI: `az login` lấy token của user, Terraform dùng token đó. CI không có browser, nên dùng service principal.

## Lệnh và code mẫu

`main.tf`:

```hcl
terraform {
  required_version = ">= 1.10.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 5.0"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

variable "subscription_id" {
  type        = string
  description = "Azure subscription ID for this lab."
}

variable "location" {
  type    = string
  default = "southeastasia"
}

resource "azurerm_resource_group" "lab" {
  name     = "rg-devops-lab"
  location = var.location
}

output "resource_group_name" {
  value = azurerm_resource_group.lab.name
}
```

`.gitignore` của thư mục lab:

```text
.terraform/
*.tfstate
*.tfstate.*
crash.log
```

Sau lần `init` đầu, commit `.terraform.lock.hcl`. Lock file ghi version provider đã chọn, để máy khác cài cùng bộ dependency. Đừng commit thư mục `.terraform/`, đừng commit state.

Lệnh lab:

```bash
az login
az account show --query '{name:name, id:id}' --output table

terraform init
terraform fmt
terraform validate
terraform plan -var "subscription_id=$(az account show --query id --output tsv)"
```

Chỉ khi plan đúng một resource group `rg-devops-lab`, đúng subscription:

```bash
terraform apply -var "subscription_id=$(az account show --query id --output tsv)"
```

Xem xong resource trên portal hoặc bằng Azure CLI thì gỡ lab:

```bash
terraform destroy -var "subscription_id=$(az account show --query id --output tsv)"
```

`apply` và `destroy` hỏi xác nhận, trừ khi có `-auto-approve`. Lab mới thì để Terraform hỏi.

## Thực hành

1. Cài Terraform CLI và Azure CLI từ trang HashiCorp và Microsoft.
2. Tạo thư mục trống, lưu `main.tf` và `.gitignore`.
3. Login và ghi lại tên subscription từ `az account show`.
4. Chạy `init`, `fmt`, `validate`, rồi `plan`.
5. Đọc plan. Subscription hoặc tên resource group không phải lab thì dừng.
6. Apply, kiểm tra resource group đã có, rồi destroy và kiểm tra nó đã mất.

## Kết quả mong đợi

- `validate` báo config hợp lệ.
- Plan có `1 to add`, đúng tên `rg-devops-lab`, đúng `southeastasia`.
- Sau apply, `az group show --name rg-devops-lab` trả về group đó.
- Sau destroy, cùng lệnh báo group không còn.
- `git status` không thấy `terraform.tfstate`.

## Checklist

- [ ] Phân biệt plan và apply.
- [ ] Nói được vì sao state không vào Git.
- [ ] Nhớ subscription ID bắt buộc từ AzureRM 4.0.
- [ ] Đọc tên subscription trước khi apply.
- [ ] Xóa resource lab bằng `terraform destroy`, đừng chỉ xóa file config.

## Lỗi thường gặp

**Xóa file `.tf` rồi tưởng resource Azure đã mất.** Hết file, mà state cũng mất, thì Terraform không biết destroy cái gì. Giữ state đến khi `destroy` chạy xong. Muốn xóa resource thì để chính state đang quản lý nó làm việc đó.

**Commit `terraform.tfstate`.** State có thể chứa dữ liệu không để trong repo. Thêm các mẫu ở trên vào `.gitignore` trước commit đầu.

**Apply nhầm subscription production vì đó là account mặc định của Azure CLI.** `az account show` nằm trong lệnh lab. Tên in ra không phải subscription học thì chạy `az account set` trước plan.

**Ghim `version = "5.9.0"` rồi không đọc upgrade guide.** Pin đúng một bản vẫn hợp lệ, nhưng bản vá sau 2026-10-12 sẽ không được nhận. `~> 5.0` nhận bản vá trong major 5. Nhảy sang major mới là thay đổi riêng, có upgrade guide của provider.

## Nguồn

1. [Terraform language](https://developer.hashicorp.com/terraform/language) — HashiCorp, cấu trúc config.
2. [terraform init](https://developer.hashicorp.com/terraform/cli/commands/init), [plan](https://developer.hashicorp.com/terraform/cli/commands/plan), [apply](https://developer.hashicorp.com/terraform/cli/commands/apply), [destroy](https://developer.hashicorp.com/terraform/cli/commands/destroy) — từng lệnh làm gì.
3. [Dependency lock file](https://developer.hashicorp.com/terraform/language/files/dependency-lock) — vì sao commit `.terraform.lock.hcl`.
4. [AzureRM provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) — auth, khối `features`, version đang phát hành.
5. [AzureRM 4.0 upgrade guide](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/4.0-upgrade-guide) — `subscription_id` trở thành bắt buộc.
6. [azurerm_resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) — schema của resource trong lab.

## Tiếp theo

[Bicep](./11-bicep.md)
