TerraformからAnsibleのplaybookを実行する


はじめに

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

TerraformとAnsible

今回はインフラの定義からサーバ設定までをシュッと実行してみようと思います。
具体的には、AWSでサーバを作成する際にTerraformでインフラの定義を行なって、その後のサーバのミドルウェアのインストールなどをAnsibleで行います。
また、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より上部の各パラメータは公式ドキュメントを参照いただけると幸いです。
今回のAnsible実行はprovisionerの箇所で実現されています。
terraformにはProvisioningの機能が様々あり、
その中でremote-execlocal-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.cfgprivate_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の設定を作成する時に少しでも使えるといいなぁと思ってやってみました。

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

参考