2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform で Azure VM を構築してみた! 〜はじめての IaC 入門(3/3)〜

2
Posted at

はじめに(3/3)

前記事(2/3)では、構成ファイル(variables.tf / terraform.tfvars)を活用したコードの分割や、VNet・Subnet の追加を通して、Terraform によるリソース管理の基本的な流れを確認しました。

本記事(3/3)では、シリーズの仕上げとして Azure VM の作成を行います。
また、実務では既存環境へリソース追加や構成変更を行うケースも多いため、既存リソースの取り込みを通して、Terraform の状態管理についても確認します。

前記事はこちら。

記事で学べること

本シリーズは3部構成です。
Azure VM の作成をゴールに、手を動かして IaC(Terraform)の理解を深めます。

1/3

  • Terraform のインストールと環境準備方法(Windows + PowerShell)
  • 構成ファイル(provider.tf / main.tf)の作成方法
  • Resource Group 作成までの基本操作(terraform init / terraform plan / terraform apply

2/3

  • 構成ファイル(variables.tf / terraform.tfvars)を活用したコード改良
  • 段階的なリソース追加(VNet・Subnet)の進め方
  • Resource Group の削除操作(terraform destroy

3/3(本記事

  • 既存リソースの取込操作(terraform import
  • 状態管理(terraform.tfstate)の理解
  • Azure VM を含む関連リソースの作成〜削除までの一連操作

前記事の最終構成イメージ

前記事(2/3)では、以下の構成まで作成しています。
本記事では、この構成を既存環境として Terraform 管理下へ取り込んだうえで、Azure VM を含む関連リソースを追加していきます。

001.png

作成ステップ

以降は前記事(2/3)の続きで、次のステップで進めます。

8.既存リソースの取り込み

ここからは、既存環境を Terraform 管理下へ取り込む流れを確認していきます。

実務では、既存環境に対してリソース追加や構成変更を行うケースが一般的です。

今回は、これまで作成してきた Resource Group・VNet・Subnet を既存リソースとして扱います。

002.png

8-1.作業フォルダの準備

まずは、新しい作業フォルダ(Terraform_Test3)を作成します。

1.デスクトップ上1に任意の名前の作業フォルダを作成します(例:Terraform_Test3)。

003.png

2.PowerShell で以下のコマンドを実行し、作業フォルダへ移動します。

PS C:\Users\<ユーザー名>> cd C:\Users\<ユーザー名>\Desktop\Terraform_Test3

PS C:\Users\<ユーザー名>\Desktop\Terraform_Test3>

8-2.Terraform 構成ファイルの準備

今回は、前記事(2/3)の「7-6」で作成した以下4ファイルを、作業フォルダ(Terraform_Test3)へコピーして利用します。

  • provider.tf
  • main.tf
  • variables.tf
  • terraform.tfvars

現在の作業フォルダの例:

作業フォルダ(例:Terraform_Test3)
├── provider.tf
├── main.tf
├── variables.tf
└── terraform.tfvars

作業フォルダ(Terraform_Test2)のファイルが残っていない場合は、新規にファイルを作成し、以下のコードをコピーしてご利用ください。

provider.tf
# --- Terraform 全体で使用するプロバイダーを宣言 ---
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>4.0"
    }
  }
}
# --- ここまで ---

# --- Azure への接続設定を定義 ---
provider "azurerm" {
  features {}
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
# --- ここまで ---

provider.tf 内の subscription_id = """" の中には、自身がリソースを作成するサブスクリプションの ID を指定します。
(参考:Azure サブスクリプションを検索する

variables.tf
# =====================
# ブロック1.Resource Group
# =====================
variable "rg_name" {
  description = "RG 名"
  type        = string
}
variable "rg_location" {
  description = "リージョン"
  type        = string
}

# =====================
# ブロック2.VNet
# =====================
variable "vnet_name" {
  description = "VNet 名"
  type        = string
}
variable "vnet_address_space" {
  description = "VNet のアドレス空間"
  type        = list(string)
}

# =====================
# ブロック3.Subnet
# =====================
variable "subnet_name" {
  description = "Subnet 名"
  type        = string
}
variable "subnet_address_prefixes" {
  description = "Subnet のアドレス空間"
  type        = list(string)
}
terraform.tfvars
# =====================
# ブロック1.Resource Group
# =====================
rg_name     = "rg-terraform"
rg_location = "japaneast"

# =====================
# ブロック2.VNet
# =====================
vnet_name          = "vnet-terraform"
vnet_address_space = ["192.168.1.0/24"]

# =====================
# ブロック3.Subnet
# =====================
subnet_name             = "snet-terraform"
subnet_address_prefixes = ["192.168.1.0/25"]
main.tf
# =====================
# ブロック1.Resource Group
# =====================
resource "azurerm_resource_group" "rg_main" {
  name     = var.rg_name
  location = var.rg_location
}

# =====================
# ブロック2.VNet
# =====================
resource "azurerm_virtual_network" "vnet_main" {
  name                = var.vnet_name
  address_space       = var.vnet_address_space
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
}

# =====================
# ブロック3.Subnet
# =====================
resource "azurerm_subnet" "subnet_main" {
  name                 = var.subnet_name
  resource_group_name  = azurerm_resource_group.rg_main.name
  virtual_network_name = azurerm_virtual_network.vnet_main.name
  address_prefixes     = var.subnet_address_prefixes
}

8-3.tfstate ファイルの役割

Terraform は、インフラの状態を terraform.tfstate(以降、tfstate ファイル)で管理しています。
tfstate ファイルをもとに管理対象のリソースを判断し、Azure 上の実際の状態との差分を確認します。

今回は、新しい作業フォルダ(Terraform_Test3)を利用するため、現在は tfstate ファイルが存在しない状態です。

つまり、現在は次の状態になります。

  • ローカル環境(Terraform)
    • OK】tf や tfvars ファイルにリソース定義(Resource Group・VNet・Subnet)あり
    • NG】tfstate ファイルでリソース情報(Resource Group・VNet・Subnet)未管理
  • Azure
    • OK】リソース(Resource Group・VNet・Subnet)は存在

004.png

このように、各 Terraform ファイルと Azure 環境の情報が一致していない状態でコマンドを実行すると、どのような挙動になるのかを確認していきます。

今回は学習用途のため、tfstate ファイルをローカル環境で管理しています。

一方で、tfstate ファイルにはリソース情報や一部の機密情報が保存される場合があります。

そのため、実運用では Azure Blob Storage などのアクセス制御された Remote Backend を利用し、tfstate ファイルを安全に管理する構成が一般的です。

8-4.import の実行

ここからは、前記事までに Terraform の基本操作を確認済みである前提で、PowerShell の実行例や出力説明は簡略化しています。

STEP①.terraform init を実行する

まずは、Terraform を実行するための初期化を行います。

terraform init

新しい作業フォルダで Terraform を使用する場合は、最初に terraform init を実行する必要があります。

STEP②.terraform plan を実行する

次に、Terraform が現在の状態をどのように認識しているかを確認します。

terraform plan

実行すると、次のように表示されます。

Plan: 3 to add, 0 to change, 0 to destroy.

今回は tfstate ファイルにリソース情報が存在しないため、Terraform は Azure 上のリソースを認識できていません。

その結果、既存リソースであっても「未管理のリソース」として扱われ、新規作成対象として認識されます。

STEP③.terraform apply を実行する

この状態で terraform apply を実行します。

terraform apply

yes を入力して実行すると、次のようなエラーが表示されます。

azurerm_resource_group.rg_main: Creating...

Error: a resource with the ID ".../resourceGroups/rg-terraform" already exists

tfstate ファイルにリソース情報が存在しないため、Terraform は Resource Group・VNet・Subnet を新規作成しようとします。

しかし、Azure 上に同じ名前のリソースが既に存在しているため、作成処理が競合しエラーとなります。

STEP④.terraform import を実行する

ここでは、Azure 上に存在しているリソースを Terraform 管理下へ取り込みます。

そのために利用するのが、terraform import コマンドです。
このコマンドを実行することで、既存リソースを Terraform の管理対象として tfstate ファイルへ紐付けることができます。

実行前後の状態イメージは次の通りです。

005.png

006.png

今回は、Resource Group・VNet・Subnet を取り込むため、以下のコマンドを実行します。

基本構文や各要素の意味は次の通りです。

terraform import <リソースタイプ.識別名> <リソースID>
  • <リソースタイプ.識別名>:main.tf で定義したリソース
  • <リソースID>:Azure 上のリソースの一意な ID
terraform import azurerm_resource_group.rg_main /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-terraform
terraform import azurerm_virtual_network.vnet_main /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-terraform/providers/Microsoft.Network/virtualNetworks/vnet-terraform
terraform import azurerm_subnet.subnet_main /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-terraform/providers/Microsoft.Network/virtualNetworks/vnet-terraform/subnets/snet-terraform

各コマンドを実行すると、次のように表示されます。

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

リソース ID は Azure Portal や Azure CLI などで確認できます。
Azure CLI の場合は、以下のコマンドを利用することで、リソース ID を取得できます。

az group show --name <ResourceGroup名> --query id -o tsv

実行例

/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-terraform

今回の Resource Group・VNet・Subnet の場合は、以下のコマンドになります。

# Resource Group
az group show --name rg-terraform --query id -o tsv

# VNet
az network vnet show --resource-group rg-terraform --name vnet-terraform --query id -o tsv

# Subnet
az network vnet subnet show --resource-group rg-terraform --vnet-name vnet-terraform --name snet-terraform --query id -o tsv

リソースの種類によって、必要なオプションは異なります。
VNet や Subnet の場合は、--resource-group--vnet-name など、親リソース情報も指定する必要があります。

Terraform で既存リソースを管理するためには、通常は次の手順で作業を行います2

  1. tf ファイル(例:main.tf)へ、実環境に合わせたリソース定義を作成する
  2. terraform import を利用して、既存リソースと tfstate ファイルを紐付ける
  3. terraform plan を実行し、コードと実環境に差分がないか確認する

terraform import は、既存リソースの情報を tfstate ファイルで管理する操作であり、tf ファイルの内容は変更されません。

そのため、terraform plan 実行時に差分が表示された場合は、tfstate ファイルと実環境の状態に合わせて、各 tf ファイルの定義を修正する必要があります。

今回は、前章までで Resource Group・VNet・Subnet のリソース定義を作成済みであり、実環境とも一致しているため、「1」の作業をスキップしています。

STEP⑤.状態を確認する

再度 terraform plan を実行します。

terraform plan

次のように表示されれば、各 Terraform ファイル・Azure 上のリソースに差分がない状態です。

No changes. Your infrastructure matches the configuration.

続けて terraform apply を実行しても、変更は行われません。

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Azure 環境のリソース情報が tfstate ファイルで管理されたことで、Terraform が Azure 上のリソースを正しく認識できる状態になりました。

これにより、Terraform を利用したリソース追加や構成変更を行えるようになります。

9.Azure VM の作成

この章では、シリーズの最終構成として Azure VM を含む関連リソースを作成します。
今回追加する範囲は、次の赤枠部分(ResourceGroup・VNet・Subnet 除く)です。

007.png

これまでの記事では、Terraform の基本操作や、段階的なリソース追加の流れを確認してきました。

本章では、Azure VM を作成するだけではなく、Terraform Registry のサンプルコードをどのように読み解き、必要な構成へ組み立てていくかも意識しながら進めます。

特に、次のような観点を意識しながら、Terraform のコードを整理していきます。

  • 必要なリソースは何か
  • どのリソース同士を関連付ける必要があるか
  • どのサンプルコードを利用するべきか

9-1.作業フォルダの準備

PowerShell で作業フォルダ(Terraform_Test3)へ移動します。

現在の作業フォルダの例:

作業フォルダ(例:Terraform_Test3)
├── provider.tf
├── main.tf
├── variables.tf
├── terraform.tfvars
├── .terraform
├── .terraform.lock.hcl
├── .terraform.tfstate
└── .terraform.tfstate.backup

9-2.provider.tf の編集

provider.tf の内容は前章と同じため、そのまま利用します。

provider.tf
# --- Terraform 全体で使用するプロバイダーを宣言 ---
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>4.0"
    }
  }
}
# --- ここまで ---
# --- Azure への接続設定を定義 ---

provider "azurerm" {
  features {}
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
# --- ここまで ---

provider.tf 内の subscription_id = """" の中には、自身がリソースを作成するサブスクリプションの ID を指定します。
(参考:Azure サブスクリプションを検索する

9-3.main.tf の編集

9-3-1.サンプルコードの入手

Terraform で各リソースを定義するため、公式リファレンスサイト(Terraform Registry) からサンプルコードを入手します。

①VM・NIC・Disk

まずは、Azure VM(以降、VM)の作成に必要なサンプルコードを確認します。

1.検索欄で「virtual machine」と入力し、「Compute」>「Resources」>「azurerm_windows_virtual_machine」をクリックします。

008.png

2.「Example Usage」から、画像の赤枠部分をコピーします。

009.png

以下は、公式リファレンスのサンプルコードです3

resource "azurerm_network_interface" "example" {
  name                = "example-nic"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.example.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_windows_virtual_machine" "example" {
  name                = "example-machine"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  size                = "Standard_F2"
  admin_username      = "xxxxxxxx"
  admin_password      = "xxxxxxxx"
  network_interface_ids = [
    azurerm_network_interface.example.id,
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2016-Datacenter"
    version   = "latest"
  }
}

VM のサンプルコードには、VM 本体だけでなく、NIC や Disk などの関連リソースも含まれています。
これは、VM を動作させるために関連リソースが必要になるためです。

Terraform のコードを作成する際は、「作成したいリソース」だけでなく、「そのリソースを動作させるために必要な構成」まで含めて考える必要があります。

今回は、Resource Group・VNet・Subnet は作成済みのため、VM・NIC・Disk 部分のみ利用します。

②NSG

続いて、NSG の作成に必要なサンプルコードを確認します。

今回は、次の構成を想定します。

  • NSG の作成
  • Subnet への NSG の関連付け

1.検索欄で「network security group」と入力し、「Network」>「Resources」>「azurerm_subnet_network_security_group_association」をクリックします。

010.png

2.「Example Usage」から、画像の赤枠部分をコピーします。

以下は、公式リファレンスのサンプルコードです。

resource "azurerm_network_security_group" "example" {
  name                = "example-nsg"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_subnet_network_security_group_association" "example" {
  subnet_id                 = azurerm_subnet.example.id
  network_security_group_id = azurerm_network_security_group.example.id
}

同じ NSG 関連リソースでも、用途によって利用するサンプルコードは異なります。

No サンプルコード 用途
1 azurerm_network_security_group NSG を作成する
2 azurerm_network_security_rule NSG のルールを定義する
3 azurerm_subnet_network_security_group_association Subnet に NSG を関連付ける

今回は、Subnet へ NSG を関連付けたいため、「3」のサンプルコードを利用しています。
なお、サンプルコード内に NSG の作成コードも含まれています。

このように、Terraform Registry では「何を作成するか」だけでなく、「どこへ関連付けるか」も意識しながらサンプルコードを選択することが重要です。

③NAT Gateway

続いて、NAT Gateway の作成に必要なサンプルコードを確認します。

今回は、次の構成を想定します。

  • NAT Gateway 用 Public IP の作成
  • NAT Gateway の作成
  • Public IP の NAT Gateway への関連付け
  • NAT Gateway の Subnet への関連付け

ここまでの内容を踏まえると、単一リソースだけでなく、関連付け用のサンプルコードも必要になることが分かります。
この場合、どのサンプルコードを利用するべきか考えてみましょう。

Answer

1.検索欄で「nat gateway」と入力し、「Network」>「Resources」から必要なサンプルコードを確認します。

011.png

今回は、次の「2」「4」のサンプルコードをベースに利用します。
なお、サンプルコード内に Public IP や NAT Gateway の作成コードも含まれています。

No サンプルコード 用途 利用有無
1 azurerm_nat_gateway NAT Gateway を作成する -
2 azurerm_nat_gateway_public_ip_association Public IP を NAT Gateway に関連付ける
3 azurerm_nat_gateway_public_ip_prefix_association Public IP(Prefix)を NAT Gateway に関連付ける -
4 azurerm_subnet_nat_gateway_association Subnet に NAT Gateway を関連付ける

2.「Example Usage」から、画像の赤枠部分をコピーします。

012.png

以下は、公式リファレンスのサンプルコードです。

resource "azurerm_public_ip" "example" {
  name                = "example-PIP"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  allocation_method   = "Static"
  sku                 = "Standard"
}

resource "azurerm_nat_gateway" "example" {
  name                = "example-NatGateway"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  sku_name            = "Standard"
}

resource "azurerm_nat_gateway_public_ip_association" "example" {
  nat_gateway_id       = azurerm_nat_gateway.example.id
  public_ip_address_id = azurerm_public_ip.example.id
}

resource "azurerm_subnet_nat_gateway_association" "example" {
  subnet_id      = azurerm_subnet.example.id
  nat_gateway_id = azurerm_nat_gateway.example.id
}

NAT Gateway は単体で利用できず、Public IP や Subnet との関連付けが必要になります。

Terraform のコードを作成する際は、単一リソースだけでなく、関連リソースや依存関係まで含めて構成を整理することが重要です。

9-3-2.コードの書き換え

前章で整理したサンプルコードを、今回の Azure 環境向けに編集します。

今回は、既存の Resource Group・VNet・Subnet に加えて、次のリソースを追加します。

  • ①VM・NIC・Disk
  • ②NSG
  • ③NAT Gateway

1.サンプルコードを今回の Azure 環境に合わせて次のように編集します34

# =====================
# ブロック1.Resource Group
# =====================
resource "azurerm_resource_group" "rg_main" {
  name     = "rg-terraform"
  location = "japaneast"
}

# =====================
# ブロック2.VNet
# =====================
resource "azurerm_virtual_network" "vnet_main" {
  name                = "vnet-terraform"
  address_space       = ["192.168.1.0/24"]
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
}

# =====================
# ブロック3.Subnet
# =====================
resource "azurerm_subnet" "subnet_main" {
  name                 = "snet-terraform"
  resource_group_name  = azurerm_resource_group.rg_main.name
  virtual_network_name = azurerm_virtual_network.vnet_main.name
  address_prefixes     = ["192.168.1.0/25"]
}

# =====================
# ブロック4.VM・ブロック5.NIC・ブロック6.Disk
# =====================
resource "azurerm_network_interface" "nic_main" {
  name                = "nic-vm-terraform"
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name

  ip_configuration {
    name                          = "config-nic-vm-terraform"
    subnet_id                     = azurerm_subnet.subnet_main.id
    private_ip_address_allocation = "Static"
    private_ip_address            = "192.168.1.4"
  }
}

resource "azurerm_windows_virtual_machine" "vm_main" {
  name                = "vm-terraform"
  resource_group_name = azurerm_resource_group.rg_main.name
  location            = azurerm_resource_group.rg_main.location
  size                = "Standard_B2s"
  admin_username      = "xxxxxxxx"
  admin_password      = "xxxxxxxx"
  network_interface_ids = [
    azurerm_network_interface.nic_main.id,
  ]

  os_disk {
    name                 = "disk-vm-terraform"
    caching              = "ReadWrite"
    storage_account_type = "StandardSSD_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2025-Datacenter"
    version   = "latest"
  }
}

# =====================
# ブロック7.NSG
# =====================
resource "azurerm_network_security_group" "nsg_main" {
  name                = "nsg-terraform"
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
  }

resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
  subnet_id                 = azurerm_subnet.subnet_main.id
  network_security_group_id = azurerm_network_security_group.nsg_main.id
}

# =====================
# ブロック8.NAT Gateway
# =====================
resource "azurerm_public_ip" "natgw_pip_main" {
  name                = "pip-natgw-terraform"
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
  allocation_method   = "Static"
  sku                 = "Standard"
}

resource "azurerm_nat_gateway" "natgw_main" {
  name                = "natgw-terraform"
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
  sku_name            = "Standard"
}

resource "azurerm_nat_gateway_public_ip_association" "natgw_assoc" {
  nat_gateway_id       = azurerm_nat_gateway.natgw_main.id
  public_ip_address_id = azurerm_public_ip.natgw_pip_main.id
}

resource "azurerm_subnet_nat_gateway_association" "subnet_natgw_assoc" {
  subnet_id      = azurerm_subnet.subnet_main.id
  nat_gateway_id = azurerm_nat_gateway.natgw_main.id
}

今回は、Terraform における「関連付け(Association)」の設定が新しく登場しています。

そのため、関連付けを行っている部分に絞って整理します。

ブロック No 記述箇所 カテゴリ 内容 補足ポイント
7 resource "azurerm_subnet_network_security_group_association" "nsg_assoc" リソースの種類・識別名 Subnet と NSG を関連付けるリソース定義 azurerm_subnet_network_security_group_association:Subnet と NSG を関連付けるリソース種類(固定)
"nsg_assoc":Terraform 内での識別名
7 subnet_id = azurerm_subnet.subnet_main.id 関連付け設定 NSG を適用する Subnet Subnet リソース(識別名:subnet_main)を参照
7 network_security_group_id = azurerm_network_security_group.nsg_main.id 関連付け設定 関連付け対象の NSG NSG リソース(識別名:nsg_main)を参照
8 resource "azurerm_nat_gateway_public_ip_association" "natgw_assoc" リソースの種類・識別名 NAT Gateway と Public IP を関連付けるリソース定義 azurerm_nat_gateway_public_ip_association:NAT Gateway と Public IP を関連付けるリソース種類(固定)
"natgw_assoc":Terraform 内での識別名
8 nat_gateway_id = azurerm_nat_gateway.natgw_main.id 関連付け設定 関連付け対象の NAT Gateway NAT Gateway リソース(識別名:natgw_main)を参照
8 public_ip_address_id = azurerm_public_ip.natgw_pip_main.id 関連付け設定 NAT Gateway に関連付ける Public IP Public IP リソース(識別名:natgw_pip_main)を参照
8 resource "azurerm_subnet_nat_gateway_association" "subnet_natgw_assoc" リソースの種類・識別名 Subnet と NAT Gateway を関連付けるリソース定義 azurerm_subnet_nat_gateway_association:Subnet と NAT Gateway を関連付けるリソース種類(固定)
"subnet_natgw_assoc":Terraform 内での識別名
8 subnet_id = azurerm_subnet.subnet_main.id 関連付け設定 NAT Gateway を適用する Subnet Subnet リソース(識別名:subnet_main)を参照
8 nat_gateway_id = azurerm_nat_gateway.natgw_main.id 関連付け設定 関連付け対象の NAT Gateway NAT Gateway リソース(識別名:natgw_main)を参照

9-4.コードの改良

ここまでのコードでも動作は可能ですが、リソース名・IP アドレス・VM サイズなどの値を main.tf に直接記載しているため、環境ごとの差分管理がしづらい状態になっています。

特に、リソース数が増えるほど、同じ値を複数箇所で管理する場面も増えていきます。

そのため、前記事と同様に、環境ごとの差分になりやすい値は変数化します。

9-4-1.variables.tf の編集

前記事(2/3)での Resource Group・VNet・Subnet の命名ルールに従い、9-3-2.コードの書き換えで整理した構成をもとに、VM・NIC・Disk・NSG・NAT Gateway に必要な値を入れるための「変数」を用意します。

変数名(任意の名称)は以下のルールで命名しています。

リソースの種類(略称)_属性名(例:rg_name,rg_location,vnet_name

variables.tf は次の通りです。

variables.tf
# =====================
# ブロック1.Resource Group
# =====================
variable "rg_name" {
  description = "Resource Group 名"
  type        = string
}
variable "rg_location" {
  description = "リージョン"
  type        = string
}

# =====================
# ブロック2.VNet
# =====================
variable "vnet_name" {
  description = "VNet 名"
  type        = string
}
variable "vnet_address_space" {
  description = "VNet のアドレス空間"
  type        = list(string)
}

# =====================
# ブロック3.Subnet
# =====================
variable "subnet_name" {
  description = "Subnet 名"
  type        = string
}
variable "subnet_address_prefixes" {
  description = "Subnet のアドレス空間"
  type        = list(string)
}

# =====================
# ブロック4.VM
# =====================
variable "vm_name" {
  description = "VM 名"
  type        = string
}
variable "vm_size" {
  description = "VM サイズ(例:Standard_B2s)"
  type        = string
}
variable "admin_username" {
  description = "VM 管理ユーザー名"
  type        = string
}
variable "admin_password" {
  description = "VM 管理者パスワード"
  type        = string
  sensitive   = true
}
variable "os_publisher" {
  description = "OS イメージの発行元(例:MicrosoftWindowsServer)"
  type        = string
}
variable "os_offer" {
  description = "OS イメージのオファー名(例:WindowsServer)"
  type        = string
}
variable "os_sku" {
  description = "OS イメージ SKU(例:2025-Datacenter)"
  type        = string
}
variable "os_version" {
  description = "OS イメージのバージョン(例:latest)"
  type        = string
}

# =====================
# ブロック5.NIC
# =====================
variable "nic_name" {
  description = "NIC 名"
  type        = string
}
variable "nic_ip_config_name" {
  description = "NIC の ip_configuration 名"
  type        = string
}
variable "nic_ip_allocation_method" {
  description = "NIC の private IP の割当方法(Static or Dynamic)"
  type        = string
}
variable "nic_private_ip" {
  description = "NIC に割り当てる固定プライベートIP(Static を使う場合)"
  type        = string
}

# =====================
# ブロック6.Disk
# =====================
variable "disk_name" {
  description = "VM の OS ディスク名"
  type        = string
}
variable "disk_sku" {
  description = "OS ディスクの SKU(例:StandardHDD_LRS)"
  type        = string
}
variable "disk_caching" {
  description = "OS ディスクのキャッシング(例:ReadWrite)"
  type        = string
}

# =====================
# ブロック7.NSG
# =====================
variable "nsg_name" {
  description = "NSG 名"
  type        = string
}

# =====================
# ブロック8.NAT Gateway
# =====================
variable "natgw_name" {
  description = "NAT Gateway 名"
  type        = string
}
variable "natgw_pip_name" {
  description = "NAT Gateway 用の Public IP 名"
  type        = string
}
variable "natgw_ip_allocation_method" {
  description = "Public IP の割当方法(Static or Dynamic)"
  type        = string
}
variable "natgw_ip_sku" {
  description = "Public IP の SKU"
  type        = string
}
variable "natgw_sku" {
  description = "NAT Gateway の SKU"
  type        = string
}
variable "natgw_idle_timeout" {
  description = "NAT Gateway の idle timeout(分)"
  type        = number
}

9-4-2.terraform.tfvars の編集

variables.tf で用意した変数に値を入れます3

terraform.tfvars は次の通りです。

terraform.tfvars
# =====================
# ブロック1.Resource Group
# =====================
rg_name     = "rg-terraform"
rg_location = "japaneast"

# =====================
# ブロック2.VNet
# =====================
vnet_name          = "vnet-terraform"
vnet_address_space = ["192.168.1.0/24"]

# =====================
# ブロック3.Subnet
# =====================
subnet_name             = "snet-terraform"
subnet_address_prefixes = ["192.168.1.0/25"]

# =====================
# ブロック4.VM
# =====================
vm_name        = "vm-terraform"
vm_size        = "Standard_B2s"
admin_username = "xxxxxxxx"
admin_password = "xxxxxxxx"
os_publisher   = "MicrosoftWindowsServer"
os_offer       = "WindowsServer"
os_sku         = "2025-Datacenter"
os_version     = "latest"

# =====================
# ブロック5.NIC
# =====================
nic_name                 = "nic-vm-terraform"
nic_ip_config_name       = "config-nic-vm-terraform"
nic_ip_allocation_method = "Static"
nic_private_ip           = "192.168.1.4"

# =====================
# ブロック6.Disk
# =====================
disk_name    = "disk-vm-terraform"
disk_sku     = "StandardSSD_LRS"
disk_caching = "ReadWrite"

# =====================
# ブロック7.NSG
# =====================
nsg_name = "nsg-terraform"

# =====================
# ブロック8.NAT Gateway
# =====================
natgw_name                 = "natgw-terraform"
natgw_pip_name             = "pip-natgw-terraform"
natgw_ip_allocation_method = "Static"
natgw_ip_sku               = "Standard"
natgw_sku                  = "Standard"
natgw_idle_timeout         = 10

9-4-3.main.tf の編集

最後に、変数を用意した variables.tf と、変数に値を入れた terraform.tfvars を参照する構成にします。

main.tf は次の通りです。

var.<変数名> は、次のファイルで用意した情報を参照する仕組みです。

  • variables.tf:変数
  • terraform.tfvars:変数に入れた値
main.tf
# =====================
# ブロック1.Resource Group
# =====================
resource "azurerm_resource_group" "rg_main" {
  name     = var.rg_name
  location = var.rg_location
}

# =====================
# ブロック2.VNet
# =====================
resource "azurerm_virtual_network" "vnet_main" {
  name                = var.vnet_name
  address_space       = var.vnet_address_space
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
}

# =====================
# ブロック3.Subnet
# =====================
resource "azurerm_subnet" "subnet_main" {
  name                 = var.subnet_name
  resource_group_name  = azurerm_resource_group.rg_main.name
  virtual_network_name = azurerm_virtual_network.vnet_main.name
  address_prefixes     = var.subnet_address_prefixes
}

# =====================
# ブロック4.VM / ブロック6.Disk
# =====================
resource "azurerm_windows_virtual_machine" "vm_main" {
  name                = var.vm_name
  resource_group_name = azurerm_resource_group.rg_main.name
  location            = azurerm_resource_group.rg_main.location
  size                = var.vm_size
  admin_username      = var.admin_username
  admin_password      = var.admin_password
  network_interface_ids = [
    azurerm_network_interface.nic_main.id
  ]

  os_disk {
    name                 = var.disk_name
    caching              = var.disk_caching
    storage_account_type = var.disk_sku
  }

  source_image_reference {
    publisher = var.os_publisher
    offer     = var.os_offer
    sku       = var.os_sku
    version   = var.os_version
  }
}

# =====================
# ブロック5.NIC
# =====================
resource "azurerm_network_interface" "nic_main" {
  name                = var.nic_name
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name

  ip_configuration {
    name                          = var.nic_ip_config_name
    subnet_id                     = azurerm_subnet.subnet_main.id
    private_ip_address_allocation = var.nic_ip_allocation_method
    private_ip_address            = var.nic_private_ip
  }
}

# =====================
# ブロック7.NSG
# =====================
resource "azurerm_network_security_group" "nsg_main" {
  name                = var.nsg_name
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
}

# NSG を Subnet に関連付け
resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
  subnet_id                 = azurerm_subnet.subnet_main.id
  network_security_group_id = azurerm_network_security_group.nsg_main.id
}

# =====================
# ブロック8.NAT Gateway
# =====================
resource "azurerm_public_ip" "natgw_pip_main" {
  name                = var.natgw_pip_name
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
  allocation_method   = var.natgw_ip_allocation_method
  sku                 = var.natgw_ip_sku
}

resource "azurerm_nat_gateway" "natgw_main" {
  name                = var.natgw_name
  location            = azurerm_resource_group.rg_main.location
  resource_group_name = azurerm_resource_group.rg_main.name
  sku_name            = var.natgw_sku
}

# Public IP を NAT Gateway に関連付け
resource "azurerm_nat_gateway_public_ip_association" "natgw_assoc" {
  nat_gateway_id       = azurerm_nat_gateway.natgw_main.id
  public_ip_address_id = azurerm_public_ip.natgw_pip_main.id
}

# Subnet に NAT Gateway を関連付け
resource "azurerm_subnet_nat_gateway_association" "subnet_natgw_assoc" {
  subnet_id      = azurerm_subnet.subnet_main.id
  nat_gateway_id = azurerm_nat_gateway.natgw_main.id
}

9-5.リソースの作成

ここまでで、Terraform の構成ファイル作成と変数化が完了しました。

実際に Terraform を実行し、Azure VM を含むリソースを作成します。

STEP①.terraform init を実行する

まずは、Terraform の初期化を行います。

terraform init

次のように表示されれば成功です。

Terraform has been successfully initialized!

STEP②.terraform plan を実行する

続けて、作成予定のリソースを確認します。

terraform plan

次のように表示されれば、8 個のリソースが新規作成対象として認識されています。

Plan: 8 to add, 0 to change, 0 to destroy.

STEP③.terraform apply を実行する

最後に、Azure 上へリソースを作成します。

terraform apply

yes を入力して実行すると、Azure 上へリソースが作成されます。

Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

azurerm_subnet_network_security_group_associationazurerm_subnet_nat_gateway_association など、関連付け(Association)用リソースも作成対象としてカウントされています。

9-6.リソースの確認

Azure Portal 上で、VM・NIC・NSG・NAT Gateway などのリソースが作成されていることを確認します。
現在の構成イメージは次の通りです。

013.png

Terraform で定義した関連付けについても確認してみましょう。

  • VM に NIC が接続されていること
  • Subnet に NSG や NAT Gateway が関連付けられていること

9-7.リソース・作業資材の削除

最後に、Azure 上へ作成したリソースを削除します。

STEP①.terraform destroy を実行する

以下のコマンドを実行します。

terraform destroy

yes を入力して実行すると、Azure 上のリソースが削除されます。

Destroy complete! Resources: 11 destroyed.

Azure Portal 上で、VM・NIC・NSG・NAT Gateway などのリソースが削除されていることも確認してみましょう。

削除後の構成イメージは次の通りです。

014.png

terraform destroy は、tfstate ファイルをもとに削除対象のリソースを判断しています。

この考え方は、前章で確認した terraform import の動作ともつながっています。

terraform destroy は Azure 上のリソースを削除するコマンドです。

一方で、tf ファイルや tfstate ファイルなどのローカルファイルは自動削除されません。

そのため、学習用途で作成した作業フォルダ(例:Terraform_Test1 ~ Terraform_Test3)は、フォルダごと手動で削除して問題ありません。

10.おわりに

本シリーズでは、Terraform の基本操作から Azure VM の作成までを段階的に確認してきました。

実際に触れながら構成を組み立てていくことで、単にコマンド操作を覚えるだけではなく、「どのリソースが何に依存しているのか」「関連付けには何が必要なのか」といった、GUI ベースでは意識しづらい構成や依存関係も自然と意識できるようになります。

また、Terraform Registry のサンプルコードを読み解きながら構成を組み立てていくことで、Terraform の理解だけでなく、Azure リソース同士の関係性や全体構成への理解にもつながります。

Terraform は「コードでインフラを管理できるツール」であると同時に、「インフラの構成や依存関係を可視化するツール」でもあります。
手を動かしながら試行錯誤することで、その本質が見えてくるはずです。

本シリーズが、これから Terraform や IaC を学び始める方や、「まず何から触ればいいのか分からない」と感じている方の一助になれば幸いです。

皆さん、快適な Azure ライフを!

We Are Hiring!

  1. ファイル格納用または作業フォルダの場所は基本的に自由ですが、次の点にご注意ください。
    - パスにスペースや日本語など特殊文字が多い場所はトラブルの原因になることがある。

  2. Terraform v1.5.0 以降では import block を利用した方法もありますが、本記事では terraform import コマンドを利用する前提で説明しています。

  3. admin_usernameadmin_passwordxxxxxxxx としています。実際の値は、terraform.tfvars 編集時(9-4-2.terraform.tfvars の編集)で任意の値をご指定ください(ユーザー名・パスワード要件)。 2 3

  4. os_disk ブロックの name は、公式の VM サンプルコードには含まれていません。そのまま作成した場合、Azure 側で OS Disk 名が自動生成されるため、本記事では azurerm_managed_disk のサンプルコードを参考に、明示的に Disk 名を追加しています。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?