Alibaba Cloud + Terraformでマルチクラウド環境を構築してみる

アリババクラウドのパートナーのAPコミュニケーションズ様より、非常に興味深いレポートをいただきましたので共有させていただきます。本編は[こちら]のサイトでご覧ください。（https://techblog.ap-com.co.jp/entry/2020/06/08/165652）

[techblogAPCについて（はじめにお読み下さい） ](https://techblog.ap-com.co.jp/info)

#はじめに
こんにちは、IaC技術推進部の山路と申します。本記事では、Alibaba Cloudに関する簡単な紹介と、Alibaba Cloudを利用する場合のユースケースの一つについて紹介します。また、ユースケースに関連して、マルチクラウド構成でAlibaba Cloudを利用することを想定し、Alibaba CloudとAzureをVPNで接続する環境を、Terraformを用いて構築する例も紹介します。

#Alibaba Cloudとは
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/eae34967-5229-c06f-dc28-fcb9b3427102.png)

[Alibaba Cloud]()は、アリババグループの運営しているクラウドプロバイダーサービスです。アリババグループではEコマースを中心とした多数の企業が含まれており、日本でもなじみのあるものといえばAliPayがまず挙げられるかと思います。元々はAlibaba Groupで利用していたプライベートクラウド環境を、サービスとしてパブリックに利用できるようにしたものであり、Alibaba Groupで利用・開発された巨大なインフラストラクチャサービスを利用することができます。

#Alibaba Cloudの特徴
Alibaba Cloudには以下に記載するように様々な特徴が見られます。

- インフラストラクチャ
    - 中国国内のリージョンが多い: 本記事を書いている時点で、Alibaba Cloudで利用できる 
全21リージョン中9リージョンが中国国内になる（ただし香港はグローバルリージョン扱いとなり 
ます）※1
    - 世界最大規模のEコマース取引日の通信に耐えうる強固な基盤: 中国で記念日にあたる「独身の日」には、2019年度の売り上げは4.1兆円、ピークトランザクションは約54万/秒という大量のアクセスが発生
- サービス
    - CDNノードは2800以上あり、そのうち中国本土には2300以上のノードが存在
    - 他のクラウドベンダーには見られない独自のサービスを展開
        - Anti-DDoS Premium: Alibaba Cloud以外も適用対象とするAnti-DDoSサービス
        - Image Search: 画像データを与えるだけで商品画像検索を実現できるサービス
- ビジネス
   - IaaSプロバイダーとして世界第3位: AWS、Azureに続いて世界第3位のシェアを持つIaaSプロバイダーです。なおアジア太平洋地域（APAC）では第1位のシェアとなります。

※参考リンク：

- [Alibaba Cloud CDNとは（中国語）](https://help.aliyun.com/document_detail/69890.html?spm=a2c4g.11174283.2.6.4dbc7035Is46jJ)

- [Alibaba Japan - アリババクラウドが3年連続で、IaaSプロバイダーとして世界で3位、APAC市場シェアでは1位に、米ガートナー調べ](https://www.alibaba.co.jp/news/2020/04/3iaas3apac1.html)

※1：Alibaba Cloudのドキュメントなどでは若干記載が異なっていることがありますが、Alibaba CloudのCloud Shellからコマンドを実行すると、以下のように出力されます。

```
shell@Alicloud:~$ aliyun ecs DescribeRegions | grep RegionId
                                "RegionId": "cn-qingdao"      # 中国（青島）
                                "RegionId": "cn-beijing"      # 中国（北京）
                                "RegionId": "cn-zhangjiakou"  # 中国（張家口）
                                "RegionId": "cn-huhehaote"    # 中国（フフホト）
                                "RegionId": "cn-hangzhou"     # 中国（杭州）
                                "RegionId": "cn-shanghai"     # 中国（上海）
                                "RegionId": "cn-shenzhen"     # 中国（深セン）
                                "RegionId": "cn-heyuan"       # 中国（河源）
                                "RegionId": "cn-chengdu"      # 中国（成都）
                                "RegionId": "cn-hongkong"     # 中国（香港）
                                "RegionId": "ap-northeast-1"  # 日本（東京）
                                "RegionId": "ap-southeast-1"  # シンガポール
                                "RegionId": "ap-southeast-2"  # オーストラリア（シドニー）
                                "RegionId": "ap-southeast-3"  # マレーシア（クアラルンプール）
                                "RegionId": "ap-southeast-5"  # インドネシア（ジャカルタ）
                                "RegionId": "ap-south-1"      # インド（ムンバイ）
                                "RegionId": "us-east-1"       # 米国（バージニア）
                                "RegionId": "us-west-1"       # 米国（シリコンバレー）
                                "RegionId": "eu-west-1"       # イギリス（ロンドン）
                                "RegionId": "me-east-1"       # UAE（ドバイ）
                                "RegionId": "eu-central-1"    # ドイツ（フランクフルト）
shell@Alicloud:~$ 
```


#Alibaba Cloudを利用するケース：中国向けビジネス展開での利用
上記の特徴を眺めたとき、真っ先に浮かぶユースケースとしては中国国内へのビジネス展開を行う際に利用する場合が挙げられるかと思います。

中国向けにビジネスを展開する場合、障壁となる要素がいくつか存在します。例えば中国国内でWebサイトを開設する場合は[ICP（Internet Content Provider）](https://jp.alibabacloud.com/solutions/icp)と呼ばれる中国特有のライセンスを取得する必要があります。このライセンスを取得するには中国国内企業が発行することのできる許可証が必要となり、現地法人や中国企業との提携を結ぶ必要があります。

また、日中間でのネットワークが不安定であるため、日中間で専用線を設置することを検討する必要もあります。

※参考リンク：

- [Alibaba Cloud - ICP登録とは](https://jp.alibabacloud.com/solutions/icp)

- [SB Cloud - 中国向けWebサイトの基礎知識①ICPライセンスって何？](https://www.sbcloud.co.jp/entry/column/icp-kiso/)

このような障壁も影響し、多くのクラウドベンダーでは中国向けビジネスを展開することが難しくなってしまいます。代表的な障壁としては以下のようなものが挙げられます。

1. サーバー：多くのベンダーは中国国内で利用できるリージョンが存在しません（香港は除く）。また中国国内にリージョンを所有するベンダーは、中国企業と提携することでサービスを提供しており、中国専用のアカウントや管理コンソール画面から操作する必要があります。

2. ネットワーク：中国国内外での通信は基本的にインターネット経由となります。例えば中国リージョンと日本リージョンとの仮想ネットワーク間通信を行う場合、仮想ネットワーク間でのピアリング接続は利用することができません。また専用線を利用する場合も、利用できる専用線サービスに制限がある場合があります。

3. 法律：ICPライセンスの申請に対するサポートは多くのベンダーでは実施しておらず、利用者が別途申請する必要があります。

一方、Alibaba Cloudを利用することで、上記障害の多くを克服することが可能になります。

1. サーバー：中国国内に多くのリージョンを所有しています。また中国国内・国外のリージョンを共通のアカウントで利用することができます。

2. ネットワーク：Alibaba Cloudにも仮想ネットワーク間のピアリング機能はまだ備わっていませんが、CEN（Cloud Enterprise Network）の機能を利用すると、仮想ネットワーク間通信をAlibaba Cloudのネットワークインフラを利用して実現することができます。またExpress Connectという専用線サービスも利用することができます。

3. 法律：Alibaba Cloudには[ICPライセンスの登録をサポートする](https://www.alibabacloud.com/ja/icp)サービスが存在します。

#Alibaba Cloudを含むマルチクラウド環境の構成
上記の通り、中国向けのビジネスを展開する場合、Alibaba Cloudは有効な選択肢の一つとなりそうです。しかし一方で、多くのユーザーや企業は既に何らかの形でクラウドを利用しているため、既存のクラウド環境と**Alibaba Cloud**とを組み合わせて利用したいというケースが多いと予想されます。

このように複数のクラウド環境を利用する、いわゆるマルチクラウドを扱う上では、単一のクラウド環境を利用する場合と比べ、様々な問題が発生します。これには各クラウドの仕様の把握やクラウド間ネットワークの設計に加え、システムの構成管理に対しても考慮が必要となります。

例えばシステムの構成管理については、管理対象のクラウドが複数になることで運用が煩雑になります。新しいリソースを作成する際に管理ポータル画面から処理を行うと、2つ以上のWebブラウザ画面を行き来しながら作業するため、作業ミスの発生リスクも高まります。また利用するクラウドの仕様を把握し、クラウド間の連携についての知識も必要となるため、プロジェクト内のメンバー間で技術的な習熟度が異なる場合、設計・構築時の考慮漏れや作業ミスが発生する可能性も上昇します。後者に関してはマルチクラウドに限らない問題ですが、複数のクラウドが管理対象となれば、設計漏れや作業ミスなどが発生する可能性が高くなることは容易に想像できます。

このような構成管理・運用の問題を軽減するため、マルチクラウドに対応した構成管理ツールであるTerraformを利用し、Alibaba Cloudを含むマルチクラウド環境の構築を試してみました。

※参考リンク：

[Alibaba Cloud - マルチクラウド環境におけるパートナーとシナリオの選択](https://jp.alibabacloud.com/whitepaper/choosing-partners-and-scenarios-in-a-multi-cloud-world_213)

#Terraformとは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/63255b97-3ac5-935e-8b90-5bb15198a548.png)

[Terraform](https://www.terraform.io/)はHashiCorp社が開発しているインフラストラクチャの構成管理・リソース作成ツールです。Terraformではtfファイルに記載された内容に基づいて、クラウドリソースやアプリケーション環境などの管理を行うことができます。Terraformを利用することで、インフラをコードとして定義することが可能となり、IaC（Infrastructure as Code）を実現する助けにもなります。

マルチクラウドの構成管理にTerraformを利用することで得られるメリットの一つは、複数のクラウドにまたがるインフラの定義を**Terraform**に集約することができる点です。Terraformは現時点でも[100以上のクラウドベンダーやサービスに対応](https://www.terraform.io/docs/providers/index.html)しており、これらをtfファイル上で同じようなリソースとして扱うことができます。これにより、複数のクラウドを構築・管理するうえでの相互運用性に役立つことが期待できます。

また、TerraformのようなIaCツールを利用することで得られるメリットの一つに、メンバー間の技術習熟度の差を埋めることに役立つ点があります。Terraformでは基本的にtfファイルで定義したものしかリソースが作成されないため、tfファイルの定義が正しければ想定通りのシステムが構成されることが期待できます。またレビューの際も、レビューの対象となるものは基本的にtfファイルの内容に絞られるので、設計・構築の考慮漏れやミスの発生する確率を軽減することができます。さらにTerraformでリソースを作成する場合は`terraform apply`という共通のコマンドを実行するだけなので、構築時の作業ミスが発生する余地はないと言えます。

#TerraformとAlibaba Cloudとの関係
Terraformは複数のクラウドプロバイダーやアプリケーションに対応しており、[Alibaba Cloudにも対応しています。](https://www.terraform.io/docs/providers/alicloud/index.html)またAlibaba Cloudの公式ドキュメントではTerraformを利用する例も紹介されていたり、[Terraform Module Web GUI](https://api.aliyun.com/#/cli?tool=Terraform)という機能が備わっています。後者はAlibaba CloudのWebコンソール画面からTerraformを操作し、作成するリソースを定義することができる機能になります。このように、Alibaba CloudとTerraformとは連携が強く、Alibaba Cloudリソースを管理するうえでTerraformが利用できることが期待できます。

※Terraform Module Web GUIの画面の例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/e84387a9-aa09-1f2b-fa69-d640da508218.png)
このような背景から、Alibaba Cloudを含むマルチクラウド環境を管理することを考えた場合、Terraformを利用することは有力な選択肢の一つになるのではないかと考えています。

※参考リンク：

- [Alibaba Cloud - Terraformの概要](https://jp.alibabacloud.com/help/doc-detail/91285.htm)

- [Alibaba Cloud - Terraform Module Web GUIページ](https://api.aliyun.com/#/cli?tool=Terraform)

#Terraformを用いたマルチクラウド環境の構築
ここからはTerraformを利用し、Alibaba Cloudを含むマルチクラウド環境の構築について紹介します。ここでは、Alibaba Cloudのみを構築する場合、Alibaba Cloud＋AzureをVPNで接続した環境を構築する場合の2つを紹介します。

#検証環境情報
- 操作端末: Windows 10
- バージョン情報
    - Terraform version: v0.12.18
       - provider.alicloud: v1.84.0
       - provider.azurerm: v2.12.0
- その他情報
   - 利用リージョン：Alibaba Cloud・Azureどちらも日本

#Alibaba Cloud環境をTerraformで作成する
####構築環境情報####
まずはAlibaba Cloudを利用した環境の構築例です。ここではシンプルに、以下のリソースを含むような環境を構築します。

| リソース名            | 用途     | 個数   |
|:-------------------|:---------|------------------------------:|
| VPC                | Alibaba Cloud上で利用する仮想ネットワーク | 1 |
| Vswitch            | 仮想ネットワーク上で分割して利用するサブネット | 1 |
| ECS                | 仮想マシン（今回はCentOSを利用） | 1 |
| Security Group     | VPCに対して適用し、IPアドレス単位で通信制御を行う | 1 |
| Security Group Rule| Security Groupで設定する各ルール (ローカルのIPアドレスの Inboundのみを許可) | 1 |
※検証環境構成図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/ba4da070-d54f-7e19-caa4-56e8081f9c17.png)

#デプロイ作業
それでは実際の構築手順になります。今回は以下のようなtfファイルを用いてリソースの作成を行います。

```
# providerの指定
provider "alicloud" {
  access_key = "<Alibaba CloudのAccessKeyId>"
  secret_key = "<Alibaba CloudのAccessKeySecret>"
  region     = "ap-northeast-1" // 日本（東京）
}
 
# VPCの定義
resource "alicloud_vpc" "vpc" {
  name       = "terraform_test_vpc"
  cidr_block = "10.0.0.0/16"
}
 
# Vswitchの定義
resource "alicloud_vswitch" "vsw" {
  vpc_id            = alicloud_vpc.vpc.id
  cidr_block        = "10.0.0.0/24"
  availability_zone = "ap-northeast-1a"
  depends_on        = [alicloud_vpc.vpc]
}
 
# Security Groupの定義
resource "alicloud_security_group" "sg" {
  name       = "terraform_test_sg"
  vpc_id     = alicloud_vpc.vpc.id
  depends_on = [alicloud_vswitch.vsw]
}
 
# Security Groupで設定するルールの定義（ローカルからのアクセスを許可）
resource "alicloud_security_group_rule" "allow_local" {
  type              = "ingress"
  ip_protocol       = "all"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "*"
  priority          = 100
  security_group_id = alicloud_security_group.sg.id
  cidr_ip           = "<Local IP Address>" // ローカルのIPアドレスを指定
  depends_on        = [alicloud_security_group.sg]
}

# ECSの定義 
resource "alicloud_instance" "ecs" {
  instance_name              = "terraform_test_ecs"
  image_id                   = "centos_7_7_x64_20G_alibase_20200220.vhd"
  instance_type              = "ecs.sn1.medium"
  system_disk_category       = "cloud_ssd"
  security_groups            = [alicloud_security_group.sg.id]
  vswitch_id                 = alicloud_vswitch.vsw.id
  internet_max_bandwidth_out = 10  //0以上の値を指定するとECSにパブリックIPが付与される
  host_name                  = "terraformtest"
  password                   = "<パスワードを指定>"
}
 
#### SSH key pairを利用する場合は、以下のリソースを作成 ####
#### 利用時はalicloud_instance.ecs.passwordを削除する ####

/* 
resource "alicloud_key_pair" "key" {
  key_name   = "my_public_key"
  public_key = "<公開鍵ファイル情報>"
}
 
resource "alicloud_key_pair_attachment" "attach" {
  key_name     = alicloud_key_pair.key.id
  instance_ids = [alicloud_instance.ecs.id]
}
*/

# リソース作成後の出力を定義
output "instance_ip_addr" {
  value = alicloud_instance.ecs.public_ip
}
```

※参考リンク：

- [Terraform - alicloud_instance](https://www.terraform.io/docs/providers/alicloud/r/instance.html)

- [Terraform - alicloud_key_pair](https://www.terraform.io/docs/providers/alicloud/r/key_pair.html)

- [Terraform - alicloud_key_pair_attachment](https://www.terraform.io/docs/providers/alicloud/r/key_pair_attachment.html)

上記tfファイルを用意したのち、`terraform apply`コマンドを実行して作成を開始します。今回は`output`としてECSインスタンスに付与されたパブリックIPアドレスを指定し、リソースの作成完了後に表示されるアドレスを指定してSSH接続を行えるかの確認を行いました。

```
# terraform initによる初期化

C:\terraform_article\alibaba>terraform init

Initializing the backend...

（中略）

* provider.alicloud: version = "~> 1.84"

Terraform has been successfully initialized!

C:\terraform_article\alibaba>


# terraform validateによるtfファイル定義の確認

C:\terraform_article\alibaba>terraform validate
Success! The configuration is valid.


# terraform planによる作成リソースの確認
C:\terraform_article\alibaba>terraform plan

（中略）

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


# terraform apply 


C:\terraform_article\alibaba>terraform apply

（中略）

Plan: 5 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: 5 added, 0 changed, 0 destroyed.

Outputs:

instance_ip_addr = 47.74.39.220
```

上記のようにリソース柵瀬宇井が完了したので、`Outputs:`に出力されたIPアドレスにSSH接続を行います。

```
# SSH接続
$ ssh root@47.74.39.220
The authenticity of host '47.74.39.220 (47.74.39.220)' can't be established.
ECDSA key fingerprint is SHA256:q+lHAUGI1qUsDvYvHfIFLtosQSW9w035E7cMN4G7Suc.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '47.74.39.220' (ECDSA) to the list of known hosts.
root@47.74.39.220's password:

Welcome to Alibaba Cloud Elastic Compute Service !

[root@terraformtest ~]# ip a
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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:00:1c:97 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.140/24 brd 10.0.0.255 scope global dynamic eth0
       valid_lft 315359930sec preferred_lft 315359930sec

[root@terraformtest ~]# lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 7.7.1908 (Core)
Release:        7.7.1908
Codename:       Core
```
ECSへのSSHによるアクセスが確認できたので、作成したリソースを削除します。

```
# terraform destroy
C:\terraform_article\alibaba>terraform destroy

（中略）

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes  # yesを入力

（中略）

Destroy complete! Resources: 5 destroyed.
```

#Alibaba Cloud＋Azure環境をTerraformで作成する
####構築環境情報
次にAlibaba Cloudに加えてAzureの環境を用意する場合を紹介します。今回はVPN Gatewayを双方の環境に用意し、接続するような構成とします。最終的には各クラウド上の仮想マシンから対向へのPing疎通が確認できることをゴールとします。

| プロバイダー            | リソース名 | 用途     | 個数              |
|:---------------------|:---------|:--------|:-----------------|
| Alibaba Cloud        | -        | -       | -                |
|                      | VPC | Alibaba Cloud上で利用する仮想ネットワーク | 1 |
| |Vswitch              | 仮想ネットワークのアドレス範囲を分割して利用する | 1 |
|| ECS                  | Elastic Compute Service。仮想マシン | 1 |
| |Security Group       | VPCに対して適用し、IPアドレス単位で通信制御を行う | 1 |
| |Security Group Rule  | Security Groupで設定する各ルール。ローカルからのSSHログイン、VPN対向からのInboud通信を許可 | 2 |
| |VPN Gateway | VPN接続を実現するAlibaba Cloudのゲートウェイ | 1 |
| |Customer Gateway | VPN接続先情報を登録するリソース | 1 |
| |VPN Connection | VPN接続を定義するリソース | 1 |
| |VPN Route Entry | 対向側へのルーティングを実現するために利用 | 1 |
| Azure | - | - | - |
| |Resource Group | Azure上で作成するリソースを管理するグループ | 1 |
| |Virtual Network | Azure上で利用する仮想ネットワーク | 1 |
|| Subnet | 仮想ネットワークのアドレスを分割して利用。AzureはGatewaySubnetというVPN Gateway専用のサブネットが必要 | 2 |
| |Network Security Group | サブネットまたはNicに適用し、IPアドレス単位で通信制御を行う | 1 |
| |Public IP | パブリックIPアドレス。VMとVPN Gatewayに割りあてる | 2 |
| |NIC | VMに割りあてるNic | 1 |
| |Virtual Machine | 仮想マシン | 1 |
| |Virtual Network Gateway | VPN接続に利用するAzure側のゲートウェイ | 1 |
| |Local Network Gateway | VPN接続先の情報を登録するリソース | 1 |
| |Virtual Network Gateway Connection | VPN接続の定義するリソース | 1 |
※検証環境構成図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/190acde5-adc3-6e21-901c-3ad2c5bc934a.png)

#デプロイ作業
今回は以下の3つのtfファイルを利用して、上記リソースの作成を行います。少々長いのでファイルの内容は折りたたんでおります。

<details><summary>tfファイル</summary><div>

```
# main.tf
# Azure Resource Groupの定義
resource "azurerm_resource_group" "rg" {
  name     = var.azure_resource_group_name
  location = var.azure_location
}

# Azure 仮想ネットワークの定義
resource "azurerm_virtual_network" "vnet" {
  name                = var.azure_vnet_name
  address_space       = [var.azure_vnet_addr]
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  depends_on          = [azurerm_resource_group.rg]
}

# Azure サブネットの定義
resource "azurerm_subnet" "subnet" {
  name                 = var.azure_subnet_name
  resource_group_name  = var.azure_resource_group_name
  virtual_network_name = var.azure_vnet_name
  address_prefixes     = [var.azure_subnet_addr]
  depends_on           = [azurerm_virtual_network.vnet]
}

# Azure NSGと通信ルールの定義
resource "azurerm_network_security_group" "nsg" {
  name                = var.azure_sg_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  depends_on          = [azurerm_subnet.subnet]

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = var.sg_ssh_source_addr
    destination_address_prefix = "*"
  }
}

# Azure NSGとサブネットの紐づけを定義
resource "azurerm_subnet_network_security_group_association" "nsg_subnet_association" {
  subnet_id                 = azurerm_subnet.subnet.id
  network_security_group_id = azurerm_network_security_group.nsg.id
  depends_on                = [azurerm_network_security_group.nsg, azurerm_subnet.subnet]
}

# Azure 仮想マシン用パブリックIPの定義
resource "azurerm_public_ip" "publicip" {
  name                = var.azure_pip_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  allocation_method   = "Dynamic"
  depends_on          = [azurerm_resource_group.rg]
}

# Azure ネットワークインターフェイスの定義
resource "azurerm_network_interface" "nic" {
  name                = var.azure_nic_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  depends_on          = [azurerm_public_ip.publicip]

  ip_configuration {
    name                          = var.azure_ipconfig_name
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Azure 仮想マシンの定義
resource "azurerm_virtual_machine" "vm" {
  name                  = var.azure_vm_name
  location              = var.azure_location
  resource_group_name   = var.azure_resource_group_name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = var.azure_vm_size
  depends_on            = [azurerm_network_interface.nic]

  storage_os_disk {
    name              = var.azure_os_disk_name
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = var.azure_os_disk_type
  }

  storage_image_reference {
    publisher = var.azure_os_publisher
    offer     = var.azure_os_offer
    sku       = var.azure_os_sku
    version   = var.azure_os_version
  }

  os_profile {
    computer_name  = var.azure_vm_hostname
    admin_username = var.azure_vm_admin_name
    admin_password = var.azure_vm_admin_password
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}

# SSH key pairを利用する場合は、azurerm_virtual_machine.vm.os_profile_linux_configを変更
# 利用時はazurerm_virtual_machine.vm.os_profile.admin_passwordを削除する
/*
  os_profile_linux_config {
    disable_password_authentication = true
    ssh_keys = var.ssh_keys
  }
*/

# Alibaba Cloud 仮想ネットワークの定義
resource "alicloud_vpc" "vpc" {
  name       = var.alibaba_vpc_name
  cidr_block = var.alibaba_vpc_cidr
}

# Alibaba Cloud Vswitchの定義
resource "alicloud_vswitch" "vsw" {
  vpc_id            = alicloud_vpc.vpc.id
  cidr_block        = var.alibaba_vswitch_cidr
  availability_zone = var.alibaba_zone
  depends_on        = [alicloud_vpc.vpc]
}

# Alibaba Cloud セキュリティグループの定義
resource "alicloud_security_group" "sg" {
  name       = var.alibaba_sg_name
  vpc_id     = alicloud_vpc.vpc.id
  depends_on = [alicloud_vswitch.vsw]
}

# Alibaba Cloud セキュリティグループルールの定義
resource "alicloud_security_group_rule" "allow_local" {
  type              = "ingress"
  ip_protocol       = "all"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "*"
  priority          = 100
  security_group_id = alicloud_security_group.sg.id
  cidr_ip           = var.sg_ssh_source_addr
  depends_on        = [alicloud_security_group.sg]
}

# Alibaba Cloud 仮想マシンの定義
resource "alicloud_instance" "ecs" {
  instance_name              = var.alibaba_ecs_name
  image_id                   = var.alibaba_image_id
  instance_type              = var.alibaba_ecs_instance_type
  system_disk_category       = var.alibaba_system_disk_type
  security_groups            = [alicloud_security_group.sg.id]
  vswitch_id                 = alicloud_vswitch.vsw.id
  internet_max_bandwidth_out = 10
  host_name                  = var.alibaba_ecs_hostname
  password                   = var.alibaba_ecs_password
}

# SSH key pairを利用する場合は、以下のリソースを作成
# 利用時はalicloud_instance.ecs.passwordを削除する
/*
resource "alicloud_key_pair" "key" {
  key_name   = "my_public_key"
  public_key = "<公開鍵ファイル情報>"
}
resource "alicloud_key_pair_attachment" "attach" {
  key_name     = alicloud_key_pair.key.id
  instance_ids = [alicloud_instance.ecs.id]
}
*/

# Azure Virtual Network Gateway用パブリックIPアドレスの定義
resource "azurerm_public_ip" "vpngw_ip" {
  name                = var.azure_vpngw_ip_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  allocation_method = "Dynamic"
  depends_on = [azurerm_resource_group.rg]
}

# Azure Virtual Network Gateway用サブネットの定義
resource "azurerm_subnet" "gw_subnet" {
  name                 = "GatewaySubnet"
  resource_group_name  = var.azure_resource_group_name
  virtual_network_name = var.azure_vnet_name
  address_prefixes     = [var.azure_gw_subnet_address_prefix]
  depends_on           = [azurerm_virtual_network.vnet]
}

# Azure Virtual Network Gatewayの定義
resource "azurerm_virtual_network_gateway" "vgw" {
  name                = var.azure_vpngw_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  type     = "Vpn"
  vpn_type = "RouteBased"
  active_active = false
  enable_bgp    = false
  sku           = var.azure_vpngw_sku
  depends_on = [azurerm_subnet.gw_subnet, azurerm_public_ip.vpngw_ip]

  ip_configuration {
    name                          = var.azure_vpngw_ipconfig_name
    public_ip_address_id          = azurerm_public_ip.vpngw_ip.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.gw_subnet.id
  }
}

# Azure Virtual Network Gateway パブリックIPアドレスのデータを定義
# Alibaba Cloudリソースから参照するために利用
data "azurerm_public_ip" "azure_vpngw_ip" {
  name                = azurerm_public_ip.vpngw_ip.name
  resource_group_name = var.azure_resource_group_name
}

# Alibaba VPN Gatewayの定義
resource "alicloud_vpn_gateway" "vpngw" {
  name                 = var.alibaba_vpngw_name
  vpc_id               = alicloud_vpc.vpc.id
  bandwidth            = "10"
  enable_ssl           = true
  instance_charge_type = "PostPaid"
  vswitch_id           = alicloud_vswitch.vsw.id
  depends_on           = [azurerm_virtual_network_gateway.vgw]
}

# Azure Local Network Gatewayの定義
resource "azurerm_local_network_gateway" "lgw" {
  name                = var.azure_localgw_name
  resource_group_name = var.azure_resource_group_name
  location            = var.azure_location
  gateway_address     = alicloud_vpn_gateway.vpngw.internet_ip
  address_space       = [var.alibaba_vswitch_cidr]
  depends_on          = [alicloud_vpn_gateway.vpngw]
}

# Azure Virtual Network Gateway Connectionの定義
resource "azurerm_virtual_network_gateway_connection" "vgw_connect" {
  name                = var.azure_vpngw_connect_name
  location            = var.azure_location
  resource_group_name = var.azure_resource_group_name
  type                       = "IPsec"
  virtual_network_gateway_id = azurerm_virtual_network_gateway.vgw.id
  local_network_gateway_id   = azurerm_local_network_gateway.lgw.id
  shared_key = var.vpngw_connect_shared_key
  depends_on = [azurerm_local_network_gateway.lgw]
}

# Alibaba Cloud Customer Gatewayの定義
resource "alicloud_vpn_customer_gateway" "cgw" {
  name       = var.alibaba_customergw_name
  ip_address = data.azurerm_public_ip.azure_vpngw_ip.ip_address
  depends_on = [azurerm_virtual_network_gateway_connection.vgw_connect]
}

# Alibaba Cloud VPN Connectionの定義
resource "alicloud_vpn_connection" "ipsec" {
  name                = var.alibaba_ipsec_name
  vpn_gateway_id      = alicloud_vpn_gateway.vpngw.id
  customer_gateway_id = alicloud_vpn_customer_gateway.cgw.id
  local_subnet        = [var.alibaba_vswitch_cidr]
  remote_subnet       = [var.azure_subnet_addr]
  effect_immediately  = true
  depends_on          = [alicloud_vpn_customer_gateway.cgw]

  ike_config {
    psk          = var.vpngw_connect_shared_key
    ike_auth_alg = "sha256"
    ike_enc_alg  = "3des"
    ike_version  = "ikev2"
    ike_mode     = "main"
    ike_lifetime = 86400
  }

  ipsec_config {
    ipsec_enc_alg  = "3des"
    ipsec_auth_alg = "sha256"
    ipsec_lifetime = 86400
  }
}

# Alibaba Cloud VPN Route Entryの定義
resource "alicloud_vpn_route_entry" "vpn_routing" {
  vpn_gateway_id = alicloud_vpn_gateway.vpngw.id
  route_dest     = var.azure_subnet_addr // azure subnet
  next_hop       = alicloud_vpn_connection.ipsec.id
  weight         = 0
  publish_vpc    = true
  depends_on     = [alicloud_vpn_connection.ipsec]
}

# Alibaba Cloud セキュリティグループルール（VPN対向からのアクセス用）
resource "alicloud_security_group_rule" "allow_remote_cidr" {
  type              = "ingress"
  ip_protocol       = "all"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "*"
  priority          = 99
  security_group_id = alicloud_security_group.sg.id
  cidr_ip           = var.azure_subnet_addr
  depends_on        = [alicloud_vpn_route_entry.vpn_routing]
}

# Azure 仮想マシン用パブリックIPのデータ定義
# 出力データで利用
data "azurerm_public_ip" "publicip" {
  name                = azurerm_public_ip.publicip.name
  resource_group_name = azurerm_virtual_machine.vm.resource_group_name
}

# リソース作成後の出力（Azure）の定義
output "azure_vm_ip_addr" {
    value = data.azurerm_public_ip.publicip.ip_address
}

# リソース作成後の出力（Alibaba Cloud）の定義
output "alibaba_ecs_ip_addr" {
  value = alicloud_instance.ecs.public_ip
}
# provider.tf
provider "azurerm" {
  features {}
  subscription_id = "<サブスクリプションID>"
  client_id       = "<アプリケーションID>"
  client_secret   = "<パスワード>"
  tenant_id       = "<テナントID>"
}

provider "alicloud" {
  access_key = "<Alibaba CloudのAccessKeyId>"
  secret_key = "<Alibaba CloudのAccessKeySecret>"
  region     = "ap-northeast-1" // 日本（東京）
}
# variables.tf
# Azure Resource Groupの定義
variable "azure_resource_group_name" {
  default = "terraform_rg"
}

variable "azure_location" {
  default = "japaneast"
}

# Azure 仮想ネットワークの定義
variable "azure_vnet_name" {
  default = "terraform_vnet"
}

variable "azure_vnet_addr" {
  default = "10.0.0.0/16"
}

# Azure サブネットの定義
variable "azure_subnet_name" {
  default = "terraform_subnet"
}

variable "azure_subnet_addr" {
  default = "10.0.0.0/24"
}

# Azure NSGと通信ルールの定義
variable "azure_sg_name" {
  default = "terraform_sg"
}

variable "sg_ssh_source_addr" {
  default = "<ローカルのパブリックIPアドレスを指定>"
}

# Azure 仮想マシン用パブリックIPの定義
variable "azure_pip_name" {
  default = "terraform_vm_pip"
}

# Azure ネットワークインターフェイスの定義
variable "azure_nic_name" {
  default = "terraform_vm_nic"
}

variable "azure_ipconfig_name" {
  default = "terraform_vm_nic_ipconfig"
}

# Azure 仮想マシンの定義
variable "azure_vm_name" {
  default = "terraform_vm"
}

variable "azure_vm_size" {
  default = "Standard_D2s_v3"
}

variable "azure_os_disk_name" {
  default = "terraform_osdisk"
}

variable "azure_os_disk_type" {
  default = "StandardSSD_LRS"
}

variable "azure_os_publisher" {
  default = "OpenLogic"
}

variable "azure_os_offer" {
  default = "CentOS"
}

variable "azure_os_sku" {
  default = "7.7"
}

variable "azure_os_version" {
  default = "latest"
}

variable "azure_vm_hostname" {
  default = "terraform-vm"
}

variable "azure_vm_admin_name" {
  default = "<ユーザー名を指定>"
}

variable "azure_vm_admin_password" {
  default = "<ログインパスワードを指定>"
}

# Alibaba Cloud 仮想ネットワークの定義
variable "alibaba_vpc_name" {
  default = "terraform_vpc"
}

variable "alibaba_vpc_cidr" {
  default = "192.168.0.0/16"
}

# Alibaba Cloud Vswitchの定義
variable "alibaba_vswitch_cidr" {
  default = "192.168.0.0/24"
}

variable "alibaba_zone" {
  default = "ap-northeast-1a"
}

# Alibaba Cloud セキュリティグループの定義
variable "alibaba_sg_name" {
  default = "terraform_sg"
}

# Alibaba Cloud 仮想マシンの定義
variable "alibaba_ecs_name" {
  default = "terraform_ecs"
}

variable "alibaba_image_id" {
  default = "centos_7_7_x64_20G_alibase_20200220.vhd"
}

variable "alibaba_ecs_instance_type" {
  default = "ecs.sn1.medium"
}

variable "alibaba_system_disk_type" {
  default = "cloud_ssd"
}

variable "alibaba_ecs_hostname" {
  default = "terraform-ecs"
}

variable "alibaba_ecs_password" {
  default = "<ログインパスワードを指定>"
}

# Azure Virtual Network Gateway用パブリックIPアドレスの定義
variable "azure_vpngw_ip_name" {
  default = "terraform_vpngw_ip"
}

# Azure Virtual Network Gateway用サブネットの定義
variable "azure_gw_subnet_address_prefix" {
  default = "10.0.255.0/24"
}

# Azure Virtual Network Gatewayの定義
variable "azure_vpngw_name" {
  default = "terraform_vpngw"
}

variable "azure_vpngw_sku" {
  default = "Basic"
}

variable "azure_vpngw_ipconfig_name" {
  default = "terraform_vpngw_ipconfig"
}

# Alibaba Cloud VPN Gatewayの定義
variable "alibaba_vpngw_name" {
  default = "terraform_vpngw"
}

# Azure Local Network Gatewayの定義
variable "azure_localgw_name" {
  default = "terraform_localgw"
}

# Azure Virtual Network Gateway Connectionの定義
variable "azure_vpngw_connect_name" {
  default = "terraform_vpngw_connect"
}

variable "vpngw_connect_shared_key" {
  default = "test1234"
}

# Alibaba Cloud Customer Gatewayの定義
variable "alibaba_customergw_name" {
  default = "terraform_customergw"
}

# Alibaba Cloud VPN Connectionの定義
variable "alibaba_ipsec_name" {
  default = "terraform_ipsec"
}
```
</div></details>

※参考リンク：

Microsoft Docs - Quickstart: Install and configure Terraform to provision Azure resources
上記tfファイルを使って`terraform apply`コマンドを実行することで、リソースの作成が行われます。ただし上記tfファイルを利用した場合、1点注意したい点があります。上記tfファイルを用いて検証環境を作成するには、現状では`terraform apply`コマンドを２回実行する必要があります。これについては後述いたします。

今回も`output`には仮想マシンに付与されたパブリックIPアドレスを指定しており、リソースの作成完了後に各仮想マシンにSSH接続を行います。そして対向の仮想マシンの持つプライベートIPアドレス宛にPingコマンドを実行し、Ping応答が返ってくるかを確認しました。

```
# terraform initによる初期化
C:\terraform_article\alibaba_azure>terraform init

（中略）

* provider.alicloud: version = "~> 1.84"
* provider.azurerm: version = "~> 2.12"

Terraform has been successfully initialized!


# terraform validateによるtfファイル定義の確認
C:\terraform_article\alibaba_azure>terraform validate
Success! The configuration is valid.


# terraform planによる作成リソースの確認
C:\terraform_article\alibaba_azure>terraform plan

（中略）

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


# terraform apply（1回目）
C:\terraform_article\alibaba_azure>terraform apply

（中略）

Plan: 23 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を入力

（中略）

Error: expected ip_address to contain a valid IP, got:

  on main.tf line 274, in resource "alicloud_vpn_customer_gateway" "cgw":
 274: resource "alicloud_vpn_customer_gateway" "cgw" {

C:\terraform_article\alibaba_azure>


# terraform apply（2回目）
C:\terraform_article\alibaba_azure>terraform apply

（中略）

Plan: 4 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: 4 added, 0 changed, 0 destroyed.

Outputs:

alibaba_ecs_ip_addr = 47.245.2.176
azure_vm_ip_addr = 52.185.130.132
```
上記のようにリソースの作成が完了したので、Alibaba Cloud・Azureの仮想マシンにSSHでログインし、それぞれ対向の仮想マシンに対してPing疎通ができるかを確認します。

```
# Alibaba Cloud ECSに接続
$ ssh root@47.245.2.176

Welcome to Alibaba Cloud Elastic Compute Service !

[root@terraform-ecs ~]# ip a
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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:00:48:21 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.83/24 brd 192.168.0.255 scope global dynamic eth0
       valid_lft 315357559sec preferred_lft 315357559sec



# Azure VMに接続
$ ssh testadm@52.185.130.132

[testadm@terraform-vm ~]$ ip a
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:0d:3a:cd:76:41 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.4/24 brd 10.0.0.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20d:3aff:fecd:7641/64 scope link
       valid_lft forever preferred_lft forever



# Alibaba CloudからAzure VMへPing疎通を確認
[root@terraform-ecs ~]# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
64 bytes from 10.0.0.4: icmp_seq=2 ttl=63 time=11.9 ms
64 bytes from 10.0.0.4: icmp_seq=3 ttl=63 time=15.1 ms
64 bytes from 10.0.0.4: icmp_seq=4 ttl=63 time=25.3 ms
64 bytes from 10.0.0.4: icmp_seq=5 ttl=63 time=35.1 ms
64 bytes from 10.0.0.4: icmp_seq=6 ttl=63 time=17.5 ms
^C
--- 10.0.0.4 ping statistics ---
6 packets transmitted, 5 received, 16% packet loss, time 5005ms
rtt min/avg/max/mdev = 11.902/20.995/35.113/8.335 ms
[root@terraform-ecs ~]#



# AzureからAlibaba Cloud ECSへPing疎通を確認
[testadm@terraform-vm ~]$ ping 192.168.0.83
PING 192.168.0.83 (192.168.0.83) 56(84) bytes of data.
64 bytes from 192.168.0.83: icmp_seq=1 ttl=63 time=11.7 ms
64 bytes from 192.168.0.83: icmp_seq=2 ttl=63 time=21.2 ms
64 bytes from 192.168.0.83: icmp_seq=3 ttl=63 time=11.5 ms
64 bytes from 192.168.0.83: icmp_seq=4 ttl=63 time=11.5 ms
64 bytes from 192.168.0.83: icmp_seq=5 ttl=63 time=11.8 ms
^C
--- 192.168.0.83 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 11.580/13.604/21.282/3.841 ms
[testadm@terraform-vm ~]$
```
上記のようにPing疎通できることが確認できました。最後にリソースを削除して終了します。

```
# terraform destroy
C:\terraform_article\alibaba_azure>terraform destroy

（中略）

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes  # yesを入力

（中略）

Destroy complete! Resources: 23 destroyed.

C:\terraform_article\alibaba_azure>
```

#`terraform apply`を2回実行する理由について
前章にて`terraform apply`コマンドを２回実行する必要がある、と記載していますが、この理由としては、１度目の`terraform apply`コマンド実行時に以下のようなエラーが発生するからです。

```
Error: expected ip_address to contain a valid IP, got:

  on main.tf line 274, in resource "alicloud_vpn_customer_gateway" "cgw":
 274: resource "alicloud_vpn_customer_gateway" "cgw" {

```
上記エラーでは、Alibaba CloudのCustomer Gatewayを作成するのに必要な、対向側（ここではAzure）のVPN GatewayのパブリックIPアドレスがうまく渡せていないために発生しています。しかし１度目の`terraform apply`実行後、`terraform show`コマンドを実行すると、AzureのVirtual Network Gatewayとそれに割り当てるPublic IPリソースは作成されております。

```
# terraform show
C:\terraform_article\alibaba_azure>terraform show

（出力から一部抜粋）

# azurerm_public_ip.vpngw_ip:
resource "azurerm_public_ip" "vpngw_ip" {
    allocation_method       = "Dynamic"
    id                      = "/subscriptions/<サブスクリプションID>/resourceGroups/terraform_rg/providers/Microsoft.Network/publicIPAddresses/terraform_vpngw_ip"
    idle_timeout_in_minutes = 4
    ip_version              = "IPv4"
    location                = "japaneast"
    name                    = "terraform_vpngw_ip"
    resource_group_name     = "terraform_rg"
    sku                     = "Basic"
}


# azurerm_virtual_network_gateway.vgw:
resource "azurerm_virtual_network_gateway" "vgw" {
    active_active       = false
    enable_bgp          = false
    generation          = "Generation1"
    id                  = "/subscriptions/<サブスクリプションID>/resourceGroups/terraform_rg/providers/Microsoft.Network/virtualNetworkGateways/terraform_vpngw"
    location            = "japaneast"
    name                = "terraform_vpngw"
    resource_group_name = "terraform_rg"
    sku                 = "Basic"
    type                = "Vpn"
    vpn_type            = "RouteBased"

    bgp_settings {
        asn             = 65515
        peer_weight     = 0
        peering_address = "10.0.255.254"
    }

    ip_configuration {
        name                          = "terraform_vpngw_ipconfig"
        private_ip_address_allocation = "Dynamic"
        public_ip_address_id          = "/subscriptions/<サブスクリプションID>/resourceGroups/terraform_rg/providers/Microsoft.Network/publicIPAddresses/terraform_vpngw_ip"
        subnet_id                     = "/subscriptions/<サブスクリプションID>/resourceGroups/terraform_rg/providers/Microsoft.Network/virtualNetworks/terraform_vnet/subnets/GatewaySubnet"
    }
}

```
またAzureポータルを確認すると、Public IPが付与されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/f39f4eb7-78be-a776-51bd-b893611aa4e2.png)

パブリックIPアドレス自体はAzureのVirtual Network Gatewayに割り当てられているため、ここでもう一度`terraform apply`コマンドを実行すると、今度は処理が最後まで実行されます。

```
# terraform apply（2回目）
C:\terraform_article\alibaba_azure>terraform apply

（中略）

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

Outputs:

alibaba_ecs_ip_addr = 47.245.2.176
azure_vm_ip_addr = 52.185.130.132
```
上記エラーに関連する情報として、AzureのパブリックIPアドレスは`allocated_method`が`dynamic`の場合、それらがデバイスにアタッチされるまではIPアドレスが割り振られないという仕様があります。これを回避するために[Terraform公式ドキュメントのこちらの情報](https://www.terraform.io/docs/providers/azurerm/r/public_ip.html#attributes-reference)をもとに、以下の2つの方法を試しましたが、現時点では解決できておりません。このエラーについての原因は現在調査中で、原因と対応が分かった際は更新いたします。

- `data.azurerm_public_ip`を利用→改善無し

- Public IP作成時のパブリックIPアドレスの割り当て方式（`allocated_method`）を`static`に変更→VPN Gatewayには`dynamic`のみ利用可能のため適用できず


※参考リンク：
- [Microsoft Docs - Create a Site-to-Site connection in the Azure portal](https://docs.microsoft.com/ja-jp/azure/vpn-gateway/vpn-gateway-howto-site-to-site-resource-manager-portal?toc=/azure/virtual-network/toc.json#2-create-the-vpn-gateway)
今回はあくまで検証が目的でしたので、`terraform apply`コマンドを再実行する形で良しとしました。しかし本番環境を管理する場合は当然色々と考えなければなりません。幸いVPN Gatewayは繰り返し作成・削除を行うような対象のリソースである場合は少ないかと思いますので、例えばVPN Gatewayとその周辺リソースは別のtfファイルで管理して個別に実行する、といった形で運用を行うのも一つの方法かと思います。

#最後に
今回はAlibaba Cloudの環境構築をTerraformで実行することをメインに、さらにマルチクラウド環境をTerraformで作成することを紹介しました。最後に検証を行う中での気づきなどを記しておきます。

##マルチクラウドを扱う難しさ##
私は今回初めてTerraformで複数のクラウドプロバイダーを対象にリソースを作成することを試したのですが、その中でいくつか難しいと感じる場面に遭遇しました。

####クラウドプロバイダー間の仕様の違い####
これは当然のことなのですが、クラウドプロバイダーにはそれぞれ特有の仕様があり、同じ機能を実装する際に異なるリソースを用意する必要も多々あります。例えばAlibaba CloudとAzureでは、仮想マシンを1台動かせる環境を用意するのに作成が必要なリソースは、以下のように異なります。

| プロバイダー | リソース名 | 用途 |
|:--|:--|:--|
| Alibaba Cloud | - | - |
| |VPC | Alibaba Cloud上で利用する仮想ネットワーク |
| |Vswitch | 仮想ネットワークのアドレス範囲を分割して利用する |
|| ECS | 仮想マシン |
| |Security Group | VPCに対して適用し、IPアドレス単位で通信制御を行う |
| |Security Group Rule | Security Groupで設定する各ルール |
| Azure | - | - |
| |Resource Group | Azure上で作成するリソースを管理するグループ |
| |Virtual Network | Azure上で利用する仮想ネットワーク |
|| Subnet | 仮想ネットワークのアドレスを分割して利用。 |
| |Network Security Group | サブネットまたはNicに適用し、IPアドレス単位で通信制御を行う |
| |Public IP | パブリックIPアドレス。VMとVPN Gatewayに割りあてる |
| |NIC | 仮想マシンに割りあてるNic |
| |Virtual Machine | 仮想マシン |

このような違いは、ベンダー間の仕様が異なることだけでなく、それに関連するTerraformのリソース定義の仕方にも影響します。これらの仕様の違いを把握することは、マルチクラウド環境を管理するためには当たり前のように必要となる知識ですが、扱うクラウドプロバイダーの数が増えることにより習得する範囲も増えるため、すぐに習得するのは難しいだろうと思います。

#####各リソース間の連携#####
各プロバイダーの仕様の違いを抑えるだけでなく、それらを組み合わせて利用したときにもトラブルが発生しました。前章で紹介したAzure Virtual Network Gatewayに関連するエラーが最も代表的な例ですが、他のリソースが所持するデータを参照して作成されるリソースの場合、参照元のリソースが作成されていないタイミングでリソースの作成を開始してしまい、エラーが発生する場合があります。

このエラーは、作成するリソース間での作成完了のタイミングが異なることが影響します。例えばAzure Virtual Network Gatewayは作成完了までにおよそ45分ほどかかり、他のリソースと作成完了のタイミングが合いません。またそれに加えて、Terraformが返すリソース作成完了のタイミングと、実際のクラウドプロバイダー側でのリソース作成完了との間にギャップがある場合もあります。

このようなリソース間での作成タイミングの問題を防ぐため、対象のリソース定義にdepends_onパラメータを与えることで、依存関係にあるリソースを明示的に指定することができます。

###Terraformのフォルダ構成をどうする？###
今回マルチクラウド環境を用意する際、本記事内に載せたtfファイルのほかに、各リソースをモジュールとして別フォルダに分け、ひとつのtfファイルからそれらモジュールを呼び出す形で各リソースを作成する構成も試していました。以下にフォルダ構成の概要図を記載します。

※Terraformフォルダ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/ce6e5271-7aa8-7965-abe5-d4db4ab8b88a.png)

複数のファイルやフォルダに分ける理由として、Terraformの各リソースをモジュールとしていくつかの塊に分けることで、tfファイルの可読性の向上や学習コストを抑制することが望めます。そうしてインフラ環境やそれらを定義するコードの管理コストを抑える効果を期待できます。

これまでTerraformのフォルダ構成については、[Terraformの公式ドキュメント](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)をはじめ、様々な形で資料として公開されています。しかしマルチクラウドに関する記載はまだそれほど多くなく、実際設計をする段階でどのようにフォルダを分けるべきかは検討が必要になります。

今回はクラウドプロバイダー毎にフォルダを分け、さらにその中でいくつかのモジュールに分けてみましたが、例えば本番環境と検証環境はどのように分けて管理するか（フォルダを分けるのか、Workspace機能を利用するのか）、変更が頻繁に走るリソースとそれ以外のリソースは同じフォルダ階層に置くべきなのか、など、まだまだ検討しなければならない要素はありそうです。

#参考ドキュメント
###Alibaba Cloud関連###
- [SlideShare - 知る人ぞ知る第4のクラウド「Alibaba Cloud」って何ですか？](https://www.slideshare.net/sbcloud/4alibaba-cloud)

- [書籍 - Alibaba Cloud構築運用完全マニュアル](https://www.shuwasystem.co.jp/book/9784798058740.html)

- [Future Stride - 中国・アジアでAlibaba Cloudが選ばれている理由](https://www.softbank.jp/biz/future_stride/entry/technology/20180618/)

- [SlideShare - 中国ビジネス向け「Alibaba Cloud」活用集](https://www.slideshare.net/sbcloud/alibaba-cloud-134339896)

###Terraform関連###
- [Terraform公式ドキュメント](https://www.terraform.io/docs/index.html)

- [書籍 - 実践Terraform AWSにおけるシステム設計とベストプラクティス](https://nextpublishing.jp/book/10983.html)

- [Future Tech Blog - Terraformのベストなプラクティスってなんだろうか](https://future-architect.github.io/articles/20190903/)

- [Qiita - Terraform職人入門：日々の運用で学んだ知見を淡々とまとめる](https://qiita.com/minamijoyo/items/1f57c62bed781ab8f4d7)