zaki work log

作業ログやら生活ログやらなんやら

Ansibleからterraform applyを実行するタスクの実装

Terraformでインフラをプロビジョニングしたあとに上物に対してAnsibleで何か(アプリをインストールしたりデータを投入したり)処理をするようなことが多いので、AnsibleからTerraformを実行するようにして一発で処理できるようにするにはどうするか実装を確認してみた。

Ansibleモジュールはcommunity.general.terraformを使用する。

docs.ansible.com

applydestroyを間違えてたか所を修正(23:55)

project_pathのパス指定

/path/to/tffiles 以下でterraform applyをする構成であれば以下の通り。

  - name: apply terraform
    community.general.terraform:
      project_path: /path/to/tffiles
      state: present

このタスクを実行すると、/path/to/tffiles以下でterraform apply -auto-approveを実行するのと同様の動作になる。
TerraformのコードをAnsibleとは別リポジトリで管理してあり、AnsibleからTerraformのコードをgit cloneしてapplyするような構成であればこれで良いと思う。

一方で、構成によってはTerraformのコードをAnsibleと同じディレクトリで管理する場合もあるかと思う。その場合は実行に使うプレイブックからの相対パスproject_pathを指定する。
ただ、現時点でのcommunity.general version 8.3.0だと、ディレクトリ名だけだとパスを認識してくれないため、末尾に/を付与する必要があるっぽい。

例えば以下のディレクトリ構成でtffiles以下でterraform applyを実行する想定の場合。

.
├── inventory.ini
├── playbook.yml
└── tffiles
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    └── variables.tf

タスクのコードとしては以下の通り。

  - name: apply terraform
    community.general.terraform:
      project_path: tffiles/
      state: present

このときにproject_pathに単にtffilesと指定すると、以下エラーとなる。

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Path for Terraform project can not be None or ''."}

コード見ればわかるんだろうけど、末尾に/が無いとエラーになるのはなぜだろうね。
ちなみに./tffilesでもOK

変数指定

Terraformでvariableを使った変数定義については、community.general.terraformモジュールのvariablesパラメタで指定できる。

  - name: apply terraform
    community.general.terraform:
      project_path: ./tffiles
      state: present
      variables:
        infra_region: "{{ region }}"
        app_ami_id: "{{ app_ami }}"
        db_ami_id: "{{ db_ami }}"

この定義で、Terraform側で定義している変数infra_regionapp_ami_iddb_ami_idをAnsibleのタスク側から上書きする動作になる。

出力変数

Terraformでプロビジョニングされたリソースの情報を取得するためのoutputを使った定義については、Ansibleではタスクの実行結果の戻り値として参照できる。なので、registerディレクティブを使って後続のタスクなどで参照できる。

output "route_table_id" {
    value       = aws_route_table.example_rtb.id
    description = "ID of Route Table"
}

output "vpc_id" {
    value       = aws_vpc.example_vpc.id
    description = "ID of VPC"
}

output "nic_id" {
    value       = aws_network_interface.example_ni.id
    description = "ID of Network Interface"
}

こんなoutput定義を行っておけば、タスク定義でregisterを使って戻り値を参照でき、クラウドリソースのIDやIPアドレスを使った後続のタスクに使用できる。
以下は、AWSのルートテーブルを書き換える例。
(デフォルトで作成されるルートテーブルは作成の段階でカスタマイズできないため、作成済みのリソースをAnsibleで更新している)

  - name: apply terraform
    community.general.terraform:
      project_path: ./tffiles
      state: present
    register: result

  - name: update routetable
    amazon.aws.ec2_vpc_route_table:
      region: "{{ region }}"
      route_table_id: "{{ result.outputs.route_table_id.value }}"
      lookup: id
      vpc_id: "{{ result.outputs.vpc_id.value }}"
      routes:
        - dest: 10.1.0.0/16
          network_interface_id: "{{ result.outputs.nic_id.value }}"

環境

$ cat /etc/fedora-release 
Fedora release 39 (Thirty Nine)
$ terraform providers -version
Terraform v1.7.4
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.38.0
$ ansible --version
ansible [core 2.16.3]
  config file = None
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/venv/ansible9/lib64/python3.12/site-packages/ansible
  ansible collection location = /home/zaki/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/zaki/venv/ansible9/bin/ansible
  python version = 3.12.2 (main, Feb  7 2024, 00:00:00) [GCC 13.2.1 20231205 (Red Hat 13.2.1-6)] (/home/zaki/venv/ansible9/bin/python)
  jinja version = 3.1.3
  libyaml = True
$ ansible-galaxy collection list community.general

# /home/zaki/venv/ansible9/lib/python3.12/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 8.3.0  

お、今年初めてプレイブック書いた気がするぞ。