Ansible provider for Terraformを試してみる

TerraformのAnsible providerがリリースされました。

https://twitter.com/Lopez/status/1648713234893922306

これにより、tfstateファイルからAnsible inventoryを動的に生成することが可能になります。
とても便利になりそうなので、さくらのクラウドで試してみます

https://www.ansible.com/blog/providing-terraform-with-that-ansible-magic
https://github.com/ansible/terraform-provider-ansible
https://registry.terraform.io/providers/ansible/ansible/latest/docs


今回はUbuntu ServerにWebサーバを構築するシナリオで進めていきます。

# 事前準備
## terraformの用意
以下のURLに従い用意します。

https://developer.hashicorp.com/terraform/downloads 

筆者は以下のバージョンを使用します。
```shell-session
$ terraform version
Terraform v1.4.0
```

## Pythonのバージョンの確認
PythonのバージョンはPython 3.8以降を使います。

筆者は以下のバージョンを使用します。
```shell-session
$ python -V
Python 3.10.11
```

## ansibleとモジュールのインストール
```shell
pip install ansible
ansible-galaxy collection install cloud.terraform
```

## ansibleからssh接続するための鍵生成
```shell
$ ssh-keygen -f ./id_rsa
```

# 1.必要なファイルの用意
以下の4ファイルを用意します。

こちらのリポジトリに置いています。

https://github.com/tar-xzvff/example-ansible-terraform-provider-for-sakura-cloud

```
$ ls -1
ansible.cfg
inventory.yml
main.tf
playbook.yml
```

## ansible.cfg
ansible実行時にSSH接続関連の警告や確認が発生するため出ないようにします。
```config
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
```

## inventory.yml
ansible側からtfstateから値を参照するために使用します
```yaml
plugin: cloud.terraform.terraform_provider
```

## main.tf
さくらのクラウドにサーバを作成するtfファイルです。
また、tfstateからansible inventoryを生成するためにansible_hostリソースを定義します。
```hcl
terraform {
  required_providers {
    ansible = {
      source  = "ansible/ansible"
      version = "~> 1.0.0"
    }
    sakuracloud = {
      source  = "sacloud/sakuracloud"
      version = "2.17.0"
    }
  }
}

provider "sakuracloud" {
  token  = "YOUR_TOKEN"
  secret = "YOUR_TOKEN_SECRET"
  zone   = "tk1a"
}

variable "password" {
  default = "P@ssw0rd"
}

data "sakuracloud_archive" "ubuntu" {
  os_type = "ubuntu2004"
}

resource "sakuracloud_disk" "web_server" {
  name              = "Web Server"
  source_archive_id = data.sakuracloud_archive.ubuntu.id
}

resource "sakuracloud_server" "web_server" {
  name        = "Web Server"
  disks       = [sakuracloud_disk.web_server.id]
  core        = 1
  memory      = 2
  description = "nginx"

  network_interface {
    upstream = "shared"
  }

  disk_edit_parameter {
    hostname        = "web-server"
    password        = var.password
    ssh_key_ids     = [sakuracloud_ssh_key.key.id]
    disable_pw_auth = true
  }
}

resource "sakuracloud_ssh_key" "key" {
  name       = "key"
  public_key = file("./id_rsa.pub")
}

resource "ansible_host" "web_server" {
  name   = sakuracloud_server.web_server.ip_address
  groups = ["web"]
  variables = {
    ansible_user                 = "ubuntu",
    ansible_ssh_private_key_file = "./id_rsa",
    ansible_python_interpreter   = "/usr/bin/python3"
    ansible_become               = "yes"
    ansible_become_method        = "sudo"
    ansible_become_pass          = var.password
  }
}
```

## playbook.yml
Webサーバをインストールします。
```yaml
- hosts: web
  tasks:
    - name: apt update
      apt:
        update_cache: yes
    - name: install nginx
      apt:
        name: nginx
        state: present
```

# 2.terraform initの実行
sakuracloudとansible providerがインストールされます。
```shell
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding sacloud/sakuracloud versions matching "2.17.0"...
- Finding ansible/ansible versions matching "~> 1.0.0"...
- Installing sacloud/sakuracloud v2.17.0...
- Installed sacloud/sakuracloud v2.17.0 (self-signed, key ID 96CEB4B93D86849D)
- Installing ansible/ansible v1.0.0...
- Installed ansible/ansible v1.0.0 (self-signed, key ID 7664CDD95312BDBD)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

3. terraform planの実行
`Plan: 4 to add` になることを確認します。
```shell
$ terraform plan
data.sakuracloud_archive.ubuntu: Reading...
data.sakuracloud_archive.ubuntu: Read complete after 1s [id=************]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # ansible_host.web_server will be created
  + resource "ansible_host" "web_server" {
      + groups    = [
          + "web",
        ]
      + id        = (known after apply)
      + name      = (known after apply)
      + variables = {
          + "ansible_become"               = "yes"
          + "ansible_become_method"        = "sudo"
          + "ansible_become_pass"          = "P@ssw0rd"
          + "ansible_python_interpreter"   = "/usr/bin/python3"
          + "ansible_ssh_private_key_file" = "./id_rsa"
          + "ansible_user"                 = "ubuntu"
        }
    }

  # sakuracloud_disk.web_server will be created
  + resource "sakuracloud_disk" "web_server" {
      + connector         = "virtio"
      + id                = (known after apply)
      + name              = "Web Server"
      + plan              = "ssd"
      + server_id         = (known after apply)
      + size              = 20
      + source_archive_id = "************"
      + zone              = (known after apply)
    }

  # sakuracloud_server.web_server will be created
  + resource "sakuracloud_server" "web_server" {
      + commitment        = "standard"
      + core              = 1
      + description       = "nginx"
      + disks             = (known after apply)
      + dns_servers       = (known after apply)
      + gateway           = (known after apply)
      + hostname          = (known after apply)
      + id                = (known after apply)
      + interface_driver  = "virtio"
      + ip_address        = (known after apply)
      + memory            = 2
      + name              = "Web Server"
      + netmask           = (known after apply)
      + network_address   = (known after apply)
      + private_host_name = (known after apply)
      + zone              = (known after apply)

      + disk_edit_parameter {
          + disable_pw_auth = true
          + hostname        = "web-server"
          + password        = (sensitive value)
          + ssh_key_ids     = (known after apply)
        }

      + network_interface {
          + mac_address     = (known after apply)
          + upstream        = "shared"
          + user_ip_address = (known after apply)
        }
    }

  # sakuracloud_ssh_key.key will be created
  + resource "sakuracloud_ssh_key" "key" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + name        = "key"
      + public_key  = <<-EOT
            ssh-rsa ****
        EOT
    }

Plan: 4 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

# 3.terraform applyの実行
`yes`を入力し実行します。
`Apply complete!`が表示されるまで待ちます。

# 4.試しにtfstateからinventoryを生成できるか確認
以下のコマンドでtfstateからinventoryを確認
```shell
$ ansible-inventory -i inventory.yml --graph --vars
```

tfstateの内容から生成できました。
```shell-session
$ ansible-inventory -i inventory.yml --graph --vars
@all:
  |--@ungrouped:
  |--@web:
  |  |--59.106.214.22
  |  |  |--{ansible_become = yes}
  |  |  |--{ansible_become_method = sudo}
  |  |  |--{ansible_become_pass = P@ssw0rd}
  |  |  |--{ansible_python_interpreter = /usr/bin/python3}
  |  |  |--{ansible_ssh_private_key_file = ./id_rsa}
  |  |  |--{ansible_user = ubuntu}
```

# 5.playbookの実行
以下のコマンドでansible playbookを実行します。
```shell
$ ansible-playbook -i inventory.yml playbook.yml
```

```shell-session
$ ansible-playbook -i inventory.yml playbook.yml

PLAY [web] *****************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [59.106.214.22]

TASK [apt update] **********************************************************************************************************************************************************************************************
changed: [59.106.214.22]

TASK [install nginx] *******************************************************************************************************************************************************************************************
changed: [59.106.214.22]

PLAY RECAP *****************************************************************************************************************************************************************************************************
59.106.214.22              : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

Webサーバが動いていることが確認できました。
```shell-session
$ curl 59.106.214.22 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

```

# 感想
tfstateからシームレスにinventoryが生成できてとてもいい感じになりました。

以前tfstateからinventoryを生成するスクリプトを書いていたりしてたので、そういった煩わしさがなくなるのでとても助かります。

今後ぜひ仕事でも活用していきたいところです。

# 補足
古いバージョンのPythonを使うと、古いバージョンのAnsibleを使うことになりサポートしていないバージョンになるためWARNINGが表示され、使うことができません。

```shell
$ ansible-inventory -i inventory.yml --graph --vars
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21) [Clang 6.0 (clang-600.0.57)]. 
This feature will be removed from ansible-core in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
[WARNING]: Collection cloud.terraform does not support Ansible version 2.11.12
...
@all:
  |--@ungrouped:

```