2026/11/07〜08 時点の検証結果です。Terraform v1.17.1、hashicorp/aws v6.72.0、HCP Terraform Agent v1.33.2 を使用しています。
前回の記事で、-minimal-refresh で plan の API の時間を減らせることを確かめました。
ただ、同じ構成を HCP Terraform の Workspace で plan すると、ローカルでは 6 秒ほどの plan が、Run の完了まで 1 分を超えます。
API の時間を減らしても、この差の大部分は残りそうです。
そこで、HCP Terraform の Run の時間が何に使われているのかを、Run の時刻と Terraform のログから分解しました。
あわせて、手元の PC をセルフホストの Agent にすると、どこが縮むのかも確かめています。
結論
ローカルでは約 6 秒の plan が、HCP Terraform のリモート実行では plan の開始から完了まで約 70 秒かかりました。
時間は「plan の前(init など)」「plan」「plan のあと」に、ほぼ 3 等分されていました。
手元の PC をセルフホストの Agent にすると、約 41 秒に縮みました。
一番縮んだのは plan の中(約 26 秒 → 約 9 秒)で、スキーマの取得と AWS API がローカルと同じくらいになりました。
plan の前後も少し縮みましたが、Agent でも合わせて 30 秒ほど残ります。
| 区間 | ローカル | HCP Terraform リモート実行 |
HCP Terraform Agent(手元の PC) |
|---|---|---|---|
| plan の前(init など) | - | 約 26 秒 | 約 19 秒 |
| plan | 約 6 秒 | 約 26 秒 | 約 9 秒 |
| plan のあと | - | 約 20 秒 | 約 12 秒 |
| plan の開始 〜 完了 | - | 70.0 秒 | 41.0 秒 |
| キュー待ち(plan の開始まで) | - | 2.0 秒 | 7.6 秒 |
300 リソースの構成で、5 回ずつ計測した中央値です。
内訳の求め方は後述します。リモート実行は実行環境のログが見られないため、内訳は Terraform のログから出した値です。
検証の構成
前回と同じく、料金のかからない AWS リソース(VPC、サブネット、Security Group)だけを使っています。
- 共通の VPC とサブネットは別の state で作り、VPC の ID などは変数で渡す(data source は使わない)
- サービスごとのモジュール(Security Group 1 つ + ルール 2 本)を 100 回呼ぶ。300 リソース
- AWS のリージョンは東京(ap-northeast-1)。plan の refresh で呼ぶ API は、どこで plan を動かしても東京リージョンに送られる
この構成を、下記の 3 つの方法で plan しました。
- ローカル: Windows 11 の PC(Core i7-1280P、14 コア 20 スレッド)で
terraform plan - HCP Terraform のリモート実行: CLI 駆動の Workspace で、HCP Terraform が用意する実行環境で動かす
- HCP Terraform の Agent: 同じ Workspace の実行モードを
agentにして、手元の PC の Docker Desktop で動かしたhashicorp/tfc-agentで実行する
① ローカルでは、PC の terraform が直接 AWS の API を呼びます。
② と ③ では、CLI は設定を HCP Terraform にアップロードするだけで、plan は HCP Terraform の実行環境か、PC の Agent で動きます。どちらも、plan の中で state を HCP Terraform から読み込みます。
HCP Terraform から AWS への認証は、動的クレデンシャル(OIDC で IAM ロールを引き受ける)です。
Workspace は 1 つにして、実行モードを計測のたびに API で切り替えました。
3 つの方法を順番に 1 回ずつ実行し、ラウンドごとに順番をずらして 5 回ずつ計測しています(plan の前に毎回 20 秒待つ)。
計測の方法
Run の時刻
Run と Plan の API の status-timestamps(秒単位)から、キュー待ち(Run の作成 〜 plan の開始)と、plan の開始 〜 完了の時間を出しました。
Agent の場合は、Agent のコンテナのログに Run の段階(Running terraform init、Running terraform plan など)が出るので、そこからも内訳を出しています。
リモート実行は、実行環境のログが見られないので、この内訳は分かりません。
Terraform のログ
Workspace の環境変数に TF_LOG=DEBUG を入れると、HCP Terraform で動いている Terraform のログも CLI に流れてきます。
このログを、ローカルと同じ方法で区間に分けました。
区切りに使ったのは、下記の行です。それぞれの行が Terraform のどの処理から出ているかは、v1.17.1 のソースコードで確かめました。
| 区切りにしたログの行 | 次の行までにしていること |
|---|---|
CLI command args |
backend の準備と、キャッシュ済みの provider のパッケージを lock ファイルのハッシュと照合する |
checking for provisioner in |
state の読み込み、設定ファイルの読み込み、モジュールツリーの構築 |
provider: starting plugin(1 回目) |
provider を起動してスキーマを取得し、終了する |
Building and walking validate graph |
validate。provider をもう一度起動して、設定を検証する |
backend/local: plan calling Plan |
plan のグラフを作る |
Starting graph walk: walkPlan |
provider を 3 回目に起動し、ConfigureProvider(認証など)をしてから refresh を始める |
provider は 1 回の plan で 3 回起動します。
1 回目に取得したスキーマはプロセスの中でキャッシュされ、2 回目と 3 回目の起動では取り直さないことも、TF_LOG=TRACE のログ(returning cached schema)で確認しました。
TF_LOG を入れると、plan のあとの処理が遅くなる
ところが Agent で試すと、TF_LOG=DEBUG を入れたときだけ、plan のあとの処理が大きく延びていました。
Agent のコンテナのログから出した時間です(1 回ずつ)。
| Agent の段階 |
TF_LOG=DEBUG あり |
TF_LOG なし |
|---|---|---|
| init | 18.7 秒 | 18.1 秒 |
| plan | 9.4 秒 | 9.3 秒 |
| plan の JSON の作成とアップロード | 2.7 秒 | 2.5 秒 |
| provider のスキーマの JSON の作成とアップロード | 10.8 秒 | 8.1 秒 |
| 秘匿値を伏せた plan の JSON の作成とアップロード | 54.4 秒 | 1.5 秒 |
TF_LOG ありのときは、Agent のログに failed unmarshalling modules json output: invalid character '-' after top-level value というエラーも出ていました。
JSON の出力にログの行が混ざって、あとの処理に影響しているように見えます(原因は確かめていません)。
そこで、全体の時間は TF_LOG なしで、Terraform の中の内訳だけを TF_LOG ありで測り、分けて扱っています。
なお、リモート実行の「plan の開始 〜 完了」は、TF_LOG ありでも約 71 秒で、なしの約 70 秒とほとんど変わりませんでした。
リモート実行の Run の内訳
TF_LOG ありで測った内訳です(5 回の中央値)。まず Run 全体を 3 つに分けます。
| 区間 | リモート実行 |
|---|---|
| plan の前(plan の開始 〜 Terraform のログの最初の行) | 25.6 秒 |
| plan(Terraform のログの範囲) | 26.5 秒 |
| plan のあと(Terraform のログの最後の行 〜 plan の完了) | 19.7 秒 |
| 合計 | 71.8 秒 |
合計は、TF_LOG なしで測った 70.0 秒とほぼ同じでした。
次に、plan(Terraform のログの範囲)の中の内訳です。ローカルの列は、同じ時間帯に実行した 5 回の結果です。
| plan の中の区間 | ローカル | リモート実行 |
|---|---|---|
| ハッシュの照合など | 0.57 秒 | 2.20 秒 |
| 設定と state の読み込み | 0.03 秒 | 0.37 秒 |
| スキーマの取得(1 回目の起動) | 1.14 秒 | 5.14 秒 |
| validate(2 回目の起動) | 0.37 秒 | 1.40 秒 |
| plan の準備(3 回目の起動) | 0.43 秒 | 3.60 秒 |
| API(refresh 300 リクエスト) | 2.78 秒 | 13.40 秒 |
| 合計(Terraform のログの範囲) | 5.56 秒 | 26.49 秒 |
合計には、表にない区間(Terraform の起動や、API のあとの処理など。合わせて 0.5 秒前後)も含みます。
ローカルとの違いは下記です。
- plan が始まる前に約 26 秒かかる。この間のログは出ないので、内訳は分かりません。CLI には
Initializing plugins and modules...とだけ表示されるので、init(provider のダウンロードなど)を含む準備の時間だと思います。ドキュメントによると、Run の実行環境は 1 回ごとに使い捨ての VM なので、provider は毎回ダウンロードしていそうです
Changes made to the worker during a run are not persisted to subsequent runs, since the VM is destroyed after the run is completed.
- スキーマの取得が約 5.1 秒と、ローカルの数倍かかる。ログには
GOMAXPROCS=2と出ていて(ローカルは 20)、Terraform から見える CPU が 2 つしかないためだと思います(確かめていません)。GOMAXPROCSは、既定ではプロセスから見える論理 CPU の数になります。なお、同じドキュメントには、実行環境の VM の仕様は保証されたものではないとも書かれているので、この数字は今回の計測時点のものです - plan の準備が約 3.6 秒。動的クレデンシャルの AssumeRoleWithWebIdentity に約 1.5 秒、GetCallerIdentity に約 0.6 秒かかっていた(ローカルの GetCallerIdentity は約 0.1 秒)
- API が約 13 秒と、ローカルの約 5 倍。1 リクエストの往復(ログの
http.duration)は、ローカルの中央値が約 57 ms、リモート実行が約 200 ms でした。今回のリソースは東京リージョンにあるので、API はどこで plan を動かしても東京リージョンのエンドポイントに送られます。リモート実行の環境から東京リージョンまでのネットワークの距離が、1 リクエストごとの往復を長くしているのだと思います(実行環境の場所はドキュメントに書かれていないので、本記事では扱いません)。ただ、往復の差は約 3.5 倍なので、それだけでは説明しきれません - plan が終わってから完了まで、さらに約 20 秒かかる。こちらも内訳は分かりません
セルフホストの Agent で変わったこと
ローカルの計測と同じ PC(日本国内、Windows 11)の Docker Desktop で Agent を動かしました。
PC の CPU は Core i7-1280P(14 コア 20 スレッド)です。Docker Desktop が使う WSL2 の VM は、CPU の数を制限していない既定の状態なので、Windows と同じ 20 個の論理 CPU が見えます(Terraform のログでも GOMAXPROCS=20)。
ただし専有ではなく、Windows 側と同じ CPU を分け合っています。
plan の中
TF_LOG ありで測った、Terraform のログの範囲の内訳です(5 回の中央値)。
| 区間 | ローカル | リモート実行 | Agent |
|---|---|---|---|
| ハッシュの照合など | 0.57 秒 | 2.20 秒 | 1.87 秒 |
| 設定と state の読み込み | 0.03 秒 | 0.37 秒 | 2.03 秒 |
| スキーマの取得 | 1.14 秒 | 5.14 秒 | 1.26 秒 |
| validate | 0.37 秒 | 1.40 秒 | 0.45 秒 |
| plan の準備 | 0.43 秒 | 3.60 秒 | 1.49 秒 |
| API | 2.78 秒 | 13.40 秒 | 3.29 秒 |
| 合計(Terraform のログの範囲) | 5.56 秒 | 26.49 秒 | 11.18 秒 |
Agent の合計は、次の節の Agent のログでは 9.12 秒です。こちらは TF_LOG を入れて測っているので、その分だけ長くなっていると思います。
- スキーマの取得と API は、ローカルと同じくらいになりました。API の 1 リクエストの往復も、中央値が約 57 ms とローカルと同じでした。Agent はローカルと同じ PC(日本国内)で動いているので、東京リージョンまでの距離もローカルと同じです
- plan の準備は 1.49 秒と、リモート実行の半分以下です。AssumeRoleWithWebIdentity は約 1.1 秒でした
- 設定と state の読み込みは、逆に 2.03 秒と長くなりました。
TF_LOG=TRACEで 1 回確かめると、reading remote stateの行から次の行までが 1.78 秒でした。HCP Terraform で動かすときは、plan の中で state を HCP Terraform から読み込んでいるので、日本国内の Agent からだと時間がかかるのだと思います
plan の前とあと
TF_LOG なしで、Agent のコンテナのログから出した内訳です(5 回の中央値)。
| 区間 | Agent の段階 | 時間 |
|---|---|---|
| plan の前 | ジョブの受信 〜 init の開始 | 0.82 秒 |
| init | 18.01 秒 | |
| plan | plan | 9.12 秒 |
| plan のあと | plan の JSON の作成とアップロード | 2.49 秒 |
| provider のスキーマの JSON の作成とアップロード | 7.86 秒 | |
| 秘匿値を伏せた plan の JSON の作成とアップロード | 1.74 秒 | |
| 合計 | 40.04 秒 |
区間ごとに足すと、plan の前が 18.8 秒、plan が 9.1 秒、plan のあとが 12.1 秒です(結論の表の値)。
合計が「plan の開始 〜 完了」の 41.0 秒より少し短いのは、API の時刻が秒単位のためです。
- init は Agent でも毎回約 18 秒かかります。ドキュメントには、Run ごとに空の一時ディレクトリで実行すると書かれているので、provider を毎回ダウンロードしているのだと思います(ダウンロードのログは見ていません)
Each execution occurs in its own temporary directory with a clean environment
- plan のあとの JSON の作成とアップロードで、合わせて約 12 秒かかります。リモート実行の「plan が終わってから完了まで約 20 秒」と同じ種類の処理だと思いますが、リモート実行の内訳は見られないので確かめていません
- キュー待ちは 1.6〜11.6 秒とばらつきました(リモート実行は 0.9〜3.1 秒)。Agent がジョブを取りに行く間隔によるものかもしれません(確かめていません)
Agent を使うときに気づいたこと
- Agent が動くのは Linux(x86_64 / ARM64)だけなので、Windows では Docker で動かしました
- Agent から HCP Terraform への接続は外向きの HTTPS だけなので、受信のためにポートを開ける必要はありません
- Free プランでも Agent は使えますが、登録できるのは 1 台まででした。2 台目を登録しようとすると
Cannot register more than 1 agents in your organization.で失敗します。止めたコンテナの Agent も、状態が変わって削除するまでは 1 台として数えられていました - Agent は起動時に自動で更新されます。バージョンを固定したい場合は
TFC_AGENT_AUTO_UPDATE=disabledを指定します
補足: ローカルの plan の API の前の時間
ローカルでも、API を呼び始めるまでに 2.5〜4.3 秒かかっていました(計測した時間帯で変わります)。
主な内訳は、provider のパッケージと lock ファイルのハッシュの照合、スキーマの取得、3 回の起動で、どれも aws provider(Windows 版の実行ファイルは約 820 MB)の大きさによるものでした。
ドキュメントでは、ハッシュの照合は provider を install するとき(terraform init)の動作として書かれています。
Terraform will also verify that each package it installs matches at least one of the checksums it previously recorded in the lock file, if any, returning an error if none of the checksums match
ソースを見ると、plan の実行時にも、キャッシュ済みのパッケージ(.terraform/providers の下)を毎回照合しています。
あわせて、ローカルで条件を変えて測った結果は下記です(5 回ずつの中央値)。
| 条件 | API の前の時間 |
|---|---|
| 300 リソース | 4.31 秒 |
| 30 リソース | 3.74 秒 |
| 300 リソース + aws の alias(us-east-1) | 4.55 秒 |
| 300 リソース + hashicorp/random | 4.10 秒 |
| hashicorp/random だけ(aws なし) | 0.49 秒 |
- resource の数(30 / 300、state は 41 KB / 412 KB)では、ほとんど変わりませんでした
- aws の alias を足すと、起動する provider のプロセスが増える分だけ長くなりました
- aws provider を使わない構成では 0.5 秒でした
まとめ
HCP Terraform のリモート実行では、plan の開始から完了まで約 70 秒かかりました。
plan が始まる前(init など)と、plan のあとに合わせて 45 秒ほどかかり、plan の中でも、見える CPU が 2 つしかないことや、実行環境から東京リージョンの API までの往復の長さで、ローカルの約 5 倍の時間がかかっていました。
手元の PC(日本国内)をセルフホストの Agent にすると、plan の中はローカルに近づき、plan の開始から完了までが約 41 秒に縮みました。
ただ、init と plan のあとの処理は Agent でも残るので、ローカルの約 6 秒には届きません。
AWS のリージョンに近い場所で、CPU に余裕のある Agent を使えば、HCP Terraform の Run の待ち時間を緩和できる可能性があります。
今回の Agent は手元の PC でしたが、EC2 などに置いた場合も、リージョンと CPU の条件が近ければ同じ傾向になると思います(検証していません)。
また、-minimal-refresh などで API の時間を減らしても、HCP Terraform では前後の時間が残るので、plan 全体に対する効果はローカルより小さく見えると思います(HCP Terraform で -minimal-refresh は試していません)。
参考