TerraformからAnsibleのplaybookを実行する

## はじめに
こんにちは、SREエンジニアの@hayaosatoです。
日々の業務では、インフラができるSREの一環として、Infrastracture As Codeを推進しています。

## TerraformとAnsible
今回はインフラの定義からサーバ設定までをシュッと実行してみようと思います。
具体的には、AWSでサーバを作成する際にTerraformでインフラの定義を行なって、その後のサーバのミドルウェアのインストールなどをAnsibleで行います。
また、Terraformでサーバ作成した直後にそのままプロビジョニングとしてAnsibleを実行します。
コードは[こちら](https://github.com/hayaosato/terraform-ansible)。

## Terraform
まずは、AWSでEC2インスタンスを作成します。
今回作成するインスタンスはUbuntu18.04の最新AMIから作成しようと思います。
Terraformおよびプロバイダのバージョンは以下の通りです。

```
Terraform v0.12.16
+ provider.aws v2.38.0
+ provider.local v1.4.0
```

### AMI
最新のAMIのidを取得するためには以下のように取得します。

```ec2_images.tf
data "aws_ami" "latest-ubuntu" {
  most_recent = true
  owners      = ["679593333241"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}
```
取得したいAMIはAWS CLIの`ec2 describe-images`コマンドで確認することができるので、
他のOSで同様に最新イメージを取得したい場合は確認していただければと思います。
一応コードの`ec2_images.tf`にCentos7とAmazon Linux2の最新イメージの取得方法も記載してあります。

### EC2
続いて、EC2インスタンスの作成ですが、以下のようになりました。

```main.tf
resource "aws_instance" "default" {
  ami             = data.aws_ami.latest-ubuntu.id
  key_name        = var.key_name
  private_ip      = var.private_ip
  count           = 1
  security_groups = var.security_groups
  subnet_id       = var.subnet_id
  instance_type   = "t3.medium"
  tags = {
    Name = "terraform-ansible"
  }

  provisioner "remote-exec" {
    connection {
      type        = "ssh"
      user        = "ubuntu"
      host        = var.private_ip
      private_key = file("~/.ssh/${var.key_name}.pem")
    }
    inline = [
      "sudo apt install -y python"
    ]
  }

  provisioner "local-exec" {
    command = "ansible-playbook -i hosts ${var.service_name}.yml"
  }
}
```
`provisioner`より上部の各パラメータは[公式ドキュメント](https://www.terraform.io/docs/providers/aws/r/instance.html)を参照いただけると幸いです。
今回のAnsible実行は`provisioner`の箇所で実現されています。
terraformには[Provisioning](https://learn.hashicorp.com/terraform/getting-started/provision.html)の機能が様々あり、
その中で`remote-exec`と`local-exec`を利用しています。
#### remote-exec
ec2リソースのプロビジョニングである`remote-exec`はサーバが作成され次第SSHなどで接続してコマンドを実行したりファイルを配置したりすることができます。
今回はAnsibleの実行のためにPythonをインストールしています。

#### local-exec
`local-exec`は文字通りterraformコマンドを実行する、ローカル環境で実行してくれるコマンドを指定することができます。
今回はそのままAnsibleコマンドを実行するように指定します。

また、ansibleコマンドの指定でハマった点としてコマンド指定を

```
command = "ansible-playbook -i hosts ${var.service_name}.yml --private-key=~/.ssh/${var.key_name}.pem"
```
のようにしていたのですが、たまに`terraform fmt`のフォーマッタで

```
command = "ansible-playbook -i hosts ${var.service_name}.yml --private-key = ~/.ssh/${var.key_name}.pem"
```
のように、`--private-key`指定のイコールの前後にスペースを入れてくれてしまうので、
できれば`~/.ansible.cfg`の`private_key_file`の設定で対応したほうがいいです。

```~/.ansible.cfg
[defaults]
host_key_checking = False
private_key_file = /path/to/private_key_file
```

## Ansbile
さて、ここまででTerraformでのEC2インスタンス作成の定義までできたと思います。
実際流すAnsibleの設定を作成していきます。
Ansibleのバージョンは以下の通りです。

```
ansible 2.7.15
```
今回のtaskはとてもシンプルにvimをaptでインストールするだけにします。

```service/task/main.yml
- name: install vim
  become: yes
  apt:
    name: vim
```
また、playbookは以下のようにします。

```service.yml
- hosts: service
  remote_user: ubuntu
  roles:
    - service
```
今回、ansibleのplaybookについての説明は省略します。
### インベントリファイル
ここで、インベントリファイルについてですが、
hostsの設定はprivate_ipを記載するだけですが、terraform側で管理していればそれを流用するだけですので、
インベントリファイルもterraformで作ってしまおうと思います。
内容はとてもシンプルで以下の通りです。
ローカルでのファイルの作成は`local`プロバイダの`local_file`

```local_file.tf
resource "local_file" "hosts_file" {
  content         = <<EOF
[${var.service_name}]
${var.private_ip}
EOF
  filename        = "hosts"
  file_permission = "0644"
}
```
このようにterraformで管理することによって、プライベートIPを変える場合に連動してくれるので良いなと思いました。

## 実行
最後に実行する際にterraformのvarを設定して

```
terraform apply -var-file sample.tfvars
```
のように実行するとec2インスタンスを作成してそのサーバに向けてansibleを実行してくれる構成ができました。

## 最後に
今回の構成はTerraformでインスタンスを作成してそのままAnsibleを実行したい人向けに作ってみました。
そもそもAnsibleでは冪等性が担保されているように構成すると思うので、そこまで必要性は感じませんが、
そもそものAnsibleの設定を作成する時に少しでも使えるといいなぁと思ってやってみました。

また、上記の構成で「こうしたほうがいいだろ！」等のご指摘あれば是非ともよろしくお願いいたします。

## 参考
- [10分で理解するTerraform](https://qiita.com/Chanmoro/items/55bf0da3aaf37dc26f73)
- [Amazon EC2 AMI Locator](https://cloud-images.ubuntu.com/locator/ec2/)
- [Terraform Documentation](https://www.terraform.io/docs/index.html)
- [Ansible Documentation](https://docs.ansible.com/ansible/latest/index.html)