TerraformのBackendにはいろいろある
Terraform Backend Types にあるように、TerraformのRemote Backendっていろいろあります。
- artifactory
- azurerm
- consul
- etcd
- etcdv3
- gcs
- http
- manta
- s3
- swift
とまぁこのくらい。
今回はこれについて調べてみました。
ちなみにデフォルトはLocal Backendになっていて、 terraform.tfstate
というファイルに保存されます。
http について調べてみる
実用的っぽいと思ったのはs3, swiftあたりだったのですが、あえていろいろ考えて http
を調べてみました。
ここにかかれている Example Usage
, Example Referencing
を見たのですが、全然分かりませんね。
何を送って、何を返してくれればOKなんでしょう、httpの場合。さっぱりです。
ということで、まずは簡単なテストサーバを立てて、それを Charles でキャプって見るとわかるかなと思いまして、こういうことをやりました。
まずtfファイルのbackend設定を変える
- 自分が今までTerraformで使っていた
.tf
があった - で、それに
backend
を単に足してみた
そうするとこうなります。
provider "openstack" {
auth_url = "*****"
user_name = "*****"
tenant_id = "*****"
tenant_name = "*****"
password = "*****"
user_domain_id = "*****"
project_domain_id = "*****"
}
terraform {
backend "http" {
address = "http://localhost.com:5000"
}
}
resource "openstack_compute_keypair_v2" "my_kp_1" {
name = "terraform-kp-1"
}
足したのは、
terraform {
backend "http" {
address = "http://localhost.com:5000"
}
}
の部分です。
localhost.com
というホスト名がbackend内に出てきますが、これはhostsで127.0.0.1に解決させています。
なんでlocalhostのままではダメなのか・・・というと、localhostのままだとCharlesでキャプチャしてくれないのですよね。
この解決策として、ブラウザからとかだと localhsot.
(末尾に".")でキャプチャしてもらえたりしますが、Terraformだとダメなようです。
次にリクエスト先を何かしら作る
次に localhost.com:5000
が何かしらHTTP的に着弾してくれないと困ります。
ということで、こんなFlaskアプリを作ってみました。
from flask import make_response, Flask
app = Flask(__name__)
@app.route('/')
def hello():
return make_response()
if __name__ == "__main__":
app.run(debug=True)
これを立ち上げておきます。
% python /path/to/flaskapp.py
そうすると localhost:5000
で待ち受けてくれるはずです。backendが出来ましたね。
terraform initしてみる
さて、この状態で、 terraform init
してみます。
% terraform init
Initializing the backend...
Initializing provider plugins...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.openstack: version = "~> 1.12"
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.
おお、なんだか成功しましたね。 terraform init
されたっぽいです。
この時何が起きていたか
この時のリクエスト/レスポンスをCharlesで見てみます。
terraform init時のリクエスト
GET / HTTP/1.1
Host: localhost.com:5000
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip
terraform init時のレスポンス
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Server: Werkzeug/0.14.1 Python/2.7.10
Date: Wed, 19 Dec 2018 08:49:27 GMT
Connection: close
なるほど。backendで指定したURLにGETが飛ぶわけですね。
init
時だからだと思いますが、レスポンスは空でOKのようです。
(まぁ、レスポンスはこういうふうにしか返さないってだけなんですけどね、Flaskが)
terraform applyしてみる
では次に terraform apply
してみましょう。
% terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ openstack_compute_keypair_v2.my_kp_1
id: <computed>
fingerprint: <computed>
name: "terraform-kp-1"
private_key: <computed>
public_key: <computed>
region: <computed>
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
openstack_compute_keypair_v2.my_kp_1: Creating...
fingerprint: "" => "<computed>"
name: "" => "terraform-kp-1"
private_key: "" => "<computed>"
public_key: "" => "<computed>"
region: "" => "<computed>"
openstack_compute_keypair_v2.my_kp_1: Creation complete after 1s (ID: terraform-kp-1)
Failed to save state: HTTP error: 405
Error: Failed to persist state to backend.
The error shown above has prevented Terraform from writing the updated state
to the configured backend. To allow for recovery, the state has been written
to the file "errored.tfstate" in the current working directory.
Running "terraform apply" again at this point will create a forked state,
making it harder to recover.
To retry writing this state, use the following command:
terraform state push errored.tfstate
エラーになりました。まぁそれはそうですよね。
Failed to save state: HTTP error: 405
Error: Failed to persist state to backend.
POST分作ってませんからね・・・。
さて、これをキャプチャしたCharles側で見てみます。
terraform apply時のリクエスト
POST / HTTP/1.1
Host: localhost.com:5000
User-Agent: Go-http-client/1.1
Content-Length: 3281
Content-Md5: BzoOkMeCZ1DPgz266a8Ckg==
Content-Type: application/json
Accept-Encoding: gzip
{
"version": 3,
"terraform_version": "0.11.10",
"serial": 1,
"lineage": "0159803e-4f38-ddaf-d403-4118c48cff6a",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"openstack_compute_keypair_v2.my_kp_1": {
"type": "openstack_compute_keypair_v2",
"depends_on": [],
"primary": {
"id": "terraform-kp-1",
"attributes": {
"fingerprint": "4d:6a:92:2c:f2:44:db:84:12:76:5d:e9:2d:d9:c7:06",
"id": "terraform-kp-1",
"name": "terraform-kp-1",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAA(略)-----END RSA PRIVATE KEY-----\n",
"public_key": "ssh-rsa AAAAB3NzaC(略) Generated-by-Nova\n",
"region": ""
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": "provider.openstack"
}
},
"depends_on": []
}
]
}
レスポンスは当然ながら405エラー(Method Not Allowed)なのでここには載せません。
つまるところ、
-
terraform init
すると、backendに対してはGETが飛ぶ。この時レスポンスBODYは空でOKである模様 -
terraform apply
した場合に、apply後のterraform.state相当のJSONがリクエストBODYとしてPOSTされる
という挙動であることがわかります。
http Backendの宛先はこんな挙動で良いのでは?
ということは、http Backend向けには、おそらくこういうWebアプリケーションを作ればいいということですね。
- POSTが飛んできたら、リクエストBODYをJSON化してストアしておく。これは常に上書く。
- GETが飛んできたら、ストアしたJSONをレスポンスBODYに入れて返す。もしストアしたデータがない場合は空レスポンスでOK
- あとロック/アンロックとかもあるようなので、これはどこかでステートごとにフラグでも持たせておいて、フラグに応じてレスポンスコードを変える
つまるところ、Backendのページに書かれていた・・・
Stores the state using a simple REST client.
State will be fetched via GET, updated via POST, and purged with DELETE. The method used for updating is configurable.
・・・という情報が、
- REST
- GET/POST/DELETE
って書いてんだから行間読め
と言っているのでしょう。まぁ、確かにそうなるわな。
しかしながら、実際にはユーザを識別するための何かしらとか、認証とか考えないといけないのでしょうけど(Provider自体の認証とは別にという意味です)、まずは大体の挙動はわかったので本記事はここまでにします。
そのうち、実際にこうすればいいんじゃないかという案を作って書いてみます。