IBM Cloudを用いたhttpタイプTerraform backend構成

Terraformのバックエンドとして、IBM Cloudを使用するドキュメントとして以下のブログ記事がありました。

Store Terraform states in Cloud Object Storage
https://www.ibm.com/cloud/blog/store-terraform-states-cloud-object-storage

( google翻訳 https://www-ibm-com.translate.goog/cloud/blog/store-terraform-states-cloud-object-storage?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp )

以下のような方法に関して記載しています。

- AWS S3 backendの仕組みを流用して、Terraformの状態をIBMCloudObjectStorageに保管する
- httpバックエンドを利用して、サーバーレスTerraformバックエンドでロックとバージョン管理を追加する

ここでは、後者の方法に関して githubにて手順が公開されていたので、どの様なものか使用してみました。

https://github.com/l2fprod/serverless-terraform-backend


(なお、local_fileリソースを使いたかったので今回は使用していませんが、IBM CloudではSchematicsというTerraformのサービスも提供していて、その場合にはバックエンドは自動的に管理されます。)


# 前提

- IBM Cloudアカウント・タイプ
少なくともリソース・グループを作成したりする為、アカウント・タイプは「従量課金」である必要がありました。
アカウント・タイプ「ライト (無料)」ではリソース・グループが１つのみの為、リソース・グループ追加のあたりで失敗しています。

追記：
010-prepare-backend/terraform.tfvarsに以下を追記することで、ライトプランのデフォルトのリソース・グループ(Default)にバックエンドを作成することが可能と思われます。(ライトプランでは未検証）

```
resource_group="Default"
```


# 検証環境

ローカルのTerraformを使用。

- Fedora Linux 35 (Workstation Edition)
- Terraform v1.1.9


# 手順

README.mdに沿って実行します。

https://github.com/l2fprod/serverless-terraform-backend/blob/master/README.md

## 検証用コードの git clone および IBM Cloud 定義情報の編集


```
$ git clone git@github.com:l2fprod/serverless-terraform-backend.git
...
```

```
$ cd serverless-terraform-backend
```

```
$ tree .
.
├── 010-prepare-backend  # <= httpバックエンド構成用tf
│   ├── backend.js
│   ├── main.tf
│   └── terraform.tfvars.template
├── 020-use-backend.     # <= httpバックエンドを利用するtf
│   ├── backend.tf
│   └── main.tf
├── LICENSE
├── README.md
├── terraform
│   ├── backend.tf
│   └── main.tf
└── xdocs
    ├── architecture.drawio
    └── step-010.png

4 directories, 11 files
```

```
$ cd 010-prepare-backend
```

```
$ cp terraform.tfvars.template terraform.tfvars
```

```
$ vi terraform.tfvars
...
```

```:terraform.tfvars
# IBM Cloud API key
ibmcloud_api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# Prefix for the resources to be created
basename="terraform-backend"

# Region where to create resources
region="us-south"
```

- ibmcloud_api_key
ユーザーやサービスID(「機能 ID」または「アプリケーション ID」のようなもの。ユーザーを表すためではなく、サービスの認証のために使用される。)に対応する、その権限での処理を行うためのkeyです。
IBM Cloudのログイン後のページにおける上部の「管理」を選択するとプルダウンしてくるサブメニューから「アクセス(IAM)」を選択後、左のサブメニューの「APIキー」を選択し、青い「IBM Cloud APIキーの作成」ボタンを押して作成可能です。

https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-iam-overview

- basename
このソリューションで作成される資源などの名前の接頭語となります。
検証環境固有の問題の可能性もありますが、元のserverless-terraform-backendではterraform apply時に以下のエラーとなった為、ここではterraform-backendとしています。

```
│ Error: BucketAlreadyExists: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.
```

- region
特に編集していません。us-southはダラスとなります。

## terraform applyにより、backendの為のサービスの定義、およびバックエンド使用時に必要な環境変数を設定するファイルを作成

```
$ terraform init 
...
```

```
$ terraform apply --auto-approve 
...

Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
```

```
$ ls
backend.js  main.tf  terraform.tfstate  terraform.tfvars  terraform.tfvars.template
```

```
$ awk '$1 == "resource"' main.tf | cat -n
     1	resource "ibm_resource_group" "group" {
     2	resource "ibm_resource_instance" "cos" {
     3	resource "ibm_resource_key" "cos_key" {
     4	resource "ibm_cos_bucket" "bucket" {
     5	resource "ibm_function_namespace" "namespace" {
     6	resource "ibm_function_package" "package" {
     7	resource "ibm_function_action" "backend" {
     8	resource "local_file" "backend-config" {
```

IBM Cloud上に、tfstateファイルを保持するバケット(terraform-backend-bucket)、tfstateファイルを保存するWeb Action(terraform-backend-package/backend)など7つのリソース、およびローカルファイルが1つ作成されている。
このapplyは、あくまで http バックエンドの準備を行う為のものであり、この際のterraform.tfstateは、ローカルに置かれる。


## 構成されたhttpバックエンドを使用したterraform applyのテスト

```
$ cd ../020-use-backend/

$ ls
backend.env  backend.tf  main.tf
```

backend.envは、先ほどのterraform applyにより作成された、httpバックエンドに必要な環境変数を設定する為のスクリプトファイル。

```:backend.env 
# TF_HTTP_ADDRESS points to the Cloud Functions action implementing the backend.
# It is reused for locking implementation too.
#
# env: name for the terraform state, e.g mystate, us/south/staging (.tfstate will be added automatically)
# versioning: set to true to keep multiple copies of the states in the storage
export TF_HTTP_ADDRESS="https://us-south.functions.cloud.ibm.com/api/v1/web/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/terraform-backend-package/backend?env=dev&versioning=true"
export TF_HTTP_PASSWORD="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# comment the following variables to disable locking
export TF_HTTP_LOCK_ADDRESS=$TF_HTTP_ADDRESS
export TF_HTTP_UNLOCK_ADDRESS=$TF_HTTP_ADDRESS
```


バックエンドは以下のtfファイルで定義
基本的にはここは変更しません。

```:backend.tf 
terraform {
  backend "http" {
    # See backend.env for configuration of the following fields:
    # address
    # lock_address
    # unlock_address
    # password

    # Do not change the following
    username = "cos"
    update_method          = "POST"
    lock_method            = "PUT"
    unlock_method          = "DELETE"
    skip_cert_verification = "false"
  }
}
```

メインのtfファイル

```:main.tf 
resource "local_file" "file" {
  content = "hello"
  filename = "hello.txt"
}
```



# 環境変数設定後、バックエンドにクラウド・ストレージを使用したアプライのテスト

```
$ source backend.env
```

bash 環境ならば、. backend.env でも可。

```
$ terraform init 

Initializing the backend...

Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v2.2.2...
- Installed hashicorp/local v2.2.2 (signed by HashiCorp)

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.
```

Initializing the backend...
Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.
より、httpバックエンドの構成に成功している。

```
$ terraform apply --auto-approve 
Acquiring state lock. This may take a few moments...

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:

  # local_file.file will be created
  + resource "local_file" "file" {
      + content              = "hello"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
local_file.file: Creating...
local_file.file: Creation complete after 0s [id=aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d]
Releasing state lock. This may take a few moments...

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

```
$ ls
backend.env  backend.tf  hello.txt  main.tf
```

意図通り、ローカルにはterraform.tfstateファイルが作成されていない。

```
$ cat hello.txt; echo
hello
```

IBM Cloud上のオプジェクト・ストレージを確認します。
IBM Cloudのダッシュボードからリソース・リストを選択

![スクリーンショット 2022-05-10 12.20.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187032/51be7404-caf4-abf1-4879-1b3a873615fc.png)

リソース・リストから作成されたオブジェクトストレージを選択


![スクリーンショット 2022-05-10 12.20.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187032/a5c14441-9da0-6ac5-ebee-9ab501b8c73f.png)

バケット内にtfstateファイルが作成されています。

![スクリーンショット 2022-05-10 12.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187032/de410d2c-5276-28bd-201c-5555a907be9c.png)

現在有効な tfstate ファイルは　states/named/dev.tfstate　です。
ここで、devは、backend.env における export TF_HTTP_ADDRESS= にて、env=dev として設定されています。


再度applyを行い、オプジェクト・ストレージを確認します。

```
$ terraform apply --auto-approve
Acquiring state lock. This may take a few moments...
local_file.file: Refreshing state... [id=aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d]

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.
Releasing state lock. This may take a few moments...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

```

![スクリーンショット 2022-05-10 12.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187032/7e122c23-d299-a9b8-b8ca-613429eec6c2.png)

以降、以下の様にバックアップされていきます。
versions/named/dev-2.tfstate
versions/named/dev-3.tfstate

ロジックは010-prepare-backend/backend.jsを参照。

# 環境変数を使用しない場合

tfファイルに直接書いてしまうことも可能。
passwordなどを書いて良いのか悩ましいですが。


```tf:backend.tf
terraform {
  backend "http" {
    address                = "https://us-south.functions.cloud.ibm.com/api/v1/web/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/terraform-backend-package/backend?env=dev&versioning=true"
    lock_address           = "https://us-south.functions.cloud.ibm.com/api/v1/web/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/terraform-backend-package/backend?env=dev&versioning=true"
    unlock_address         = "https://us-south.functions.cloud.ibm.com/api/v1/web/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/terraform-backend-package/backend?env=dev&versioning=true"
    password               = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    username               = "cos"
    update_method          = "POST"
    lock_method            = "PUT"
    unlock_method          = "DELETE"
    skip_cert_verification = "false"
  }
}

```

# 逆にできるだけ環境変数に逃す対応も可能か。

https://www.terraform.io/language/settings/backends/http

より、追加で

```
export TF_HTTP_USERNAME="cos"
export TF_HTTP_LOCK_METHOD="PUT"
export TF_HTTP_UNLOCK_METHOD="DELETE"
```

としておけば

```tf:backend.tf
terraform {
  backend "http" {
  }
}
```

でよさそう。

# 感想

思っていたより長いロジックを書かないと実装できないのが、悩ましい。
ただ動かしておけば後は楽か。


# nodejsのバージョン

nodejsのバージョンは010-prepare-backend/main.tfの118行目で設定されていますが、nodejs:10は現在非推奨で、このドキュメント記載時点(2022/05)ではnodejs:16が推奨となっていました。


```:010-prepare-backend/main.tf
113 # the backend implementation
114 resource "ibm_function_action" "backend" {
115   name = "${ibm_function_package.package.name}/backend"
116   namespace = ibm_function_namespace.namespace.name
117   exec {           
118     kind = "nodejs:16"  # <= nodejs:10(非推奨)から書き換え
119     code = file("backend.js")
120   }                
121   publish = true   
```

https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-runtimes&locale=en