はじめに
前回のTerraformからAzureを構築する【準備編】ではterraformのインストールから、最小構成でterraformからリソースグループを作成するまでを行いました。
今回はより実践的なAzureVMをterraformで作っていきたいと思います。
想定する読者
- TerraformからAzureを構築する【準備編】を読んでいる人
- terraformで作業ディレクトリの初期化ができる人
- terraformでリソースグループを作成できる人
何を作るか
今回はLinuxのVMを作成していきます。
Azure VMを作成する
MSさんが出してくれている以下の記事と
terraformのリファレンスを見ると作成できます!
と、それだとこの記事の意味がなくなってしまうので最小構成で分かりやすく細かい解説を入れながらやって行こうと思います。
ファイル構成
terraform (作業ディレクトリ)
∟ main.tf (tfファイル)
∟ terraform.tfstate (tfstateファイル) <= 自動で生成されます。
ファイル構成は前回の「TerraformからAzureを構築する【準備編】」と同じになります。
main.tf (tfファイル)の解説
リソースの設定を記述するtfファイルをいくつかに分割して説明していきます。
terraformの設定とリソースグループ作成
terraform {
backend "local" {}
required_version = "1.3.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.34.0"
}
tls = { #SSHの秘密鍵を生成するために必要なprovider
source = "hashicorp/tls"
version = "~>4.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "resource-group" {
location = "japaneast"
name = "terraform-test"
tags = {
ENVIRONMENT = "dev"
}
}
前回で使用した部分に、tlsの項目を追加しています。
SSHで秘密鍵を生成するために必要なプロバイダーです。
ネットワークの設定
# 仮想ネットワーク
resource "azurerm_virtual_network" "test_network" {
name = "test-vnet"
address_space = ["192.168.0.0/16"]
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
}
# サブネット
resource "azurerm_subnet" "test_subnet" {
name = "test-subnet"
resource_group_name = azurerm_resource_group.resource-group.name
virtual_network_name = azurerm_virtual_network.test_network.name
address_prefixes = ["192.168.0.0/24"]
}
# パブリックIPアドレス
resource "azurerm_public_ip" "test_public_ip" {
name = "test-public-ip"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
allocation_method = "Dynamic"
}
# NSG(ネットワークセキュリティグループ)
resource "azurerm_network_security_group" "test_nsg" {
name = "test-network-security-group"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
security_rule {
name = "SSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# NIC(ネットワークインタフェースカード)
resource "azurerm_network_interface" "test_nic" {
name = "test-nic"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
ip_configuration {
name = "test_nic_configuration"
subnet_id = azurerm_subnet.test_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.test_public_ip.id
}
}
# NSGとNICの紐付け
resource "azurerm_network_interface_security_group_association" "nsg-nic" {
network_interface_id = azurerm_network_interface.test_nic.id
network_security_group_id = azurerm_network_security_group.test_nsg.id
}
ネットワークやNSGに関する設定になります。
locationやresource_group_nameなど、文字列でそのまま書いても良いのですが
リソースの種類.tfファイル内でのリソース名.プロパティ
と指定する事で別のリソースの情報を参照する事ができます。
このようにすることで、汎用的なテンプレートを作る事ができます。
NSGにて複数のsecurity_ruleを指定したい場合はsecurity_ruleを別リソースにして紐づけます。
# NSG(ネットワークセキュリティグループ)
resource "azurerm_network_security_group" "test_nsg" {
name = "test-network-security-group"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
}
# セキュリティルール(SSH)
resource "azurerm_network_security_rule" "test_nsg-rule-ssh" {
access = "Allow"
destination_address_prefix = "*"
destination_port_range = "22"
direction = "Inbound"
name = "AllowAnySSHInbound"
network_security_group_name = azurerm_network_security_group.test_nsg.name #ここで紐付け
priority = 100
protocol = "Tcp"
resource_group_name = azurerm_resource_group.resource-group.name
source_address_prefix = "*"
source_port_range = "*"
}
# セキュリティルール(http)
resource "azurerm_network_security_rule" "test_nsg-rule-http" {
access = "Allow"
destination_address_prefix = "*"
destination_port_range = "80"
direction = "Inbound"
name = "AllowAnyHTTPInbound"
network_security_group_name = azurerm_network_security_group.test_nsg.name #ここで紐付け
priority = 110
protocol = "Tcp"
resource_group_name = azurerm_resource_group.resource-group.name
source_address_prefix = "*"
source_port_range = "*"
}
このようにterraformでは、親リソースの中にまとめて記述するパターンと、子リソースにして外出しする記述パターンとが混在しているのでややこしい部分があります。
SSHキーの作成
# SSHキー
resource "tls_private_key" "test_ssh" {
algorithm = "RSA"
rsa_bits = 4096
}
SSHの秘密鍵を生成します。生成した秘密鍵は後述のoutputという項目でterraformからエクスポートできるようにします。
VMの設定
# VM
resource "azurerm_linux_virtual_machine" "test_vm" {
name = "test-vm"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
network_interface_ids = [azurerm_network_interface.test_nic.id]
size = "Standard_DS1_v2"
os_disk {
name = "test-on-disk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
computer_name = "test-vm"
admin_username = "azureuser"
disable_password_authentication = true
admin_ssh_key {
username = "azureuser"
public_key = tls_private_key.test_ssh.public_key_openssh
}
}
sizeやstorage_account_type、OSの種類などVMのパラメータを設定できます。
エクスポート用の設定
output "resource_group_name" {
value = azurerm_resource_group.resource-group.name
}
output "public_ip_address" {
value = azurerm_linux_virtual_machine.test_vm.public_ip_address
}
output "tls_private_key" {
value = tls_private_key.test_ssh.private_key_pem
sensitive = true
}
outputとしてリソース名を指定しておくと、リソースのプロパティの値をエクスポートする事ができます。
より正確にいうと、tfstateファイルから参照しているだけなので、実際の値はtfstateファイルに記載されています。
tfstateファイルには機密情報であっても平文で保存されているので管理する方法を考えなくてはいけませんが、それはまた別の記事で解説していきます。
main.tf (tfファイル)全体
terraform {
backend "local" {}
required_version = "1.3.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.34.0"
}
tls = {
source = "hashicorp/tls"
version = "~>4.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "resource-group" {
location = "japaneast"
name = "terraform-test"
tags = {
ENVIRONMENT = "dev"
}
}
# 仮想ネットワーク
resource "azurerm_virtual_network" "test_network" {
name = "test-vnet"
address_space = ["192.168.0.0/16"]
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
}
# サブネット
resource "azurerm_subnet" "test_subnet" {
name = "test-subnet"
resource_group_name = azurerm_resource_group.resource-group.name
virtual_network_name = azurerm_virtual_network.test_network.name
address_prefixes = ["192.168.0.0/24"]
}
# パブリックIPアドレス
resource "azurerm_public_ip" "test_public_ip" {
name = "test-public-ip"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
allocation_method = "Dynamic"
}
# NSG(ネットワークセキュリティグループ)
resource "azurerm_network_security_group" "test_nsg" {
name = "test-network-security-group"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
security_rule {
name = "SSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# NIC(ネットワークインタフェースカード)
resource "azurerm_network_interface" "test_nic" {
name = "test-nic"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
ip_configuration {
name = "test_nic_configuration"
subnet_id = azurerm_subnet.test_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.test_public_ip.id
}
}
# NSGとNICの紐付け
resource "azurerm_network_interface_security_group_association" "nsg-nic" {
network_interface_id = azurerm_network_interface.test_nic.id
network_security_group_id = azurerm_network_security_group.test_nsg.id
}
# SSHキー
resource "tls_private_key" "test_ssh" {
algorithm = "RSA"
rsa_bits = 4096
}
# VM
resource "azurerm_linux_virtual_machine" "test_vm" {
name = "test-vm"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
network_interface_ids = [azurerm_network_interface.test_nic.id]
size = "Standard_DS1_v2"
os_disk {
name = "test-on-disk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
computer_name = "test-vm"
admin_username = "azureuser"
disable_password_authentication = true
admin_ssh_key {
username = "azureuser"
public_key = tls_private_key.test_ssh.public_key_openssh
}
}
output "resource_group_name" {
value = azurerm_resource_group.resource-group.name
}
output "public_ip_address" {
value = azurerm_linux_virtual_machine.test_vm.public_ip_address
}
output "tls_private_key" {
value = tls_private_key.test_ssh.private_key_pem
sensitive = true
}
※各リソースのnameに入る部分は同じサブスクリプション内で競合する名前がある場合実行中にエラーになります。被らないような値に修正して下さい。
terraformでVMを作成
実際に作成していきます。
作業ディレクトリの初期化
terraform init
前回の記事で作成したディレクトリで実行するとエラーが表示されるかもしれません。
その場合はオプションを指定して以下のように実行して下さい。
terraform init -upgrade
変更内容の確認
terraform plan
今回作成対象のリソースが表示されます(確認のみ)
リソースの作成
terraform apply
差分を確認後「yes」を実行するとリソースが作成されます。
Outputsとして指定した値が出力されていますが、tls_private_keyに関してはsensitiveオプションをTrueにしているのでディスプレイ上には表示されません(tfstateファイルには平文で記載されています)
秘密鍵のエクスポート
terraform output -raw tls_private_key > id_rsa
terraform outputで秘密鍵をエクスポートします。
chmod 600 id_rsa
秘密鍵のアクセス権を変更します。
接続確認
ssh -i id_rsa azureuser@<public_ip_address>
sshで接続できるようになっています
Azureポータルでも確認
一旦お片付け
terraform destroy
AzureVMを10台作ってみる
tfファイルを書き換えて、terraformからAzureVMを10台作ってみたいと思います。
main.tf (tfファイル)の修正箇所
VM用の変数を定義
locals {
vm_map = {
"test-vm01" = {"subnet":"192.168.1.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm02" = {"subnet":"192.168.2.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm03" = {"subnet":"192.168.3.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm04" = {"subnet":"192.168.4.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm05" = {"subnet":"192.168.5.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm06" = {"subnet":"192.168.6.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm07" = {"subnet":"192.168.7.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm08" = {"subnet":"192.168.8.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm09" = {"subnet":"192.168.9.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm10" = {"subnet":"192.168.10.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
}
}
localsはtfファイル内で使えるローカル変数です。
map形式で作成するVM分のパラメータを記載します。
複数作成するリソース毎に行う設定
# サブネット
resource "azurerm_subnet" "test_subnet" {
for_each = local.vm_map # ローカル変数として定義したVMのMAPを指定する
name = "${each.key}-subnet"
resource_group_name = azurerm_resource_group.resource-group.name
virtual_network_name = azurerm_virtual_network.test_network.name
address_prefixes = ["${each.value.subnet}"]
}
ここではサブネットを取り出して、どうやってループ処理を描くか見ていきます。
まずlocal.要素
でlocals
で定義した変数を参照する事ができます。
ループ処理させたいリソースの中でfor_each
を追加し、値として定義したVMのMAPを指定します。
文字列の中で変数を扱いたい場合は${}
を利用します。
nameはサブスクリプションの中でユニークである必要があるので、each.key
でMAPのkeyを指定します。
address_prefixesのようにMAPの値を指定したい場合はeach.value.要素
という形で指定します。
これはループ処理が必要なリソース全てに追記していきます。
for_each利用時のoutputの指定方法
output "public_ip_address" {
value = [ for value in azurerm_linux_virtual_machine.test_vm : value.public_ip_address ]
}
ここではパブリックIPを取り出して、どうやってfor_each利用時にoutputを指定するかを見ていきます。
for 変数 in リソース種類.tfファイル上のリソース名 : 変数.プロパティ
このような形で複数のoutputをリスト形式にして出力できるようにします。
修正後のmain.tf (tfファイル)全体
terraform {
backend "local" {}
required_version = "1.3.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.34.0"
}
tls = {
source = "hashicorp/tls"
version = "~>4.0"
}
}
}
provider "azurerm" {
features {}
}
locals {
vm_map = {
"test-vm01" = {"subnet":"192.168.1.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm02" = {"subnet":"192.168.2.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm03" = {"subnet":"192.168.3.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm04" = {"subnet":"192.168.4.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm05" = {"subnet":"192.168.5.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm06" = {"subnet":"192.168.6.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm07" = {"subnet":"192.168.7.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm08" = {"subnet":"192.168.8.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm09" = {"subnet":"192.168.9.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
"test-vm10" = {"subnet":"192.168.10.0/24","size":"Standard_DS1_v2","storage_account_type":"Premium_LRS","admin_username":"azureuser","ssh_username":"azureuser"},
}
}
resource "azurerm_resource_group" "resource-group" {
location = "japaneast"
name = "terrafrom-test"
tags = {
ENVIRONMENT = "dev"
}
}
# 仮想ネットワーク
resource "azurerm_virtual_network" "test_network" {
name = "test-vnet"
address_space = ["192.168.0.0/16"]
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
}
# サブネット
resource "azurerm_subnet" "test_subnet" {
for_each = local.vm_map
name = "${each.key}-subnet"
resource_group_name = azurerm_resource_group.resource-group.name
virtual_network_name = azurerm_virtual_network.test_network.name
address_prefixes = ["${each.value.subnet}"]
}
# パブリックIPアドレス
resource "azurerm_public_ip" "test_public_ip" {
for_each = local.vm_map
name = "${each.key}-public-ip"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
allocation_method = "Dynamic"
}
# NSG(ネットワークセキュリティグループ)
resource "azurerm_network_security_group" "test_nsg" {
for_each = local.vm_map
name = "${each.key}-network-security-group"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
security_rule {
name = "SSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# NIC(ネットワークインタフェースカード)
resource "azurerm_network_interface" "test_nic" {
for_each = local.vm_map
name = "${each.key}-nic"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
ip_configuration {
name = "${each.key}_nic_configuration"
subnet_id = azurerm_subnet.test_subnet[each.key].id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.test_public_ip[each.key].id
}
}
# NSGとNICの紐付け
resource "azurerm_network_interface_security_group_association" "nsg-nic" {
for_each = local.vm_map
network_interface_id = azurerm_network_interface.test_nic[each.key].id
network_security_group_id = azurerm_network_security_group.test_nsg[each.key].id
}
# SSHキー
resource "tls_private_key" "test_ssh" {
for_each = local.vm_map
algorithm = "RSA"
rsa_bits = 4096
}
# VM
resource "azurerm_linux_virtual_machine" "test_vm" {
for_each = local.vm_map
name = "${each.key}"
location = azurerm_resource_group.resource-group.location
resource_group_name = azurerm_resource_group.resource-group.name
network_interface_ids = [azurerm_network_interface.test_nic[each.key].id]
size = "${each.value.size}"
os_disk {
name = "${each.key}-on-disk"
caching = "ReadWrite"
storage_account_type = "${each.value.storage_account_type}"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
computer_name = "${each.key}"
admin_username = "${each.value.admin_username}"
disable_password_authentication = true
admin_ssh_key {
username = "${each.value.ssh_username}"
public_key = tls_private_key.test_ssh[each.key].public_key_openssh
}
}
output "resource_group_name" {
value = azurerm_resource_group.resource-group.name
}
output "public_ip_address" {
value = [ for value in azurerm_linux_virtual_machine.test_vm : value.public_ip_address ]
}
output "tls_private_key" {
value = [ for value in tls_private_key.test_ssh : value.private_key_pem ]
sensitive = true
}
terraformでVM10台を作成
実際に作っていきます。
変更内容の確認
terraform plan
今回作成対象のリソースが表示されます(確認のみ)
リソースの作成
terraform apply
差分を確認後「yes」を実行するとリソースが作成されます。
約1分程度で10台のVMが作成できました。
Azureポータルで確認
見切れていますが10台のVMとそれに紐づくリソースが作成されています。
またお片付け
terraform destroy
まとめ
今回はterraformにてVMを作成しました。
for_eachを使うと同じ構成や一部分だけ変更するようなリソースが簡単に複製する事が可能です。
次回は、terraformでAzureFunctionsを作成してみたいと思います。