はじめに
私は現在、SaaS型ECサイト構築プラットフォームのフロントエンド開発に従事しており、New Relicを活用したオブザーバビリティの推進にも携わっています。
前回の記事では、管理外ドメイン監視の日次Slack通知をNew Relic Workflow Automationで構築しました。前回記事の中で、この仕組みは作って終わりではなく、除外条件(既知の無害なドメイン)を地道に追加し続ける継続的チューニングが不可欠だと書きました。
具体的には、除外ドメインの追加、通知文言の調整、しきい値の見直しといった細かい変更が継続的に発生します。そして、そのたびにUIで本番の定義を直接編集する運用には限界があります。
UI上ではversion番号として履歴は残るものの、Gitのように差分を確認したり、Pull Requestで事前レビューしたりする運用には向きません。
本記事ではその続編として、UIで作り込んだWorkflow Automation定義をTerraform管理へ移行した手順を、実際に踏んだ罠とあわせて共有します。
移行の目的は、定義の変更をGitで管理し、Pull Requestでレビューし、terraform planで影響範囲を確認したうえで本番に反映できるフローを整えることです。さらに、設定をコードとしてリポジトリ内で管理しておくことで、IDE上のAIコーディングエージェントに細かな運用改善を依頼し、その変更提案を通常のコード変更と同じようにdiffで確認・レビューできます。
先に結論です。
- Workflow Automationは、newrelicプロバイダー v3.83.0(2026-03-30)で公式リソース
newrelic_workflow_automationに対応済み12 - importに対応しているため、既存定義を削除・再作成せず、定義や実行スケジュールに変更を加えずに取り込める
- Workflow AutomationのYAML定義をTerraform管理する目的であれば、NerdGraph mutationを直接実行する回避策(graphqlプロバイダー等)を作り込む必要はない
Workflow Automationは2026年2月にGAとなった比較的新しい機能で3、日本語の情報がまだ少なく、「Terraformは未対応」という前提で回避策を検討し始めている方がいるかもしれません(私は実際に回避策の実装に着手してから公式対応に気づきました)。この記事がその手戻りを防げれば幸いです。
対象読者
- New Relic Workflow Automationを運用中、または導入を検討している方
- UIで育てた設定資産をTerraform(IaC)管理へ移行したい方
- 稼働中のリソースを止めずにTerraformへ取り込む実践例を知りたい方
検証環境(2026年7月時点)
| 項目 | バージョン |
|---|---|
| Terraform | v1.x系(importブロックを使用するため v1.5 以上) |
| newrelic/newrelic プロバイダー | v3.94.0 |
| リージョン | US |
前提の整理
紛らわしい別リソースに注意
newrelic_workflow というリソースが以前から存在しますが、これはアラートの通知ルーティング(Workflows)用で、Workflow Automationとは別物です。名前が紛らわしいので、検索時はご注意ください。本記事で扱うのは newrelic_workflow_automation です2。
プロバイダーのバージョン要件
newrelic_workflow_automation はv3.83.0で追加され、v3.93.1でドキュメントが整備されています1。本記事では、ドキュメント整備後のバージョンを前提とします。バージョン制約が ~> 3.0 のような書き方であれば、terraform init -upgrade でアップグレードできます。
terraform {
required_providers {
newrelic = {
source = "newrelic/newrelic"
version = ">= 3.93.1"
}
}
}
Step 1: 既存定義のエクスポート
既存定義のYAMLは、UIの Settings > Versions のプレビューでバージョンごとに確認できます。
当初はこの画面からのコピーで済ませるつもりでしたが、実際にやってみるとエディタ上での選択・コピーの操作性が悪く、コピー漏れや改行の欠落といった手作業由来の混入のリスクも高そうでした。そこで、NerdGraphで取得する方法に切り替えました。APIから取得した内容をそのままGitに入れられるため、結果的にこちらの方が確実でした。
New Relic UIの Settings > Versions の画面で、明示的なエクスポート機能は見当たりませんでした。
上記は、2026年7月時点の仕様です。
まず一覧クエリで、定義の name / version / scope.id を確認します4。
※また、以下の例では、1234567をNew RelicのアカウントIDに置き換えてください。
curl -s https://api.newrelic.com/graphql \
-H "Content-Type: application/json" \
-H "API-Key: $TF_VAR_newrelic_api_key" \
-d '{"query":"{ actor { account(id: 1234567) { workflowAutomation { workflows { results { definition { name version scope { id } } } } } } } }"}' \
| jq .
続いて、確認したname/versionでYAML本文を取得し、ファイルに保存します5。
curl -s https://api.newrelic.com/graphql \
-H "Content-Type: application/json" \
-H "API-Key: $TF_VAR_newrelic_api_key" \
-d '{"query":"{ actor { account(id: 1234567) { workflowAutomation { workflow(name: \"report_daily_unauthorized_domain\", version: 21) { definition { yaml } } } } } }"}' \
| jq -r '.data.actor.account.workflowAutomation.workflow.definition.yaml' \
> workflows/report_daily_unauthorized_domain.yaml
保存後は、YAMLとして妥当かを必ず確認します6。
head -3 workflows/report_daily_unauthorized_domain.yaml
# name: report_daily_unauthorized_domain
# description: ''
# steps: ← 行が分かれていればOK。YAML全体が1行で \n が並んでいればNG
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1])); print('OK')" workflows/report_daily_unauthorized_domain.yaml
Tips①: jq -r を忘れると \n エスケープが混入する
jq -r を付けない場合、JSON文字列としてエスケープされた状態で出力されるため、改行が文字としての \n のままファイルに入ります。このまま保存すると、期待するYAML構造ではなく、\n を含んだ1行の文字列として保存されてしまいます。
Tips②: ドキュメントの「base64」表記と実際の挙動
NerdGraphのAPIドキュメントでは definition.yaml は「base64-encoded」と記載されています5。
ただし私の環境での実測(2026年7月・USリージョン、curlでNerdGraphを直接実行)では、取得クエリの definition.yaml は平文のYAML文字列として扱える形で返ってきました。base64 -d を挟むと文字化けしたため、本記事では平文として保存しています。
なお、Terraformリソースの definition 引数も平文YAMLを受け取る仕様です2。
ドキュメントと実装に乖離のある可能性があるため、まず取得結果を確認し、平文YAMLであればそのまま保存します。base64文字列として返っている場合のみデコードしてください。
補足: APIキーの用意と渡し方
- NerdGraphの実行とTerraformプロバイダーの認証の両方で、USERキーが必要です。INGEST-LICENSEキーではNerdGraphを実行できません
-
.tfファイルへのハードコードは厳禁です。variables.tfでsensitive = trueを付けて宣言し、環境変数TF_VAR_newrelic_api_keyで渡します -
.envrcにexportを書いて direnv で自動読み込みするのが手軽です(direnv未使用ならsource .envrcを実行する) - チーム運用では、ローカルの
.envrcに直接キーを書くのではなく、1Password CLIや各CI/CDのSecretsなどから注入する形にするとより安全です -
.gitignoreに.envrcと*.tfstate*を必ず追加してください。sensitive = trueはCLI上の表示を抑制する指定であり、stateを暗号化するものではありません
# .envrc(gitignore対象)
export TF_VAR_newrelic_api_key="NRAK-xxxx"
ここで設定した環境変数は、後述のterraform実行でもそのまま使われます。
Step 2: リソース定義とimportブロック
ディレクトリ構成はこうしました。ワークフローYAMLはHCLに埋め込まず、外部ファイルとして分離します(公式ドキュメントでもベストプラクティスとして推奨されています2)。
workflow_automation/
├── providers.tf
├── variables.tf
├── main.tf
├── imports.tf
└── workflows/
└── report_daily_unauthorized_domain.yaml
main.tf は、今後のワークフロー追加が「YAMLを置いてマップに1行足すだけ」になるよう、for_each で書きます。ここでは、locals.workflows のキーをWorkflow名そのものとして扱います。そのため、キー名とYAML内の name: は一致させます。
locals {
workflows = {
report_daily_unauthorized_domain = "${path.module}/workflows/report_daily_unauthorized_domain.yaml"
# ワークフローを追加するときは、YAMLを配置してここに1行足すだけ
}
}
resource "newrelic_workflow_automation" "workflow" {
for_each = local.workflows
name = each.key
scope_id = var.newrelic_account_id
scope_type = "ACCOUNT"
definition = file(each.value)
}
既存定義の取り込みには、importブロック(Terraform 1.5+)を使います。
IDの形式は <scope_type>#<scope_id>#<workflow_name> です2。
※以下の例では、1234567をNew RelicのアカウントIDに置き換えてください。
# imports.tf(既存ワークフロー1本につき1ブロック)
import {
to = newrelic_workflow_automation.workflow["report_daily_unauthorized_domain"]
id = "ACCOUNT#1234567#report_daily_unauthorized_domain"
}
制約: name はYAML内の name: フィールドと完全一致が必須
不一致の場合、validateの時点でエラーになります。また name / scope_id / scope_type を変更すると、Terraform上では再作成扱いになります2。
Step 3: plan → apply
期待どおり「1 to import, 0 to change」であれば、importは既存定義をstateに取り込むだけで、New Relic側のWorkflow Automation定義には変更が発生しません。スケジュールもそのまま動き続けるため、通知が止まる時間はありません。
……が、私の環境では初回planがこうなりました。
Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.
planの差分表示はheredocで折り返されるため、何が違うのか視認できません。こういうときは、planをJSON化して definition のbefore/afterを抽出し、バイト単位で比較します。
terraform plan -out=/tmp/wf.tfplan
terraform show -json /tmp/wf.tfplan > /tmp/wf.json
jq -j '.resource_changes[]
| select(.address == "newrelic_workflow_automation.workflow[\"report_daily_unauthorized_domain\"]")
| .change.before.definition' /tmp/wf.json > /tmp/remote.yaml
jq -j '.resource_changes[]
| select(.address == "newrelic_workflow_automation.workflow[\"report_daily_unauthorized_domain\"]")
| .change.after.definition' /tmp/wf.json > /tmp/local.yaml
cmp /tmp/remote.yaml /tmp/local.yaml
# 差分がある場合、最初に異なるバイト位置と行番号が表示されます
# 例: /tmp/remote.yaml /tmp/local.yaml differ: byte XXXX, line XX
od -c /tmp/remote.yaml | tail -2
od -c /tmp/local.yaml | tail -2
Tips③: 差分の正体は末尾改行1バイトだった
私のケースでは、差分の正体はエクスポート時にファイル末尾へ紛れ込んだ改行1つだけでした。少なくとも私の環境では、行末スペースも含めて投入した内容がそのまま保存・返却されているように見えます。Terraformの文字列属性の比較は「意味」ではなく「バイト」で行われるため、末尾改行1つでも差分として扱われます。
なお、上記のjqでは -r ではなく -j(改行を付けずに出力)を使うのがポイントです。-r は出力末尾に改行を足すため、まさにその末尾改行を調べたいこの場面では使えません。
移行時点では既存のリモート定義を正とし、そのバイト列にローカルファイルを合わせました。逆に、applyでNew Relic側をローカルファイルに合わせると、内容は同じでもversionが1つ上がり、内容変更を伴わないversion履歴が追加されます。
なお、エディタの「保存時にfinal newlineを追加する」設定で同じ差分が再発し得ます。ワークフローYAMLを置くディレクトリには .editorconfig 等で対策しておくと安心です。
Tips④: 認証失敗が「Cannot import non-existent remote object」に化ける
もう1つ、importならではの罠があります。今回のように新規リソース定義だけを置いた状態では、APIキーが未設定でもplanが通っていましたが、importブロックを含むplanはリモートの読み取りが必須です。このときAPIキーが渡っていない場合でも、401系のエラーではなく、次のようなメッセージになることがあります。
Error: Cannot import non-existent remote object
「IDのフォーマットを間違えた?」と誤読させる出力ですが、原因が認証のこともあります。IDを疑う前に、まず環境変数を確認してください。
echo $TF_VAR_newrelic_api_key
新しいターミナルを開いた直後は source .envrc を忘れがちです(詳細はStep 1の折りたたみを参照)。
apply
差分を解消し「1 to import, 0 to change」になったら、applyします。
Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.
繰り返しになりますが、この操作ではWorkflow Automationの定義に変更を加えません。UI(Settings > Versions)で定義のversionが上がっていないこと、スケジュールが従来通り動作していることを確認できれば移行完了です。
移行後の運用
- 以降の変更は「YAML編集 → plan → apply」。更新のたびにNew Relic側で
versionが自動で上がります2 - 移行後は、UIでの直接編集は原則行わない運用ルールにしました。UI側で編集するとローカルYAMLと乖離しますが、この乖離は次のplanまで検知されません
- ワークフローの追加は、YAMLを1ファイル置いて
locals.workflowsに1行足すだけです - apply済みのimportブロックは削除して問題ありません。実行履歴はGitのコミットで管理できます。有効なまま放置すると、将来リソースを整理した際にplanエラーの原因になり得ます
本記事の限界・注意点
-
スケジュールは
newrelic_workflow_automationの管理対象外です(2026年7月、newrelicプロバイダーv3.94.0で確認)2。実行スケジュールはUIまたはNerdGraphのスケジューリングAPIなどで別途管理する必要があります - UI編集による乖離は自動検知できません。運用ルールでの担保が前提です
- Tips②のbase64の挙動は、冒頭の検証環境における実測です。リージョンや今後のAPI変更で挙動が異なる可能性があります
まとめ
- Workflow Automationの定義管理は、v3.83.0以降なら公式リソース+importブロックで移行できる。回避策を作り込む前に、まずプロバイダーのCHANGELOGを確認する1
- importの差分が視認できないときは、plan JSONからbefore/afterを抽出し、バイト単位で比較する
- エラーメッセージは原因をそのまま語らない。「存在しない」は「認証できていない」かもしれない
UIで気軽に作れるWorkflow Automationですが、前回構築した管理外ドメイン監視の通知は、除外条件・通知文言・しきい値を運用の中で直し続けるものです。移行後は、「除外ドメインを1つ追加する」という日常のチューニングが、YAML1行のdiffとしてレビューできる変更になりました。前回記事で「不可欠」と書いた継続的チューニングを安全に回す土台ができたことが、今回の移行の一番の収穫です。
