TerraformとAnsible
TerraformとAnsible、どちらもIaCツールでありインフラ構築の自動化を行うことができます。
TerraformとAnsible、この両者はどちらも一方から他方を呼ぶことができます。
ということでTerraform → AnsibleとAnsible → Terraformで両方のパターンの橋渡しの部分についてを書いていきます。
Terraform → Ansible
Terraformが主体となりAnsible Playbookを実行するやり方で大きくは2つのパターンがあるのでそれぞれ紹介します。
output
でinventory.ini
ファイルを作成してlocal-exec
で実行ansible/ansible
のprovider
を利用する方法
output
でinventory.ini
ファイルを作成してlocal-exec
で実行
Terraformのoutput
を利用し、Ansibleで利用するためのinventory.ini
を作成し、local-exec
でansible-playbook
コマンドを実行する方法です。
Ansibleで使用するinventory
ファイルはtemplatefile
メソッドで作成します。
Jinja2っぽいテンプレートファイル(微妙にJinja2とは違う)を利用してあとは利用している変数のマッピングをtemplatefile
メソッドの引数で指定すればOKです。
output.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 Documentation templatefile - Functions - Configuration Language | Terraform | HashiCorp Developer Ansible vs. Terraform, clarified Providing Terraform with that Ansible Magic
...
Ansible → Terraformの方が順序性がわかりやすくて個人的に好き。
ansible/ansible
のansible_playbook
のリソースが使い勝手悪いせいもあるので、今後アップデートがかかることに期待。