はじめに
ここ最近はAzureを検証環境としてよく利用しています。これまではAzureポータルから各リソースをデプロイしていましたが、同じ作業を何度もやるのがつらくなり、Azureの検証環境をサクッと用意できると楽が出来るなあと考えていました。今回はTerraformを使ってAzureリソースを用意する方法を調べたので、簡単にまとめます。
今回の検証環境は、仮想マシンを1台以上用意し、場合によってはデプロイ時に共通コマンド(yum update
など)を実行できる環境を用意します。
Azureで最低限必要なリソース
AzureのIaaSを利用するには、それに関連するリソースも用意する必要があります。今回は仮想マシンを作成するのに合わせ、以下のリソースを用意するtfファイルを用意します。
Azureサブスクリプション情報の取得
まずはAzureのこちらのドキュメントの手順に従い、Terraformの環境変数で利用するAzureの情報を取得します。
Azure Cloud Shellを開き、以下のコマンドを入力します。
# サブスクリプションID、テナントIDを取得する user@Azure:~$ az account list --query "[].{name:name, subscriptionId:id, tenantId:tenantId}" [ { "name": "従量課金制", "subscriptionId": "サブスクリプションID", "tenantId": "テナントID" } ] # アプリケーションID、パスワードの取得 user@Azure:~$ az account set --subscription="サブスクリプションID" user@Azure:~$ az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/サブスクリプションID" Creating a role assignment under the scope of "/subscriptions/サブスクリプションID" Retrying role assignment creation: 1/36 Retrying role assignment creation: 2/36 Retrying role assignment creation: 3/36 { "appId": "アプリケーションID", "displayName": "azure-cli-2020-01-08-01-51-27", "name": "http://azure-cli-2020-01-08-01-51-27", "password": "パスワード", "tenant": "テナントID" } user@Azure:~$
上記コマンドで生成された情報をtfファイルに入力して利用します。
Terraformのインストール
Terraformを利用するには様々な方法があります。上述のAzure Cloud ShellはデフォルトでTerraformが利用可能です。またTerraformはこちらからファイルをダウンロードしてローカル環境で利用することもできます。
今回はWindowsのローカルPCでコマンドを実行しましたが、いずれの環境でも同様のコマンドを実行することでAzureリソースの作成を行えます。
tfファイルの内容
今回利用したtfファイルは以下のようになります。仮想マシンを複数作成できるようcount_vm
という変数を設定しており、必要に応じてcount_vm
の数値を変更します。
# 利用するAzure Providerの情報を設定 provider "azurerm" { subscription_id = "サブスクリプションID" client_id = "アプリケーションID" client_secret = "パスワード" tenant_id = "テナントID" } # 変数の設定 variable "count_vm" { default = "1" } # リソースグループの作成 resource "azurerm_resource_group" "rg" { name = "azuretest_rg" location = "japaneast" tags = { environment = "Terraform" } } # 仮想ネットワークの作成 resource "azurerm_virtual_network" "vnet" { name = "azuretest_vnet" address_space = ["10.0.0.0/16"] location = "japaneast" resource_group_name = azurerm_resource_group.rg.name tags = { environment = "Terraform" } } # サブネットの作成 resource "azurerm_subnet" "subnet" { name = "default" resource_group_name = azurerm_resource_group.rg.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefix = "10.0.1.0/24" } # パブリックIPの作成 resource "azurerm_public_ip" "publicip" { name = "pip-${count.index}" location = "japaneast" resource_group_name = azurerm_resource_group.rg.name allocation_method = "Dynamic" count = "${var.count_vm}" tags = { environment = "Terraform" } } # NSGの作成と通信ルールの設定 resource "azurerm_network_security_group" "nsg" { name = "azuretest_nsg" location = "japaneast" resource_group_name = azurerm_resource_group.rg.name security_rule { name = "SSH" priority = 1001 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefix = "仮想マシンにアクセスする送信元のグローバルIPアドレス" destination_address_prefix = "*" } tags = { environment = "Terraform" } } # ネットワークインターフェイスの作成 resource "azurerm_network_interface" "nic" { name = "nic-${count.index}" location = "japaneast" resource_group_name = azurerm_resource_group.rg.name network_security_group_id = azurerm_network_security_group.nsg.id count = "${var.count_vm}" ip_configuration { name = "ipconfig${count.index}" subnet_id = azurerm_subnet.subnet.id private_ip_address_allocation = "Dynamic" public_ip_address_id = "${element(azurerm_public_ip.publicip.*.id, count.index)}" } tags = { environment = "Terraform" } } # ストレージアカウント用IDの生成(Azure内で一意なIDを割り当てる必要がある) resource "random_id" "randomId" { keepers = { # Generate a new ID only when a new resource group is defined resource_group = azurerm_resource_group.rg.name } byte_length = 8 } # ブート診断用ストレージアカウントの作成 resource "azurerm_storage_account" "sa" { name = "diag${random_id.randomId.hex}" resource_group_name = azurerm_resource_group.rg.name location = "japaneast" account_tier = "Standard" account_replication_type = "LRS" tags = { environment = "Terraform" } } # 仮想マシンの作成 resource "azurerm_virtual_machine" "vm" { name = "vm${count.index}" location = "japaneast" resource_group_name = azurerm_resource_group.rg.name network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] vm_size = "Standard_D2s_v3" count = "${var.count_vm}" storage_os_disk { name = "osdisk${count.index}" caching = "ReadWrite" create_option = "FromImage" managed_disk_type = "StandardSSD_LRS" } storage_data_disk { name = "datadisk${count.index}" managed_disk_type = "StandardSSD_LRS" create_option = "Empty" lun = 0 disk_size_gb = "100" } storage_image_reference { publisher = "OpenLogic" offer = "CentOS" sku = "7.7" version = "latest" } os_profile { computer_name = "vm${count.index}" admin_username = "任意のユーザー名を指定" admin_password = "任意のパスワードを指定" } os_profile_linux_config { disable_password_authentication = false } boot_diagnostics { enabled = "true" storage_uri = azurerm_storage_account.sa.primary_blob_endpoint } tags = { environment = "Terraform" } } resource "azurerm_virtual_machine_extension" "test" { name = "vm${count.index}" location = "japaneast" resource_group_name = azurerm_resource_group.rg.name virtual_machine_name = "${element(azurerm_virtual_machine.vm.*.name, count.index)}" publisher = "Microsoft.Azure.Extensions" type = "CustomScript" type_handler_version = "2.0" count = "${var.count_vm}" settings = <<SETTINGS { "script": "${filebase64("custom_script.sh")}" } SETTINGS } # パブリックPIアドレス表示用 data "azurerm_public_ip" "publicip" { count = "${var.count_vm}" name = "${element(azurerm_public_ip.publicip.*.name, count.index)}" resource_group_name = "${element(azurerm_virtual_machine.vm.*.resource_group_name, count.index)}" } output "publicip_address" { value = "${data.azurerm_public_ip.publicip.*.ip_address}" }
上記tfファイルと同じディレクトリにcustom_script.sh
を配置して実行します。custom_script.sh
の内容は任意に変更すればよいですが、今回は以下のような内容にしました。
#!/bin/sh # update yum update -y # SELinux setenforce 0 sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config # Docker yum install -y docker systemctl start docker && systemctl enable docker
上記ファイルを利用するときの注意点を残しておきます。
Network Security Group
では、セキュリティを考慮し、仮想マシンへアクセスする送信元のIPアドレスのみを許可しています。- ストレージアカウントは無くても問題ありません。
- Virtual Machineの
os_profile
で指定するadmin_username
admin_password
は、以下の条件を満たす必要があります。admin_username
:admin
root
などの予約済みアカウントは利用不可admin_password
:アルファベットの大文字、任意の記号、数字を含む12文字以上を指定
※参考リンク
Terraform - azurerm_virtual_machine
Microsoft Azure - Create a complete Linux virtual machine infrastructure in Azure with Terraform
Terraform - Data Source: azurerm_managed_disk
Terraformコマンドの実行
上記ファイルを用意したら、各種Terraformコマンドを実行します。
# 初期化 C:\Users>terraform init Initializing the backend... Initializing provider plugins... - Checking for available provider plugins... - Downloading plugin for provider "azurerm" (hashicorp/azurerm) 1.39.0... - Downloading plugin for provider "random" (hashicorp/random) 2.2.1... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.azurerm: version = "~> 1.39" * provider.random: version = "~> 2.2" 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. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. C:\Users> # tfファイルの記載方法が正しいか確認(terraform validate) C:\Users>terraform validate Success! The configuration is valid. C:\Users> # 作成予定のリソースを確認(terraform plan) C:\Users>terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: (中略) Plan: 9 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. C:\Users> # リソースの作成(terraform apply) C:\Users>terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: (中略) Plan: 9 to add, 0 to change, 0 to destroy. 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 # yesと入力 (中略) Apply complete! Resources: 9 added, 0 changed, 0 destroyed. Outputs: publicip_address = [ "13.71.135.94", ] C:\Users>
コマンドが完了すると、publicip_address
が出力され、アクセスするパブリックIPアドレスが表示されます。
provisioning時に実行するコマンドの指定方法
TerraformでAzureリソースを操作する場合、コマンドを指定する方法がいくつかあります。
1. remote-exec
Provisioner
remote-exec
Provisionerはリモートホストにアクセスしてコマンドを実行することができます。利用するためにはconnection
blockを合わせて指定し、Linuxの場合はssh鍵のパスを指定する必要があります。実行コマンドはinline
に記載します。
resource "aws_instance" "web" { # ... connection { type = "ssh" user = "root" password = "${var.root_password}" host = "${var.host}" } provisioner "remote-exec" { inline = [ "puppet apply", "consul join ${aws_instance.web.private_ip}", ] } }
※参考リンク:
Terraform Doc - remote-exec Provisioner
Terraform Doc - Provisioner Connection Settings
2. commandToExecute
property
azurerm_virtual_machine_extension
は、デプロイ後のAzure仮想マシンに対して実行コマンドなどを指定し、設定を追加することができるリソースです。azurerm_virtual_machine_extension
内でcommandToExecute
を利用し、仮想マシン上で実行するコマンドを直接指定します。
resource "azurerm_virtual_machine_extension" "example" { name = "hostname" location = "${azurerm_resource_group.example.location}" resource_group_name = "${azurerm_resource_group.example.name}" virtual_machine_name = "${azurerm_virtual_machine.example.name}" publisher = "Microsoft.Azure.Extensions" type = "CustomScript" type_handler_version = "2.0" settings = <<SETTINGS { "commandToExecute": "hostname && uptime" } SETTINGS tags = { environment = "Production" } }
3. script
property
今回利用したのは、azurerm_virtual_machine_extension
リソース内で利用するscript
propertyです。script
で指定したスクリプトファイル内にある記述内容を実行します。
commandToExecute
と比べて複雑なコマンドを実行できること、またremote-exec
のようにssh鍵を指定する必要がないことから、今回はこちらを採用しました。
resource "azurerm_virtual_machine_extension" "test" { name = "hostname" location = "${azurerm_resource_group.test.location}" resource_group_name = "${azurerm_resource_group.test.name}" virtual_machine_name = "${azurerm_virtual_machine.test.name}" publisher = "Microsoft.Azure.Extensions" type = "CustomScript" type_handler_version = "2.0" settings = <<SETTINGS { "script": "${filebase64("custom_script.sh")}" } SETTINGS }
※参考リンク
Terraform Doc - azurerm_virtual_machine_extension
hypernephelist - Azure VM Custom Script Extensions with Terraform