Terraform + Cloud-init + Ansible で AWS EC2 プロビジョニングの共通化

AWS と IBM Cloud の二つのクラウドで、Terraform 仮想サーバーをプロビジョニングして、マルチクラウド化ツールの実態を体験して理解したいと思い、前回、IBM Cloudで作成した Terraform の構成ファイルを AWS EC2 用に書き直して、検証をおこないました。

この記事は、[Terraform + Cloud-init + Ansible で IBM Cloud VSIプロビジョニング自動化](https://qiita.com/MahoTakara/items/0b23d9bca3edcfe0081c)の記事のAWS版です。

Terraform の詳細なコードは、[takara9/terraform-aws-ec2](https://github.com/takara9/terraform-aws-ec2) にあります。Terraform の実行環境は、[takara9/vagrant-terraform](https://github.com/takara9/vagrant-terraform)を想定しています。

次のように、GitHubからクローンしたディレクトリで、ファイル名を挙げながら、どのように対応したのかを記述しておきます。

~~~console
~/terraform-aws-ec2$ git clone https://github.com/takara9/terraform-aws-ec2
~/terraform-aws-ec2$ tree
.
├── ansible.cfg
├── creat_inventry_ansible_f_terraform.py
├── install.yml
├── main.tf
├── masters.tf
├── nodes.tf
├── playbooks
│   ├── hosts
│   └── setup.yml
├── README.md
└── terraform.tfvars
~~~


## AWS EC2 用に修正が必要な箇所

### creat_inventry_ansible_f_terraform.py　
このファイルは、Terraform の構成ファイルに記述された outputs の項目を読んで、Ansibleのインベントリファイルに、ノードのIPアドレス、ドメイン名、認証鍵を記述します。

AWS EC2 用に書き直しことで、特に変更することはありませんでした。　しかし、今回は Terraform の構成ファイルを、main.tf を、分割して、３本の構成ファイルに変更しました。そのため、aws_instance が２つのファイルに分かれたために、outputsの出力が変わったために、コードを修正しました。また、それ以外に、機能が変わった部分はありません。

### install.yml
これは、[Terraform + Cloud-init + Ansible で IBM Cloud VSIプロビジョニング自動化](https://qiita.com/MahoTakara/items/0b23d9bca3edcfe0081c) の記事で書いたとおり、Cloud-init の構成ファイルです。

IBM Cloud では Ubuntu 18.04 を使ったので、Amazon マシンイメージ (AMI) の Ubuntu 18.04 を利用しました。 この二つのイメージにインストールされたパッケージには、違いがあり、その違いを無くすために、追加のパッケージを記述しました。

それから、IBM Cloud の Ubuntu のログインのユーザーは、root ですが、AWS EC2では、ユーザーは Ubuntu になり、root ではログインできません。そのため、Ansibleからも操作できないという課題が発生します。この課題を解決するために、root でログインできるように追加する処理を install.yml に記述しました。

### main.tf, masters.tf, nodes.tf
Terraformの構成ファイルを置いたディレクトの拡張子 tf のファイルは、全部のファイルが読み込まれ適用されます。そこで、ファイルを今回は main.tfを ３つに分割して、つぎのような役割を持たせました。

* main.tf: 変数宣言、セキュリティグループ、認証鍵の登録などの共通部分
* masters.tf: EC2 の master-0 を起動するための構成ファイル
* nodes.tf: EC2 の worker-0, worker-1 を起動するための構成ファイル

この部分は、AWS用の Terraform-Provider [AWS Provider](https://www.terraform.io/docs/providers/aws/index.html) の項目に従って、記述をします。もちろん、記述のシンタックスは、[HashiCorp Configuration Language](https://github.com/hashicorp/hcl）に従って記述しますが、扱う項目は、IBM Cloud と AWS では項目名がことなり、全面的に書き直さなければなりません。

### playbooks
これは、Ansibleのプレイブックを収めるディレクトリです。　同じLinuxディストリビューションでも、導入されているパッケージに違い、また、設定上の差異があります。これらの違いは、Cloud-init のYAMLファイルで違いが無くなるように、レベル合わせをすれば、Playbookの下のAnsibleプレイブックは、クラウドベンダーが異なっても、変更なしで、そのまま利用することができます。

### terraform.tfvars
変数を集めたファイルですが、前述のTerraformの構成ファイルの変更に伴って、必要な修正を施します。


## Terraform による AWS EC2 のプロビジョニング

IBM Cloud との違いは、１か所です。それは、Terraform-Provider が、自動的にダウンロードされるため、次のように、サブコマンド init を実行するだけで良い点です。

~~~console
$ git clone https://github.com/takara9/terraform-aws-ec2
$ cd terraform-aws-ec2
$ terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.58.0)...

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.aws: version = "~> 1.58"

Terraform has been successfully initialized!
＜中略＞

$ terraform plan --var-file=/vagrant/.secret_aws.tfvars
＜中略＞


$ terraform apply --var-file=/vagrant/.secret_aws.tfvars
＜中略＞
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:

master_hostname = [
    ec2-52-69-**-***.ap-northeast-1.compute.amazonaws.com
]
master_public_ip = [
    52.69.**.***
]
worker_hostname = [
    ec2-52-193-**-**.ap-northeast-1.compute.amazonaws.com,
    ec2-54-249-***-***.ap-northeast-1.compute.amazonaws.com
]
worker_public_ip = [
    52.193.**.**,
    54.249.***.***
]
~~~


## Ansible による AWS EC2 の自動設定

Ansible のプレイブックは、クラウドプロバイダー各社とも共有の設定を利用できる可能性があります。今回のプレイブックは、実際に設定をおこないませんが、Ansibleでアクセスしてコマンドを実行することが確認できました。

Terraform の terraform.tfstate を読み込んで、インベントリファイル　playbooks/hosts に追加する部分は、Terraform の構成ファイルの変更に合わせて修正しました。

~~~console
$ creat_inventry_ansible_f_terraform.py
$ ansible -i playbooks/hosts -m ping nodes
ec2-52-193-53-92.ap-northeast-1.compute.amazonaws.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
ec2-52-69-53-181.ap-northeast-1.compute.amazonaws.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
ec2-54-249-221-204.ap-northeast-1.compute.amazonaws.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

$ ansible-playbook -i playbooks/hosts playbooks/setup.yml

PLAY [nodes] ************************************************************************************

TASK [Gathering Facts] **************************************************************************
ok: [ec2-52-193-53-92.ap-northeast-1.compute.amazonaws.com]
ok: [ec2-54-249-221-204.ap-northeast-1.compute.amazonaws.com]
ok: [ec2-52-69-53-181.ap-northeast-1.compute.amazonaws.com]

TASK [Test] *************************************************************************************
changed: [ec2-52-193-53-92.ap-northeast-1.compute.amazonaws.com]
changed: [ec2-54-249-221-204.ap-northeast-1.compute.amazonaws.com]
changed: [ec2-52-69-53-181.ap-northeast-1.compute.amazonaws.com]

TASK [Debug] ************************************************************************************
ok: [ec2-52-69-53-181.ap-northeast-1.compute.amazonaws.com] => {
    "result.stdout": "ip-172-31-7-241"
}
ok: [ec2-52-193-53-92.ap-northeast-1.compute.amazonaws.com] => {
    "result.stdout": "ip-172-31-6-60"
}
ok: [ec2-54-249-221-204.ap-northeast-1.compute.amazonaws.com] => {
    "result.stdout": "ip-172-31-10-232"
}

PLAY RECAP **************************************************************************************
ec2-52-193-53-92.ap-northeast-1.compute.amazonaws.com : ok=3    changed=1    unreachable=0    failed=0
ec2-52-69-53-181.ap-northeast-1.compute.amazonaws.com : ok=3    changed=1    unreachable=0    failed=0
ec2-54-249-221-204.ap-northeast-1.compute.amazonaws.com : ok=3    changed=1    unreachable=0    failed=0
~~~


## まとめ

Terraform を利用してマルチクラウドを実現するにあたり、この検証を通じて、どこまで共通化できるかとの課題について、考察しました。その結果は次のようなものでした。

* Terraform 構成ファイル　main.tf, masters.tf, nodes.tf は AWS用プロバイダーの仕様に合わせて変更
* Cloud-init の設定ファイル install.yml は、AWS EC2 と　IBM Cloud VSI のイメージの差異を埋めるため修正
* Ansible プレイブック 共通化できると考えられる


Kubernetes と Terraform をマルチクラウドのツールとして比較すると、つぎのような違いがあることが判りました。

* Terraformの 構成記述言語 HCL (HashiCorp Configuration Language)に従って、共通の記述文法で、各社プロバイダーの仮想サーバーをプロビジョニングできる。
* しかし、Terraform-Providerの記述仕様に従って構成ファイルを記述しなければならない。この記述仕様を使うためには、各社のクラウドの仕様を良く理解していなければならない。
* Kubernetesでは、クラスタを構成するマスターやノードは、各社プロバイダーの仕様に従ってプロビジョニングする必要があるが、開発者が利用するマニフェストやコンテナ・イメージのレベルでは共通化できる。

すこし粗っぽい「まとめ」ではありますが、凡そ、こんな感じと思います。　Terraformを使って、各社プロバイダーに自動的に Kubernetes クラスタを構築するという事は、簡単にできそうです。　Kubernetesクラスタの構成を、各社共通で動作するようにする考慮は必要ですが、十分できると思います。