Terraform ↔ Ansible
TerraformとAnsible
TerraformとAnsible、どちらもIaCツールでありインフラ構築の自動化を行うことができます。
TerraformとAnsible、この両者はどちらも一方から他方を呼ぶことができます。
ということでTerraform → AnsibleとAnsible → Terraformで両方のパターンの橋渡しの部分についてを書いていきます。
Terraform → Ansible
Terraformが主体となりAnsible Playbookを実行するやり方で大きくは2つのパターンがあるのでそれぞれ紹介します。
local_fileリソースでinventory.iniファイルを作成してlocal-execで実行ansible/ansibleのproviderを利用する方法
local_fileリソースでinventory.iniファイルを作成してlocal-execで実行
Terraformのlocal_fileリソースを利用し、Ansibleで利用するためのinventory.iniを作成し、local-execでansible-playbookコマンドを実行する方法です。
Ansibleで使用するinventoryファイルはtemplatefileメソッドで作成します。
Jinja2っぽいテンプレートファイル(微妙にJinja2とは違う)を利用してあとは利用している変数のマッピングをtemplatefileメソッドの引数で指定すればOKです。
local_file.tf
resource "local_file" "inventory" {
depends_on = [aws_instance.myServer]
content = templatefile("./inventory.tftpl",
{
server = aws_instance.myServer
}
)
filename = "./inventory.ini"
}
inventory.tftpl
[server]
${server.tags.Name} ansible_host=${server.public_ip}
[server:vars]
ansible_user="ec2-user"
[all:vars]
ansible_ssh_private_key_file="./private_key.pem"
これでinventory.iniのファイルが生成されるのでそれを利用してansible-playbookコマンドを実行します。
Ansibleを実行するための.tfファイルは下記になります。
ansible.tf
resource "null_resource" "provisioning" {
depends_on = [local_file.inventory]
provisioner "remote-exec" {
connection {
host = aws_instance.myServer.public_ip
user = "ec2-user"
private_key = file("./ssh_key.pem")
}
inline = [ "echo 'ready to do ansible!'" ]
}
provisioner "local-exec" {
command = "ansible-playbook -i inventory.ini setup_server.yaml"
}
}
この時、対象のEC2が起動してくるのを待つためにremote-execでのコマンド実行を挟んでおくのがポイントです。
これがないと起動しきっていないインスタンスにPlaybookを実行してしまいます。
ansible/ansibleのproviderを利用する
ansible/ansibleプロバイダーが公式より提供されています。 https://registry.terraform.io/providers/ansible/ansible/latest
このansible/ansibleプロバイダーではansible_playbookのresourceでansible-playbookコマンドを実行することができます。
また、ansible_hostリソースを使用すると、Ansibleのcloud.terraform.terraform_pluginで参照するためのインベントリ情報を作成することもできます。
これは後述のAnsible → Terraformにて使用します。
話を戻してTerraform → Ansibleについて。
このansible_playbookのresourceですが、少々癖があり、リソースの設定ではinventoryファイルの指定ができない他、プロバイダーに同梱のansible_hostを全く参照してくれません。
そのため、リソースのnameか、extra_varsの部分でansible_hostやansible_userを渡してあげる必要があります。
GitHubのIssueでは外部変数でinventory_fileを指定すると良いというような記述がありますが、inventory_fileはマジック変数でAnsibleの公式ドキュメントではマジック変数はユーザー側から指定できないとあるので、この解決方法はちょっと眉唾です。(ちなみに私はこれでは解決しませんでした。Issueにもそれでうまくいかなかった人のコメントがあります。)
ansible.tf
resource "ansible_playbook" "setup_server" {
playbook = "./setup_server.yaml"
name = "myServer"
replayable = false
ignore_playbook_failure = true # ここをtrueにしないとproviderが呼べませんでした
extra_vars = {
ansible_host = aws_instance.myServer.public_ip
ansible_user = "ec2-user"
ansible_ssh_private_key_file = "./ssh_key.pem"
}
}
output "playbook_stdout" {
value = ansible_playbook.setup_server.ansible_playbook_stdout
}
outputでansible_playbook_stdoutを指定してあげるとAnsibleの実行結果をoutputとして返してくれます。
ただ、ここでもEC2インスタンスが作成される前にAnsible Playbookが実行されてしまう問題があったので、最終的に下記の形になりました。
ansible.tf
resource "null_resource" "wait_instance" {
depends_on = [aws_instance.myServer]
provisioner "remote-exec" {
connection {
host = aws_instance.myServer.public_ip
user = "ec2-user"
private_key = file("./ssh_key.pem")
}
inline = ["echo 'ready to do ansible!'"]
}
}
resource "ansible_playbook" "setup_server" {
depends_on = [null_resource.wait_instance]
playbook = "./setup_server.yaml"
name = "myServer"
replayable = false
ignore_playbook_failure = true
extra_vars = {
ansible_host = aws_instance.myServer.public_ip
ansible_user = "ec2-user"
ansible_ssh_private_key_file = "./ssh_key.pem"
}
}
output "playbook_stdout" {
value = ansible_playbook.setup_server.ansible_playbook_stdout
}
output "playbook_stderr" {
value = ansible_playbook.setup_server.ansible_playbook_stderr
}
null_resourceのwait_instanceで対象のインスタンスへremote-execをし、それをansible_playbookからdepends_onで依存関係に設定することでインスタンスの立ち上がりを完全に待つことができました。
Ansible → Terraform
AnsibleからTerraformを呼ぶ方法は大きく下記の2つがあります。
ansible.builtin.shellモジュールでコマンドから実行する方法cloud.terraformコレクションを利用する方法
ansible.builtin.shellモジュールでコマンドから実行
terraform initとterraform applyをansible.builtin.shellモジュールで実行します。
インベントリについてはプラットフォームのinventory pluginを用いてみます。
後述のcloud.terraform.terraform_pluginをつかっても良いのですが、差別化のためにここはあえて。
---
plugin: amazon.aws.aws_ec2
regions:
- ap-northeast-1
filters:
instance-state-name: running
keyed_groups:
- key: tags.Name
hostnames:
- ip-address
inventory pluginを使う場合はansible.cfgで有効化が必要です。
ansible.cfg
[inventory]
enable_pluginsf = amazon.aws.aws_ec2
これでPlaybook側で呼び出せばOKです。
---
# AnsibleからTerraformを呼ぶ
- name: Provisioning
hosts: localhost
gather_facts: false
tasks:
- name: Call Terraform
ansible.builtin.shell:
cmd: terraform init && terraform apply -auto-approve
# Terraformで立ち上げたEC2に設定を行う
- name: Setting
hosts: myServer
gather_facts: true
become: true
tasks:
- name: Install package
ansible.builtin.yum:
name:
- httpd
...
cloud.terraformコレクションを利用する
cloud.terraformコレクションのcloud.terraform.terraformモジュールでTerraformのプロジェクトを呼び出します。
Ansibleによる設定投入を実行する際に必要となるインベントリについてはansible/ansibleのproviderとcloud.terraform.terraform_pluginを今回は使います。
inventory pluginを使う場合はTerraformとAnsibleのそれぞれでファイルに手を入れる必要があります。
Terraform側では、ansible_groupまたはansible_hostのresourceを作成します。
inventory.tf
resource "ansible_host" "myServer" {
depends_on = [aws_instance.myServer]
name = aws_instance.myServer.tags.Name
groups = ["server"]
variables = {
ansible_host = aws_instance.myServer.public_ip
ansible_user = "ec2-user"
ansible_ssh_private_key_file = "ssh_key.pem"
}
}
Ansible側ではcloud.terraform.terraform_pluginを利用するinventoryファイルを作成します。
inventory.yaml
---
plugin: cloud.terraform.terraform_provider
ansible.cfgファイルにcloud.terraform.terraform_providerを有効化する設定を記述します。
[inventory]
enable_plugin = ini, cloud.terraform.terraform_provider
これでTerraformで作成したインスタンスをAnsibleのインベントリにつなげることができました。
call_terraform.yaml
---
# AnsibleからTerraformを呼ぶ
- name: Provisioning
hosts: localhost
gather_facts: false
tasks:
- name: Call Terraform
cloud.terraform.terraform:
project_path: terraform/
state: present
force_init: true
# Terraformで立ち上げたEC2に設定を行う
- name: Setting
hosts: myServer
gather_facts: true
become: true
tasks:
- name: Install package
ansible.builtin.yum:
name:
- httpd
...
このcloud.terraform.terraformのドキュメントはこちら
community.general.terraformでもほとんど同じことができます。
ほとんど差がないですが、inventory pluginがある分cloud.terraformの方がお得な気がします。
参考
GitHub - ansible/terraform-provider-ansible: community terraform provider for ansible Inventory plugins — Ansible Community Documentation templatefile - Functions - Configuration Language | Terraform | HashiCorp Developer Ansible vs. Terraform, clarified Providing Terraform with that Ansible Magic | Ansible Collaborative
…
Ansible → Terraformの方が順序性がわかりやすくて個人的に好き。 ansible/ansibleのansible_playbookのリソースが使い勝手悪いせいもあるので、今後アップデートがかかることに期待。