はじめに
AnsibleとTerraformを組み合わせると楽できそうだったので、AnsibleのTerraformモジュールを使ってみました。
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モジュールを使ったPlaybook
terraform apply相当のPlaybookです。
ログを確認したいため、実行結果をデバッグ表示します。
--- - hosts: localhost gather_facts: no tasks: - name: terraform apply community.general.terraform: project_path: 'terraform/' # Terraform ディレクトリのルートへのパス force_init: yes # terraform initの実行 state: present register: result - name: debug debug: msg: "{{ result }}"
terraform apply相当のPlaybook実行
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 $
terraform apply相当のPlaybook実行(2回目)
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 $
terraform destroy相当のPlaybook実行
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 を実行する