TerraformInventory で、TerrafromとAnsibleを2コマンドで連携させる

記事タイトルとURLをコピーする

全国のTerrafrom愛好家の皆様こんにちは。
技術4課の岩本です。

Terraformのプロビジョナーには非常に残念ながらAnsibleがありません。
通常であればTerraformで環境を構築後に、別途Ansible用のInventoryファイルを作成してという流れになりますが、
Inventoryファイルの作成なしにTerraform Inventoryを使って、TerraformとAnsibleを2コマンドで連携させてみました。

Terraform Inventory とは?

AnsibleのDynamicInventoryをTerrafromのStateファイルから生成するプログラムです。

Terraform Inventory

また、AWS以外にも

  • DigitalOcean
  • CloudStack
  • VMware
  • OpenStack
  • Google Compute Engine
  • SoftLayer
  • Exoscale
  • Scaleway

に対応しています。

インストール

Mac環境では brew からインストール可能です。

brew install terraform-inventory

使い方

AnsibleのInventoryとして、terraform-inventoryを指定することで、
Terraformで作成したEC2インスタンスにAnsibleが実行されます。

ansible-playbook --inventory-file=/path/to/terraform-inventory deploy/playbook.yml
  • 参照されるTerrafromのStateファイルは実行したコマンドを実行したディレクトリ内のものが参照されます。
  • 別ディレクトリ内にあるStateファイルを参照する場合は、環境変数TF_STATEで指定をします。
TF_STATE=deploy/terraform.tfstate ansible-playbook --inventory-file=/path/to/terraform-inventory deploy/playbook.yml

他にも、下記コマンドを実行することで、Stateファイル内からホスト/ホストグループを確認することができます。

terraform-inventory --list fixtures/example.tfstate

やってみた

とりあえず作成したTerraformを使ってAWS環境を構築します。

  • main.tf
provider "aws" {
    access_key = "XXXXXXXXXXXXXXXXXX"
    secret_key = "XXXXXXXXXXXXXXXXXX"
    region = "ap-northeast-1"
}

resource "aws_vpc" "my-vpc" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "my-vpc"
    }
}

resource "aws_internet_gateway" "my-igw" {
    vpc_id = "${aws_vpc.my-vpc.id}"
}

resource "aws_subnet" "public-a" {
    vpc_id = "${aws_vpc.my-vpc.id}"
    cidr_block = "10.1.1.0/24"
    availability_zone = "ap-northeast-1a"
}

resource "aws_route_table" "public-route" {
    vpc_id = "${aws_vpc.my-vpc.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.my-igw.id}"
    }
}

resource "aws_route_table_association" "puclic-a" {
    subnet_id = "${aws_subnet.public-a.id}"
    route_table_id = "${aws_route_table.public-route.id}"
}

resource "aws_security_group" "admin" {
    name = "admin"
    description = "Allow SSH inbound traffic"
    vpc_id = "${aws_vpc.my-vpc.id}"
    ingress {
        from_port = 22
        to_port = 22
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_instance" "ansible-test" {
    ami = "ami-ceafcba8"
    instance_type = "t2.micro"
    key_name = "XXXXXXXXXXXXX"
    vpc_security_group_ids = [
      "${aws_security_group.admin.id}"
    ]
    subnet_id = "${aws_subnet.public-a.id}"
    associate_public_ip_address = "true"
    root_block_device = {
      volume_type = "gp2"
      volume_size = "10"
    }
}
  • plan
$ terraform plan
Plan: 7 to add, 0 to change, 0 to destroy.
  • apply
$ terraform apply
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

次に上記で作成されたStateファイルを指定し、Ansibleを実行します。
(ホストにPINGをおこなうだけの簡単なプレイブックです。)

$ TF_STATE=../sample01-terraform/terraform.tfstate ansible-playbook --inventory-file=/usr/local/bin/terraform-inventory setup.yml

PLAY [all] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [xxx.xxx.xxx.xxx]

TASK [sample-roles : PING] *****************************************************
ok: [xxx.xxx.xxx.xxx]

PLAY RECAP *********************************************************************
xxx.xxx.xxx.xxx                : ok=2    changed=0    unreachable=0    failed=0

タイトルには、2ステップとありましたが、実はterraform planを入れると3ステップとなります。
ごめんなさい、じゃっかんの釣りタイトルでした・・・
ですが、ご覧いただく通りInventoryの修正なしで、Ansibleの実行が可能です。
ただし、2ステップ(Planを入れると3ステップ)で実行をを行うには、Ansible側で工夫が少し必要でした。

  • setup.yml
- hosts: all
  become: yes
  remote_user: ec2-user

  vars:
    ansible_ssh_private_key_file: /PATH/TO/KEY/xxxxxx.pem
    ansible_ssh_extra_args: '-o StrictHostKeyChecking=no'

  roles:
  - role: sample-roles

Ansibleを適用する場合、ssh_configを作成し、InventoryファイルおよびGroup_varsを環境に応じて設定することが可能でした。

同じ様にGroup_versを利用するには、下記の様にtagsを設定します。

なお、Terraform Inventoryでは配布元で説明があるとおり、Ansibleで指定するホスト名は、
プロバイダ上でのホスト名ではなくTerraformで定義したリソース名となります。

This will provide the resource names and IP addresses of any instances found in the state file to Ansible, which can then be used as hosts patterns in your playbooks.
resource "aws_instance" "ansible-test" {
    ami = "ami-ceafcba8"
    instance_type = "t2.micro"
    tags = {
      Env = "dev"
    }
    .....
  }

上記、Terraform内にて設定したタグは、ホストグループenv_devとして定義されますので、
group_versディレクトリ内に、env_dev.ymlファイルを設置します。

また、同様に

    tags = {
      Env = "dev"
      Role = "web"
    }

と設定し、AnsibleのPlaybook内では

- hosts: role_web

と指定することで、役割毎に実行するPlaybookの変更も可能です。

上記2つを組み合わせることで、1つのTerraform・Playbookを元に開発・検証・本番などの様に
設定値だけを切り替える構成が、インフラ環境の構築からEC2内の設定まで可能となります。

夢が広がりますね!

では、また!!!