Terraform でいろいろ作った後にそれを EC2 インスタンスに渡す方法

最近は Terraform で AWS リソースを作る → Ansible で EC2 インスタンスの中身をプロビジョニング、とすることが多いですが、Terraform で作った AWS リソースの情報を  EC2 インスタンスの中で使いたいこと、よくあります。

例えば次のようなものです。

- RDS 接続情報
- SES クレデンシャル

ある程度の命名規則があったり固定の名前だったりするなら Terraform の tf ファイルと Ansible のプレイブックとかにベタに書いても良いと思いますが、ランダムで生成される場合、

1. Terraform でリソースを作成
2. 目で見て Ansible の inventory に仕込む

みたいにする必要があり泥臭いです。

これを解消する方法、いくつか考えられますが。。。

## terraform-inventory

- https://github.com/adammck/terraform-inventory

Terraform の tfstate ファイルをインベントリとして使う Ansible Dynamic Inventory です。
が、たぶんリモートバックエンドに対応していないため、使え無さそうです。

Terraform でリモートバックエンドを使わないなら、使えるのかも、しれない？

## local-exec provisioner 

- https://www.terraform.io/docs/provisioners/local-exec.html

Terraform の local-exec プロビジョナで、作成されたリソースの情報をローカルのファイルに書き出します。

```sh
resource "aws_iam_access_key" "smtp" {
  user = "${aws_iam_user.smtp.name}"
  provisioner "local-exec" {
    command = "echo '${aws_iam_access_key.smtp.id}:${aws_iam_access_key.smtp.ses_smtp_password}' > smtp_credential.txt"
  }
}
```

これ、使ったことないですけど、リソースを作成したときだけ実行されるものなので複数人でコードを共有している場合はダメな気がする。

あるいは local-exec プロビジョナで作成したファイルをリポジトリに含めるとか？ SES クレデンシャルとかをどうにかしたいわけなのでそれは辛い。

## remote-exec provisioner 

- https://www.terraform.io/docs/provisioners/remote-exec.html

Terraform の remote-exec プロビジョナで、作成された EC2 インスタンスにファイルを書き出します。

```sh
resource "aws_instance" "web" {
  # ...

  provisioner "remote-exec" {
    inline = [
      "echo '${aws_iam_access_key.smtp.id}:${aws_iam_access_key.smtp.ses_smtp_password}' > smtp_credential.txt",
    ]
  }
}
```

これ、使ったことないですけど、Ansible 使わずに Terraform だけで完結させる、とかでない限りは併用は辛そう。

## Terraform のリソースとしてどこかに保存

後から参照できる場所に Terraform のリソースとして保存します。

例えば S3 とか。

```sh
resource "aws_s3_bucket_object" "secret" {
  bucket = "oreore-bucket"
  key    = "secret"
  content = <<EOS
SMTP_USERNAME=${aws_iam_access_key.smtp.id}
SMTP_PASSWORD=${aws_iam_access_key.smtp.ses_smtp_password}
EOS
}
```

SSM のパラメータストアでも良いかも。

```sh
resource "aws_ssm_parameter" "secret" {
  name  = "/oreore/smtp/username"
  type  = "SecureString"
  value = "${aws_iam_access_key.smtp.id}"
}

resource "aws_ssm_parameter" "secret" {
  name  = "/oreore/smtp/password"
  type  = "SecureString"
  value = "${aws_iam_access_key.smtp.ses_smtp_password}"
}
```

## EC2 インスタンスのユーザーデータに入れて /etc/environment に書き出す

EC2 インスタンスのユーザーデータに入れて cloud-init で /etc/environment とかに書き出す。

```sh
resource "aws_instance" "redmine" {
  # ...

  user_data = <<EOS
#cloud-config
timezone: "Asia/Tokyo"
write_files:
  - path: "/etc/environment"
    content: |
      SMTP_USERNAME=${aws_iam_access_key.smtp.id}
      SMTP_PASSWORD=${aws_iam_access_key.smtp.ses_smtp_password}
EOS
}
```

## さいごに

S3 や SSM Parameter store に保存する方法は保存した値をどうやって使うかも考える必要がありますが、EC2 ユーザーデータからの /etc/environment なら Ansible から次のように簡単に参照できます。

```yaml
- name: generate sasl_passwd
  copy:
    dest: /etc/postfix/sasl_passwd
    mode: 0600
    content: |
      email-smtp.us-west-2.amazonaws.com:587 {{ansible_env.SMTP_USERNAME}}:{{ansible_env.SMTP_PASSWORD}}
  notify:
    - postmap sasl_passwd
    - postfix restart
```

ただ、Terraform だと EC2 ユーザーデータに変更があるときはインスタンスを作り直そうとしてしまうのと、cloud-init による書き出しもインスタンスの開始時にしか行われないので、S3 や SSM Parameter store から値を取ってくる Dynamic Inventory があるならそっちのほうが運用性は高いかもしれません。