【Equinix Metal】Terraformで"クラウドに一番近いベアメタルサーバ"をデプロイ

Terraform、便利ですよね。
パブリッククラウドの構成管理・構築自動化でよく使われるイメージがありますが、**物理ベアメタルサーバをデプロイ**する事もできるんです。
※ベアメタルサーバ=OSを自由に入れられる専有サーバみたいなもんです

本記事では、Terraformを使って**クラウドに一番近いベアメタルサーバ**である**Equinix Metal**上の物理サーバをデプロイしてみます。

Equinix Metalについては以下サイトを参照してみてください。
[Equinix Metal](https://metal.equinix.com/)

## Terraformで物理サーバをデプロイ??

今回利用するEquinix Metalは、**BMaaS(BareMetal as a Service)**を標榜しているプラットフォームです。
ベアメタルの物理サーバやNWをクラウドのようにデプロイ/利用できるよう、**高度にAPI化**された物理基盤をサービスとして提供してます。　
(ユーザがやらなければいけないことは多いですが、自由度はかなり高いです)

このAPI化されている所がポイントで、**Terraformを使ってEquinix MetalのAPIを呼び出す**事で、デプロイ/構成管理が可能になっています。
Terraformが主要パブリッククラウドを操作する際も、このAPI化されている操作をIaCツールとしてラップしている為であり、Equinix Metalでも同様にAPI操作に対応した部分を操作/管理可能という訳です。

## クラウドに一番近いベアメタルサーバ??

Equinixは主要クラウドへの閉域接続に対応した相互接続サービス- **Equinix Fabric**を提供しており、Equinix MetalもEquinix Fabricを経由して主要クラウドと接続することができます。

ただ接続するだけであれば、VPNでもなんでも使えばいいと思うのですが、EquinixのDC上にクラウドサービスがそもそも入居しており、**DC内で閉域接続されている**という点で**【クラウドに一番近いベアメタルサーバ】**と言えるんじゃないかと思ってます。

~~※本記事では"クラウドに一番近い"感はあまり出ていません、ご了承ください。。~~

## 今回やること

前置きはさておき、実際にTerraformでEquinix Metal上のベアメタルサーバをデプロイしていきます。
サーバ上の構成管理もしてみたいので、AWS等パブリッククラウドで利用可能なUserdata(cloud-init)を使い、デプロイ時に簡単なサーバ設定も加えてみます。

* デプロイする場所：シンガポール　
* 課金タイプ：オンデマンドサーバ
* インスタンスサイズ：c3.small.x86
* OS：AlmaLinux

![em_tf_image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1662665/e2a2df7b-3054-bf12-91b8-94412e16479e.jpeg)

## 事前準備
### 1. Equinix Metalへサインアップ、プロジェクトIDを確認する
Equinix Metalのアカウント発行・組織/プロジェクト作成を行います。
※登録時はクレカが必要ですが、タイミング良ければクレジット付与の案内メールが来るかもしれないです。

[EquinixMetal signup](https://console.equinix.com/signup)

プロジェクト作成後、プロジェクト設定画面からプロジェクトIDを確認します。　※後で使います
[MetalDocs Projects](https://metal.equinix.com/developers/docs/accounts/projects/#project-settings)

### 2. Metalポータルで、APIキーを発行する
Equinix Metal登録後は、Terraformで使うためのAPIキーを発行します。
ログイン後、ユーザ画面から**編集許可**を設定したAPIキーを発行してください。
![00.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1662665/e1c1de9d-1784-2039-20cd-f5b127fa928d.jpeg)

なお、現在の仕様では編集許可を付与したAPIキーの操作範囲が細かく制御できない仕様のため、[同一プロジェクト内の他のユーザーが作成したサーバ]を削除できたりします。
APIを使った操作・キー自体の取り扱いには十分ご注意ください。

※今後のロードマップでRBACの導入が予定されています。
[Role Based Access Control (RBAC) / Delegated Administration](https://feedback.equinixmetal.com/platform/p/role-based-access-control-rbac-delegated-administration)


### 3. Metalポータルで、SSHキーを登録する
デプロイしたサーバで利用するためのSSHキー(公開鍵ファイル)を登録します。
以下ガイドに沿って登録してみてください。
[MetalDocs ssh-keys](https://metal.equinix.com/developers/docs/accounts/ssh-keys/#adding-your-ssh-key-to-your-account)

### 4. 作業用PCに、Terraformをインストールする

インストール手順はこちらを参照してみてください。
筆者はWindows10+WSL(ubuntu)にインストールして使っています。
[Install Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli)

※以降の手順も、WSL環境でTerraformを操作するイメージで記載していきます。
他環境の方は適宜読み替えてご利用ください。

## Terraformプロジェクトを作成・デプロイ

任意のディレクトリを作成して、Terraformプロジェクトを作っていきます。

### ファイル構成
Terraformコードを記述していく**main.tf**ファイル、ポータルで発行したAPIキーを記載した**.metal_key**ファイル、プロジェクトIDを記載した**.metal_pjid**ファイルを配置します。
※公式手順では環境変数にAPIキーを設定する案内になってますが、面倒(というか設定したことを忘れる)な為、構成管理コードにベタ書きを控える為、別ファイル参照としています。 
※git管理される場合は.gitignoreファイルへ登録する等、取り扱いにご注意ください。

```
pj_root
 └main.tf  #Terraformコードを書くファイル
　　└.metal_key  #APIキーを記載したファイル
　　└.metal_pjid  #プロジェクトIDを記載したファイル
```

なお、再利用可能にする場合はモジュール化すると便利だったりします。
※モジュール化については機会があれば別記事を書きます。

### main.tf
以下内容のコードを記述・保存します。
各設定項目の詳細についてはEquinix MetalのTerraformプロバイダードキュメントを参照してみてください。
[EquinixMetal Provider](https://registry.terraform.io/providers/equinix/metal/latest/docs)

なお、ただリソースを記述するだけでは**デプロイ完了後にサーバの情報が分かりづらい**ので、Outputsでサーバ情報を出力する**親切設計**となっております(ｷﾘｯ)

``` main.tf
terraform {
  required_providers {
    metal = {
      source  = "equinix/metal"
      version = "3.0.0"
    }
  }
}

provider "metal" {
  auth_token = file("./metal_token")
}

resource "metal_device" "device" {
  hostname         = "test.sg.c3.small.x86-tf" # ホスト名
  plan             = "c3.small.x86" #インスタンスサイズ
  metro            = "SG" #デプロイするロケーション
  operating_system = "alma_8" #インストールOS(AlmaLinux)
  billing_cycle    = "hourly" #オンデマンドサーバを指定
  project_id       = file("./metal_pjid")
  ip_address {
    type = "private_ipv4"
    cidr = "30"
  }
  ip_address {
    type = "public_ipv4"
    cidr = "30"
  }

  # タイムゾーン・言語設定・Apacheインストール/起動
  user_data = <<DATA
    #cloud-config
    timezone: Asia/Tokyo
    locale: en_US.utf8
    runcmd:
      - dnf install httpd -y
      - systemctl start httpd
  DATA
}


# サーバ情報出力用
output "device_info" {
  value = {
    "hostname"              = metal_device.device.hostname,
    "os"                    = metal_device.device.operating_system,
    "plan"                  = metal_device.device.plan,
    "ipaddress_public_ipv4" = metal_device.device.access_public_ipv4,
    "created"               = metal_device.device.created,
    "ssh_cmd_example"       = format("ssh root@%s -i [ssh_key]", metal_device.device.network[0].address),
    "SOS_cmd_example"       = format("ssh %s@sos.%s.platformequinix.com -i [ssh_key]", metal_device.device.id, metal_device.device.deployed_facility),
    "root_PW"               = metal_device.device.root_password
  }
```

### metal_key、metal_pjid

参考までに、こんな感じの桁数/フォーマットになります　※値はダミーです

```　.metal_key
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

```　.metal_pjid
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```

## デプロイしてみる
### Terraform デプロイ開始

ファイルの準備ができたら、いよいよ実行します。
初期化(init)・構成確認(plan)・実行(apply)の順で操作します。

```
$ terraform init                  

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of equinix/metal from the dependency lock file
- Using previously-installed equinix/metal v3.0.0

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.

```
```
$ terraform plan

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:

  # metal_device.device will be created
  + resource "metal_device" "device" {
      + access_private_ipv4              = (known after apply)
      + access_public_ipv4               = (known after apply)
      + access_public_ipv6               = (known after apply)
      + always_pxe                       = false
      + billing_cycle                    = "hourly"
      + created                          = (known after apply)
      + deployed_facility                = (known after apply)
      + deployed_hardware_reservation_id = (known after apply)
      + force_detach_volumes             = false
      + hostname                         = "test.sg.c3.small.x86-tf"
      + id                               = (known after apply)
      + locked                           = (known after apply)
      + metro                            = "ty"
      + network                          = (known after apply)
      + network_type                     = (known after apply)
      + operating_system                 = "alma_8"
      + plan                             = "c3.small.x86"
      + ports                            = (known after apply)
      + project_id                       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      + root_password                    = (sensitive value)
      + ssh_key_ids                      = (known after apply)
      + state                            = (known after apply)
      + updated                          = (known after apply)
      + user_data                        = (sensitive value)
      + wait_for_reservation_deprovision = false

      + ip_address {
          + cidr = 30
          + type = "private_ipv4"
        }
      + ip_address {
          + cidr = 30
          + type = "public_ipv4"
        }
    }

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

Changes to Outputs:
  + device_info = {
      + SOS_cmd_example       = (known after apply)
      + created               = (known after apply)
      + hostname              = "test.sg.c3.small.x86-tf"
      + ipaddress_public_ipv4 = (known after apply)
      + os                    = "alma_8"
      + plan                  = "c3.small.x86"
      + root_PW               = (known after apply)
      + ssh_cmd_example       = (known after apply)
    }

------------------------------------------------------------------------

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.
```

```
$ terraform apply --auto-approve
metal_device.device: Creating...
metal_device.device: Still creating... [10s elapsed]
metal_device.device: Still creating... [20s elapsed]
metal_device.device: Still creating... [30s elapsed]
(中略)

```
実行後、Metalポータル上でも以下キャプチャのようにデプロイ処理が走り始めます。

![01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1662665/6c74edf5-70dc-6ff3-cef7-0edb3bbd7c24.jpeg)

### Terraform デプロイ完了

デプロイ完了すると以下のような出力が表示されます。

```
(中略)
metal_device.device: Still creating... [10m40s elapsed]
metal_device.device: Creation complete after 10m48s [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]

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

Outputs:

device_info = {
  "SOS_cmd_example" = "ssh xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@sos.sg1.platformequinix.com -i [ssh_key]"
  "created" = "2021-12-22T14:44:47Z"
  "hostname" = "test.sg.c3.small.x86-tf"
  "ipaddress_public_ipv4" = "[サーバのパブリックIPアドレス]"
  "os" = "alma_8"
  "plan" = "c3.small.x86"
  "root_PW" = "XXXXXXXXXX"
  "ssh_cmd_example" = "ssh root@[サーバのパブリックIPアドレス] -i [ssh_key]"
}
```
無事にデプロイされたので、サーバに乗り込んで確認してみます。

## デプロイ後の動作確認

### サーバへSSHログイン

サーバ情報出力に記載のある、sshコマンドサンプルでログインします。
[ssh_key]は、事前準備で設定した公開鍵に対応した秘密鍵を指定します。

``` 
# 通常のSSHログインの場合
ssh root@[サーバのパブリックIPアドレス] -i [ssh_key] 

# SOSログインの場合
ssh xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@sos.sg1.platformequinix.com -i [ssh_key]
```
SOSはちょっとおもしろ機能で、**サーバのシリアルコンソールにSSHからログインする**事ができます。
使い方はこちらのガイドを参照してみてください。 
[MetalDocs SOS](https://metal.equinix.com/developers/docs/resilience-recovery/serial-over-ssh/)

### サーバの状態確認
通常のSSHログイン後、設定状態を確認してみます。
諸々設定できていて、インストールしたApacheも起動してます。
※SSH接続しただけでは実感湧きませんが、**物理サーバをデプロイしてます**。

```
[root@test ~]# hostname
test.sg.c3.small.x86-tf
[root@test ~]# cat /etc/redhat-release 
AlmaLinux release 8.5 (Arctic Sphynx)
[root@test ~]# date
Thu Dec 22 10:24:46 JST 2021
[root@test ~]# localectl
   System Locale: LANG=en_US.utf8
       VC Keymap: us
      X11 Layout: us
[root@test ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2021-12-23 00:23:41 JST; 3min 38s ago
     Docs: man:httpd.service(8)
```

### ブラウザからApacheスタートページを確認
ブラウザからサーバのパブリックIPアドレスにアクセスすると、Apacheスタートページが表示されます。
こちらも問題ないようです。
![02.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1662665/950ec7dc-1210-2813-f807-d66e45f25d41.jpeg)

## 後片付け
### Terraformでサーバ削除

うまくデプロイできて気が済んだので、リソース削除(destroy)します。

```
$ terraform destroy --auto-approve
metal_device.device: Destroying... [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
metal_device.device: Destruction complete after 4s

Destroy complete! Resources: 1 destroyed.
```

## まとめ
TerraformでEquinix Metalのベアメタルサーバをデプロイしてみました。
使用感は正直、**パブリッククラウドでVM建てるのとほぼ同じ**。なのに**物理サーバをデプロイしている**という**不思議体験**でしたね。
今後もTerraformで面白デプロイを試してみたいと思います。