TerraformとAnsibleを役割分担させる設計|プロビジョナーを使わずにterraform applyとAnsible Playbookを連携するCI/CDパターン

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Terraform > TerraformとAnsibleを役割分担させる設計|プロビジョナーを使わずにterraform applyとAnsible Playbookを連携するCI/CDパターン
「Terraformでサーバーを建てた後、Ansibleで設定したい。でもterraform provisionerでAnsibleを呼ぶのが正しいのか、それとも別々に実行すべきなのか判断がつかない」 Terraformは「インフラのプロビジョニング」に強く、Ansibleは「サーバーの構成管理」に強いツールです。2つを組み合わせることで、それぞれの得意領域を活かしつつ、手動作業なしに再現性の高い環境構築が実現できます。 この記事では、Terraform provisionerを使わない安全な連携設計と、GitHub ActionsでTerraform→Ansibleを順次実行するCI/CDパターンを解説します。想定環境はTerraform 1.x系、Ansible 2.15以降、AWS上のEC2インスタンスです。

この記事のポイント

・Terraform provisionerでAnsibleを呼ぶのはアンチパターン——設計の問題点を解説する
・terraform outputでIPを取り出し、Ansibleのインベントリに動的に渡すのが正しい連携設計
・GitHub ActionsでTerraform jobとAnsible jobを順次実行してCI/CDで自動化できる
・AWS SSMセッションマネージャー経由でSSHなしにAnsibleを実行できる設計も選択肢になる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

TerraformとAnsibleはなぜ役割を分けるべきか

Terraformが得意とするのはAWSのリソース定義(VPC・EC2・RDS・IAMなど)の宣言的管理です。一方Ansibleが得意とするのは、起動したサーバーに対してパッケージのインストール・設定ファイルの配置・サービスの起動を行う構成管理です。 この2つの役割は本質的に異なります。 ・Terraform:インフラの「何を」「どこに」「いくつ」作るかを管理する(プロビジョニング層)
・Ansible:起動したサーバーの中身を「どうする」かを管理する(構成管理層)
この役割分担を守ることで、Terraformのtfstateがサーバー内部の状態を管理しなくて済み、AnsibleのPlaybookがインフラの差分計算に巻き込まれることもなくなります。

Terraform provisionerをAnsible起動に使ってはいけない理由

Terraform provisionerにはlocal-execとremote-execがあり、EC2作成後にAnsibleを起動することは技術的には可能です。しかしHashiCorp自身が「provisionerは最後の手段であり、本来の用途ではない」と明示しており、以下の問題があります。 ・冪等性の崩壊:Terraformのapplyが失敗してリトライすると、provisionerが2回実行されてAnsibleが重複適用されることがある
・エラーが捕捉しづらい:Ansible Playbookの失敗がTerraformのapplyエラーとして報告されるため、原因の切り分けが難しくなる
・stateに副作用が残る:provisioner実行の成否がtfstateに記録されず、次回applyでの挙動が予測しにくくなる
・SSH依存が増える:remote-execはEC2のSSHポートが開いている前提で動作するため、SSMベースのセキュア設計と相性が悪い
代わりにCI/CDパイプラインでTerraformとAnsibleを別ステップとして順次実行する設計が推奨されます。

terraform outputでEC2のIPをAnsibleに渡す設計

TerraformとAnsibleを連携する鍵は、terraform outputコマンドでEC2のIPアドレスを取り出し、Ansibleのインベントリとして渡すことです。

1. TerraformでEC2のIPをoutputに定義する

# outputs.tf output "web_instance_ips" { description = "Web servers public IP list" value = aws_instance.web[*].public_ip } output "db_instance_ip" { description = "DB server private IP" value = aws_instance.db.private_ip }

aws_instance.web[*].public_ipはfor_eachやcountで複数EC2を作成した場合にIPのリストを返します。

2. terraform outputからAnsibleのインベントリを生成する

Terraform applyが完了した後、terraform output -jsonで出力をJSON形式で取り出し、シェルスクリプトでAnsibleのインベントリファイルに変換します。

#!/bin/bash # generate-inventory.sh # terraform outputのJSON出力からAnsibleのINI形式インベントリを生成する WEB_IPS=$(terraform output -json web_instance_ips | jq -r '.[]') DB_IP=$(terraform output -raw db_instance_ip) # [web]セクションにIPを追記してインベントリを生成する echo "[web]" > inventory.ini echo "${WEB_IPS}" >> inventory.ini echo "" >> inventory.ini echo "[db]" >> inventory.ini echo "${DB_IP}" >> inventory.ini echo "" >> inventory.ini echo "[all:vars]" >> inventory.ini echo "ansible_user=ec2-user" >> inventory.ini echo "ansible_ssh_private_key_file=~/.ssh/deploy.pem" >> inventory.ini

CI/CD環境ではjqコマンドが必要です。GitHub Actionsのubuntu-latestランナーには標準でインストールされています。

3. Ansible Playbookを実行する

# site.yml の例(Webサーバーにnginxをインストール) --- - hosts: web become: true roles: - nginx - php-fpm - app-deploy - hosts: db become: true roles: - mariadb - database-init

# 実行コマンド例 ansible-playbook -i inventory.ini site.yml --check # ドライラン確認 ansible-playbook -i inventory.ini site.yml # 本番実行 # 出力例(環境により異なります) # PLAY [web] ************************************* # TASK [nginx : Install nginx] ******************* # ok: [10.0.1.100] # TASK [nginx : Enable nginx service] ************ # changed: [10.0.1.100] # PLAY RECAP ************************************* # 10.0.1.100 : ok=12 changed=3 unreachable=0 failed=0

GitHub ActionsでTerraform→Ansible順次実行パイプラインを設計する

CI/CDでTerraformとAnsibleを自動連携する場合、1つのワークフローファイルに2つのjobを定義してneedsで依存関係を宣言します。 Terraformの実践的なIaC設計については、Linux Master Pro Seminar(Terraform・IaC実践)で体系的に学べます。

# .github/workflows/deploy.yml name: Infrastructure and Configuration Deploy on: push: branches: [main] jobs: terraform: name: Terraform Apply runs-on: ubuntu-latest outputs: web_ips: ${{ steps.tf_output.outputs.web_ips }} steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 with: terraform_version: "1.9.0" - name: Terraform Init run: terraform init env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Terraform Apply run: terraform apply -auto-approve - name: Get Terraform Outputs id: tf_output run: | echo "web_ips=$(terraform output -json web_instance_ips)" >> "$GITHUB_OUTPUT" ansible: name: Ansible Configuration runs-on: ubuntu-latest needs: terraform # terraform jobが完了してから実行 steps: - uses: actions/checkout@v4 - name: Install Ansible run: pip3 install ansible - name: Generate Inventory from Terraform Output run: | echo '[web]' > inventory.ini echo '${{ needs.terraform.outputs.web_ips }}' | jq -r '.[]' >> inventory.ini echo '' >> inventory.ini echo '[all:vars]' >> inventory.ini echo 'ansible_user=ec2-user' >> inventory.ini - name: Run Ansible Playbook run: | ansible-playbook -i inventory.ini site.yml env: ANSIBLE_HOST_KEY_CHECKING: "false"

needs: terraformを指定することで、Terraform jobが失敗した場合はAnsible jobが実行されません。インフラのプロビジョニングに失敗したままAnsibleが動いて接続エラーを大量に出す、という問題を防げます。

AWS SSM経由でAnsibleを実行するSSHレス設計

EC2にSSHポートを開けたくない場合、AWS Systems Manager Session Manager経由でAnsibleを実行できます。community.aws.aws_ssmconnectionプラグインを使う方法です。

# inventory.ini(SSM経由の場合) [web] i-0abc1234defg5678 # インスタンスIDで指定(IPではなく) [all:vars] ansible_connection=community.aws.aws_ssm ansible_aws_ssm_region=ap-northeast-1 ansible_user=ec2-user

SSM接続を使う場合は、EC2インスタンスにAmazonSSMManagedInstanceCoreポリシーを付けたIAMロールが必要です。TerraformでIAMロールを作る際に設定してください。

# Terraform側でSSMアクセス用IAMロールを定義する例 resource "aws_iam_role_policy_attachment" "ssm_managed" { role = aws_iam_role.web_instance.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" }

トラブルシュート:AnsibleがホストにUNREACHABLEを返す場合

「UNREACHABLE: Failed to connect to the host」が出る場合

・セキュリティグループ:CIのrunner IPからポート22が許可されているか確認する
・SSH鍵の改行コード:GitHub SecretsのSSH_PRIVATE_KEYが正しいPEM形式かどうか確認する(base64エンコードして保管すると改行問題を回避できる)
・インスタンスの起動待ち:EC2がrunning状態でも、SSHdが起動するまで数秒かかる。Playbookの冒頭にwait_for_connectionを設けると安全

# Playbookの冒頭にwait_for_connectionを追加する例 - hosts: web gather_facts: false tasks: - name: Wait for SSH to become available ansible.builtin.wait_for_connection: delay: 10 timeout: 120 - hosts: web become: true roles: - nginx

「terraform output」で空の値が返る場合

TerraformがまだapplyされていないかEC2が作成される前にoutputを取得しようとすると、値が空になります。GitHub ActionsではTerraform applyのstepが完了した後のstepでoutputを取得しているか順序を確認してください。

本記事のまとめ

役割 ツール 担当する領域
インフラのプロビジョニング Terraform VPC・EC2・RDS・IAMなどAWSリソースの作成・変更・削除
サーバーの構成管理 Ansible パッケージインストール・設定ファイル配置・サービス起動
連携の橋渡し terraform output + シェル EC2のIPをAnsibleのインベントリに動的に渡す
CI/CDでの実行順序 GitHub Actions(needs) Terraform成功後にAnsibleを実行する依存関係の宣言
SSHレス設計 AWS SSM + Ansible ポート22を開けずにAnsibleでEC2を設定管理する
TerraformとAnsibleは競合するツールではなく、プロビジョニング層と構成管理層を分担する補完関係にあります。provisioner依存を排除してCI/CDでの順次実行設計に切り替えることで、インフラ変更とアプリ設定変更のどちらに問題があるかが明確になり、障害対応の速度が上がります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformとAnsibleを組み合わせたCI/CD設計から現代のサーバー管理まで体系的に学べる講座を用意しています。詳しくは>>Linux Master Pro Seminar(Terraform・IaC実践)をご覧ください。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。