---
title: "TerraformとAnsibleの連携手順：インベントリの受け渡しとactionsでの自動実行【2026年10月】"
url: "https://www.issoh.co.jp/tech/details/18475/"
published: 2026-10-18
updated: 2026-10-18
categories: ["IaC"]
publisher: "株式会社一創"
---

# TerraformとAnsibleの連携手順：インベントリの受け渡しとactionsでの自動実行【2026年10月】

TerraformとAnsibleの連携で決めることは、Terraformが作ったサーバーの情報をどうやってAnsibleのインベントリに渡すか、というインベントリへの情報の受け渡し方法の1点です。この記事では、EC2を2台作ってnginxを入れる構成を例に、`ansible_host`リソースとcloud.terraformのインベントリプラグインでstateを読む方式、amazon.awsの`aws_ec2`プラグインでタグから引く方式、Terraform 1.14で入ったactionsで`terraform apply`の中からPlaybookを流す方式の3つを、コピーして動かせるHCLとYAMLで示します。SSH待ちやホスト鍵などの詰まりどころと、連携させない方がよい構成も扱います。版番号は2026年10月時点の公式情報で確かめた内容です。

## まとめ：TerraformとAnsibleの連携はインベントリの渡し方を先に決める

役割分担は単純で、VPCやEC2のような箱をTerraformが作り、その中のOS設定やミドルウェアをAnsibleが入れます。迷うのは受け渡しの方式です。

- Terraformとサーバーが1対1で対応する小〜中規模の構成なら、`ansible_host`リソースでホストをstateに書き、cloud.terraformの`terraform_provider`プラグインで読む方式を既定にする
- Terraform以外（Auto Scalingや手作業）でもサーバーが増減するなら、`aws_ec2`プラグインでタグから引く方式に切り替える
- `terraform apply`1回で構成まで終わらせたいなら、Terraform 1.14以降のactionsで`ansible_playbook_run`を呼ぶ。ただしPlaybookの失敗をapplyの失敗として扱う運用を受け入れられる場合に限る
- `local-exec`で`ansible-playbook`を呼ぶ構成は新しく作らない

どの方式でも、SSHが通るまで待つ処理とホスト鍵の扱いは必ず書きます。

## 連携の前提：Terraformが作りAnsibleが構成する分担と4つの受け渡し方式

AnsibleとTerraformのどちらを採用するかという比較や、ChefやPuppetを含めた選定の話は[構成管理ツールの比較と選び方｜Ansible・Terraform・Chef・Puppet・OpenTofuの選定条件](https://www.issoh.co.jp/column/details/15293/)で扱っています。ここでは両方を使うと決めたあとの、つなぎ方だけを書きます。

### local-exec・state読取・タグ検索・actionsの4方式を並べた比較表

Terraformの出力をAnsibleへ渡す方法は、次の4つに整理できます。どれもTerraformとAnsibleの公式配布物だけで組めるもので、2018年頃によく紹介された`terraform-inventory`のような個人製ツールは含めていません。

| 方式                    | 誰がPlaybookを起動するか  | インベントリの出どころ        | 本記事の扱い     |
| --------------------- | ----------------- | ------------------ | ---------- |
| local-exec            | Terraform（apply中） | コマンド引数のIP          | 新規では選ばない   |
| ansible\_host＋state読取 | 人またはCI            | Terraformのstate    | 既定の方式      |
| aws\_ec2のタグ検索         | 人またはCI            | AWSのAPI            | 台数が変わる構成   |
| actions               | Terraform（apply中） | ansible\_inventory | 1.14以降で選択肢 |

分かれ目は、Playbookを起動するのがTerraformか外か、インベントリの正がstateかクラウド側の実体か、の2軸です。

### local-execでansible-playbookを呼ぶ構成を避ける2つの理由

EC2リソースの中に`provisioner "local-exec"`を書き、作成直後のIPを渡して`ansible-playbook`を呼ぶ例は今も検索上位に残っています。HashiCorpの[provisionerのドキュメント](https://developer.hashicorp.com/terraform/language/provisioners)は、provisionerを使う前にあらゆる代替手段を尽くすよう求めており、マシンイメージや構成管理が使えない場合の最後の手段という位置づけです。

実務上の理由は2つあります。1つは失敗時の扱いで、作成時のprovisionerが失敗するとリソースはtaintedとされ、次のapplyで作り直されます。Playbookの1タスクが落ちただけでEC2が破棄・再作成されるのは過剰です。もう1つは、provisionerは作成時にしか動かないため、Playbookだけ流し直す手段が無いことです。

## Terraform 1.16系・ansible-core 2.21系の検証環境

以降の例は、制御ノード1台（Linux・macOS・WSL）から東京リージョンにEC2を2台作り、nginxを入れる構成です。AWSの認証とS3バックエンドの設定は[TerraformでAWSを構築する手順｜aws 6系の認証とS3バックエンド・エラー対処](https://www.issoh.co.jp/tech/details/1900/)のとおりに済んでいる前提で、Ansible側のインストールと`ansible.cfg`は[Ansibleの使い方入門](https://www.issoh.co.jp/tech/details/18473/)の手順で用意します。

### ディレクトリ構成とrequired\_providersでのansibleの版固定

Terraformの最新は2026年10月18日時点で1.16.6です。actionsは[1.14.0（2025年11月19日）のリリースノート](https://github.com/hashicorp/terraform/blob/v1.14/CHANGELOG.md)で追加された機能なので、actionsを使う予定があれば`required_version`を1.14以上にします。Ansibleプロバイダは[Terraform Registry](https://registry.terraform.io/providers/ansible/ansible/latest/docs)上の`ansible/ansible`で、[GitHubのリリース](https://github.com/ansible/terraform-provider-ansible)では1.5.0（2026年6月）が最新です。

```
# ディレクトリ構成（TerraformとAnsibleを同じリポジトリに置く）
infra/
├── terraform/
│   ├── versions.tf
│   ├── main.tf
│   └── variables.tf
└── ansible/
    ├── ansible.cfg
    ├── inventory.yml
    └── site.yml

# terraform/versions.tf
terraform {
  required_version = ">= 1.14"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
    ansible = {
      source  = "ansible/ansible"
      version = "~> 1.5"
    }
  }
}
```

Ansibleプロバイダ自体はサーバーに何も作りません。インベントリ情報をstateに書く`ansible_host`・`ansible_group`、Playbookを動かす`ansible_playbook`とaction`ansible_playbook_run`、データソース`ansible_inventory`などを提供します。Playbookを実際に動かすのは手元の`ansible-playbook`なので、Terraformを実行するマシンにAnsibleも入れておきます。

## ansible\_hostリソースでTerraformのstateからインベントリを作る手順

最初に勧めるのが、このstate読取の方式です。Terraformが作ったEC2と同じapplyでインベントリ情報もstateに書くため、サーバーとインベントリがずれません。

### EC2の作成と同時にansible\_hostでグループと接続変数を登録する

[ansible\_hostのスキーマ](https://github.com/ansible/terraform-provider-ansible/blob/main/docs/resources/host.md)で必須なのはホスト名の`name`だけで、所属グループの`groups`と、任意のホスト変数を入れる`variables`を持てます。EC2の数に合わせて`count`で並べます。

```
# terraform/main.tf（VPC・サブネット・セキュリティグループは省略）
resource "aws_instance" "web" {
  count                  = 2
  ami                    = var.ami_id
  instance_type          = "t3.micro"
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [var.web_sg_id]
  key_name               = var.key_name

  tags = {
    Name = "web-${count.index + 1}"
    Role = "web"
  }
}

resource "ansible_host" "web" {
  count  = length(aws_instance.web)
  name   = aws_instance.web[count.index].public_ip
  groups = ["web"]

  variables = {
    ansible_user                 = "ec2-user"
    ansible_ssh_private_key_file = var.private_key_path
    ansible_ssh_common_args      = "-o StrictHostKeyChecking=accept-new"
  }
}
```

`name`にはSSHで到達できるアドレスを入れ、踏み台やVPN経由なら`private_ip`に替えます。`Role`タグは、タグ検索の方式へ移るときに同じ値でグループを作るために今のうちに付けておきます。

### cloud.terraformのterraform\_providerでstate読取設定

stateに書いた`ansible_host`を読むのは、Red Hatが配布する[cloud.terraformコレクション](https://github.com/ansible-collections/cloud.terraform)のインベントリプラグインです。コレクションの要件はansible-core 2.15以上・Python 3.9以上で、Ansible Galaxyでの最新は4.0.0です。

```
# コレクションを入れる
ansible-galaxy collection install cloud.terraform

# ansible/inventory.yml
plugin: cloud.terraform.terraform_provider
project_path: ../terraform
```

[プラグインのソース](https://github.com/ansible-collections/cloud.terraform/blob/main/plugins/inventory/terraform%5Fprovider.py)を読むと、stateファイルを直接開くのではなく`terraform show`を実行して中身を得ています。このため`project_path`のディレクトリで`terraform init`が済んでいる必要があり、S3バックエンドの場合はAnsibleを実行するユーザーにもstateを読むIAM権限が要ります。`project_path`はリストで複数のディレクトリを並べられ、子モジュールの中の`ansible_host`まで読むには`search_child_modules: true`を足します（既定は`false`）。stateに何が入っているかは[Terraform stateとは？tfstateの構造とS3バックエンド・移動削除の安全手順](https://www.issoh.co.jp/tech/details/16525/)で確認できます。

### ansible-inventory –graphでの所属確認とPlaybook実行

Playbookを流す前に、Terraformの出力がインベントリとして読めているかを必ず確かめます。

```
cd infra/terraform
terraform init
terraform apply

cd ../ansible
# webグループに2台のIPが並べば受け渡しは成功
ansible-inventory -i inventory.yml --graph --vars

# 疎通を確かめてからPlaybookを流す
ansible web -i inventory.yml -m ansible.builtin.ping
ansible-playbook -i inventory.yml site.yml
```

`--graph`の結果が`@ungrouped`だけになるときは、`project_path`の指す先が違うか、`search_child_modules`が`false`のままです。EC2を`terraform destroy`すれば`ansible_host`も消えるので、削除済みのサーバーにPlaybookが向かうことはありません。

## amazon.awsのaws\_ec2プラグインでタグからインベントリを引く手順

Auto Scalingのようにapplyと無関係にサーバーが増減する構成では、AWSのAPIに直接問い合わせます。

### Roleタグで絞るaws\_ec2.ymlの書き方とkeyed\_groupsのグループ化

[amazon.awsコレクションのaws\_ec2プラグイン](https://docs.ansible.com/projects/ansible/latest/collections/amazon/aws/aws%5Fec2%5Finventory.html)（ドキュメントは11.4.0時点）は、設定ファイル名の末尾が`aws_ec2.yml`か`aws_ec2.yaml`でないと読み込まれません。制御ノードには`boto3`と`botocore`の1.35.0以上が要ります。

```
# ansible/inventory.aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
filters:
  tag:Role: web
  instance-state-name: running
keyed_groups:
  - key: tags.Role
    prefix: role
hostnames:
  - ip-address
compose:
  ansible_user: "'ec2-user'"
```

`keyed_groups`により、`Role=web`のインスタンスは`role_web`グループに入ります。Playbookの`hosts`に指定するのも、接続対象が所属するこのグループ名です。`filters`で`running`に絞っておかないと、停止中や終了処理中のインスタンスにも接続しに行って失敗します。Terraform側の仕事は、`Role`タグを正しく付けることだけになります。

### state読取とタグ検索を選び分ける条件と両方式を混ぜない理由

選び分けは、サーバーの増減を誰が起こすかで決めます。

- EC2を`aws_instance`で1台ずつ書いていて、台数を変えるのも`terraform apply`だけなら、state読取の方式。AWSのAPI権限をAnsible側に渡さずに済む
- Auto Scalingや別チームの手作業でもサーバーが変わるなら、タグ検索の方式。stateに載らないサーバーも拾える

同じグループに両方の方式でホストを足すのは避けます。同じサーバーがIPとホスト名の2通りで登録され、Playbookが2回流れる原因になるためです。

## Terraform actionsでapply後にPlaybookを流す手順

Terraform 1.14で入ったactionsのansible\_playbook\_runを使うと、apply1回の中でPlaybookまで実行できます。

### ansible\_inventoryとaction\_triggerをつなぐHCL設定例

[ansible\_playbook\_runのドキュメント](https://github.com/ansible/terraform-provider-ansible/blob/main/docs/actions/playbook%5Frun.md)では、必須の引数は`playbooks`だけで、インベントリはJSONで渡す`inventories`かファイルを渡す`inventory_files`で指定します。JSONは`ansible_inventory`データソースで組み立てられます。起動のきっかけは、[actionsのドキュメント](https://developer.hashicorp.com/terraform/language/invoke-actions)にあるとおり、リソースの`lifecycle`の中に書く`action_trigger`です。

```
# terraform/actions.tf
data "ansible_inventory" "web" {
  group {
    name = "web"

    dynamic "host" {
      for_each = aws_instance.web
      content {
        name                     = host.value.public_ip
        ansible_user             = "ec2-user"
        ansible_private_key_file = var.private_key_path
        ansible_ssh_extra_args   = "-o StrictHostKeyChecking=accept-new"
      }
    }
  }
}

action "ansible_playbook_run" "configure_web" {
  config {
    playbooks   = ["${path.module}/../ansible/site.yml"]
    inventories = [data.ansible_inventory.web.json]
  }
}

# EC2の顔ぶれが変わったときだけPlaybookを流すための中継リソース
resource "terraform_data" "web_config" {
  input = aws_instance.web[*].id

  lifecycle {
    action_trigger {
      events  = [after_create, after_update]
      actions = [action.ansible_playbook_run.configure_web]
    }
  }
}
```

`action_trigger`を`aws_instance`に直接書くと、`count = 2`の各インスタンスごとにPlaybookが起動します。インスタンスIDの一覧を入力に持つ`terraform_data`に書けば、台数や置き換えが起きたapplyで1回だけ流れます。`ansible_inventory`の`json`属性はsensitive扱いなので、planの出力に接続情報が出ることはありません。

### \-invokeでの手動再実行とapply中にPlaybookが失敗したときの扱い

Playbookだけを変えた場合、EC2に変化が無いので`action_trigger`は動きません。そのときはactionを名指しで呼びます。

```
# EC2に変更が無くても、Playbookだけを流し直す
terraform apply -invoke=action.ansible_playbook_run.configure_web

# 変更を加えずに差分だけ見たいときは、actionの設定でcheck_modeとdiff_modeを有効にする
#   config { check_mode = true  diff_mode = true  ... }
```

actionsはstateに結果を残しません。Playbookが途中で落ちるとapplyが失敗として終わりますが、provisionerのようにEC2がtaintedになって作り直されることはありません。直して`-invoke`で流し直せば済みます。ただ、applyの成否にPlaybookの成否が混ざるため、インフラ変更の承認とミドルウェア設定の承認を別の人が行う組織では、この方式は合いません。その場合はstate読取の方式に戻して、2つのコマンドを別のジョブにします。

## SSH待ち・ホスト鍵・CIでの実行順まで含めたPlaybook側の作り込み

Terraformが作成完了と返した時点では、EC2のOSはまだ起動途中です。

### wait\_for\_connectionで起動直後のSSH失敗を防ぐPlaybook例

Playbookの冒頭でファクト収集を止め、[ansible.builtin.wait\_for\_connection](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/wait%5Ffor%5Fconnection%5Fmodule.html)でSSHが通るまで待ってから、改めてファクトを集めます。

```
# ansible/site.yml
- name: Webサーバーを構成する
  hosts: web
  gather_facts: false
  become: true
  tasks:
    - name: SSHで接続できるまで最大300秒待つ
      ansible.builtin.wait_for_connection:
        timeout: 300

    - name: 接続できてからファクトを集める
      ansible.builtin.setup:

    - name: nginxを入れる
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: nginxを起動して自動起動を有効にする
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true
```

`gather_facts: true`のままだと、待つ処理より先にファクト収集が接続を試みて落ちます。タグ検索の方式で`role_web`グループを使う場合は、`hosts`をそのグループ名に替えます。

### 新しいIPのホスト鍵をaccept-newで受け入れる設定と本番での注意点

Terraformで作り直すたびにIPとホスト鍵が変わるので、`known_hosts`に未登録の鍵で止まります。前の章の例で`-o StrictHostKeyChecking=accept-new`を入れたのはこのためです。OpenSSHの`accept-new`は、未登録の鍵は登録して進み、登録済みの鍵が変わったときだけ拒否します。

`host_key_checking = False`で検査そのものを止める例も見かけますが、接続先のすり替えも素通しにするので使いません。同じIPを使い回すElastic IPの構成では、作り直したサーバーの鍵が「変わった鍵」として拒否されるので、`terraform apply`の後に`ssh-keygen -R <IP>`で古い鍵を消す手順をジョブに入れます。

### CIでterraform apply後にansible-playbookを流す構成

state読取の方式をCIで流す場合、1つのジョブの中で次の順に実行します。AWSの認証をOIDCで行う設定とS3バックエンドのロックは[GitHub ActionsとTerraformでAWSのCI/CDを構築する手順](https://www.issoh.co.jp/tech/details/2715/)と同じです。

```
# CIジョブのステップ（GitHub ActionsやGitLab CIのrunに書く内容）
terraform -chdir=infra/terraform init
terraform -chdir=infra/terraform apply -auto-approve

pipx install "ansible-core==2.21.*"
ansible-galaxy collection install cloud.terraform

cd infra/ansible
ansible-inventory -i inventory.yml --graph
ansible-playbook -i inventory.yml site.yml
```

applyとPlaybookを別のジョブに分ける場合は、後のジョブでも`terraform init`を実行して初期化を済ませる手順が必要です。インベントリプラグインが`terraform show`を呼ぶためで、ここを忘れると「初期化されていない」というエラーでインベントリが空になります。PlaybookにVaultで暗号化した変数があるときは、復号パスワードをCIのシークレットから渡す手順が[Ansible Vaultの使い方](https://www.issoh.co.jp/tech/details/18471/)にあります。

## TerraformとAnsibleを連携させるべき構成と分けたまま運用すべき場面

採用と見送りの線を、条件付きで言い切ります。

### 連携を採用する条件：環境の作り直しとサーバー台数の変更が月1回以上

検証環境を案件ごとに作っては壊す、負荷試験のたびに台数を変える、といった使い方で、サーバーの作成から構成までを月に1回以上繰り返すなら連携させます。手作業でインベントリにIPを書き写す回数がそのまま事故の機会になるためです。この条件なら、state読取の方式で始め、Auto Scalingを入れた時点でタグ検索に移るのが手戻りの少ない順番です。

### 連携を見送る場面：コンテナ中心の構成とイメージ焼き込みで足りる構成

次の2つでは連携を作りません。1つはECSやKubernetesが中心の構成で、設定はコンテナイメージとタスク定義に入るためAnsibleの出番がありません。もう1つは、ミドルウェアの構成が月単位で変わらず、起動の速さを優先したい構成です。この場合はAnsibleで設定したイメージを[Packer](https://www.issoh.co.jp/tech/details/15578/)で事前に焼き、Terraformはそのイメージから起動するだけにします。起動のたびにPlaybookを流すより速く、起動時の失敗点も1つ減ります。

サーバーが3台以下で、年に数回しか作り直さないなら、連携の仕組みを作る工数の方が大きくなります。その規模では、`terraform output`でIPを出して静的なインベントリに書く運用で十分です。

### 社内で連携を組みきれないときに外部の支援を入れる作業範囲の決め方

TerraformとAnsibleを単体で書ける人がいても、CIの権限設計やVaultの鍵の受け渡しまで含めて1本のパイプラインにまとめる段階で止まる例は多くあります。止まっている箇所がCIと権限の設計なら、[DevOps・CI/CD導入支援](https://www.issoh.co.jp/service/system/devops/)のような外部の支援に、パイプラインの設計とレビューだけを任せる方法も選択肢です。Playbookの中身は社内に残すと、運用を引き取るときの負担が小さくなります。

## よくある質問

TerraformとAnsibleを組み合わせるときによく出る疑問に、2026年10月時点の仕様で答えます。

### TerraformだけでOSの設定までできるのに、Ansibleを併用する意味はありますか？

あります。`user_data`で渡すcloud-initのスクリプトは起動時に1回動くだけで、稼働中のサーバーの設定変更には使えません。パッケージ更新や設定ファイルの差し替えを冪等に繰り返すのはAnsibleの役割です。初回起動の最小設定だけを`user_data`に書き、残りをAnsibleに寄せます。

### AnsibleからTerraformを実行する構成でもよいですか？

使えます。cloud.terraformコレクションの`cloud.terraform.terraform`モジュールで、Playbookの中から`terraform apply`を実行できます。AWXなどのワークフローに作成から構成までを1ジョブで載せたい場合に向く構成です。planをプルリクエストでレビューする体制なら、本記事のようにTerraformを先に流す順番の方が素直です。

### terraform-inventoryはまだ使えますか？

2018年頃の記事で紹介されている`terraform-inventory`は個人が公開したツールで、新しいTerraformへの追従は保証されていません。同じことはcloud.terraformの`terraform_provider`プラグインと`ansible_host`で実現できるので、これから組むなら公式の組み合わせを選びます。

### ansible\_playbookリソースとansible\_playbook\_runアクションはどう使い分けますか？

`ansible_playbook`はリソースなので、実行結果の保存先はstateです。`replayable = true`にするとapplyのたびに作り直されてPlaybookが流れ、`false`なら最初のapplyで1回だけ流れます。一方の`ansible_playbook_run`はstateに何も残さず、`action_trigger`か`-invoke`で呼んだときだけ動く仕組みです。Terraformが1.14以上ならactionの方を選びます。

### OpenTofuでも同じ連携はできますか？

state読取とタグ検索の方式は、Terraform本体の機能に依存しないのでOpenTofuでも同じように組めます。cloud.terraformのプラグインは`binary_path`で`tofu`のバイナリを指定して試します。actionsはTerraform 1.14で入った機能なので、OpenTofuでの対応状況は使う版のリリースノートで確かめてから採用してください。

## 関連記事

- [IaCとは？Infrastructure as Codeの仕組み・メリットと導入判断を解説](https://www.issoh.co.jp/column/details/13004/)：TerraformとAnsibleを含むIaC全体の考え方と導入判断。
- [Ansibleとは？エージェントレスの仕組みとメリット・デメリット、Red Hat AAPの制約](https://www.issoh.co.jp/tech/details/6533/)：Ansibleがエージェントを入れずに動く仕組み。
- [Ansible Playbookの書き方と実行手順：–checkでのdry-runとhandlers【2.21系】](https://www.issoh.co.jp/tech/details/18457/)：本記事のsite.ymlを本番向けに育てるときの書き方。
- [Semaphore UIとは？Ansible・TerraformのWeb UIの導入手順と料金、AWXとの違い【v2.19】](https://www.issoh.co.jp/tech/details/12399/)：TerraformとAnsibleの実行をWeb画面から管理する選択肢。
- [構成管理ツールの比較と選び方｜Ansible・Terraform・Chef・Puppet・OpenTofuの選定条件](https://www.issoh.co.jp/column/details/15293/)：連携の前に、どのツールを使うかを決めるための比較。

---

出典: [TerraformとAnsibleの連携手順：インベントリの受け渡しとactionsでの自動実行【2026年10月】](<https://www.issoh.co.jp/tech/details/18475/>)（株式会社一創）
