AzureのTerraformでVNetとLinux VMをコード化する方法|azurermプロバイダーの設定からapply・destroy実践まで

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Azure > AzureのTerraformでVNetとLinux VMをコード化する方法|azurermプロバイダーの設定からapply・destroy実践まで
「AzureのリソースをPortalやazコマンドで毎回手動作成しているが、本番と検証で同じ構成を何度も再現しなければならない。Terraformでコード化してGit管理したい」
そう感じているLinuxエンジニアは多い。AzureのVNetとLinux VMをTerraformでコード化すれば、同じ構成を何度でも一発で再現でき、インフラの変更履歴もGitのpull requestとして残せる。

この記事では、TerraformのazurermプロバイダーでAzureのVNet・サブネット・NSG・Linux VMをコード化する実践手順を解説します。versions.tf / variables.tf / main.tf の設計から、terraform plan/apply/destroyの実行と実際のコマンド出力まで一気通貫で進めます。動作確認環境はUbuntu 24.04 LTS(Terraform 1.9.x、Azure CLI 2.65.x)です。

この記事のポイント

・AzureのVNetとLinux VMはazurermプロバイダーで完全コード化できる
・terraform planで変更内容を事前確認してからapplyするのが鉄則
・terraform destroyで検証後のリソースを一括削除でき、コスト管理がしやすい
・本番ではtfstateをAzure Blob Storageのリモートバックエンドで管理する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜAzureのインフラをTerraformで管理するのか

azコマンドでも同じリソースを作成できる。それでもTerraformを使う理由は何か。主な違いは「冪等性(べきとうせい)」にある。

azコマンドは命令的(Imperative): 「今すぐVMを1台作れ」という命令を実行する。同じコマンドを2回実行すれば2台立ち上がる(またはエラーになる)
Terraformは宣言的(Declarative): 「この構成が存在すべき」と宣言する。既に存在すれば何もせず、差分だけを適用する
Gitでの変更管理: .tfファイルをリポジトリで管理することで、誰がいつ何を変えたかがpull requestに残る
本番と検証の再現性: 変数(variables.tf)でVMサイズや環境名だけ切り替えれば、同じ.tfを本番と検証で使い回せる

azコマンドは「今すぐ1回だけ作る」ときに手早い。Terraformは「何度でも同じ構成を再現したい」「チームで管理したい」ときに力を発揮する。

Terraformのインストールと認証設定

1. Terraformをインストールする

Ubuntu 24.04 LTSの場合は、HashiCorpの公式APTリポジトリからインストールする。

# HashiCorpの署名キーを追加 sudo apt update && sudo apt install -y gnupg software-properties-common wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | \ sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null # APTリポジトリを登録 echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ sudo tee /etc/apt/sources.list.d/hashicorp.list # Terraformをインストール sudo apt update && sudo apt install -y terraform

RHEL 9 / Rocky Linux 9の場合:

sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo sudo yum install -y terraform

インストール後にバージョンを確認する:

$ terraform -version Terraform v1.9.5 on linux_amd64

2. Azure CLIでログインする

TerraformがazurermプロバイダーでAzureへ接続する認証方法は主に2つある。

az login(ブラウザ認証): 開発・検証環境向け。最もシンプル
サービスプリンシパル(環境変数): CI/CDやcron環境向け

この記事では az login を使った方法で進める。Azure CLIがインストール済みであれば以下を実行する:

$ az login

ブラウザが開いてMicrosoftアカウントでログイン後、ターミナルにサブスクリプション情報が表示される:

[ { "cloudName": "AzurePublicCloud", "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "isDefault": true, "name": "MySubscription", "state": "Enabled", "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "user": { "name": "user@example.com", "type": "user" } } ]

az login 後は、TerraformがローカルのAzure CLI認証情報を自動で参照する。サブスクリプションIDは後で使うので控えておく:

$ az account show --query id -o tsv xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

プロジェクトディレクトリとファイル構成の設計

作業ディレクトリを作成して、以下の構成でファイルを用意する:

$ mkdir azure-terraform-lab && cd azure-terraform-lab

azure-terraform-lab/ ├── versions.tf # プロバイダー・バージョン設定 ├── variables.tf # 変数定義 ├── main.tf # リソース定義(VNet・NSG・VM) ├── outputs.tf # 出力値(IPアドレス等) └── terraform.tfvars # 変数の実際の値(Gitには含めない)

1. versions.tf — プロバイダーとバージョンを固定する

terraform { required_version = ">= 1.9.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } } } provider "azurerm" { features {} subscription_id = var.subscription_id }

required_providers でバージョンを固定(~> 4.0 は 4.x 系の最新を使う指定)することで、プロバイダーの予期しないバージョンアップによる挙動変化を防ぐ。

2. variables.tf — 変数を定義する

variable "subscription_id" { description = "Azure サブスクリプションID" type = string } variable "location" { description = "Azureリージョン" type = string default = "japaneast" } variable "resource_group_name" { description = "リソースグループ名" type = string default = "rg-terraform-lab" } variable "vm_size" { description = "VMサイズ" type = string default = "Standard_B1ms" } variable "admin_username" { description = "VM管理者ユーザー名" type = string default = "azureuser" } variable "ssh_public_key" { description = "SSH公開鍵の内容(~/.ssh/id_rsa.pub)" type = string }

3. terraform.tfvars — 変数の実際の値(Git管理外)

subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ssh_public_key = "ssh-rsa AAAAB3Nz..."

注意: terraform.tfvars にはサブスクリプションIDとSSH公開鍵が含まれるため、.gitignore に追加してリポジトリに含めないこと。

# .gitignore terraform.tfvars .terraform/ *.tfstate *.tfstate.backup

VNetとサブネット・NSGをTerraformで定義する

main.tf にリソースを記述していく。まずネットワーク関連のリソース(リソースグループ・VNet・サブネット・NSG)から定義する。

1. リソースグループとVNet

# ---- リソースグループ ---- resource "azurerm_resource_group" "main" { name = var.resource_group_name location = var.location } # ---- 仮想ネットワーク(VNet)---- resource "azurerm_virtual_network" "main" { name = "vnet-terraform-lab" address_space = ["10.0.0.0/16"] location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } # ---- サブネット ---- resource "azurerm_subnet" "main" { name = "subnet-main" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.1.0/24"] }

address_space はVNet全体のCIDRブロック(/16)、address_prefixes はサブネットの範囲(/24)を指定する。

2. NSG(SSH許可ルール)とパブリックIP

# ---- ネットワークセキュリティグループ ---- resource "azurerm_network_security_group" "main" { name = "nsg-terraform-lab" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name security_rule { name = "AllowSSH" priority = 1001 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefix = "*" destination_address_prefix = "*" } } # NSGをサブネットに関連付け resource "azurerm_subnet_network_security_group_association" "main" { subnet_id = azurerm_subnet.main.id network_security_group_id = azurerm_network_security_group.main.id } # ---- パブリックIP ---- resource "azurerm_public_ip" "main" { name = "pip-terraform-lab" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name allocation_method = "Static" sku = "Standard" }

NSGのセキュリティルールは priority が小さいほど先に評価される。本番では source_address_prefix を特定のIPレンジに絞ること。

Linux VMをTerraformで定義する

1. NIC(ネットワークインターフェース)

resource "azurerm_network_interface" "main" { name = "nic-terraform-lab" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name ip_configuration { name = "ipconfig1" subnet_id = azurerm_subnet.main.id private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.main.id } }

2. Linux VM本体

resource "azurerm_linux_virtual_machine" "main" { name = "vm-terraform-lab" resource_group_name = azurerm_resource_group.main.name location = azurerm_resource_group.main.location size = var.vm_size admin_username = var.admin_username network_interface_ids = [ azurerm_network_interface.main.id, ] # パスワード認証を無効にしてSSH公開鍵認証のみ許可 disable_password_authentication = true admin_ssh_key { username = var.admin_username public_key = var.ssh_public_key } os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "Canonical" offer = "ubuntu-24_04-lts" sku = "server" version = "latest" } }

source_image_referenceoffer 値(ubuntu-24_04-lts)はハイフンとアンダースコアが混在するため注意。az vm image list で正しい値を確認できる。

3. outputs.tf — VMのIPアドレスを出力する

output "public_ip_address" { description = "VMのパブリックIPアドレス" value = azurerm_public_ip.main.ip_address } output "ssh_command" { description = "SSH接続コマンド" value = "ssh -i ~/.ssh/id_rsa ${var.admin_username}@${azurerm_public_ip.main.ip_address}" }

terraform init / plan / applyの実行

1. terraform init — プロバイダーをダウンロードする

$ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/azurerm versions matching "~> 4.0"... - Installing hashicorp/azurerm v4.14.0... - Installed hashicorp/azurerm v4.14.0 (signed by HashiCorp) Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure.

init が成功すると .terraform/ ディレクトリにプロバイダーのバイナリがダウンロードされる。

2. terraform plan — 変更内容を事前確認する

apply 前に必ず plan を実行して「何が作られるか」を確認する。これがTerraformを使う上での鉄則だ。

$ terraform plan Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # azurerm_resource_group.main will be created + resource "azurerm_resource_group" "main" { + location = "japaneast" + name = "rg-terraform-lab" } # azurerm_virtual_network.main will be created + resource "azurerm_virtual_network" "main" { + address_space = [ + "10.0.0.0/16", ] + location = "japaneast" + name = "vnet-terraform-lab" } # azurerm_linux_virtual_machine.main will be created + resource "azurerm_linux_virtual_machine" "main" { + admin_username = "azureuser" + location = "japaneast" + name = "vm-terraform-lab" + size = "Standard_B1ms" ... } ... Plan: 7 to add, 0 to change, 0 to destroy.

「7 to add」は今回作成されるリソースの数だ。ここで内容を確認してから apply に進む。

3. terraform apply — リソースを作成する

$ 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 azurerm_resource_group.main: Creating... azurerm_resource_group.main: Creation complete after 2s [id=/subscriptions/.../rg-terraform-lab] azurerm_virtual_network.main: Creating... azurerm_virtual_network.main: Creation complete after 4s azurerm_subnet.main: Creating... azurerm_subnet.main: Creation complete after 3s azurerm_network_security_group.main: Creating... azurerm_network_security_group.main: Creation complete after 3s azurerm_public_ip.main: Creating... azurerm_public_ip.main: Creation complete after 2s azurerm_subnet_network_security_group_association.main: Creating... azurerm_subnet_network_security_group_association.main: Creation complete after 2s azurerm_network_interface.main: Creating... azurerm_network_interface.main: Creation complete after 2s azurerm_linux_virtual_machine.main: Creating... azurerm_linux_virtual_machine.main: Still creating... [1m0s elapsed] azurerm_linux_virtual_machine.main: Creation complete after 1m28s Apply complete! Resources: 7 added, 0 changed, 0 destroyed. Outputs: public_ip_address = "52.185.123.45" ssh_command = "ssh -i ~/.ssh/id_rsa azureuser@52.185.123.45"

約90秒でVNet・サブネット・NSG・パブリックIP・NIC・VMが一括で出来上がる。パブリックIPアドレスが outputs に表示されるのでコピーしておく。

4. SSH接続で動作確認する

outputs に表示された ssh_command をそのまま実行する:

$ ssh -i ~/.ssh/id_rsa azureuser@52.185.123.45 Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-1019-azure x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/pro Last login: Sun Aug 24 04:20:10 2026 from 203.0.113.1 azureuser@vm-terraform-lab:~$

SSH接続が通れば動作確認は完了だ。接続できない場合は、NSGのインバウンドルールでポート22が開いているか確認する。Linuxサーバー上でポートの開放状況を確認するコマンドについては Linux ポート確認の全コマンド も参照してほしい。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、AzureのVNetやVM構築をコードで再現できる実践スキルを養うには Azure実践ハンズオン講座 をご覧ください。現役エンジニアが教える実務直結カリキュラムで、Terraformを使ったインフラ自動化まで一気に習得できます。

terraform destroyでリソースを一括削除する

検証が終わったら terraform destroy でリソースをまとめて削除できる。Azure Portalで一つずつ消す手間が不要で、削除し忘れによる課金ミスも防げる。

$ terraform destroy Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes azurerm_linux_virtual_machine.main: Destroying... azurerm_linux_virtual_machine.main: Still destroying... [30s elapsed] azurerm_linux_virtual_machine.main: Destruction complete after 58s azurerm_network_interface.main: Destroying... azurerm_subnet_network_security_group_association.main: Destroying... ... azurerm_resource_group.main: Destruction complete after 7s Destroy complete! Resources: 7 destroyed.

「7 destroyed」と表示されれば完全削除だ。azコマンドで念のため確認できる:

$ az group show --name rg-terraform-lab ResourceNotFoundError: (ResourceGroupNotFound) ...

本番向けTips — tfstateをAzure Blob Storageで管理する

ローカルの terraform.tfstate はチームで共有できない。本番ではAzure Blob Storageにステートを保管する(リモートバックエンド)。まずストレージアカウントとコンテナーを作成する:

# ステート管理用のリソースグループとストレージアカウントを作成 az group create --name rg-tfstate --location japaneast az storage account create \ --resource-group rg-tfstate \ --name satfstate$(openssl rand -hex 4) \ --sku Standard_LRS \ --allow-blob-public-access false az storage container create \ --name tfstate \ --account-name satfstateXXXX

versions.tf の terraform ブロックに backend を追加する:

terraform { required_version = ">= 1.9.0" backend "azurerm" { resource_group_name = "rg-tfstate" storage_account_name = "satfstateXXXX" container_name = "tfstate" key = "terraform-lab.tfstate" } required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } } }

バックエンドを変更したら terraform init を再実行する。これでチームメンバーが同じステートを参照でき、apply/destroy の競合を防げる。

トラブルシュート — よくあるエラーと対処法

「subscription_id が見つからない」エラー

Error: The `subscription_id` was not found in the Azure CLI credentials

対処: terraform.tfvars に subscription_id を設定したか確認する。az account show --query id -o tsv でIDを確認してから設定する。

SSH接続できない(タイムアウト)

NSGとサブネットのアソシエーションが正しく apply されているか確認する:

$ az network nsg show \ --resource-group rg-terraform-lab \ --name nsg-terraform-lab \ --query "securityRules[].{name:name,priority:priority,access:access,destPort:destinationPortRange}" \ --output table Name Priority Access DestPort -------- ---------- -------- ---------- AllowSSH 1001 Allow 22

AllowSSH の Allow ルールが表示されていれば NSG は正常だ。クライアント側のファイアウォールやセキュリティグループを確認する。

「ImageReference が不正」エラー

Error: Code="InvalidParameter" Message="The value of parameter imageReference is invalid"

source_image_reference の値が間違っている。az vm image list で正しい値を確認する:

$ az vm image list \ --publisher Canonical \ --offer ubuntu-24_04-lts \ --sku server \ --all \ --query "[0].{publisher:publisher,offer:offer,sku:sku,version:version}" \ --output table Publisher Offer Sku Version ----------- ----------------- ------ ---------- Canonical ubuntu-24_04-lts server 24.04.202408140

offer の値(ubuntu-24_04-lts: ハイフンとアンダースコアの混在)が間違っていることが多いので注意する。

「tfstateロック」エラー

Error: Error acquiring the state lock

別のターミナルやチームメンバーが apply/destroy 実行中の場合に発生する。処理が終わるのを待つか、明らかに孤立したロックであれば terraform force-unlock LOCK_ID で解除する。

本記事のまとめ

やりたいこと コマンドまたはファイル
azurermプロバイダーを設定する versions.tf に required_providers { azurerm ... }
変数を定義して値を渡す variables.tf + terraform.tfvars
プロバイダーを初期化する terraform init
変更内容を事前確認する terraform plan
リソースを作成する terraform apply
リソースを一括削除する terraform destroy
リモートバックエンドに切り替える versions.tf に backend "azurerm" を追加して terraform init
TerraformのazurermプロバイダーでVNetとLinux VMをコード化すると、同じ構成を何度でも再現でき、インフラの変更をGitでレビューできる体制が整う。検証環境を destroy で丸ごと削除できるのもコスト管理上の大きなメリットだ。まずはこの記事の構成をそのまま試して、動作を確認してから自分の環境向けに変数をカスタマイズしてみてほしい。

Azureの学習をもっと体系的に進めたいなら、Azure実践ハンズオン講座をご覧ください。現役エンジニアが教える実務直結カリキュラムで、VM構築からインフラ管理まで一気に習得できます。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。