はじめに
AnsibleとTerraformを組み合わせると楽できそうだったので、AnsibleのTerraformモジュールを使ってみました。
community.general.terraform module – Manages a Terraform deployment (and plans) — Ansible Documentation
terraformとansibleを機能と用途から比較してみた、今の感想です。
- Terraformはtfstateもあり作ったリソース(ガワ)を管理しやすいが、中身の構築はremote-exec(Ansibleのshellモジュール相当)しかない。
- Ansibleはモジュールが冪等性を持っているので中身を構築しやすいが、リソース(ガワ)の管理は外部に任すことになる。
環境
- Ubuntu: 22.04.1
- ansible core: 2.13.6
- terraform: 1.3.4
- community.general: 6.0.0
tfファイル
tfファイルは、ubuntuを1台立てます。
outputでパブリックIPを表示させ、どのような感じでログが出力されるかも確認します。
provider "aws" {
region = "ap-northeast-1"
access_key = "XXXXXX"
secret_key = "XXXXXX"
}
resource "aws_instance" "mito-ec2" {
ami = "ami-03f4fa076d2981b45"
instance_type = "t2.micro"
associate_public_ip_address = "true"
tags = {
Name = "mito-ec2"
}
}
output "ec2_ip"{
value = aws_instance.mito-ec2.public_ip
}
terraform apply相当のPlaybookです。
ログを確認したいため、実行結果をデバッグ表示します。
---
- hosts: localhost
gather_facts: no
tasks:
- name: terraform apply
community.general.terraform:
project_path: 'terraform/'
force_init: yes
state: present
register: result
- name: debug
debug:
msg: "{{ result }}"
Playbook実行前のディレクトリ構造です。
terraformのプラグインはまだダウンロードしていません。
$ tree
.
├── terraform
│ └── main.tf
└── terraform_module.yml
1 directory, 2 files
$
terraform apply相当のPlaybookを実行します。
結果、outputsのパブリックIPが無事表示されました。これなら扱いやすいです。
terraformコマンドのようにApply complete!
も表示されました。
$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ****************************************************************************************
TASK [terraform apply] **********************************************************************************
changed: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmp8yoeiy_e.tfplan",
"failed": false,
"outputs": {
"ec2_ip": {
"sensitive": false,
"type": "string",
"value": "3.113.21.61"
}
},
"state": "present",
"stderr": "",
"stderr_lines": [],
"stdout": "aws_instance.mito-ec2: Creating...aws_instance.mito-ec2: Still creating... [10s
elapsed]aws_instance.mito-ec2: Still creating... [20s elapsed]aws_instance.mito-ec2: Creation
complete after 22s [id=i-02d5e9a2f24dc2de8]Apply complete! Resources: 1 added, 0 changed, 0
destroyed.Outputs:ec2_ip = \"3.113.21.61\"",
"stdout_lines": [
"aws_instance.mito-ec2: Creating...",
"aws_instance.mito-ec2: Still creating... [10s elapsed]",
"aws_instance.mito-ec2: Still creating... [20s elapsed]",
"aws_instance.mito-ec2: Creation complete after 22s [id=i-02d5e9a2f24dc2de8]",
"",
"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.",
"",
"Outputs:",
"",
"ec2_ip= \"3.113.21.61\""
],
"workspace": "default"
}
}
PLAY RECAP **********************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
ディレクトリ構造をみると、プラグインもダウンロードされています。
$ tree -a
.
├── terraform
│ ├── .terraform
│ │ └── providers
│ │ └── registry.terraform.io
│ │ └── hashicorp
│ │ └── aws
│ │ └── 4.39.0
│ │ └── linux_amd64
│ │ └── terraform-provider-aws_v4.39.0_x5
│ ├── .terraform.lock.hcl
│ ├── main.tf
│ └── terraform.tfstate
└── terraform_module.yml
8 directories, 5 files
$
Playbookを再実行してみます。
Taskの実行結果はokで、ログNo changes
も表示されました。
問題なさそうです。
$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ****************************************************************************************
TASK [terraform apply] **********************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": {
"changed": false,
"command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/
tmprz34gv4k.tfplan",
"failed": false,
"outputs": {
"ec2_ip": {
"sensitive": false,
"type": "string",
"value": "3.113.21.61"
}
},
"state": "present",
"stderr": "",
"stderr_lines": [],
"stdout": "aws_instance.mito-ec2: Refreshing state... [id=i-02d5e9a2f24dc2de8]No changes. Your
infrastructure matches the configuration.Terraform has compared your real infrastructure against
your configurationand found no differences, so no changes are needed.",
"stdout_lines": [
"aws_instance.mito-ec2: Refreshing state... [id=i-02d5e9a2f24dc2de8]",
"",
"No changes. Your infrastructure matches the configuration.",
"",
"Terraform has compared your real infrastructure against your configuration",
"and found no differences, so no changes are needed."
],
"workspace": "default"
}
}
PLAY RECAP *********************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
Playbookのstateを、presentからabsentに書き換えて実行します。
結果、applyと同様に、terraformコマンドの結果が表示され、Destroy complete!
も確認できました。
plan結果も出力されています。
$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ****************************************************************************************
TASK [terraform destroy] ********************************************************************************
changed: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"command": "/usr/bin/terraform destroy -no-color -auto-approve -lock=true",
"failed": false,
"outputs": {},
"state": "absent",
"stderr": "",
"stderr_lines": [],
"stdout": "aws_instance.mito-ec2: Refreshing state... [id=i-02d5e9a2f24dc2de8] Terraform used the
selected providers to generate the following executionplan. Resource actions are indicated with
the following symbols: - destroy Terraform will perform the following actions: # aws_instance.
mito-ec2 will be destroyed - resource \"aws_instance\" \"mito-ec2\" { -
ami = \"ami-03f4fa076d2981b45\" -> null -
(略)
Plan: 0 to add, 0 to change, 1 to destroy.
Changes to Outputs: - ec2_ip = \"3.113.21.61\" -> null aws_instance.mito-ec2:
Destroying... [id=i-02d5e9a2f24dc2de8] aws_instance.mito-ec2: Still destroying...
[id=i-02d5e9a2f24dc2de8, 10s elapsed] aws_instance.mito-ec2: Still destroying...
[id=i-02d5e9a2f24dc2de8, 20s elapsed] aws_instance.mito-ec2: Still destroying...
[id=i-02d5e9a2f24dc2de8, 30s elapsed] aws_instance.mito-ec2: Destruction complete after
40s Destroy complete! Resources: 1 destroyed.",
"stdout_lines": [
"aws_instance.mito-ec2: Refreshing state... [id=i-02d5e9a2f24dc2de8]",
"",
"Terraform used the selected providers to generate the following execution",
"plan. Resource actions are indicated with the following symbols:",
" - destroy",
"",
"Terraform will perform the following actions:",
"",
" # aws_instance.mito-ec2 will be destroyed",
" - resource \"aws_instance\" \"mito-ec2\" {",
" - ami = \"ami-03f4fa076d2981b45\" -> null",
(略)
" - root_block_device {",
" - delete_on_termination = true -> null",
" - device_name = \"/dev/sda1\" -> null",
" - encrypted = false -> null",
" - iops = 100 -> null",
" - tags = {} -> null",
" - throughput = 0 -> null",
" - volume_id = \"vol-0a085b087b420fd22\" -> null",
" - volume_size = 8 -> null",
" - volume_type = \"gp2\" -> null",
" }",
" }",
"",
"Plan: 0 to add, 0 to change, 1 to destroy.",
"",
"Changes to Outputs:",
" - ec2_ip = \"3.113.21.61\" -> null",
"aws_instance.mito-ec2: Destroying... [id=i-02d5e9a2f24dc2de8]",
"aws_instance.mito-ec2: Still destroying... [id=i-02d5e9a2f24dc2de8, 10s elapsed]",
"aws_instance.mito-ec2: Still destroying... [id=i-02d5e9a2f24dc2de8, 20s elapsed]",
"aws_instance.mito-ec2: Still destroying... [id=i-02d5e9a2f24dc2de8, 30s elapsed]",
"aws_instance.mito-ec2: Destruction complete after 40s",
"",
"Destroy complete! Resources: 1 destroyed."
],
"workspace": "default"
}
}
PLAY RECAP **********************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
初回実行時にinitしていない場合のエラー
Playbookの初回実行時にforce_init: yes
がない場合、プラグインがないため、以下のエラーメッセージが出力されます。
$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ****************************************************************************************
TASK [terraform apply] **********************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": "/usr/bin/terraform validate", "msg": "╷Error:
Missing required providerThis configuration requires provider registry.terraform.io/hashicorp/aws,but
that provider isn't available. You may be able to install itautomatically by running: terraform init╵",
"rc": 1, "stderr": "╷Error: Missing required providerThis configuration requires provider registry.
terraform.io/hashicorp/aws,but that provider isn't available. You may be able to install itautomatically
by running: terraform init╵", "stderr_lines": ["╷", "Error: Missing required provider", "", "This
configuration requires provider registry.terraform.io/hashicorp/aws,", "but that provider isn't
available. You may be able to install it", "automatically by running:", " terraform init", "╵"],
"stdout": "", "stdout_lines": []}
PLAY RECAP **********************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
$
備考
Terraformモジュールは、デフォルト設定のパラメータがいくつかあります。
一部のデフォルト値と内容をメモします。
- force_init: no
- yesの場合、terraform initを実行する
- check_destroy: no
- yesの場合、applyによりdestroyのみが発生する場合は実行しない。ただしdestroyしてaddする場合は実行する
- yesでも、stateがabsentの場合(terraform destroy)は実行する
- overwrite_init : yes
- project_pathに、tfstateが既に存在する場合でも init を実行する