初めてのAlibaba CloudリソースをTerraformで構築する

# この記事で書くこと

先日AlibabaCloudにリソースを構築する機会があったのですが
普段はAlibabaCloudもTerraformもあまり使ったことがなかったため、リソース構築をするまでに行ったことについてまとめます。

# Alibaba Cloudでリソースを構築する際に行ったこと

## STEP1: 使うIaCツールを決める

社内的にIaCが推奨で、AWS上のリソースはCloudFormationを用いて管理・運用しているため
今回AlibabaCloudもリソースはコードで定義し、最終的には構築も自動化することを前提としました。

いくつか選択肢はあるようなのですが今回比較したのは2つです。
1つはチームで利用しているTerraform、もう１つはAlibaba CloudのサービスであるResource Orchestration Service(ROS)です。

▼Terraform
[Alibaba Cloud Provider](https://registry.terraform.io/providers/aliyun/alicloud/latest/docs)

▼ROS
[Resource Orchestration Service](https://www.alibabacloud.com/help/product/28850.htm?spm=a3c0i.133950.1050947630.3.67d63bfcEGqsw3)

ROSはAWSのCloudFormationと同じような概念のサービスで、テンプレートを元にスタックを作成し、リソースを構築・管理することができます。

非常にざっくりですが、TerraformとROSを比較してみました。(2021/2時点)

|判断軸にするか|項目|ROS|Terraform|
|:---|:---|:---|:---|
|-|ファイル言語|JSON、YAML|HCL|
|WANT|サポートしているリソースの数|64|64|
|MUST|プログラムで操作可能か|CLIで操作可能|CLIで操作可能|
|MUST|変更差分を確認できるか|できる|できる|

変更差分の確認等、もう少し細かく見ていくと違いが出てくるかもしれないですが
おおよそはほぼ変わらない印象でした。

直近チームではTerraformを用いて業務で利用している各種ツール(ex.Datadog,PagerDuty)の設定コード化を進めているという背景があり、技術スタックを統一できるという点が魅力的だったため、今回はTerraformを採用することにしました。

## STEP2: Terraformのファイル構成を考える

色々なやり方があると思うのですが、今回は以下のような形で試してみています。

```
$ tree
.
└── terraform
    └── alibaba
        ├── README.md
        ├── main.tf //AlibabaCloudのリソースを定義
        ├── staging
        │   ├── backend.tf //バックエンドを指定
        │   └── terraform.tfvars //variables.tfで定義した変数の値を指定
        └── variables.tf //利用する変数を定義
```

構築するリソースは多くないため、全てmain.tfに記述していくことを想定をしています。

## STEP3: Terraform用のユーザ作成

AWSでCLIからスタック操作を行う際にIAMユーザを作成しクレデンシャル等を用意しますが、
この辺はAlibaba Cloudも同様でした。

https://registry.terraform.io/providers/aliyun/alicloud/latest/docs#testing)

Alibaba CloudではユーザIDの管理やそのアクセス制限を行うResource Access Management(RAM)というサービスがあるため、こちらを利用します。

[RAM の概要](https://www.alibabacloud.com/help/ja/doc-detail/28627.htm)

ユーザはRAMユーザ、権限付与はRAMポリシーをユーザに紐付けるといった形で提供されており、
その他にもRAMロールやユーザを束ねるグループも提供されているため、こちらもAWSのIAMとおおよそ同じような概念かと思います。

terraformを実行するのに必要なので、以下のような形でwebコンソールからユーザ(RAMユーザ)を作成しました。

![スクリーンショット 2021-02-26 午後8.04.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413013/24c13b57-5a6c-2be9-bf39-cccc6ec57596.png)

プログラムによるアクセスでユーザを作成するとアクセスキーが発行されるため、控えておきます。

また、ユーザが作成された後に**Add Peremissions**から権限を追加します。
![スクリーンショット 2021-02-26 午後8.15.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413013/d10f6aa5-665d-1779-0874-61cfea6d897b.png)

Add Permissionsから開くと以下のような画面が表示されるので
利用したいポリシー等を設定します。
![スクリーンショット 2021-02-26 午後8.25.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413013/1bad01ff-cb66-1b07-be79-79f469e0da31.png)

ポリシーはAlibaba Cloudがマネージドで用意しているSystem Policyと自分で用意するCustom Policy、どちらも利用可能でした。

※（Terraformの話とはずれますが）今回エンジニア等開発者のAlibaba Cloudへのログイン環境はSSOで用意するため、それぞれのRAMユーザは作成していませんが、そうでない場合は個別にRAMユーザを作成し、ルートユーザは使わないのが良いかと思います。

## STEP4: Terraform実行に必要な環境変数を設定する

先程作成したユーザの認証情報を環境変数に設定します。

```
ALICLOUD_ACCESS_KEY=STEP2で作成したアクセスキーを設定
ALICLOUD_SECRET_KEY=STEP2で作成したシークレットキーを設定
ALICLOUD_REGION=リソースを構築したいリージョンのID
```

## STEP5: tfstateファイルを管理するリモートバックエンドを用意する

tfstateファイルはterraformが管理しているリソースの現在の状態を表すファイルです。
Terraform planを実行すると、Terraformはtfstateファイルと.tfファイルの内容を比較し、差分を出力します。

このtfstateの保管先等はバックエンドと呼ばれるリソースで管理するのですが、別段設定をしないままだとバックエンドはローカルになり、tfstateもローカルに保存され、読み込まれます。

CIツールと連携させたり、複数人がTerraformの実行を行う場合はローカルだと不便なので
リモートバックエンドを用意して共有できる状態を作るのですが、今回はバックエンドにAlibaba CloudのObject Storage Service(OSS)を利用することにしました。

webコンソールから作成する場合は、必要事項を選択して作成するのですが
AWSのs3と同じような設定項目が設けられていることがわかります。

![スクリーンショット 2021-03-01 午前0.33.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413013/2781b40f-d129-2780-7b90-d8973733b6db.png)

また、OSSの名前はs3同様にOSS全体でユニークである必要がありました。

### STEP6. リソースを構築する

main.tfファイルに構築したいリソースを記述し

```main.tf
terraform {
  required_version = "= 0.14.5"

  backend "oss" {
  }

  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "1.114.1"
    }
  }
}

resource "alicloud_ram_user" "hoge" {
  name         = "hoge"
  display_name = "hoge"
}

```

以下のコマンドで確認・反映を行いました。

```.sh
# 実行内容を確認する
$ terraform plan -var-file='terraform/alibaba/staging/terraform.tfvars' terraform/alibaba

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:

  # alicloud_ram_user.hoge will be created
  + resource "alicloud_ram_user" "hoge" {
      + display_name = "hoge"
      + force        = false
      + id           = (known after apply)
      + name         = "hoge"
    }

Plan: 1 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.

# applyする
$ terraform plan -var-file='terraform/alibaba/staging/terraform.tfvars' terraform/alibaba

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

  # alicloud_ram_user.hoge will be created
  + resource "alicloud_ram_user" "hoge" {
      + display_name = "hoge"
      + force        = false
      + id           = (known after apply)
      + name         = "hoge"
    }

Plan: 1 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

alicloud_ram_user.hoge: Modifying... [id=xxxxxxxx]
alicloud_ram_user.hoge: Modifications complete after 4s [id=xxxxxxxx]
```

# Q. リモートバックエンドのOSSをTerraformで管理できるか

先程webコンソールから作成したOSSですが、Terraformで作成した後にbackendに指定すれば
コード管理できるのではないか？と思ったので試してみました。

### 1. main.tfでOSSを作る

```main.tf
terraform {
  required_version = "= 0.14.5"

  backend "oss" {
  }

  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "1.114.1"
    }
  }
}

resource "alicloud_oss_bucket" "infra" {
  bucket        = "infra"
  acl           = "private"
  storage_class = "Standard"
  versioning {
    status = "Enabled"
  }
}
```

backendがローカルな状態で先述のterraform plan&applyを実行し、
OSSが用意できたらローカルに作成されたtfstateファイルをOSSにアップロードします。

### ２. OSSが用意できたらbackendを再設定する

backend.tfファイルを用意し

```staging/backend.tf
bucket = "infra"
region = "利用したいリージョンのID"
prefix = "terraform/alibaba/" #tfstateファイルをアップロードしたOSSのパス
key    = "terraform.tfstate"  
```

以下コマンドでbackendを再設定することで、今回作成したOSSがバックエンドとして設定されました。

```.sh
$ terraform init -reconfigure -backend-config='terraform/alibaba/staging/backend.tf' terraform/alibaba

Initializing the backend...

Successfully configured the backend "oss"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of aliyun/alicloud from the dependency lock file
- Installing aliyun/alicloud v1.114.1...
- Installed aliyun/alicloud v1.114.1 (signed by a HashiCorp partner, key ID xxxxxxxxxxxxxxx)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/plugins/signing.html

Terraform has been successfully initialized!
```

できるにはできるのですが、シンプルな手順ではないので
割り切ってWebコンソールから作る、というのも１つの手なのではないかと思いました。
(やってから気づいたのですが、Terraform Importを使う方が簡単だったかもしれません...）

# まとめ
- 困ったこと
    - RAMユーザやリモートバックエンドとしてのOSSの構成管理をどうするか
        - terraformの実行に必要で卵が先か鶏が先か状態になる
- わかったこと
    - Alibaba Cloudでも構成管理を行うROSというサービスが提供されていた
    - terraform実行に利用するOSSやRAMユーザをコード管理したい場合はwebコンソールから作成後Terraform Importを使うのが良さそう