【Terraform de Azure】 Ubuntu の VM を作成し SSH してみました

### 概要
「Infrastructure as Code」ということで、Terraform を用いて Azure環境上に Ubuntu のVMを作成し、SSH接続してみます。 [この記事](https://qiita.com/turupon/items/7c8c409a3ae841854cde)は Windows10 ですが、今回は Linux を構築します。

### ローカル環境

- macOS Monterey 12.1
- python 3.8.3
- Azure CLI 2.28.0
- terraform v1.0.11

### 前提条件

1. Azure環境がすでに用意されていること（テナント/サブスクリプション）
2. ローカル環境に「azure cli」がインストールされていること
3. ローカル環境に「terraform」環境が構成されていること
4. TerraformでAzure上に環境構築するための[サービスプリンシパルが作成](https://qiita.com/turupon/items/5d024d43a60cd101be94)されており、Terraform のためのローカル環境変数が定義されていること

### 事前準備

ローカル環境からインターネットアクセス時に自動割当されているグローバルアドレスの取得

```
## 利用グローバルアドレス
$ curl inet-ip.info
153.134.22.135
```

構築する仮想マシンのImage情報の取得

```
## Image
$ az vm image list --location japaneast --offer UbuntuServer --sku 18.04-LTS --all --output table

Offer         Publisher    Sku        Urn                                               Version
------------  -----------  ---------  ------------------------------------------------  ---------------
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.201804262  18.04.201804262
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.201805170  18.04.201805170
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.201805220  18.04.201805220
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.201806130  18.04.201806130
     ：
    省略
     ：
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.202111300  18.04.202111300
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.202112020  18.04.202112020
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.202201060  18.04.202201060
UbuntuServer  Canonical    18.04-LTS  Canonical:UbuntuServer:18.04-LTS:18.04.202201180  18.04.202201180
```

---

## Terraform で Ubuntu の VM を作成してみる

### terraform 定義ファイルの作成

プロバイダの定義

```main.tf
# プロバイダーの定義
terraform {
  required_providers {
    azurerm =  "~> 2.33"
  }
}

provider "azurerm" {
  features {}
  tenant_id     = var.ARM_TENANT_ID
  client_id     = var.ARM_CLIENT_ID
  client_secret = var.ARM_CLIENT_SECRET
}


# リソースグループ
resource "azurerm_resource_group" "this" {
  name     = var.resource_group_name
  location = var.region
  tags     = var.tags_def
}
```

パラメータ定義ファイル

```variables.tf
# 環境変数（Azureサービスプリンシパル）
variable ARM_TENANT_ID {}
variable ARM_CLIENT_ID {}
variable ARM_CLIENT_SECRET {}

# タグ情報
variable tags_def {
  default = {
    owner      = "ituru"
    period     = "2022-03-31"
    CostCenter = "PSG2"
    Environment = "Demo"
    Project = "DUP_IaC"
  }
}

# 各種パラメータ
variable region {}                  // 利用リージョン
variable resource_group_name {}     // リソースグループ名

variable vnet_name {}               // vNet名
variable vnet_address_space {}      // vNetアドレス範囲
variable subnet_name {}             // サブネット名
variable subnet_address {}          // サブネットアドレス
variable public_ip_name {}          // パブリックIP名
variable security_group_name {}     // セキュリティグループ名
variable network_interface_name {}  // ネットワーク・インターフェース名

variable vm_name {}                 // 仮想マシン名
variable vm_size {}                 // 仮想マシンサイズ
variable computer_name {}           // コンピュータ名
variable admin_username {}          // 管理者名
```

パラメータ値定義ファイル

```terraform.tfvars
# 環境変数の定義（Azureサービスプリンシパル）
ARM_TENANT_ID       = "zzzzzzzz-cccc-4645-5757-zzzzzzzzzzzz"
ARM_CLIENT_ID       = "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx"
ARM_CLIENT_SECRET   = "hogehogehogehogehogehogehogehogege"

# パラメータ値の定義
region                  = "japaneast"           // 利用リージョン
resource_group_name     = "rg_ituru_vm02"       // リソースグループ名

vnet_name               = "vnet_ituru_vm02"     // vNet名
vnet_address_space      = "10.0.0.0/16"         // vNetアドレス範囲
subnet_name             = "snet_ituru_vm02"     // サブネット名
subnet_address          = "10.0.1.0/24"         // サブネットアドレス
public_ip_name          = "pip_ituru_vm02"      // パブリックIP名
security_group_name     = "nsg_ituru_vm02"      // セキュリティグループ名
network_interface_name  = "nic_ituru_vm02"      // ネットワーク・インターフェース名

vm_name                 = "vm-ituru-ubuntu"     // 仮想マシン名
vm_size                 = "Standard_D11_v2"     // 仮想マシンサイズ
computer_name           = "ubuntu1804"          // コンピュータ名
admin_username          = "nmcadmin"            // 管理者名
```

仮想マシン定義ファイル

```computer.tf
# Linux（Ubuntu 18.04） 仮想マシン
resource "azurerm_linux_virtual_machine" "this" {
  name                  = var.vm_name
  location              = azurerm_resource_group.this.location
  resource_group_name   = azurerm_resource_group.this.name
  size                  = var.vm_size
  computer_name         = var.computer_name
  admin_username        = var.admin_username
  network_interface_ids = [
    azurerm_network_interface.this.id,
  ]

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

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

  admin_ssh_key {
    username       = var.admin_username
    public_key     = tls_private_key.this.public_key_openssh
  }

  tags  = var.tags_def
}

# SSHキーの作成
resource "tls_private_key" "this" {
  algorithm = "RSA"
  rsa_bits = 4096
}
```

ネットワーク＋セキュリティ定義ファイル

```network.tf
// 仮想ネットワーク
resource "azurerm_virtual_network" "this" {
  name                = var.vnet_name
  address_space       = [var.vnet_address_space]
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  tags                = var.tags_def
}

// サブネット
resource "azurerm_subnet" "internal" {
  name                  = var.subnet_name
  resource_group_name   = azurerm_resource_group.this.name
  virtual_network_name  = azurerm_virtual_network.this.name
  address_prefixes      = [var.subnet_address]
}

// パブリック IP アドレス
resource "azurerm_public_ip" "this" {
  name                = var.public_ip_name
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  allocation_method   = "Dynamic"
  tags                = var.tags_def
}

// ネットワーク セキュリティ グループ
resource "azurerm_network_security_group" "this" {
  name                = var.security_group_name
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name

  security_rule {
    name                       = "SSH"
    description                = "Allow SSH Access"
    priority                   = 1002
    direction                  = "Inbound"   
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "153.134.22.135/32"
    destination_address_prefix = "*"
  }

  tags  = var.tags_def
}

// ネットワーク インターフェイス
resource "azurerm_network_interface" "this" {
  name                = var.network_interface_name
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name

  ip_configuration {
    name                          = "ipconfig"
    subnet_id                     = azurerm_subnet.internal.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.this.id
  }

  tags  = var.tags_def
}

// ネットワークインターフェースへのネットワークセキュリティグループの割当
resource "azurerm_network_interface_security_group_association" "this" {
    network_interface_id      = azurerm_network_interface.this.id
    network_security_group_id = azurerm_network_security_group.this.id
}
```

### terraform の実行

```
## init
$ terraform init
    :
Terraform has been successfully initialized!

## plan
$ terraform plan
    :
Plan: 9 to add, 0 to change, 0 to destroy.

## apply
$ terraform apply
    :
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

admin_username = "nmcadmin"
public_ip_id = [
  "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_vm02/providers/Microsoft.Network/publicIPAddresses/pip_ituru_vm02",
]
tls_private_key = <sensitive>
```

### ローカルの作業ディレクトの状況

```
$ tree -a
.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               ├── azurerm
│               │   └── 2.93.0
│               │       └── darwin_amd64
│               │           └── terraform-provider-azurerm_v2.93.0_x5
│               └── tls
│                   └── 3.1.0
│                       └── darwin_amd64
│                           └── terraform-provider-tls_v3.1.0_x5
├── .terraform.lock.hcl
├── compute.tf
├── main.tf
├── network.tf
├── outputs.tf
├── terraform.tfstate
├── terraform.tfstate.backup
├── terraform.tfvars
├── variables.tf
└── vm_ubuntu.pem
```

---

## terraform 実行後の確認

### リソースグループ の確認

```
$ az group show --name <Resource_Group_name>
{
  "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_vm02",
  "location": "japaneast",
  "managedBy": null,
  "name": "rg_ituru_vm02",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "CostCenter": "PSG2",
    "Environment": "Demo",
    "Project": "DUP_IaC",
    "owner": "ituru",
    "period": "2022-03-31"
  },
  "type": "Microsoft.Resources/resourceGroups"
}
```

### 仮想マシン の確認

```
$ az vm list -g <Resource_Group_Name> -d --output table

Name             ResourceGroup    PowerState    PublicIps      Fqdns    Location    Zones
---------------  ---------------  ------------  -------------  -------  ----------  -------
vm-ituru-ubuntu  rg_ituru_vm02    VM running    20.188.24.42            japaneast
```

### ネットワークセキュリティグループ の確認

```
$ az network nsg rule list -g <Resource_Group_Name> --nsg-name <Network_Security_Group_Namr> -o table

Name    ResourceGroup    Priority    SourcePortRanges    SourceAddressPrefixes    SourceASG    Access    Protocol    Direction    DestinationPortRanges    DestinationAddressPrefixes    DestinationASG
------  ---------------  ----------  ------------------  -----------------------  -----------  --------  ----------  -----------  -----------------------  ----------------------------  ----------------
SSH     rg_ituru_vm02    1002        *                   153.134.22.135/32        None         Allow     Tcp         Inbound      22                       *                             None
```

---

### 仮想マシンへの接続

#### グローバルアドレスの取得

```
$ az vm list-ip-addresses --resource-group <Resource_Group_Name> --name <VM_Name> --output table
VirtualMachine    PublicIPAddresses    PrivateIPAddresses
----------------  -------------------  --------------------
vm-ituru-ubuntu   20.188.24.42         10.0.1.4
```

#### 秘密鍵の取得とパーミッションの変更

```
$ terraform output -raw tls_private_key > vm_ubuntu.pem
$ chmod 600 vm_ubuntu.pem
```

## SSH接続

```
$ ssh -i vm_ubuntu.pem nmcadmin@20.188.24.42

The authenticity of host '20.188.24.42 (20.188.24.42)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx64.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '20.188.24.42' (ED25519) to the list of known hosts.
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-1067-azure x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon Jan 24 09:05:02 UTC 2022

  System load:  0.0               Processes:           116
  Usage of /:   4.6% of 28.90GB   Users logged in:     0
  Memory usage: 1%                IP address for eth0: 10.0.1.4
  Swap usage:   0%

0 updates can be applied immediately.


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

nmcadmin@ubuntu1804:~$ 
```

#### 接続（ログイン）後の確認

```
## OSバージョン
nmcadmin@ubuntu1804:~$ uname -a
Linux ubuntu1804 5.4.0-1067-azure #70~18.04.1-Ubuntu SMP Thu Jan 13 19:46:01 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

## IPアドレス
nmcadmin@ubuntu1804:~$ ip add show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:22:48:e7:02:80 brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.4/24 brd 10.0.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::222:48ff:fee7:280/64 scope link 
       valid_lft forever preferred_lft forever

## グローバルアドレス
nmcadmin@ubuntu1804:~$ curl ifconfig.me
20.188.24.42
```

---

### terraform による作成したリソースの削除

```
$ terraform destroy
```

---

### まとめ

これで、Terraform でサクッと Azure環境上に Ubuntu を構成し SSH接続 できました。 Azure Portal や Azure CLI で構築するのもいいですが、IaC化もいいですね。

### 参考記事

以下の記事を参考にさせていただきました。感謝申し上げます。
[SSH公開鍵認証で接続するまで](https://qiita.com/kazokmr/items/754169cfa996b24fcbf5)