こんにちは、ネットアシスト運用チームのhaokiです。
今回は、さくらのクラウド向けのTerraformプロバイダーv3を実際に触ってみました。
普通に構築してみた、で終わらせるつもりだったのですが、検証の途中で「Plan: 0 to add, 1 to change, 0 to destroy」という一見安全な表示のまま、サーバが約2分間停止するという挙動に遭遇しました。planの出力からはそれが一切読み取れません。
セットアップでハマったポイントとあわせて、実測結果を紹介します。
検証環境
| 項目 | バージョン |
|---|---|
| Terraform | v1.15.8 |
| プロバイダー | sacloud/sakura v3.12.8 |
| 操作端末 | WSL2 |
| ゾーン | is1b(石狩第1) |
| サーバ | 1core / 1GB / SSD 20GB / Ubuntu |
Terraformプロバイダーv3とv2は別物です
まず押さえておきたいのが、さくらのクラウド向けTerraformプロバイダーには現在2つのバージョンが存在するという点です。
- v2:
sacloud/sakuracloud/ 2026年12月末でメンテナンス終了予定 - v3:
sacloud/sakura/ Terraform Plugin Framework ベースの新実装
両者に互換性はありません。ただしTerraform Registry上では別プロバイダーとして公開されているので、v2で管理中の構成が勝手にv3へ更新されることはなく、新規に使いたいリソースだけv3を使うといった段階的な移行も可能です。
これから始めるなら v3 が推奨されています。ここで一つ問題があって、ネット上の日本語記事はほとんどがv2ベースです。参考にしてそのまま書くと、まず間違いなくエラーになります。
プロバイダー設定
terraform {
required_providers {
sakura = {
source = "sacloud/sakura"
version = "~> 3.0"
}
}
}
provider "sakura" {
zone = "is1b"
}
APIキーは環境変数で渡します。tfファイルへの直書きは、GitHubなどへの誤公開リスクがあるため非推奨とされています。
export SAKURACLOUD_ACCESS_TOKEN="アクセストークン"
export SAKURACLOUD_ACCESS_TOKEN_SECRET="アクセストークンシークレット"
export SAKURACLOUD_ZONE="is1b"
ちなみに筆者は、これらを ~/.sakura-env にまとめて chmod 600 し、必要なときだけ source する形にしています。.bashrc に書きっぱなしにするより漏洩経路が減るのでおすすめです。
$ terraform init
Initializing provider plugins...
- Finding sacloud/sakura versions matching "~> 3.0"...
- Installing sacloud/sakura v3.12.8...
- Installed sacloud/sakura v3.12.8 (self-signed, key ID 8D264CA32277CF9A)
Terraform has been successfully initialized!
ハマりどころ1:ブロックが属性になっている
v2の記事を参考に、サーバリソースをこう書きました。
resource "sakura_server" "web01" {
name = "web01"
core = 1
memory = 1
network_interface { # ← v2の書き方
upstream = "shared"
}
}
結果はエラーです。
│ Error: Unsupported block type
│
│ 36: network_interface {
│
│ Blocks of type "network_interface" are not expected here.
│ Did you mean to define argument "network_interface"?
│ If so, use the equals sign to assign it a value.
v3はTerraform Plugin Frameworkで実装されているため、v2でブロック { } だったものが属性 = { } に変わっています。プレフィックスを sakuracloud_ から sakura_ に置換するだけでは動きません。
解決策:スキーマを直接見る
v3はまだ情報が少ないので、ドキュメントを探し回るよりプロバイダー自身が持つスキーマを見るのが早いです。
$ terraform providers schema -json > schema.json
$ jq '.provider_schemas["registry.terraform.io/sacloud/sakura"]
.resource_schemas.sakura_server.block.attributes.network_interface' schema.json
{
"nested_type": {
"attributes": { ... },
"nesting_mode": "list"
},
"optional": true
}
"nesting_mode": "list" とあるので、正解はリスト形式でした。一方 disk_edit_parameter は単一オブジェクトなので書き方が違います。同じ「属性化」でも形が2種類あるので、ここは推測せず確認したほうが早いです。
resource "sakura_server" "web01" {
name = "web01"
core = 1
memory = 1
disks = [sakura_disk.web01.id]
network_interface = [{ # リスト
upstream = "shared"
}]
disk_edit_parameter = { # 単一オブジェクト
hostname = "web01"
ssh_key_ids = [sakura_ssh_key.lab.id]
disable_pw_auth = true
}
}
このスキーマダンプ、型・必須/任意の別・説明文まで、実際にインストールしたバージョンの正確な情報が出てきます。password の説明に「12〜128文字」という制限まで書かれていて、これだけで実装できるレベルでした。新しいプロバイダーを触るときの定番手段として覚えておくと便利です。
おまけ:リソース名も変わっている
プレフィックスの変更だけでなく、リソース名自体がリネームされているものもあります。
| v2 | v3 |
|---|---|
| sakuracloud_load_balancer | sakura_dsr_lb |
| sakuracloud_proxylb | sakura_enhanced_lb |
| sakuracloud_vpc_router | sakura_vpn_router |
利用可能なリソース一覧も、同じくスキーマから取れます。
$ jq '.provider_schemas["registry.terraform.io/sacloud/sakura"].resource_schemas | keys' schema.json
ハマりどころ2:パスワードがtfstateに平文で残る
書き方を修正して、無事に apply が通りました。所要時間はディスク作成が2分15秒、サーバ作成が1分42秒で、合計約4分です。SSHでログインできることも確認できました。
ただ、apply中にこんな警告が出ていました。
│ Warning: Available Write-Only Attribute Alternative
│
│ 46: password = var.server_password
│
│ This attribute has a WriteOnly version disk_edit_parameter.password_wo
│ available. Use the WriteOnly version of the attribute when possible.
気になったのでstateファイルを確認してみます。
$ grep -o '"password"[^,]*' terraform.tfstate
"password": "**************"
$ ls -l terraform.tfstate
-rw-r--r-- 1 haoki haoki 6180 Aug 15 01:59 terraform.tfstate
サーバのOSログインパスワードが平文で保存されていました。しかもパーミッションは644なので、同じマシンの他ユーザーからでも cat するだけで読めてしまいます。
なお、ここで言うパスワードはAPIキーとは別物です。APIキーは環境変数で渡しているだけなのでstateには入りません。問題になっているのは、disk_edit_parameter で設定した「作成したサーバのOSログインパスワード」のほうです。
password_wo とは
Terraform 1.11で導入されたwrite-only属性です。値をプロバイダーに渡すだけで、stateファイルにもplanファイルにも記録しません。暗号化して隠すのではなく、そもそも書き込まないという考え方ですね。
ただ、記録しないということはTerraform側が現在の値を知らないということでもあります。そこで相棒になるのが password_wo_version です。
disk_edit_parameter = {
hostname = "web01"
password_wo = var.server_password # stateに残らない
password_wo_version = 1 # stateに残る
ssh_key_ids = [sakura_ssh_key.lab.id]
disable_pw_auth = true
}
パスワードを変更するときは、値と一緒にversionの数字を1つ上げます。Terraformは「versionが変わった」という事実だけを見て「新しい値を送り直そう」と判断する仕組みです。中身を知らないまま更新できる、というわけです。
書き換えて適用したあと、あらためて確認します。
$ grep -o '"password[^,]*' terraform.tfstate
"password": null
"password_wo": null
"password_wo_version": 1
平文が消えました。
「緊急時にパスワードが分からなくなるのでは?」
これは筆者も最初に思ったので補足しておきます。
password_wo が消すのはtfstate上の記録だけで、サーバ本体にはパスワードが設定されたままです。SSHが繋がらなくなったときにコントロールパネルのコンソールから入る、といった用途はこれまで通り使えます。
| 保存場所 | password | password_wo |
|---|---|---|
| サーバ本体(OS内) | 設定される | 設定される(変わらず) |
| tfstate | 平文で残る | 残らない |
| 手元の管理場所 | ある | ある |
そもそもtfstateは構成管理ファイルであって、パスワード台帳ではありません。値の正本はパスワードマネージャなどに置いて、Terraformには渡すだけ。緊急時はそちらから取得する、というのが本来の姿かと思います。
逆に言えば、パスワードの管理場所を用意しないまま _wo に移行すると本当に分からなくなりますので、そこはセットで考えてください。
ハマりどころ3:「0 destroyed」なのにサーバが止まる
さて、ここからが本題です。
先ほどの password → password_wo の書き換えですが、applyの前にplanを確認していました。
Terraform will perform the following actions:
# sakura_server.web01 will be updated in-place
~ resource "sakura_server" "web01" {
~ disk_edit_parameter = {
- password = (sensitive value) -> null
+ password_wo_version = 1
}
...
}
Plan: 0 to add, 1 to change, 0 to destroy.
update in-place で 0 to destroy。リソースは作り直されず、その場で更新されるだけ。安全そうに見えますよね。
実行してみます。
sakura_server.web01: Modifying... [id=113801823663]
sakura_server.web01: Still modifying... [id=113801823663, 00m10s elapsed]
...
sakura_server.web01: Modifications complete after 1m49s [id=113801823663]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
1分49秒。ちょっと長いなと思いつつSSHで接続すると、こうなりました。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
SSHホスト鍵が変わっています。これは怪しい。
journalctlで裏を取る
サーバにログインして起動履歴を確認しました。
$ journalctl --list-boots
IDX BOOT ID FIRST ENTRY LAST ENTRY
-1 cfca805cfc1a4827bdc4467a796677c1 Sat 2026-08-15 02:08:49 JST Sat 2026-08-15 02:14:05 JST
0 1a7f1e3cac1b4ec4818e3107b57806ff Sat 2026-08-15 02:15:56 JST Sat 2026-08-15 02:18:52 JST
$ uptime
02:17:07 up 1 min, 0 users, load average: 0.59, 0.29, 0.11
- 02:14:05 — 停止
- 02:15:56 — 起動
- ダウンタイム 1分51秒
サーバIDは 113801823663 のまま変わっていません。Terraform的には「同じリソースを更新しただけ」ですが、実際には内部でサーバ停止 → ディスク再編集 → 起動という処理が走っていたわけです。
disk_edit_parameter はディスク作成時の初期設定を注入する仕組みなので、後から変更するとディスクの再編集が必要になり、そのためにサーバを止める必要がある。理屈としては納得できます。問題は、それがplanの出力から一切読み取れないことです。
再現性を確認する
1回目がたまたまだった可能性もあるので、今度は password_wo_version を 1 から 2 に変更してみました。差分はたった1行です。
~ disk_edit_parameter = {
~ password_wo_version = 1 -> 2
}
Plan: 0 to add, 1 to change, 0 to destroy.
apply中に別ターミナルからSSHを試みると、こうなります。
ssh: connect to host 153.127.196.80 port 22: Connection refused
そして起動履歴です。
$ journalctl --list-boots
IDX BOOT ID FIRST ENTRY LAST ENTRY
-2 cfca805cfc1a4827bdc4467a796677c1 Sat 2026-08-15 02:08:49 JST Sat 2026-08-15 02:14:05 JST
-1 1a7f1e3cac1b4ec4818e3107b57806ff Sat 2026-08-15 02:15:56 JST Sat 2026-08-15 02:22:42 JST
0 fb71d84c7aae45e6a38e6c45e46e04bb Sat 2026-08-15 02:24:02 JST Sat 2026-08-15 02:24:17 JST
02:22:42に停止、02:24:02に起動。ダウンタイムは1分20秒でした。
差分1行のin-place updateでも、しっかりサーバは止まります。再現性ありです。
よかった点
指摘ばかりになってしまったので、公平に書いておきます。v3プロバイダーは以下の点で好印象でした。
- 余計な差分が出ない:何も変更せずに
planを実行すると、きちんとNo changes.が返ります - 警告が親切:
password_woの存在をちゃんと教えてくれました。無視した筆者が悪いです - スキーマが整備されている:説明文に文字数制限まで書かれていて、
providers schemaだけで実装できます - 再現性:destroy → apply で約4分。同じ構成が確実に戻ってきます
一点だけ気になったのは、plan出力で hostname や mac_address など実際には変わらない値まで (known after apply) と表示されることです。ノイズが多く、本当に変わる箇所が埋もれてしまう印象でした。
まとめ
- さくらのクラウド向けTerraformプロバイダーはv3が推奨。ただしv2とは互換性がなく、既存の日本語記事はほぼv2ベースなのでそのままでは動きません
- v3ではブロック
{ }が属性= { }に変わっており、リストか単一オブジェクトかの違いもあります disk_edit_parameter.passwordはtfstateに平文で残ります。password_woを使えば記録されません0 destroyedは無停止を意味しません。in-place updateでも1〜2分のダウンタイムが発生しました
今回の検証でハマりやすいポイントをまとめると:
- v2の記事をコピペしても動かない。書き方が変わっているので
terraform providers schema -jsonで確認するのが確実 sakuracloud_load_balancer→sakura_dsr_lbのように、リソース名自体がリネームされているものがある- tfstateはパーミッション644で作られる。ローカルstateのまま放置しない。
.gitignoreへの追加も必須 password_woへの移行作業自体がダウンタイムを伴う。「コード整理のついでに」やると事故ります- パスワード変更(versionのインクリメント)でも毎回停止する。運用設計に織り込んでおく
- 停止を伴う変更かどうかはplanから読めない。本番反映の前に検証環境で同じ変更をapplyし、
journalctl --list-bootsで確認するのが確実です
「Terraformで管理しているから安全」ではなく、「planを読めるようになって初めて安全」ということを、身をもって確認した検証でした。
次回は、コントロールパネルから手動で設定変更を加えた場合に、v3プロバイダーがどこまでその差分(ドリフト)を検知できるのかを検証してみたいと思います。
後片付けも忘れずに。検証用のサーバとディスクは停止していても課金対象なので、終わったら terraform destroy で削除しておきましょう。
$ terraform destroy
さくらのクラウドを使ったサーバーの構築・監視・運用体制の整備についてご相談がある方は、ぜひネットアシストにご連絡ください。
それではまた!